6dc17e30bb2a51c2019c97b314ff56514d73cdd0
[jalview.git] / forester / java / src / org / forester / application / cladinator.java
1 // $Id:
2 // FORESTER -- software libraries and applications
3 // for evolutionary biology research and applications.
4 //
5 // Copyright (C) 2017 Christian M. Zmasek
6 // Copyright (C) 2017 J. Craig Venter Institute
7 // All rights reserved
8 //
9 // This library is free software; you can redistribute it and/or
10 // modify it under the terms of the GNU Lesser General Public
11 // License as published by the Free Software Foundation; either
12 // version 2.1 of the License, or (at your option) any later version.
13 //
14 // This library is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 // Lesser General Public License for more details.
18 //
19 // You should have received a copy of the GNU Lesser General Public
20 // License along with this library; if not, write to the Free Software
21 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 //
23 // Contact: phyloxml @ gmail . com
24 // WWW: https://sites.google.com/site/cmzmasek/home/software/forester
25
26 package org.forester.application;
27
28 import java.io.File;
29 import java.io.IOException;
30 import java.text.DecimalFormat;
31 import java.util.ArrayList;
32 import java.util.List;
33 import java.util.SortedMap;
34 import java.util.regex.Pattern;
35 import java.util.regex.PatternSyntaxException;
36
37 import org.forester.clade_analysis.AnalysisMulti;
38 import org.forester.clade_analysis.Prefix;
39 import org.forester.clade_analysis.ResultMulti;
40 import org.forester.io.parsers.PhylogenyParser;
41 import org.forester.io.parsers.util.ParserUtils;
42 import org.forester.phylogeny.Phylogeny;
43 import org.forester.phylogeny.factories.ParserBasedPhylogenyFactory;
44 import org.forester.phylogeny.factories.PhylogenyFactory;
45 import org.forester.util.BasicTable;
46 import org.forester.util.BasicTableParser;
47 import org.forester.util.CommandLineArguments;
48 import org.forester.util.EasyWriter;
49 import org.forester.util.ForesterUtil;
50 import org.forester.util.UserException;
51
52 public final class cladinator {
53
54     final static private String        PRG_NAME                             = "cladinator";
55     final static private String        PRG_VERSION                          = "1.04";
56     final static private String        PRG_DATE                             = "170915";
57     final static private String        PRG_DESC                             = "clades within clades of annotated labels -- analysis of pplacer-type outputs";
58     final static private String        E_MAIL                               = "phyloxml@gmail.com";
59     final static private String        WWW                                  = "https://sites.google.com/site/cmzmasek/home/software/forester";
60     final static private String        HELP_OPTION_1                        = "help";
61     final static private String        HELP_OPTION_2                        = "h";
62     final static private String        SEP_OPTION                           = "s";
63     final static private String        QUERY_PATTERN_OPTION                 = "q";
64     final static private String        SPECIFICS_CUTOFF_OPTION              = "c";
65     final static private String        MAPPING_FILE_OPTION                  = "m";
66     final static private String        EXTRA_PROCESSING_OPTION1             = "x";
67     final static private String        EXTRA_PROCESSING1_SEP_OPTION         = "xs";
68     final static private String        EXTRA_PROCESSING1_KEEP_EXTRA_OPTION  = "xk";
69     final static private String        QUIET_OPTION                         = "Q";
70     final static private String        VERBOSE_OPTION                       = "v";
71     final static private double        SPECIFICS_CUTOFF_DEFAULT             = 0.8;
72     final static private String        SEP_DEFAULT                          = ".";
73     final static private Pattern       QUERY_PATTERN_DEFAULT                = AnalysisMulti.DEFAULT_QUERY_PATTERN_FOR_PPLACER_TYPE;
74     final static private String        EXTRA_PROCESSING1_SEP_DEFAULT        = "|";
75     final static private boolean       EXTRA_PROCESSING1_KEEP_EXTRA_DEFAULT = false;
76     private final static DecimalFormat df                                   = new DecimalFormat( "0.0###" );
77
78     public static void main( final String args[] ) {
79         try {
80             ForesterUtil.printProgramInformation( PRG_NAME,
81                                                   PRG_DESC,
82                                                   PRG_VERSION,
83                                                   PRG_DATE,
84                                                   E_MAIL,
85                                                   WWW,
86                                                   ForesterUtil.getForesterLibraryInformation() );
87             CommandLineArguments cla = null;
88             try {
89                 cla = new CommandLineArguments( args );
90             }
91             catch ( final Exception e ) {
92                 ForesterUtil.fatalError( PRG_NAME, e.getMessage() );
93             }
94             if ( cla.isOptionSet( HELP_OPTION_1 ) || cla.isOptionSet( HELP_OPTION_2 ) ) {
95                 System.out.println();
96                 print_help();
97                 System.exit( 0 );
98             }
99             if ( ( cla.getNumberOfNames() != 1 ) && ( cla.getNumberOfNames() != 2 ) ) {
100                 print_help();
101                 System.exit( -1 );
102             }
103             final List<String> allowed_options = new ArrayList<>();
104             allowed_options.add( SEP_OPTION );
105             allowed_options.add( QUERY_PATTERN_OPTION );
106             allowed_options.add( SPECIFICS_CUTOFF_OPTION );
107             allowed_options.add( MAPPING_FILE_OPTION );
108             allowed_options.add( EXTRA_PROCESSING_OPTION1 );
109             allowed_options.add( EXTRA_PROCESSING1_SEP_OPTION );
110             allowed_options.add( EXTRA_PROCESSING1_KEEP_EXTRA_OPTION );
111             allowed_options.add( VERBOSE_OPTION );
112             allowed_options.add( QUIET_OPTION );
113             final String dissallowed_options = cla.validateAllowedOptionsAsString( allowed_options );
114             if ( dissallowed_options.length() > 0 ) {
115                 ForesterUtil.fatalError( PRG_NAME, "unknown option(s): " + dissallowed_options );
116             }
117             double cutoff_specifics = SPECIFICS_CUTOFF_DEFAULT;
118             if ( cla.isOptionSet( SPECIFICS_CUTOFF_OPTION ) ) {
119                 if ( cla.isOptionValueSet( SPECIFICS_CUTOFF_OPTION ) ) {
120                     cutoff_specifics = cla.getOptionValueAsDouble( SPECIFICS_CUTOFF_OPTION );
121                     if ( cutoff_specifics < 0 ) {
122                         ForesterUtil.fatalError( PRG_NAME, "cutoff cannot be negative" );
123                     }
124                 }
125                 else {
126                     ForesterUtil.fatalError( PRG_NAME, "no value for cutoff for specifics" );
127                 }
128             }
129             String separator = SEP_DEFAULT;
130             if ( cla.isOptionSet( SEP_OPTION ) ) {
131                 if ( cla.isOptionValueSet( SEP_OPTION ) ) {
132                     separator = cla.getOptionValue( SEP_OPTION );
133                 }
134                 else {
135                     ForesterUtil.fatalError( PRG_NAME, "no value for separator option" );
136                 }
137             }
138             Pattern compiled_query_str = null;
139             if ( cla.isOptionSet( QUERY_PATTERN_OPTION ) ) {
140                 if ( cla.isOptionValueSet( QUERY_PATTERN_OPTION ) ) {
141                     final String query_str = cla.getOptionValue( QUERY_PATTERN_OPTION );
142                     try {
143                         compiled_query_str = Pattern.compile( query_str );
144                     }
145                     catch ( final PatternSyntaxException e ) {
146                         ForesterUtil.fatalError( PRG_NAME, "error in regular expression: " + e.getMessage() );
147                     }
148                 }
149                 else {
150                     ForesterUtil.fatalError( PRG_NAME, "no value for query pattern option" );
151                 }
152             }
153             File mapping_file = null;
154             if ( cla.isOptionSet( MAPPING_FILE_OPTION ) ) {
155                 if ( cla.isOptionValueSet( MAPPING_FILE_OPTION ) ) {
156                     final String mapping_file_str = cla.getOptionValue( MAPPING_FILE_OPTION );
157                     final String error = ForesterUtil.isReadableFile( mapping_file_str );
158                     if ( !ForesterUtil.isEmpty( error ) ) {
159                         ForesterUtil.fatalError( PRG_NAME, error );
160                     }
161                     mapping_file = new File( mapping_file_str );
162                 }
163                 else {
164                     ForesterUtil.fatalError( PRG_NAME, "no value for mapping file" );
165                 }
166             }
167             final Pattern pattern = ( compiled_query_str != null ) ? compiled_query_str : QUERY_PATTERN_DEFAULT;
168             final File intreefile = cla.getFile( 0 );
169             final String error_intreefile = ForesterUtil.isReadableFile( intreefile );
170             if ( !ForesterUtil.isEmpty( error_intreefile ) ) {
171                 ForesterUtil.fatalError( PRG_NAME, error_intreefile );
172             }
173             final File outtablefile;
174             if ( cla.getNumberOfNames() > 1 ) {
175                 outtablefile = cla.getFile( 1 );
176                 final String error_outtablefile = ForesterUtil.isWritableFile( outtablefile );
177                 if ( !ForesterUtil.isEmpty( error_outtablefile ) ) {
178                     ForesterUtil.fatalError( PRG_NAME, error_outtablefile );
179                 }
180             }
181             else {
182                 outtablefile = null;
183             }
184             final BasicTable<String> t;
185             final SortedMap<String, String> map;
186             if ( mapping_file != null ) {
187                 t = BasicTableParser.parse( mapping_file, '\t' );
188                 if ( t.getNumberOfColumns() != 2 ) {
189                     ForesterUtil.fatalError( PRG_NAME,
190                                              "mapping file needs to have 2 tab-separated columns, not "
191                                                      + t.getNumberOfColumns() );
192                 }
193                 map = t.getColumnsAsMap( 0, 1 );
194             }
195             else {
196                 t = null;
197                 map = null;
198             }
199             final boolean extra_processing1;
200             if ( cla.isOptionSet( EXTRA_PROCESSING_OPTION1 ) ) {
201                 extra_processing1 = true;
202             }
203             else {
204                 extra_processing1 = false;
205             }
206             String extra_processing1_sep = EXTRA_PROCESSING1_SEP_DEFAULT;
207             if ( cla.isOptionSet( EXTRA_PROCESSING1_SEP_OPTION ) ) {
208                 if ( !extra_processing1 ) {
209                     ForesterUtil.fatalError( PRG_NAME,
210                                              "extra processing is not enabled, cannot set -"
211                                                      + EXTRA_PROCESSING1_SEP_OPTION + " option" );
212                 }
213                 if ( cla.isOptionValueSet( EXTRA_PROCESSING1_SEP_OPTION ) ) {
214                     extra_processing1_sep = cla.getOptionValue( EXTRA_PROCESSING1_SEP_OPTION );
215                 }
216                 else {
217                     ForesterUtil.fatalError( PRG_NAME, "no value for extra processing separator" );
218                 }
219             }
220             if ( ( extra_processing1_sep != null ) && extra_processing1_sep.equals( separator ) ) {
221                 ForesterUtil.fatalError( PRG_NAME,
222                                          "extra processing separator must not be the same the annotation-separator" );
223             }
224             boolean extra_processing1_keep = EXTRA_PROCESSING1_KEEP_EXTRA_DEFAULT;
225             if ( cla.isOptionSet( EXTRA_PROCESSING1_KEEP_EXTRA_OPTION ) ) {
226                 if ( !extra_processing1 ) {
227                     ForesterUtil.fatalError( PRG_NAME,
228                                              "extra processing is not enabled, cannot set -"
229                                                      + EXTRA_PROCESSING1_KEEP_EXTRA_OPTION + " option" );
230                 }
231                 extra_processing1_keep = true;
232             }
233             final boolean verbose;
234             if ( cla.isOptionSet( VERBOSE_OPTION ) ) {
235                 verbose = true;
236             }
237             else {
238                 verbose = false;
239             }
240             final boolean quit;
241             if ( cla.isOptionSet( QUIET_OPTION ) ) {
242                 quit = true;
243             }
244             else {
245                 quit = false;
246             }
247             System.out.println( "Input tree                 : " + intreefile );
248             System.out.println( "Specific-hit support cutoff: " + cutoff_specifics );
249             if ( mapping_file != null ) {
250                 System.out.println( "Mapping file               : " + mapping_file + " (" + t.getNumberOfRows()
251                         + " rows)" );
252             }
253             System.out.println( "Annotation-separator       : " + separator );
254             System.out.println( "Query pattern              : " + pattern );
255             System.out.println( "Extra processing           : " + extra_processing1 );
256             if ( extra_processing1 ) {
257                 System.out.println( "Extra processing separator : " + extra_processing1_sep );
258                 System.out.println( "Keep extra annotations     : " + extra_processing1_keep );
259             }
260             if ( outtablefile != null ) {
261                 System.out.println( "Output table               : " + outtablefile );
262             }
263             Phylogeny phys[] = null;
264             try {
265                 final PhylogenyFactory factory = ParserBasedPhylogenyFactory.getInstance();
266                 final PhylogenyParser pp = ParserUtils.createParserDependingOnFileType( intreefile, true );
267                 phys = factory.create( intreefile, pp );
268             }
269             catch ( final IOException e ) {
270                 ForesterUtil.fatalError( PRG_NAME, "Could not read \"" + intreefile + "\" [" + e.getMessage() + "]" );
271             }
272             if ( phys.length == 0 ) {
273                 ForesterUtil.fatalError( PRG_NAME, "\"" + intreefile + "\" does not contain any trees" );
274             }
275             System.out.println( "Number of input trees      : " + phys.length );
276             if ( phys.length == 1 ) {
277                 System.out.println( "Ext. nodes in input tree   : " + phys[ 0 ].getNumberOfExternalNodes() );
278             }
279             else {
280                 System.out.println( "Ext. nodes in input tree 1 : " + phys[ 0 ].getNumberOfExternalNodes() );
281             }
282             final EasyWriter outtable_writer;
283             if ( outtablefile != null ) {
284                 outtable_writer = ForesterUtil.createEasyWriter( outtablefile );
285             }
286             else {
287                 outtable_writer = null;
288             }
289             int counter = 0;
290             for( final Phylogeny phy : phys ) {
291                 if ( map != null ) {
292                     AnalysisMulti.performMapping( pattern, map, phy, verbose );
293                 }
294                 if ( extra_processing1 ) {
295                     AnalysisMulti.performExtraProcessing1( pattern,
296                                                            phy,
297                                                            extra_processing1_sep,
298                                                            extra_processing1_keep,
299                                                            separator,
300                                                            verbose );
301                 }
302                 final ResultMulti res = AnalysisMulti.execute( phy, pattern, separator, cutoff_specifics );
303                 if ( !quit ) {
304                     if ( phys.length == 1 ) {
305                         printResult( res, -1 );
306                     }
307                     else {
308                         printResult( res, counter );
309                     }
310                 }
311                 if ( outtable_writer != null ) {
312                     writeResultToTable( res, outtable_writer );
313                     outtable_writer.flush();
314                 }
315                 ++counter;
316             }
317             if ( outtable_writer != null ) {
318                 outtable_writer.flush();
319                 outtable_writer.close();
320             }
321         }
322         catch ( final UserException e ) {
323             ForesterUtil.fatalError( PRG_NAME, e.getMessage() );
324         }
325         catch ( final IOException e ) {
326             ForesterUtil.fatalError( PRG_NAME, e.getMessage() );
327         }
328         catch ( final Exception e ) {
329             e.printStackTrace();
330             ForesterUtil.fatalError( PRG_NAME, "Unexpected errror!" );
331         }
332     }
333
334     private final static void printResult( final ResultMulti res, final int counter ) {
335         System.out.println();
336         if ( counter == -1 ) {
337             System.out.println( "Result for " + res.getQueryNamePrefix() );
338         }
339         else {
340             System.out.println( "Result for " + res.getQueryNamePrefix() + " [tree " + counter + "]" );
341         }
342         if ( ( res.getAllMultiHitPrefixes() == null ) | ( res.getAllMultiHitPrefixes().size() < 1 ) ) {
343             System.out.println( " No match to query pattern!" );
344         }
345         else {
346             System.out.println( " Matching Clade(s):" );
347             for( final Prefix prefix : res.getCollapsedMultiHitPrefixes() ) {
348                 System.out.println( " " + prefix );
349             }
350             if ( res.isHasSpecificMultiHitsPrefixes() ) {
351                 System.out.println();
352                 System.out.println( " Specific-hit(s):" );
353                 for( final Prefix prefix : res.getSpecificMultiHitPrefixes() ) {
354                     System.out.println( " " + prefix );
355                 }
356                 System.out.println();
357                 System.out.println( " Matching Clade(s) with Specific-hit(s):" );
358                 for( final Prefix prefix : res.getCollapsedMultiHitPrefixes() ) {
359                     System.out.println( " " + prefix );
360                     for( final Prefix spec : res.getSpecificMultiHitPrefixes() ) {
361                         if ( spec.getPrefix().startsWith( prefix.getPrefix() ) ) {
362                             System.out.println( "     " + spec );
363                         }
364                     }
365                 }
366             }
367             if ( !ForesterUtil.isEmpty( res.getAllMultiHitPrefixesDown() ) ) {
368                 System.out.println();
369                 System.out.println( " Matching Down-tree Bracketing Clade(s):" );
370                 for( final Prefix prefix : res.getCollapsedMultiHitPrefixesDown() ) {
371                     System.out.println( " " + prefix );
372                 }
373             }
374             if ( !ForesterUtil.isEmpty( res.getAllMultiHitPrefixesUp() ) ) {
375                 System.out.println();
376                 System.out.println( " Matching Up-tree Bracketing Clade(s):" );
377                 for( final Prefix prefix : res.getCollapsedMultiHitPrefixesUp() ) {
378                     System.out.println( " " + prefix );
379                 }
380             }
381             System.out.println();
382             System.out.println( " Total Number of Matches: " + res.getNumberOfMatches() + "/"
383                     + res.getReferenceTreeNumberOfExternalNodes() );
384         }
385         System.out.println();
386     }
387
388     private final static void writeResultToTable( final ResultMulti res, final EasyWriter w ) throws IOException {
389         if ( ( res.getAllMultiHitPrefixes() == null ) | ( res.getAllMultiHitPrefixes().size() < 1 ) ) {
390             w.print( res.getQueryNamePrefix() );
391             w.print( "\t" );
392             w.println( "No match to query pattern!" );
393         }
394         else {
395             for( final Prefix prefix : res.getCollapsedMultiHitPrefixes() ) {
396                 w.print( res.getQueryNamePrefix() );
397                 w.print( "\t" );
398                 w.print( "Matching Clades" );
399                 w.print( "\t" );
400                 w.print( prefix.getPrefix() );
401                 w.print( "\t" );
402                 w.print( df.format( prefix.getConfidence() ) );
403                 w.print( "\t" );
404                 w.print( String.valueOf( res.getNumberOfMatches() ) );
405                 w.print( "\t" );
406                 w.print( String.valueOf( res.getReferenceTreeNumberOfExternalNodes() ) );
407                 w.println();
408             }
409             if ( res.isHasSpecificMultiHitsPrefixes() ) {
410                 for( final Prefix prefix : res.getSpecificMultiHitPrefixes() ) {
411                     w.print( res.getQueryNamePrefix() );
412                     w.print( "\t" );
413                     w.print( "Specific-hits" );
414                     w.print( "\t" );
415                     w.print( prefix.getPrefix() );
416                     w.print( "\t" );
417                     w.print( df.format( prefix.getConfidence() ) );
418                     w.print( "\t" );
419                     w.print( String.valueOf( res.getNumberOfMatches() ) );
420                     w.print( "\t" );
421                     w.print( String.valueOf( res.getReferenceTreeNumberOfExternalNodes() ) );
422                     w.println();
423                 }
424             }
425             if ( !ForesterUtil.isEmpty( res.getAllMultiHitPrefixesDown() ) ) {
426                 for( final Prefix prefix : res.getCollapsedMultiHitPrefixesDown() ) {
427                     w.print( res.getQueryNamePrefix() );
428                     w.print( "\t" );
429                     w.print( "Matching Down-tree Bracketing Clades" );
430                     w.print( "\t" );
431                     w.print( prefix.getPrefix() );
432                     w.print( "\t" );
433                     w.print( df.format( prefix.getConfidence() ) );
434                     w.print( "\t" );
435                     w.print( String.valueOf( res.getNumberOfMatches() ) );
436                     w.print( "\t" );
437                     w.print( String.valueOf( res.getReferenceTreeNumberOfExternalNodes() ) );
438                     w.println();
439                 }
440             }
441             if ( !ForesterUtil.isEmpty( res.getAllMultiHitPrefixesUp() ) ) {
442                 for( final Prefix prefix : res.getCollapsedMultiHitPrefixesUp() ) {
443                     w.print( res.getQueryNamePrefix() );
444                     w.print( "\t" );
445                     w.print( "Matching Up-tree Bracketing Clades" );
446                     w.print( "\t" );
447                     w.print( prefix.getPrefix() );
448                     w.print( "\t" );
449                     w.print( df.format( prefix.getConfidence() ) );
450                     w.print( "\t" );
451                     w.print( String.valueOf( res.getNumberOfMatches() ) );
452                     w.print( "\t" );
453                     w.print( String.valueOf( res.getReferenceTreeNumberOfExternalNodes() ) );
454                     w.println();
455                 }
456             }
457         }
458     }
459
460     private final static void print_help() {
461         System.out.println( "Usage:" );
462         System.out.println();
463         System.out.println( PRG_NAME + " [options] <input tree(s) file> [output table file]" );
464         System.out.println();
465         System.out.println( " options:" );
466         System.out.println( "  -" + SPECIFICS_CUTOFF_OPTION
467                 + "=<double>        : the cutoff for \"specific-hit\" support values (default: "
468                 + SPECIFICS_CUTOFF_DEFAULT + ")" );
469         System.out.println( "  -" + SEP_OPTION + "=<separator>     : the annotation-separator to be used (default: "
470                 + SEP_DEFAULT + ")" );
471         System.out.println( "  -" + MAPPING_FILE_OPTION
472                 + "=<mapping table> : to map node names to appropriate annotations (tab-separated, two columns) (default: no mapping)" );
473         System.out.println( "  -" + EXTRA_PROCESSING_OPTION1
474                 + "                 : to enable extra processing of annotations (e.g. \"Q16611|A.1.1\" becomes \"A.1.1\")" );
475         System.out.println( "  -" + EXTRA_PROCESSING1_SEP_OPTION
476                 + "=<separator>    : the separator for extra annotations (default: \"" + EXTRA_PROCESSING1_SEP_DEFAULT
477                 + "\")" );
478         System.out.println( "  -" + EXTRA_PROCESSING1_KEEP_EXTRA_OPTION
479                 + "                : to keep extra annotations (e.g. \"Q16611|A.1.1\" becomes \"A.1.1.Q16611\")" );
480         System.out.println( "  -" + VERBOSE_OPTION + "                 : verbose" );
481         System.out.println( "  --" + QUERY_PATTERN_OPTION
482                 + "=<query pattern>: the regular expression for the query (default: \"" + QUERY_PATTERN_DEFAULT
483                 + "\" for pplacer output)" );
484         System.out.println();
485         System.out.println( "Examples:" );
486         System.out.println();
487         System.out.println( " " + PRG_NAME + " my_tree.nh result.tsv" );
488         System.out.println( " " + PRG_NAME + " -c=0.5 -s=. my_tree.nh result.tsv" );
489         System.out.println( " " + PRG_NAME + " -c=0.9 -s=_ -m=map.tsv my_tree.nh result.tsv" );
490         System.out.println( " " + PRG_NAME + " -x -xs=& -xk my_tree.nh result.tsv" );
491         System.out.println( " " + PRG_NAME + " -x -xs=\"|\" my_tree.nh result.tsv" );
492         System.out.println( " " + PRG_NAME + " -x -xk -m=map.tsv pplacer_out_trees.sing.tre result.tsv" );
493         System.out.println();
494     }
495 }