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 != 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       mapfile   = nil
66       input     = nil
67       output    = nil
68       list_file = nil
69
70       if cla.get_number_of_files == 4
71         mapfile   = cla.get_file_name( 0 )
72         input     = cla.get_file_name( 1 )
73         output    = cla.get_file_name( 2 )
74         list_file = cla.get_file_name( 3 )
75       elsif cla.get_number_of_files == 3
76         input     = cla.get_file_name( 0 )
77         output    = cla.get_file_name( 1 )
78         list_file = cla.get_file_name( 2 )
79       end
80
81
82       allowed_opts = Array.new
83       allowed_opts.push( EXTRACT_TAXONOMY_OPTION )
84
85       disallowed = cla.validate_allowed_options_as_str( allowed_opts )
86       if ( disallowed.length > 0 )
87         Util.fatal_error( PRG_NAME, "unknown option(s): " + disallowed )
88       end
89
90       extract_taxonomy = false
91       if ( cla.is_option_set?( EXTRACT_TAXONOMY_OPTION ) )
92         extract_taxonomy = true
93       end
94
95       if ( File.exists?( output ) )
96         Util.fatal_error( PRG_NAME, "outfile [" + output + "] already exists" )
97       end
98       if ( File.exists?( list_file ) )
99         Util.fatal_error( PRG_NAME, "list file [" + list_file + "] already exists" )
100       end
101       if ( !File.exists?( input) )
102         Util.fatal_error( PRG_NAME, "infile [" + input + "] does not exist" )
103       end
104       if ( mapfile != nil && !File.exists?( mapfile ) )
105         Util.fatal_error( PRG_NAME, "mapfile [" + mapfile + "] does not exist" )
106       end
107
108       fasta_like = Util.looks_like_fasta?( input )
109
110       puts()
111       if mapfile != nil
112         puts( "Map file        : " + mapfile )
113       end
114       puts( "Input alignment : " + input )
115       puts( "Output alignment: " + output )
116       puts( "Name list       : " + list_file )
117       if ( fasta_like )
118         puts( "Format          : Fasta"  )
119       else
120         puts( "Format          : Phylip like" )
121       end
122       if ( extract_taxonomy )
123         puts( "Extract taxonomy: true"  )
124       end
125       puts()
126
127       species_map = Hash.new
128       if mapfile != nil
129         File.open( mapfile ) do | file |
130           while line = file.gets
131             if ( line =~/(.+)#(.+)/ || line =~/(.+)\s+(.+)/ )
132               species_map[ $1 ] = $2
133               Util.print_message( PRG_NAME, "mapping: " + $1 + ' => ' + $2 )
134             end
135           end
136         end
137       end
138
139       f = MsaFactory.new()
140       begin
141         if ( fasta_like )
142           msa = f.create_msa_from_file( input, FastaParser.new() )
143         else
144           msa = f.create_msa_from_file( input, GeneralMsaParser.new() )
145         end
146       rescue Exception => e
147         Util.fatal_error( PRG_NAME, "failed to read file: " + e.to_s )
148       end
149
150       if ( msa == nil || msa.get_number_of_seqs() < 1 )
151         Util.fatal_error( PRG_NAME, "failed to read MSA" )
152       end
153       begin
154         Util.check_file_for_writability( list_file )
155       rescue Exception => e
156         Util.fatal_error( PRG_NAME, "error: " + e.to_, STDOUT )
157       end
158
159       #removed = msa.remove_redundant_sequences!( true )
160       #if removed.size > 0
161       #  Util.print_message( PRG_NAME, "going to ignore the following " + removed.size.to_s + " redundant sequences:" )
162       #  removed.each { | seq_name |
163       #    puts seq_name
164       #  }
165       #  Util.print_message( PRG_NAME, "will process " + msa.get_number_of_seqs.to_s + " non redundant sequences" )
166       #end
167
168       lf = File.open( list_file, "a" )
169       for i in 0 ... msa.get_number_of_seqs
170         seq  = msa.get_sequence( i )
171         seq.set_name( Util::normalize_seq_name( modify_name( seq.get_name(), i, lf, species_map, extract_taxonomy ), 10 ) )
172       end
173
174       io = MsaIO.new()
175       w = nil
176       if ( fasta_like )
177         w = FastaWriter.new()
178       else
179         w = PhylipSequentialWriter.new()
180       end
181       w.set_max_name_length( 10 )
182       w.clean( true )
183       begin
184         io.write_to_file( msa, output, w )
185       rescue Exception => e
186         Util.fatal_error( PRG_NAME, "failed to write file: " + e.to_s )
187       end
188       lf.close()
189       if ( @taxonomies.length > 0 )
190         Util.print_message( PRG_NAME, "number of unique taxonomies: " + @taxonomies.length.to_s )
191       end
192       Util.print_message( PRG_NAME, "wrote: " + list_file )
193       Util.print_message( PRG_NAME, "wrote: " + output )
194       Util.print_message( PRG_NAME, "OK" )
195     end
196
197     private
198
199     def modify_name( desc, counter, file, species_map, extract_taxonomy )
200       new_desc = nil
201       my_species = nil
202      # if desc =~ /^>?\s*\S{1,10}_([0-9A-Z]{3,5})/
203       if desc =~ /^>?\s*\S{1,10}_([A-Z]{3,5})/
204         new_desc = counter.to_s( 16 ) + "_" + $1
205       elsif SIMPLE
206         new_desc = counter.to_s( 16 )
207       elsif extract_taxonomy
208         if ( desc.count( "[" ) != desc.count( "]" ) )
209           Util.fatal_error( PRG_NAME, "illegal bracket count in: " + desc )
210         end
211         species = nil
212         species_map.each_key do | key |
213           if desc =~ /[\b|_]#{key}\b/  # Added boundaries to prevent e.g. RAT matching ARATH.
214             species = species_map[ key ]
215             new_desc = counter.to_s( 16 ) + "_" + species
216             break
217           end
218         end
219         if species == nil
220           if desc =~/.*\[(\S{3,}?)\]/
221             species = $1
222             species.strip!
223             species.upcase!
224             species.gsub!( /\s+/, " " )
225             species.gsub!( /-/, "" )
226             species.gsub!( /\)/, "" )
227             species.gsub!( /\(/, "" )
228             species.gsub!( /\'/, "" )
229             if species =~ /\S+\s\S+/ || species =~ /\S{3,5}/
230               if species =~ /(\S+)\s(\S+)/
231                 code = $1[ 0..2 ] + $2[ 0..1 ]
232               elsif  species =~ /\S{3,5}/
233                 code = species
234               elsif species.count( " " ) > 2
235                 species =~ /(\S+)\s+(\S+)\s+(\S+)$/
236                 third_last = $1
237                 second_last = $2
238                 last = $3
239                 code = code[ 0 ] + third_last[ 0 ] + second_last[ 0 ] + last[ 0 ] + last[ last.size - 1 ]
240               elsif species.count( " " ) > 1
241                 species =~ /(\S+)\s+(\S+)$/
242                 second_last = $1
243                 last = $2
244                 code = code[ 0..1 ] + second_last[ 0 ] + last[ 0 ] + last[ last.size - 1 ]
245               end
246               new_desc = counter.to_s( 16 ) + "_" + code
247               if @taxonomies.has_key?( code )
248                 if ( !@taxonomies.has_value?( species ) )
249                   Util.fatal_error( PRG_NAME, "code [#{code}] is not unique in [#{desc}]" )
250                 end
251               else
252                 if ( @taxonomies.has_value?( species ) )
253                   Util.fatal_error( PRG_NAME, "genome [#{species}] is not unique in [#{desc}]" )
254                 else
255                   @taxonomies[ code ] = species
256                 end
257               end
258             else
259               Util.fatal_error( PRG_NAME, "illegal format [#{species}] in: " + desc )
260             end
261           else
262             Util.fatal_error( PRG_NAME, "illegal format in: " + desc )
263           end
264         end
265       else
266         species = nil
267         my_species = nil
268         species_map.each_key do | key |
269           if desc =~ /#{key}/
270             species = species_map[ key ]
271             species = species.gsub( /\s+/, "" )
272             species = species.gsub( /_/, " " )
273             my_species = species
274             if species =~ /(\S+)\s+(\S+)/
275               species = $1[0..2] + $2[0..1]
276             end
277             species = species.gsub( /\s+/, "" )
278             species = species.slice(0, 5)
279             species.upcase!
280             break
281           end
282         end
283         if species == nil
284           Util.fatal_error( PRG_NAME, "species not found in: " + desc  )
285         else
286           new_desc = counter.to_s( 16 ) + "_" + species
287         end
288       end
289       if new_desc == nil
290         Util.fatal_error( PRG_NAME, "failed to extract species from: " + desc  )
291       end
292       if my_species != nil
293         file.print( new_desc + ": " + desc + " [" + my_species + "]" + "\n" )
294       else
295         file.print( new_desc + ": " + desc + "\n" )
296       end
297       new_desc
298     end
299
300   end # class TaxonomyProcessor
301
302 end # module Evoruby