in progress
[jalview.git] / forester / ruby / evoruby / lib / evo / apps / taxonomy_processor.rb
1 #
2 # = lib/evo/apps/taxonomy_processor - TaxonomyProcessor class
3 #
4 # Copyright::  Copyright (C) 2006-2007 Christian M. Zmasek
5 # License::    GNU Lesser General Public License (LGPL)
6 #
7 # $Id: taxonomy_processor.rb,v 1.26 2010/12/13 19:00:11 cmzmasek Exp $
8
9
10 require 'lib/evo/util/util'
11 require 'lib/evo/msa/msa_factory'
12 require 'lib/evo/msa/msa'
13 require 'lib/evo/io/msa_io'
14 require 'lib/evo/io/parser/fasta_parser'
15 require 'lib/evo/io/parser/general_msa_parser'
16 require 'lib/evo/io/writer/fasta_writer'
17 require 'lib/evo/io/writer/phylip_sequential_writer'
18 require 'lib/evo/util/command_line_arguments'
19
20 module Evoruby
21
22   class TaxonomyProcessor
23
24     PRG_NAME       = "tap"
25     PRG_DATE       = "2010.02.24"
26     PRG_DESC       = "replacement of species names in multiple sequence files"
27     PRG_VERSION    = "1.01"
28     COPYRIGHT      = "2010 Christian M Zmasek"
29     CONTACT        = "phylosoft@gmail.com"
30     WWW            = "www.phylosoft.org"
31
32     SIMPLE = true
33
34     EXTRACT_TAXONOMY_OPTION = "t"
35
36     def initialize()
37       @taxonomies = Hash.new()
38     end
39
40     def run()
41
42       Util.print_program_information( PRG_NAME,
43         PRG_VERSION,
44         PRG_DESC,
45         PRG_DATE,
46         COPYRIGHT,
47         CONTACT,
48         WWW,
49         STDOUT )
50
51       if ( ARGV == nil || (  ARGV.length != 1 && ARGV.length != 3 && ARGV.length != 4 && ARGV.length != 5 && ARGV.length != 6 ) )
52         puts( "Usage: #{PRG_NAME}.rb [options] [input map file] <input sequences> [output sequences] [output id list]" )
53         puts()
54         puts( "  options: -" + EXTRACT_TAXONOMY_OPTION + ": to extract taxonomy information from bracketed expression" )
55         puts()
56         exit( -1 )
57       end
58
59       begin
60         cla = CommandLineArguments.new( ARGV )
61       rescue ArgumentError => e
62         Util.fatal_error( PRG_NAME, "error: " + e.to_s )
63       end
64
65       
66       mapfile   = nil
67       input     = nil
68       output    = nil
69       list_file = nil
70
71     
72       
73       if cla.get_number_of_files == 4
74         mapfile   = cla.get_file_name( 0 )
75         input     = cla.get_file_name( 1 )
76         output    = cla.get_file_name( 2 )
77         list_file = cla.get_file_name( 3 )
78       elsif cla.get_number_of_files == 3
79         input     = cla.get_file_name( 0 )
80         output    = cla.get_file_name( 1 )
81         list_file = cla.get_file_name( 2 )
82       elsif cla.get_number_of_files == 1
83         input     = cla.get_file_name( 0 )  
84         i = nil
85         if input.downcase.end_with?( ".fasta" )
86           i = input[ 0 .. input.length - 6 ]
87         else
88           i = input
89         end  
90         output    = i + "_ni.fasta"
91         list_file = i + ".nim"
92       end
93
94
95       allowed_opts = Array.new
96       allowed_opts.push( EXTRACT_TAXONOMY_OPTION )
97
98       disallowed = cla.validate_allowed_options_as_str( allowed_opts )
99       if ( disallowed.length > 0 )
100         Util.fatal_error( PRG_NAME, "unknown option(s): " + disallowed )
101       end
102
103       extract_taxonomy = false
104       if ( cla.is_option_set?( EXTRACT_TAXONOMY_OPTION ) )
105         extract_taxonomy = true
106       end
107
108       if ( File.exists?( output ) )
109         Util.fatal_error( PRG_NAME, "outfile [" + output + "] already exists" )
110       end
111       if ( File.exists?( list_file ) )
112         Util.fatal_error( PRG_NAME, "list file [" + list_file + "] already exists" )
113       end
114       if ( !File.exists?( input) )
115         Util.fatal_error( PRG_NAME, "infile [" + input + "] does not exist" )
116       end
117       if ( mapfile != nil && !File.exists?( mapfile ) )
118         Util.fatal_error( PRG_NAME, "mapfile [" + mapfile + "] does not exist" )
119       end
120
121       fasta_like = Util.looks_like_fasta?( input )
122
123       puts()
124       if mapfile != nil
125         puts( "Map file        : " + mapfile )
126       end
127       puts( "Input alignment : " + input )
128       puts( "Output alignment: " + output )
129       puts( "Name list       : " + list_file )
130       if ( fasta_like )
131         puts( "Format          : Fasta"  )
132       else
133         puts( "Format          : Phylip like" )
134       end
135       if ( extract_taxonomy )
136         puts( "Extract taxonomy: true"  )
137       end
138       puts()
139
140       species_map = Hash.new
141       if mapfile != nil
142         File.open( mapfile ) do | file |
143           while line = file.gets
144             if ( line =~/(.+)#(.+)/ || line =~/(.+)\s+(.+)/ )
145               species_map[ $1 ] = $2
146               Util.print_message( PRG_NAME, "mapping: " + $1 + ' => ' + $2 )
147             end
148           end
149         end
150       end
151
152       f = MsaFactory.new()
153       begin
154         if ( fasta_like )
155           msa = f.create_msa_from_file( input, FastaParser.new() )
156         else
157           msa = f.create_msa_from_file( input, GeneralMsaParser.new() )
158         end
159       rescue Exception => e
160         Util.fatal_error( PRG_NAME, "failed to read file: " + e.to_s )
161       end
162
163       if ( msa == nil || msa.get_number_of_seqs() < 1 )
164         Util.fatal_error( PRG_NAME, "failed to read MSA" )
165       end
166       begin
167         Util.check_file_for_writability( list_file )
168       rescue Exception => e
169         Util.fatal_error( PRG_NAME, "error: " + e.to_, STDOUT )
170       end
171
172       #removed = msa.remove_redundant_sequences!( true )
173       #if removed.size > 0
174       #  Util.print_message( PRG_NAME, "going to ignore the following " + removed.size.to_s + " redundant sequences:" )
175       #  removed.each { | seq_name |
176       #    puts seq_name
177       #  }
178       #  Util.print_message( PRG_NAME, "will process " + msa.get_number_of_seqs.to_s + " non redundant sequences" )
179       #end
180
181       lf = File.open( list_file, "a" )
182       for i in 0 ... msa.get_number_of_seqs
183         seq  = msa.get_sequence( i )
184         seq.set_name( Util::normalize_seq_name( modify_name( seq.get_name(), i, lf, species_map, extract_taxonomy ), 10 ) )
185       end
186
187       io = MsaIO.new()
188       w = nil
189       if ( fasta_like )
190         w = FastaWriter.new()
191       else
192         w = PhylipSequentialWriter.new()
193       end
194       w.set_max_name_length( 10 )
195       w.clean( true )
196       begin
197         io.write_to_file( msa, output, w )
198       rescue Exception => e
199         Util.fatal_error( PRG_NAME, "failed to write file: " + e.to_s )
200       end
201       lf.close()
202       if ( @taxonomies.length > 0 )
203         Util.print_message( PRG_NAME, "number of unique taxonomies: " + @taxonomies.length.to_s )
204       end
205       Util.print_message( PRG_NAME, "wrote: " + list_file )
206       Util.print_message( PRG_NAME, "wrote: " + output )
207       Util.print_message( PRG_NAME, "OK" )
208     end
209
210     private
211
212     def modify_name( desc, counter, file, species_map, extract_taxonomy )
213       new_desc = nil
214       my_species = nil
215      # if desc =~ /^>?\s*\S{1,10}_([0-9A-Z]{3,5})/
216       if desc =~ /^>?\s*\S{1,10}_([A-Z]{3,5})/
217         new_desc = counter.to_s( 16 ) + "_" + $1
218       elsif SIMPLE
219         new_desc = counter.to_s( 16 )
220       elsif extract_taxonomy
221         if ( desc.count( "[" ) != desc.count( "]" ) )
222           Util.fatal_error( PRG_NAME, "illegal bracket count in: " + desc )
223         end
224         species = nil
225         species_map.each_key do | key |
226           if desc =~ /[\b|_]#{key}\b/  # Added boundaries to prevent e.g. RAT matching ARATH.
227             species = species_map[ key ]
228             new_desc = counter.to_s( 16 ) + "_" + species
229             break
230           end
231         end
232         if species == nil
233           if desc =~/.*\[(\S{3,}?)\]/
234             species = $1
235             species.strip!
236             species.upcase!
237             species.gsub!( /\s+/, " " )
238             species.gsub!( /-/, "" )
239             species.gsub!( /\)/, "" )
240             species.gsub!( /\(/, "" )
241             species.gsub!( /\'/, "" )
242             if species =~ /\S+\s\S+/ || species =~ /\S{3,5}/
243               if species =~ /(\S+)\s(\S+)/
244                 code = $1[ 0..2 ] + $2[ 0..1 ]
245               elsif  species =~ /\S{3,5}/
246                 code = species
247               elsif species.count( " " ) > 2
248                 species =~ /(\S+)\s+(\S+)\s+(\S+)$/
249                 third_last = $1
250                 second_last = $2
251                 last = $3
252                 code = code[ 0 ] + third_last[ 0 ] + second_last[ 0 ] + last[ 0 ] + last[ last.size - 1 ]
253               elsif species.count( " " ) > 1
254                 species =~ /(\S+)\s+(\S+)$/
255                 second_last = $1
256                 last = $2
257                 code = code[ 0..1 ] + second_last[ 0 ] + last[ 0 ] + last[ last.size - 1 ]
258               end
259               new_desc = counter.to_s( 16 ) + "_" + code
260               if @taxonomies.has_key?( code )
261                 if ( !@taxonomies.has_value?( species ) )
262                   Util.fatal_error( PRG_NAME, "code [#{code}] is not unique in [#{desc}]" )
263                 end
264               else
265                 if ( @taxonomies.has_value?( species ) )
266                   Util.fatal_error( PRG_NAME, "genome [#{species}] is not unique in [#{desc}]" )
267                 else
268                   @taxonomies[ code ] = species
269                 end
270               end
271             else
272               Util.fatal_error( PRG_NAME, "illegal format [#{species}] in: " + desc )
273             end
274           else
275             Util.fatal_error( PRG_NAME, "illegal format in: " + desc )
276           end
277         end
278       else
279         species = nil
280         my_species = nil
281         species_map.each_key do | key |
282           if desc =~ /#{key}/
283             species = species_map[ key ]
284             species = species.gsub( /\s+/, "" )
285             species = species.gsub( /_/, " " )
286             my_species = species
287             if species =~ /(\S+)\s+(\S+)/
288               species = $1[0..2] + $2[0..1]
289             end
290             species = species.gsub( /\s+/, "" )
291             species = species.slice(0, 5)
292             species.upcase!
293             break
294           end
295         end
296         if species == nil
297           Util.fatal_error( PRG_NAME, "species not found in: " + desc  )
298         else
299           new_desc = counter.to_s( 16 ) + "_" + species
300         end
301       end
302       if new_desc == nil
303         Util.fatal_error( PRG_NAME, "failed to extract species from: " + desc  )
304       end
305       if my_species != nil
306         file.print( new_desc + ": " + desc + " [" + my_species + "]" + "\n" )
307       else
308         file.print( new_desc + ": " + desc + "\n" )
309       end
310       new_desc
311     end
312
313   end # class TaxonomyProcessor
314
315 end # module Evoruby