993e295d74cfe645ee22b39b488723373f8f235a
[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.03";
56     final static private String        PRG_DATE                             = "170913";
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        VERBOSE_OPTION                       = "v";
70     final static private double        SPECIFICS_CUTOFF_DEFAULT             = 0.8;
71     final static private String        SEP_DEFAULT                          = ".";
72     final static private Pattern       QUERY_PATTERN_DEFAULT                = AnalysisMulti.DEFAULT_QUERY_PATTERN_FOR_PPLACER_TYPE;
73     final static private String        EXTRA_PROCESSING1_SEP_DEFAULT        = "|";
74     final static private boolean       EXTRA_PROCESSING1_KEEP_EXTRA_DEFAULT = false;
75     private final static DecimalFormat df                                   = new DecimalFormat( "0.0###" );
76
77     public static void main( final String args[] ) {
78         try {
79             ForesterUtil.printProgramInformation( PRG_NAME,
80                                                   PRG_DESC,
81                                                   PRG_VERSION,
82                                                   PRG_DATE,
83                                                   E_MAIL,
84                                                   WWW,
85                                                   ForesterUtil.getForesterLibraryInformation() );
86             CommandLineArguments cla = null;
87             try {
88                 cla = new CommandLineArguments( args );
89             }
90             catch ( final Exception e ) {
91                 ForesterUtil.fatalError( PRG_NAME, e.getMessage() );
92             }
93             if ( cla.isOptionSet( HELP_OPTION_1 ) || cla.isOptionSet( HELP_OPTION_2 ) ) {
94                 System.out.println();
95                 print_help();
96                 System.exit( 0 );
97             }
98             if ( ( cla.getNumberOfNames() != 1 ) && ( cla.getNumberOfNames() != 2 ) ) {
99                 print_help();
100                 System.exit( -1 );
101             }
102             final List<String> allowed_options = new ArrayList<>();
103             allowed_options.add( SEP_OPTION );
104             allowed_options.add( QUERY_PATTERN_OPTION );
105             allowed_options.add( SPECIFICS_CUTOFF_OPTION );
106             allowed_options.add( MAPPING_FILE_OPTION );
107             allowed_options.add( EXTRA_PROCESSING_OPTION1 );
108             allowed_options.add( EXTRA_PROCESSING1_SEP_OPTION );
109             allowed_options.add( EXTRA_PROCESSING1_KEEP_EXTRA_OPTION );
110             allowed_options.add( VERBOSE_OPTION );
111             final String dissallowed_options = cla.validateAllowedOptionsAsString( allowed_options );
112             if ( dissallowed_options.length() > 0 ) {
113                 ForesterUtil.fatalError( PRG_NAME, "unknown option(s): " + dissallowed_options );
114             }
115             double cutoff_specifics = SPECIFICS_CUTOFF_DEFAULT;
116             if ( cla.isOptionSet( SPECIFICS_CUTOFF_OPTION ) ) {
117                 if ( cla.isOptionValueSet( SPECIFICS_CUTOFF_OPTION ) ) {
118                     cutoff_specifics = cla.getOptionValueAsDouble( SPECIFICS_CUTOFF_OPTION );
119                     if ( cutoff_specifics < 0 ) {
120                         ForesterUtil.fatalError( PRG_NAME, "cutoff cannot be negative" );
121                     }
122                 }
123                 else {
124                     ForesterUtil.fatalError( PRG_NAME, "no value for cutoff for specifics" );
125                 }
126             }
127             String separator = SEP_DEFAULT;
128             if ( cla.isOptionSet( SEP_OPTION ) ) {
129                 if ( cla.isOptionValueSet( SEP_OPTION ) ) {
130                     separator = cla.getOptionValue( SEP_OPTION );
131                 }
132                 else {
133                     ForesterUtil.fatalError( PRG_NAME, "no value for separator option" );
134                 }
135             }
136             Pattern compiled_query_str = null;
137             if ( cla.isOptionSet( QUERY_PATTERN_OPTION ) ) {
138                 if ( cla.isOptionValueSet( QUERY_PATTERN_OPTION ) ) {
139                     final String query_str = cla.getOptionValue( QUERY_PATTERN_OPTION );
140                     try {
141                         compiled_query_str = Pattern.compile( query_str );
142                     }
143                     catch ( final PatternSyntaxException e ) {
144                         ForesterUtil.fatalError( PRG_NAME, "error in regular expression: " + e.getMessage() );
145                     }
146                 }
147                 else {
148                     ForesterUtil.fatalError( PRG_NAME, "no value for query pattern option" );
149                 }
150             }
151             File mapping_file = null;
152             if ( cla.isOptionSet( MAPPING_FILE_OPTION ) ) {
153                 if ( cla.isOptionValueSet( MAPPING_FILE_OPTION ) ) {
154                     final String mapping_file_str = cla.getOptionValue( MAPPING_FILE_OPTION );
155                     final String error = ForesterUtil.isReadableFile( mapping_file_str );
156                     if ( !ForesterUtil.isEmpty( error ) ) {
157                         ForesterUtil.fatalError( PRG_NAME, error );
158                     }
159                     mapping_file = new File( mapping_file_str );
160                 }
161                 else {
162                     ForesterUtil.fatalError( PRG_NAME, "no value for mapping file" );
163                 }
164             }
165             final Pattern pattern = ( compiled_query_str != null ) ? compiled_query_str : QUERY_PATTERN_DEFAULT;
166             final File intreefile = cla.getFile( 0 );
167             final String error_intreefile = ForesterUtil.isReadableFile( intreefile );
168             if ( !ForesterUtil.isEmpty( error_intreefile ) ) {
169                 ForesterUtil.fatalError( PRG_NAME, error_intreefile );
170             }
171             final File outtablefile;
172             if ( cla.getNumberOfNames() > 1 ) {
173                 outtablefile = cla.getFile( 1 );
174                 final String error_outtablefile = ForesterUtil.isWritableFile( outtablefile );
175                 if ( !ForesterUtil.isEmpty( error_outtablefile ) ) {
176                     ForesterUtil.fatalError( PRG_NAME, error_outtablefile );
177                 }
178             }
179             else {
180                 outtablefile = null;
181             }
182             final BasicTable<String> t;
183             final SortedMap<String, String> map;
184             if ( mapping_file != null ) {
185                 t = BasicTableParser.parse( mapping_file, '\t' );
186                 if ( t.getNumberOfColumns() != 2 ) {
187                     ForesterUtil.fatalError( PRG_NAME,
188                                              "mapping file needs to have 2 tab-separated columns, not "
189                                                      + t.getNumberOfColumns() );
190                 }
191                 map = t.getColumnsAsMap( 0, 1 );
192             }
193             else {
194                 t = null;
195                 map = null;
196             }
197             final boolean extra_processing1;
198             if ( cla.isOptionSet( EXTRA_PROCESSING_OPTION1 ) ) {
199                 extra_processing1 = true;
200             }
201             else {
202                 extra_processing1 = false;
203             }
204             String extra_processing1_sep = EXTRA_PROCESSING1_SEP_DEFAULT;
205             if ( cla.isOptionSet( EXTRA_PROCESSING1_SEP_OPTION ) ) {
206                 if ( !extra_processing1 ) {
207                     ForesterUtil.fatalError( PRG_NAME,
208                                              "extra processing is not enabled, cannot set -"
209                                                      + EXTRA_PROCESSING1_SEP_OPTION + " option" );
210                 }
211                 if ( cla.isOptionValueSet( EXTRA_PROCESSING1_SEP_OPTION ) ) {
212                     extra_processing1_sep = cla.getOptionValue( EXTRA_PROCESSING1_SEP_OPTION );
213                 }
214                 else {
215                     ForesterUtil.fatalError( PRG_NAME, "no value for extra processing separator" );
216                 }
217             }
218             if ( ( extra_processing1_sep != null ) && extra_processing1_sep.equals( separator ) ) {
219                 ForesterUtil.fatalError( PRG_NAME,
220                                          "extra processing separator must not be the same the annotation-separator" );
221             }
222             boolean extra_processing1_keep = EXTRA_PROCESSING1_KEEP_EXTRA_DEFAULT;
223             if ( cla.isOptionSet( EXTRA_PROCESSING1_KEEP_EXTRA_OPTION ) ) {
224                 if ( !extra_processing1 ) {
225                     ForesterUtil.fatalError( PRG_NAME,
226                                              "extra processing is not enabled, cannot set -"
227                                                      + EXTRA_PROCESSING1_KEEP_EXTRA_OPTION + " option" );
228                 }
229                 extra_processing1_keep = true;
230             }
231             final boolean verbose;
232             if ( cla.isOptionSet( VERBOSE_OPTION ) ) {
233                 verbose = true;
234             }
235             else {
236                 verbose = false;
237             }
238             System.out.println( "Input tree                 : " + intreefile );
239             System.out.println( "Specific-hit support cutoff: " + cutoff_specifics );
240             if ( mapping_file != null ) {
241                 System.out.println( "Mapping file               : " + mapping_file + " (" + t.getNumberOfRows()
242                         + " rows)" );
243             }
244             System.out.println( "Annotation-separator       : " + separator );
245             System.out.println( "Query pattern              : " + pattern );
246             System.out.println( "Extra processing           : " + extra_processing1 );
247             if ( extra_processing1 ) {
248                 System.out.println( "Extra processing separator : " + extra_processing1_sep );
249                 System.out.println( "Keep extra annotations     : " + extra_processing1_keep );
250             }
251             if ( outtablefile != null ) {
252                 System.out.println( "Output table               : " + outtablefile );
253             }
254             Phylogeny phys[] = null;
255             try {
256                 final PhylogenyFactory factory = ParserBasedPhylogenyFactory.getInstance();
257                 final PhylogenyParser pp = ParserUtils.createParserDependingOnFileType( intreefile, true );
258                 phys = factory.create( intreefile, pp );
259             }
260             catch ( final IOException e ) {
261                 ForesterUtil.fatalError( PRG_NAME, "Could not read \"" + intreefile + "\" [" + e.getMessage() + "]" );
262             }
263             if ( phys.length == 0 ) {
264                 ForesterUtil.fatalError( PRG_NAME, "\"" + intreefile + "\" does not contain any trees" );
265             }
266             System.out.println( "Number of input trees      : " + phys.length );
267             if ( phys.length == 1 ) {
268                 System.out.println( "Ext. nodes in input tree 1 : " + phys[ 0 ].getNumberOfExternalNodes() );
269             }
270             else {
271                 System.out.println( "Ext. nodes in input tree   : " + phys[ 0 ].getNumberOfExternalNodes() );
272             }
273             final EasyWriter outtable_writer;
274             if ( outtablefile != null ) {
275                 outtable_writer = ForesterUtil.createEasyWriter( outtablefile );
276             }
277             else {
278                 outtable_writer = null;
279             }
280             for( final Phylogeny phy : phys ) {
281                 if ( map != null ) {
282                     AnalysisMulti.performMapping( pattern, map, phy, verbose );
283                 }
284                 if ( extra_processing1 ) {
285                     AnalysisMulti.performExtraProcessing1( pattern,
286                                                            phy,
287                                                            extra_processing1_sep,
288                                                            extra_processing1_keep,
289                                                            separator,
290                                                            verbose );
291                 }
292                 final ResultMulti res = AnalysisMulti.execute( phy, pattern, separator, cutoff_specifics );
293                 printResult( res );
294                 if ( outtable_writer != null ) {
295                     writeResultToTable( res, outtable_writer );
296                     outtable_writer.flush();
297                 }
298             }
299             if ( outtable_writer != null ) {
300                 outtable_writer.flush();
301                 outtable_writer.close();
302             }
303         }
304         catch ( final UserException e ) {
305             ForesterUtil.fatalError( PRG_NAME, e.getMessage() );
306         }
307         catch ( final IOException e ) {
308             ForesterUtil.fatalError( PRG_NAME, e.getMessage() );
309         }
310         catch ( final Exception e ) {
311             e.printStackTrace();
312             ForesterUtil.fatalError( PRG_NAME, "Unexpected errror!" );
313         }
314     }
315
316     private final static void printResult( final ResultMulti res ) {
317         System.out.println();
318         System.out.println( "Result for " + res.getQueryNamePrefix() );
319         System.out.println();
320         if ( ( res.getAllMultiHitPrefixes() == null ) | ( res.getAllMultiHitPrefixes().size() < 1 ) ) {
321             System.out.println( " No match to query pattern!" );
322         }
323         else {
324             System.out.println( " Matching Clade(s):" );
325             for( final Prefix prefix : res.getCollapsedMultiHitPrefixes() ) {
326                 System.out.println( " " + prefix );
327             }
328             if ( res.isHasSpecificMultiHitsPrefixes() ) {
329                 System.out.println();
330                 System.out.println( " Specific-hit(s):" );
331                 for( final Prefix prefix : res.getSpecificMultiHitPrefixes() ) {
332                     System.out.println( " " + prefix );
333                 }
334                 System.out.println();
335                 System.out.println( " Matching Clade(s) with Specific-hit(s):" );
336                 for( final Prefix prefix : res.getCollapsedMultiHitPrefixes() ) {
337                     System.out.println( " " + prefix );
338                     for( final Prefix spec : res.getSpecificMultiHitPrefixes() ) {
339                         if ( spec.getPrefix().startsWith( prefix.getPrefix() ) ) {
340                             System.out.println( "     " + spec );
341                         }
342                     }
343                 }
344             }
345             if ( !ForesterUtil.isEmpty( res.getAllMultiHitPrefixesDown() ) ) {
346                 System.out.println();
347                 System.out.println( " Matching Down-tree Bracketing Clade(s):" );
348                 for( final Prefix prefix : res.getCollapsedMultiHitPrefixesDown() ) {
349                     System.out.println( " " + prefix );
350                 }
351             }
352             if ( !ForesterUtil.isEmpty( res.getAllMultiHitPrefixesUp() ) ) {
353                 System.out.println();
354                 System.out.println( " Matching Up-tree Bracketing Clade(s):" );
355                 for( final Prefix prefix : res.getCollapsedMultiHitPrefixesUp() ) {
356                     System.out.println( " " + prefix );
357                 }
358             }
359         }
360         System.out.println();
361     }
362
363     private final static void writeResultToTable( final ResultMulti res, final EasyWriter w ) throws IOException {
364         if ( ( res.getAllMultiHitPrefixes() == null ) | ( res.getAllMultiHitPrefixes().size() < 1 ) ) {
365             w.print( res.getQueryNamePrefix() );
366             w.print( "\t" );
367             w.println( "No match to query pattern!" );
368         }
369         else {
370             for( final Prefix prefix : res.getCollapsedMultiHitPrefixes() ) {
371                 w.print( res.getQueryNamePrefix() );
372                 w.print( "\t" );
373                 w.print( "Matching Clades" );
374                 w.print( "\t" );
375                 w.print( prefix.getPrefix() );
376                 w.print( "\t" );
377                 w.print( df.format( prefix.getConfidence() ) );
378                 w.println();
379             }
380             if ( res.isHasSpecificMultiHitsPrefixes() ) {
381                 for( final Prefix prefix : res.getSpecificMultiHitPrefixes() ) {
382                     w.print( res.getQueryNamePrefix() );
383                     w.print( "\t" );
384                     w.print( "Specific-hits" );
385                     w.print( "\t" );
386                     w.print( prefix.getPrefix() );
387                     w.print( "\t" );
388                     w.print( df.format( prefix.getConfidence() ) );
389                     w.println();
390                 }
391             }
392             if ( !ForesterUtil.isEmpty( res.getAllMultiHitPrefixesDown() ) ) {
393                 for( final Prefix prefix : res.getCollapsedMultiHitPrefixesDown() ) {
394                     w.print( res.getQueryNamePrefix() );
395                     w.print( "\t" );
396                     w.print( "Matching Down-tree Bracketing Clades" );
397                     w.print( "\t" );
398                     w.print( prefix.getPrefix() );
399                     w.print( "\t" );
400                     w.print( df.format( prefix.getConfidence() ) );
401                     w.println();
402                 }
403             }
404             if ( !ForesterUtil.isEmpty( res.getAllMultiHitPrefixesUp() ) ) {
405                 for( final Prefix prefix : res.getCollapsedMultiHitPrefixesUp() ) {
406                     w.print( res.getQueryNamePrefix() );
407                     w.print( "\t" );
408                     w.print( "Matching Up-tree Bracketing Clades" );
409                     w.print( "\t" );
410                     w.print( prefix.getPrefix() );
411                     w.print( "\t" );
412                     w.print( df.format( prefix.getConfidence() ) );
413                     w.println();
414                 }
415             }
416         }
417     }
418
419     private final static void print_help() {
420         System.out.println( "Usage:" );
421         System.out.println();
422         System.out.println( PRG_NAME + " [options] <input tree(s) file> [output table file]" );
423         System.out.println();
424         System.out.println( " options:" );
425         System.out.println( "  -" + SPECIFICS_CUTOFF_OPTION
426                 + "=<double>        : the cutoff for \"specific-hit\" support values (default: "
427                 + SPECIFICS_CUTOFF_DEFAULT + ")" );
428         System.out.println( "  -" + SEP_OPTION + "=<separator>     : the annotation-separator to be used (default: "
429                 + SEP_DEFAULT + ")" );
430         System.out.println( "  -" + MAPPING_FILE_OPTION
431                 + "=<mapping table> : to map node names to appropriate annotations (tab-separated, two columns) (default: no mapping)" );
432         System.out.println( "  -" + EXTRA_PROCESSING_OPTION1
433                 + "                 : to enable extra processing of annotations (e.g. \"Q16611|A.1.1\" becomes \"A.1.1\")" );
434         System.out.println( "  -" + EXTRA_PROCESSING1_SEP_OPTION
435                 + "=<separator>    : the separator for extra annotations (default: \"" + EXTRA_PROCESSING1_SEP_DEFAULT
436                 + "\")" );
437         System.out.println( "  -" + EXTRA_PROCESSING1_KEEP_EXTRA_OPTION
438                 + "                : to keep extra annotations (e.g. \"Q16611|A.1.1\" becomes \"A.1.1.Q16611\")" );
439         System.out.println( "  -" + VERBOSE_OPTION + "                 : verbose" );
440         System.out.println( "  --" + QUERY_PATTERN_OPTION
441                 + "=<query pattern>: the regular expression for the query (default: \"" + QUERY_PATTERN_DEFAULT
442                 + "\" for pplacer output)" );
443         System.out.println();
444         System.out.println( "Examples:" );
445         System.out.println();
446         System.out.println( " " + PRG_NAME + " my_tree.nh result.tsv" );
447         System.out.println( " " + PRG_NAME + " -c=0.5 -s=. my_tree.nh result.tsv" );
448         System.out.println( " " + PRG_NAME + " -c=0.9 -s=_ -m=map.tsv my_tree.nh result.tsv" );
449         System.out.println( " " + PRG_NAME + " -x -xs=& -xk my_tree.nh result.tsv" );
450         System.out.println( " " + PRG_NAME + " -x -xs=\"|\" my_tree.nh result.tsv" );
451         System.out.println( " " + PRG_NAME + " -x -xk -m=map.tsv pplacer_out_trees.sing.tre result.tsv" );
452         System.out.println();
453     }
454 }