32e21c821a135607ea4126209adf0b4662c5cd16
[jalview.git] / forester / java / src / org / forester / archaeopteryx / tools / PhyloInferenceDialog.java
1 // $Id:
2 // forester -- software libraries and applications
3 // for genomics and evolutionary biology research.
4 //
5 // Copyright (C) 2010 Christian M Zmasek
6 // Copyright (C) 2010 Sanford-Burnham Medical Research 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: phylosoft @ gmail . com
24 // WWW: www.phylosoft.org/forester
25
26 package org.forester.archaeopteryx.tools;
27
28 import java.awt.Color;
29 import java.awt.FlowLayout;
30 import java.awt.event.ActionEvent;
31 import java.awt.event.ActionListener;
32 import java.util.List;
33
34 import javax.swing.BoxLayout;
35 import javax.swing.ButtonGroup;
36 import javax.swing.JButton;
37 import javax.swing.JCheckBox;
38 import javax.swing.JDialog;
39 import javax.swing.JFormattedTextField;
40 import javax.swing.JLabel;
41 import javax.swing.JOptionPane;
42 import javax.swing.JPanel;
43 import javax.swing.JRadioButton;
44 import javax.swing.JTextField;
45 import javax.swing.border.Border;
46 import javax.swing.border.LineBorder;
47
48 import org.forester.archaeopteryx.AptxUtil;
49 import org.forester.archaeopteryx.MainFrameApplication;
50 import org.forester.evoinference.distance.PairwiseDistanceCalculator.PWD_DISTANCE_METHOD;
51 import org.forester.sequence.Sequence;
52 import org.forester.util.BasicDescriptiveStatistics;
53 import org.forester.util.DescriptiveStatistics;
54
55 public class PhyloInferenceDialog extends JDialog implements ActionListener {
56
57     private static final long                  serialVersionUID = 8337543508238133614L;
58     private final JPanel                       _pnl;
59     private final JButton                      _launch_btn;
60     private final JButton                      _cancel_btn;
61     private final JFormattedTextField          _bootstrap_tf;
62     private final JCheckBox                    _bootstrap_cb;
63     private final PhylogeneticInferenceOptions _opts;
64     private JTextField                         _input_msa_file_tf;
65     private JButton                            _select_input_msa_btn;
66     private final MainFrameApplication         _parent_frame;
67     private JTextField                         _msa_length_tf;
68     private JTextField                         _msa_size_tf;
69     private JTextField                         _msa_type_tf;
70     private final JRadioButton                 _distance_calc_kimura_rb;
71     private final JRadioButton                 _distance_calc_poisson_rb;
72     private final JRadioButton                 _distance_calc_fract_dissimilarity_rb;
73     private int                                _value           = JOptionPane.CANCEL_OPTION;
74     private JTextField                         _input_seqs_tf;
75     private JButton                            _select_input_seqs_btn;
76     private JTextField                         _input_seqs_number_tf;
77     private JTextField                         _input_seqs_median_length_tf;
78     private JTextField                         _input_seqs_min_length_tf;
79     private JTextField                         _input_seqs_max_length_tf;
80     private JTextField                         _input_seqs_type_tf;
81     private JTextField                         _mafft_paramenters_tf;
82     private JTextField                         _clustalo_paramenters_tf;
83     private JTextField                         _msa_processing_max_allowed_gap_ratio_tf;
84     private JTextField                         _msa_processing_min_allowed_length_tf;
85     private JTextField                         _random_seed_tf;
86     private JCheckBox                          _execute_msa_processing_cb;
87     private JCheckBox                          _msa_processing_remove_all_gap_columns_cb;
88     private JCheckBox                          _mafft_cb;
89     private JCheckBox                          _clustalo_cb;
90     private JCheckBox                          _save_pwd_file_cb;
91     private JCheckBox                          _save_processed_msa_cb;
92     private JCheckBox                          _save_original_msa_cb;
93     private JTextField                         _pwd_outfile_tf;
94     private JTextField                         _processed_msa_outfile_tf;
95     private JTextField                         _original_msa_outfile_tf;
96
97     public PhyloInferenceDialog( final MainFrameApplication frame,
98                                  final PhylogeneticInferenceOptions options,
99                                  final boolean from_unaligned_seqs ) {
100         super( frame, true );
101         setVisible( false );
102         _parent_frame = frame;
103         _opts = options;
104         _pnl = new JPanel();
105         getContentPane().add( _pnl );
106         final BoxLayout box_layout = new BoxLayout( _pnl, BoxLayout.PAGE_AXIS );
107         _pnl.setLayout( box_layout );
108         if ( from_unaligned_seqs ) {
109             setTitle( "Phylogenetic Inference (including multiple sequence alignment)" );
110             final JPanel inputfile_pnl_1 = new JPanel();
111             final JPanel inputfile_pnl_2 = new JPanel();
112             final JPanel inputfile_pnl_3 = new JPanel();
113             final JPanel inputfile_pnl_4 = new JPanel();
114             inputfile_pnl_1.setLayout( new FlowLayout() );
115             inputfile_pnl_2.setLayout( new FlowLayout() );
116             inputfile_pnl_3.setLayout( new FlowLayout() );
117             inputfile_pnl_4.setLayout( new FlowLayout() );
118             inputfile_pnl_1.add( new JLabel( "Input Sequence File:" ) );
119             inputfile_pnl_1.add( _input_seqs_tf = new JTextField() );
120             inputfile_pnl_1.add( _select_input_seqs_btn = new JButton( "Select Input File" ) );
121             inputfile_pnl_2.add( new JLabel( "Sequences: " ) );
122             inputfile_pnl_2.add( new JLabel( "Number of Sequences:" ) );
123             inputfile_pnl_2.add( _input_seqs_number_tf = new JTextField() );
124             inputfile_pnl_2.add( new JLabel( "Length: median:" ) );
125             inputfile_pnl_2.add( _input_seqs_median_length_tf = new JTextField() );
126             inputfile_pnl_2.add( new JLabel( "min:" ) );
127             inputfile_pnl_2.add( _input_seqs_min_length_tf = new JTextField() );
128             inputfile_pnl_2.add( new JLabel( "max:" ) );
129             inputfile_pnl_2.add( _input_seqs_max_length_tf = new JTextField() );
130             inputfile_pnl_2.add( new JLabel( "Type:" ) );
131             inputfile_pnl_2.add( _input_seqs_type_tf = new JTextField() );
132             inputfile_pnl_3.add( _mafft_cb = new JCheckBox( "MAFFT" ) );
133             inputfile_pnl_3.add( new JLabel( "Parameters: " ) );
134             inputfile_pnl_3.add( _mafft_paramenters_tf = new JTextField() );
135             inputfile_pnl_4.add( _clustalo_cb = new JCheckBox( "ClustalO" ) );
136             inputfile_pnl_4.add( new JLabel( "Parameters: " ) );
137             inputfile_pnl_4.add( _clustalo_paramenters_tf = new JTextField() );
138             _input_seqs_median_length_tf.setColumns( 4 );
139             _input_seqs_min_length_tf.setColumns( 4 );
140             _input_seqs_max_length_tf.setColumns( 4 );
141             _input_seqs_number_tf.setColumns( 4 );
142             _input_seqs_type_tf.setColumns( 2 );
143             _input_seqs_tf.setColumns( 20 );
144             _input_seqs_tf.setEditable( false );
145             _input_seqs_median_length_tf.setEditable( false );
146             _input_seqs_min_length_tf.setEditable( false );
147             _input_seqs_max_length_tf.setEditable( false );
148             _input_seqs_number_tf.setEditable( false );
149             _input_seqs_type_tf.setEditable( false );
150             _mafft_paramenters_tf.setColumns( 26 );
151             _mafft_paramenters_tf.setText( "--maxiterate 1000 --localpair" );
152             _clustalo_paramenters_tf.setColumns( 26 );
153             _clustalo_paramenters_tf.setText( "clustalo options" );
154             _select_input_seqs_btn.addActionListener( this );
155             _pnl.add( inputfile_pnl_1 );
156             _pnl.add( inputfile_pnl_2 );
157             _pnl.add( inputfile_pnl_3 );
158             _pnl.add( inputfile_pnl_4 );
159         }
160         else {
161             setTitle( "Phylogenetic Inference (from already aligned sequences) " );
162             // Inputfile (MSA):
163             final JPanel inputfile_pnl_1 = new JPanel();
164             final JPanel inputfile_pnl_2 = new JPanel();
165             inputfile_pnl_1.setLayout( new FlowLayout() );
166             inputfile_pnl_2.setLayout( new FlowLayout() );
167             inputfile_pnl_1.add( new JLabel( "Input MSA File:" ) );
168             inputfile_pnl_1.add( _input_msa_file_tf = new JTextField() );
169             inputfile_pnl_1.add( _select_input_msa_btn = new JButton( "Select Input File" ) );
170             inputfile_pnl_2.add( new JLabel( "MSA: " ) );
171             inputfile_pnl_2.add( new JLabel( "Number of Sequences:" ) );
172             inputfile_pnl_2.add( _msa_size_tf = new JTextField() );
173             inputfile_pnl_2.add( new JLabel( "Length:" ) );
174             inputfile_pnl_2.add( _msa_length_tf = new JTextField() );
175             inputfile_pnl_2.add( new JLabel( "Type:" ) );
176             inputfile_pnl_2.add( _msa_type_tf = new JTextField() );
177             _msa_length_tf.setColumns( 4 );
178             _msa_size_tf.setColumns( 4 );
179             _msa_type_tf.setColumns( 2 );
180             _input_msa_file_tf.setColumns( 20 );
181             _input_msa_file_tf.setEditable( false );
182             _msa_length_tf.setEditable( false );
183             _msa_size_tf.setEditable( false );
184             _msa_type_tf.setEditable( false );
185             _select_input_msa_btn.addActionListener( this );
186             _pnl.add( inputfile_pnl_1 );
187             _pnl.add( inputfile_pnl_2 );
188         }
189         //
190         final JPanel inputfile_pnl_4 = new JPanel();
191         inputfile_pnl_4.setLayout( new FlowLayout() );
192         inputfile_pnl_4.add( new JLabel( "MSA Processing: " ) );
193         inputfile_pnl_4.add( _execute_msa_processing_cb = new JCheckBox( "Process MSA" ) );
194         inputfile_pnl_4.add( _msa_processing_remove_all_gap_columns_cb = new JCheckBox( "Remove all gap columns" ) );
195         inputfile_pnl_4.add( new JLabel( "Max allowed gap ratio: " ) );
196         inputfile_pnl_4.add( _msa_processing_max_allowed_gap_ratio_tf = new JTextField() );
197         inputfile_pnl_4.add( new JLabel( "Min allowed non-gap sequence length: " ) );
198         inputfile_pnl_4.add( _msa_processing_min_allowed_length_tf = new JTextField() );
199         _msa_processing_max_allowed_gap_ratio_tf.setColumns( 4 );
200         _msa_processing_min_allowed_length_tf.setColumns( 4 );
201         final Border b = new LineBorder( Color.DARK_GRAY );
202         inputfile_pnl_4.setBorder( b );
203         _pnl.add( inputfile_pnl_4 );
204         //
205         // Distance calculation:
206         // TODO if type==AA...
207         final JPanel distance_calc_pnl_1 = new JPanel();
208         distance_calc_pnl_1.setLayout( new FlowLayout() );
209         distance_calc_pnl_1.add( new JLabel( "Distance calculation:" ) );
210         distance_calc_pnl_1.add( _distance_calc_kimura_rb = new JRadioButton( "Kimura correction" ) );
211         distance_calc_pnl_1.add( _distance_calc_poisson_rb = new JRadioButton( "Poisson" ) );
212         distance_calc_pnl_1
213                 .add( _distance_calc_fract_dissimilarity_rb = new JRadioButton( "Fractional dissimilarity" ) );
214         final ButtonGroup distance_calc_group_1 = new ButtonGroup();
215         distance_calc_group_1.add( _distance_calc_kimura_rb );
216         distance_calc_group_1.add( _distance_calc_poisson_rb );
217         distance_calc_group_1.add( _distance_calc_fract_dissimilarity_rb );
218         _pnl.add( distance_calc_pnl_1 );
219         // Bootstrap resampling:
220         final JPanel bootstrap_pnl = new JPanel();
221         bootstrap_pnl.setLayout( new FlowLayout() );
222         bootstrap_pnl.add( _bootstrap_cb = new JCheckBox( "Perform Bootstrap Resampling" ) );
223         bootstrap_pnl.add( new JLabel( "Number of Bootstrap Samples:" ) );
224         bootstrap_pnl.add( _bootstrap_tf = new JFormattedTextField( AptxUtil.createMaskFormatter( "###" ) ) );
225         _bootstrap_tf.setColumns( 4 );
226         // TODO see
227         // http://download.oracle.com/javase/tutorial/uiswing/components/formattedtextfield.html
228         // _bootstrap_tf.setColumns( 4 );
229         bootstrap_pnl.add( new JLabel( "Random Seed:" ) );
230         bootstrap_pnl.add( _random_seed_tf = new JTextField() );
231         _random_seed_tf.setColumns( 4 );
232         _pnl.add( bootstrap_pnl );
233         final JPanel launch_pnl = new JPanel();
234         launch_pnl.setLayout( new FlowLayout() );
235         _launch_btn = new JButton( "Go!" );
236         _launch_btn.addActionListener( this );
237         launch_pnl.add( _launch_btn );
238         _cancel_btn = new JButton( "Cancel" );
239         _cancel_btn.addActionListener( this );
240         launch_pnl.add( _cancel_btn );
241         _pnl.add( launch_pnl );
242         initializeValues( from_unaligned_seqs );
243         pack();
244         setLocationRelativeTo( getParentFrame() );
245         setResizable( false );
246     }
247
248     @Override
249     public void actionPerformed( final ActionEvent e ) {
250         if ( e.getSource() == _select_input_msa_btn ) {
251             readInputFile();
252         }
253         else if ( e.getSource() == _select_input_seqs_btn ) {
254             readInputSeqsFile();
255         }
256         else if ( e.getSource() == _launch_btn ) {
257             launch();
258         }
259         else if ( e.getSource() == _cancel_btn ) {
260             cancel();
261         }
262     }
263
264     public void activate() {
265         setVisible( true );
266     }
267
268     private MainFrameApplication getParentFrame() {
269         return _parent_frame;
270     }
271
272     public PhylogeneticInferenceOptions getPhylogeneticInferenceOptions() {
273         return _opts;
274     }
275
276     public int getValue() {
277         return _value;
278     }
279
280     private void initializeValues( final boolean from_unaligned_seqs ) {
281         _value = JOptionPane.CANCEL_OPTION;
282         if ( from_unaligned_seqs ) {
283             updateSeqsItems();
284         }
285         else {
286             updateMsaItems();
287         }
288         updateMsaProcessingItem();
289         updateDistanceCalcMethod();
290         _bootstrap_tf.setText( getPhylogeneticInferenceOptions().getBootstrapSamples() + "" );
291         _random_seed_tf.setText( getPhylogeneticInferenceOptions().getRandomNumberGeneratorSeed() + "" );
292     }
293
294     private void launch() {
295         processPerformBootstrapResampling();
296         if ( _bootstrap_cb.isSelected() ) {
297             processBootstrapSamplesNumber();
298             processRandomNumberGeneratorSeed();
299         }
300         if ( true ) {
301             //TODO
302             processMsaProcessing();
303         }
304         processDistanceCalcMethod();
305         processMsaPrgParameters();
306         setVisible( false );
307         _value = JOptionPane.OK_OPTION;
308     }
309
310     private void cancel() {
311         setVisible( false );
312         _value = JOptionPane.CANCEL_OPTION;
313     }
314
315     private void processBootstrapSamplesNumber() {
316         int bootstrap_samples = 0;
317         try {
318             bootstrap_samples = Integer.parseInt( _bootstrap_tf.getText().trim() );
319         }
320         catch ( final NumberFormatException e ) {
321             // JOptionPane.showMessageDialog( this, "Could not parse number of bootstrap resamplings from: " +  _bootstrap_tf.getText().trim(), "User Error", JOptionPane.ERROR_MESSAGE );
322             return;
323         }
324         if ( bootstrap_samples >= 0 ) {
325             getPhylogeneticInferenceOptions().setBootstrapSamples( bootstrap_samples );
326         }
327     }
328
329     private void processRandomNumberGeneratorSeed() {
330         long seed = PhylogeneticInferenceOptions.RANDOM_NUMBER_SEED_DEFAULT;
331         try {
332             seed = Long.parseLong( _random_seed_tf.getText().trim() );
333         }
334         catch ( final NumberFormatException e ) {
335             return;
336         }
337         getPhylogeneticInferenceOptions().setRandomNumberGeneratorSeed( seed );
338     }
339
340     private void processMsaProcessing() {
341         getPhylogeneticInferenceOptions().setExecuteMsaProcessing( _execute_msa_processing_cb.isSelected() );
342         getPhylogeneticInferenceOptions()
343                 .setMsaProcessingRemoveAllGapColumns( _msa_processing_remove_all_gap_columns_cb.isSelected() );
344         int min_length = -1;
345         try {
346             min_length = Integer.parseInt( _msa_processing_min_allowed_length_tf.getText().trim() );
347         }
348         catch ( final NumberFormatException e ) {
349             min_length = -1;
350         }
351         if ( min_length > 0 ) {
352             getPhylogeneticInferenceOptions().setMsaProcessingMinAllowedLength( min_length );
353         }
354         double msa_processing_max_allowed_gap_ratio = -1.0;
355         try {
356             msa_processing_max_allowed_gap_ratio = Double.parseDouble( _msa_processing_max_allowed_gap_ratio_tf
357                     .getText().trim() );
358         }
359         catch ( final NumberFormatException e ) {
360             msa_processing_max_allowed_gap_ratio = -1.0;
361         }
362         if ( ( msa_processing_max_allowed_gap_ratio >= 0.0 ) && ( msa_processing_max_allowed_gap_ratio <= 1.0 ) ) {
363             getPhylogeneticInferenceOptions().setMsaProcessingMaxAllowedGapRatio( msa_processing_max_allowed_gap_ratio );
364         }
365     }
366
367     private void processDistanceCalcMethod() {
368         if ( ( _distance_calc_kimura_rb != null ) && _distance_calc_kimura_rb.isSelected() ) {
369             getPhylogeneticInferenceOptions().setPwdDistanceMethod( PWD_DISTANCE_METHOD.KIMURA_DISTANCE );
370         }
371         else if ( ( _distance_calc_poisson_rb != null ) && _distance_calc_poisson_rb.isSelected() ) {
372             getPhylogeneticInferenceOptions().setPwdDistanceMethod( PWD_DISTANCE_METHOD.POISSON_DISTANCE );
373         }
374         else if ( ( _distance_calc_fract_dissimilarity_rb != null )
375                 && _distance_calc_fract_dissimilarity_rb.isSelected() ) {
376             getPhylogeneticInferenceOptions().setPwdDistanceMethod( PWD_DISTANCE_METHOD.FRACTIONAL_DISSIMILARITY );
377         }
378     }
379
380     private void processPerformBootstrapResampling() {
381         getPhylogeneticInferenceOptions().setPerformBootstrapResampling( _bootstrap_cb.isSelected() );
382     }
383
384     private void processMsaPrgParameters() {
385         if ( _mafft_paramenters_tf != null ) {
386             getPhylogeneticInferenceOptions().setMsaPrgParameters( _mafft_paramenters_tf.getText() );
387         }
388     }
389
390     private void readInputFile() {
391         getParentFrame().readMsaFromFile();
392         updateMsaItems();
393     }
394
395     private void readInputSeqsFile() {
396         getParentFrame().readSeqsFromFile();
397         updateSeqsItems();
398     }
399
400     private void updateDistanceCalcMethod() {
401         switch ( getPhylogeneticInferenceOptions().getPwdDistanceMethod() ) {
402             case KIMURA_DISTANCE:
403                 _distance_calc_kimura_rb.setSelected( true );
404                 break;
405             case POISSON_DISTANCE:
406                 _distance_calc_poisson_rb.setSelected( true );
407                 break;
408             case FRACTIONAL_DISSIMILARITY:
409                 _distance_calc_fract_dissimilarity_rb.setSelected( true );
410                 break;
411             default:
412                 throw new RuntimeException( "invalid distance calc method" );
413         }
414     }
415
416     private void updateMsaProcessingItem() {
417         _execute_msa_processing_cb.setSelected( getPhylogeneticInferenceOptions().isExecuteMsaProcessing() );
418         _msa_processing_remove_all_gap_columns_cb.setSelected( getPhylogeneticInferenceOptions()
419                 .isMsaProcessingRemoveAllGapColumns() );
420         if ( _opts.getMsaProcessingMaxAllowedGapRatio() > 0 ) {
421             _msa_processing_max_allowed_gap_ratio_tf.setText( _opts.getMsaProcessingMaxAllowedGapRatio() + "" );
422         }
423         if ( _opts.getMsaProcessingMinAllowedLength() > 0 ) {
424             _msa_processing_min_allowed_length_tf.setText( _opts.getMsaProcessingMinAllowedLength() + "" );
425         }
426     }
427
428     private void updateMsaItems() {
429         if ( getParentFrame().getMsa() != null ) {
430             _input_msa_file_tf.setText( getParentFrame().getMsaFile().toString() );
431             _msa_length_tf.setText( getParentFrame().getMsa().getLength() + "" );
432             _msa_size_tf.setText( getParentFrame().getMsa().getNumberOfSequences() + "" );
433             _msa_type_tf.setText( getParentFrame().getMsa().getType() + "" );
434             _input_msa_file_tf.setEnabled( true );
435             _msa_length_tf.setEnabled( true );
436             _msa_size_tf.setEnabled( true );
437             _msa_type_tf.setEnabled( true );
438             _launch_btn.setEnabled( true );
439         }
440         else {
441             _input_msa_file_tf.setText( "" );
442             _msa_length_tf.setText( "" );
443             _msa_size_tf.setText( "" );
444             _msa_type_tf.setText( "" );
445             _input_msa_file_tf.setEnabled( false );
446             _msa_length_tf.setEnabled( false );
447             _msa_size_tf.setEnabled( false );
448             _msa_type_tf.setEnabled( false );
449             _launch_btn.setEnabled( false );
450         }
451     }
452
453     private void updateSeqsItems() {
454         if ( getParentFrame().getSeqs() != null ) {
455             final DescriptiveStatistics stats = calcSequenceStats( getParentFrame().getSeqs() );
456             _input_seqs_tf.setText( getParentFrame().getSeqsFile().toString() );
457             _input_seqs_median_length_tf.setText( ( int ) stats.median() + "" );
458             _input_seqs_min_length_tf.setText( ( int ) stats.getMin() + "" );
459             _input_seqs_max_length_tf.setText( ( int ) stats.getMax() + "" );
460             _input_seqs_number_tf.setText( getParentFrame().getSeqs().size() + "" );
461             _input_seqs_type_tf.setText( getParentFrame().getSeqs().get( 0 ).getType() + "" );
462             _input_seqs_tf.setEnabled( true );
463             _input_seqs_median_length_tf.setEnabled( true );
464             _input_seqs_min_length_tf.setEnabled( true );
465             _input_seqs_max_length_tf.setEnabled( true );
466             _input_seqs_number_tf.setEnabled( true );
467             _input_seqs_type_tf.setEnabled( true );
468             _launch_btn.setEnabled( true );
469         }
470         else {
471             _input_seqs_tf.setText( "" );
472             _input_seqs_median_length_tf.setText( "" );
473             _input_seqs_min_length_tf.setText( "" );
474             _input_seqs_max_length_tf.setText( "" );
475             _input_seqs_number_tf.setText( "" );
476             _input_seqs_type_tf.setText( "" );
477             _input_seqs_tf.setEnabled( false );
478             _input_seqs_median_length_tf.setEnabled( false );
479             _input_seqs_min_length_tf.setEnabled( false );
480             _input_seqs_max_length_tf.setEnabled( false );
481             _input_seqs_number_tf.setEnabled( false );
482             _input_seqs_type_tf.setEnabled( false );
483             _launch_btn.setEnabled( false );
484         }
485     }
486
487     DescriptiveStatistics calcSequenceStats( final List<Sequence> seqs ) {
488         final DescriptiveStatistics stats = new BasicDescriptiveStatistics();
489         for( final Sequence s : seqs ) {
490             stats.addValue( s.getLength() );
491         }
492         return stats;
493     }
494 }