From: Jim Procter Date: Fri, 12 May 2017 14:09:45 +0000 (+0100) Subject: Merge branch 'features/JAL-2388OverviewWindow' into develop X-Git-Tag: Release_2_10_2~3^2~92 X-Git-Url: http://source.jalview.org/gitweb/?a=commitdiff_plain;h=e54e2eee927260cd650ffe17b1422b29b8b2f122;hp=140b0d3f1d7a31d14cb02deb55214bbad44874be;p=jalview.git Merge branch 'features/JAL-2388OverviewWindow' into develop --- diff --git a/examples/groovy/featureCounter.groovy b/examples/groovy/featureCounter.groovy deleted file mode 100644 index 9059dd0..0000000 --- a/examples/groovy/featureCounter.groovy +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) - * Copyright (C) $$Year-Rel$$ The Jalview Authors - * - * This file is part of Jalview. - * - * Jalview is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation, either version 3 - * of the License, or (at your option) any later version. - * - * Jalview is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Jalview. If not, see . - * The Jalview Authors are detailed in the 'AUTHORS' file. - */ - -import jalview.workers.FeatureCounterI; -import jalview.workers.AlignmentAnnotationFactory; - -/* - * Example script that registers two alignment annotation calculators - * - one that counts residues in a column with Pfam annotation - * - one that counts only charged residues with Pfam annotation - * - * To try: - * 1. load uniref50.fa from the examples folder - * 2. load features onto it from from examples/exampleFeatures.txt - * 3. Open this script in the Groovy console. - * 4. Either execute this script from the console, or via Calculate->Run Groovy Script - - * To explore further, try changing this script to count other kinds of occurrences of - * residue and sequence features at columns in an alignment. - */ - -/* - * A closure that returns true for any Charged residue - */ -def isCharged = { residue -> - switch(residue) { - case ['D', 'd', 'E', 'e', 'H', 'h', 'K', 'k', 'R', 'r']: - return true - } - false -} - -/* - * A closure that returns 1 if sequence features include type 'Pfam', else 0 - * Argument should be a list of SequenceFeature - */ -def hasPfam = { features -> - for (sf in features) - { - /* - * Here we inspect the type of the sequence feature. - * You can also test sf.description, sf.score, sf.featureGroup, - * sf.strand, sf.phase, sf.begin, sf.end - * or sf.getValue(attributeName) for GFF 'column 9' properties - */ - if ("Pfam".equals(sf.type)) - { - return true - } - } - false -} - -/* - * Closure that computes an annotation based on - * presence of particular residues and features - * Parameters are - * - the name (label) for the alignment annotation - * - the description (tooltip) for the annotation - * - a closure (groovy function) that tests whether to include a residue - * - a closure that tests whether to increment count based on sequence features - */ -def getColumnCounter = { name, desc, acceptResidue, acceptFeatures -> - [ - getName: { name }, - getDescription: { desc }, - getMinColour: { [0, 255, 255] }, // cyan - getMaxColour: { [0, 0, 255] }, // blue - count: - { res, feats -> - def c = 0 - if (acceptResidue.call(res)) - { - if (acceptFeatures.call(feats)) - { - c++ - } - } - c - } - ] as FeatureCounterI -} - -/* - * Define an annotation row that counts any residue with Pfam domain annotation - */ -def pfamAnnotation = getColumnCounter("Pfam", "Count of residues with Pfam domain annotation", {true}, hasPfam) - -/* - * Define an annotation row that counts charged residues with Pfam domain annotation - */ -def chargedPfamAnnotation = getColumnCounter("Pfam charged", "Count of charged residues with Pfam domain annotation", isCharged, hasPfam) - -/* - * Register the annotations - */ -AlignmentAnnotationFactory.newCalculator(pfamAnnotation) -AlignmentAnnotationFactory.newCalculator(chargedPfamAnnotation) diff --git a/examples/groovy/featuresCounter.groovy b/examples/groovy/featuresCounter.groovy new file mode 100644 index 0000000..dc4c97c --- /dev/null +++ b/examples/groovy/featuresCounter.groovy @@ -0,0 +1,73 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ + +import jalview.workers.AlignmentAnnotationFactory; +import jalview.workers.FeatureSetCounterI; + +/* + * Example script to compute two alignment annotations + * - count of Phosphorylation features + * - count of Turn features + * To try this, first load example file uniref50.fa and load on features file + * exampleFeatures.txt, before running this script + * + * The script only needs to be run once - it will be registered by Jalview + * and recalculated automatically when the alignment changes. + * + * Note: The feature api provided by 2.10.2 is not compatible with scripts + * that worked with earlier Jalview versions. Apologies for the inconvenience. + */ + +def annotator = + [ + getNames: { ['Phosphorylation', 'Turn'] as String[] }, + getDescriptions: { ['Count of Phosphorylation features', 'Count of Turn features'] as String[] }, + getMinColour: { [0, 255, 255] as int[] }, // cyan + getMaxColour: { [0, 0, 255] as int[] }, // blue + count: + { res, feats -> + int phos + int turn + for (sf in feats) + { + /* + * Here we inspect the type of the sequence feature. + * You can also test sf.description, sf.score, sf.featureGroup, + * sf.strand, sf.phase, sf.begin, sf.end + * or sf.getValue(attributeName) for GFF 'column 9' properties + */ + if (sf.type.contains('TURN')) + { + turn++ + } + if (sf.type.contains('PHOSPHORYLATION')) + { + phos++ + } + } + [phos, turn] as int[] + } + ] as FeatureSetCounterI + +/* + * Register the annotation calculator with Jalview + */ +AlignmentAnnotationFactory.newCalculator(annotator) diff --git a/examples/groovy/multipleFeatureAnnotations.groovy b/examples/groovy/multipleFeatureAnnotations.groovy deleted file mode 100644 index 592c7f5..0000000 --- a/examples/groovy/multipleFeatureAnnotations.groovy +++ /dev/null @@ -1,110 +0,0 @@ -import jalview.workers.AlignmentAnnotationFactory; -import jalview.workers.AnnotationProviderI; -import jalview.datamodel.AlignmentAnnotation; -import jalview.datamodel.Annotation; -import jalview.util.ColorUtils; -import jalview.util.Comparison; -import java.awt.Color; - -/* - * Example script to compute two alignment annotations - * - count of Phosphorylation features - * - count of Turn features - * To try this, first load example file uniref50.fa and load on features file - * exampleFeatures.txt, before running this script - * - * The script only needs to be run once - it will be registered by Jalview - * and recalculated automatically when the alignment changes. - */ - -/* - * A closure that returns true if value includes "PHOSPHORYLATION" - */ -def phosCounter = { type -> type.contains("PHOSPHORYLATION") } - -/* - * A closure that returns true if value includes "TURN" - */ -def turnCounter = { type -> type.contains("TURN") } - -/* - * A closure that computes and returns an array of Annotation values, - * one for each column of the alignment - */ -def getAnnotations(al, fr, counter) -{ - def width = al.width - def counts = new int[width] - def max = 0 - - /* - * count features in each column, record the maximum value - */ - for (col = 0 ; col < width ; col++) - { - def count = 0 - for (row = 0 ; row < al.height ; row++) - { - seq = al.getSequenceAt(row) - if (seq != null && col < seq.getLength()) - { - def res = seq.getCharAt(col) - if (!Comparison.isGap(res)) - { - pos = seq.findPosition(col) - features = fr.findFeaturesAtRes(seq, pos) - for (feature in features) - { - if (counter.call(feature.type)) - { - count++ - } - } - } - } - } - counts[col] = count - if (count > max) - { - max = count - } - } - - /* - * make the Annotation objects, with a graduated colour scale - * (from min value to max value) for the histogram bars - */ - def zero = '0' as char - def anns = new Annotation[width] - for (col = 0 ; col < width ; col++) - { - def c = counts[col] - if (c > 0) - { - Color color = ColorUtils.getGraduatedColour(c, 0, Color.cyan, - max, Color.blue) - anns[col] = AlignmentAnnotationFactory.newAnnotation(String.valueOf(c), - String.valueOf(c), zero, c, color) - } - } - anns -} - -/* - * Define the method that performs the calculations, and builds two - * AlignmentAnnotation objects - */ -def annotator = - [ calculateAnnotation: { al, fr -> - def phosAnns = getAnnotations(al, fr, phosCounter) - def ann1 = AlignmentAnnotationFactory.newAlignmentAnnotation("Phosphorylation", "Count of Phosphorylation features", phosAnns) - def turnAnns = getAnnotations(al, fr, turnCounter) - def ann2 = AlignmentAnnotationFactory.newAlignmentAnnotation("Turn", "Count of Turn features", turnAnns) - return [ann1, ann2] - } - ] as AnnotationProviderI - -/* - * Register the annotation calculator with Jalview - */ -AlignmentAnnotationFactory.newCalculator(annotator) diff --git a/examples/groovy/visibleFeaturesCounter.groovy b/examples/groovy/visibleFeaturesCounter.groovy new file mode 100644 index 0000000..b3180f8 --- /dev/null +++ b/examples/groovy/visibleFeaturesCounter.groovy @@ -0,0 +1,89 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ +import jalview.bin.Jalview +import jalview.workers.FeatureSetCounterI +import jalview.workers.AlignmentAnnotationFactory + +/* + * Demonstration of FeatureSetCounterI + * compute annotation tracks counting number of displayed + * features of each type in each column + */ + +/* + * discover features on the current view + */ + +def featuresDisp=Jalview.currentAlignFrame.currentView.featuresDisplayed +if (featuresDisp == null) { + print 'Need at least one feature visible on alignment' +} +def visibleFeatures=featuresDisp.visibleFeatures.toList() +assert 'java.util.ArrayList' == visibleFeatures.class.name + +/* + * A closure that returns an array of features present + * for each feature type in visibleFeatures + * Argument 'features' will be a list of SequenceFeature + */ +def getCounts = + { features -> + int[] obs = new int[visibleFeatures.size] + for (sf in features) + { + /* + * Here we inspect the type of the sequence feature. + * You can also test sf.description, sf.score, sf.featureGroup, + * sf.strand, sf.phase, sf.begin, sf.end + * or sf.getValue(attributeName) for GFF 'column 9' properties + */ + int pos = 0 + for (type in visibleFeatures) + { + if (type.equals(sf.type)) + { + obs[pos]++ + } + pos++ + } + } + obs +} + +/* + * Define something that counts each visible feature type + */ +def columnSetCounter = + [ + getNames: { visibleFeatures as String[] }, + getDescriptions: { visibleFeatures as String[] }, + getMinColour: { [0, 255, 255] as int[] }, // cyan + getMaxColour: { [0, 0, 255] as int[] }, // blue + count: + { res, feats -> + getCounts.call(feats) + } + ] as FeatureSetCounterI + +/* + * and register the counter + */ +AlignmentAnnotationFactory.newCalculator(columnSetCounter) diff --git a/help/help.jhm b/help/help.jhm index f931e7e..c6ce57d 100755 --- a/help/help.jhm +++ b/help/help.jhm @@ -130,7 +130,7 @@ - + diff --git a/help/helpTOC.xml b/help/helpTOC.xml index 989d70b..f76da16 100755 --- a/help/helpTOC.xml +++ b/help/helpTOC.xml @@ -24,6 +24,7 @@ + @@ -158,7 +159,7 @@ - + diff --git a/help/html/features/groovy.html b/help/html/features/groovy.html index 254f92e..d9bf76e 100644 --- a/help/html/features/groovy.html +++ b/help/html/features/groovy.html @@ -108,7 +108,7 @@ print currentAlFrame.getTitle(); simplified the alignment analysis programming interface in Jalview 2.10 to make it easy for you to add your own dynamic annotation tracks with Groovy. Have a look at the featureCounter.groovy + href="../groovy/featuresCounter.html">featuresCounter.groovy example for more information.

diff --git a/help/html/groovy/featureCounter.html b/help/html/groovy/featureCounter.html deleted file mode 100644 index 2ebaf45..0000000 --- a/help/html/groovy/featureCounter.html +++ /dev/null @@ -1,269 +0,0 @@ - - - -Extending Jalview with Groovy - Feature Counter Example - - -

- Extending Jalview with Groovy - A customisable - feature counter

The groovy script below shows how to - add a new calculation track to a Jalview alignment window. -

-

As currently written, it will add two tracks to a protein - alignment view which count Pfam features in each column, and ones - where a charge residue also occur.

-

To try it for yourself:

-
    -
  1. Copy and paste it into the groovy script console
  2. -
  3. Load the example Feredoxin project (the one that opens by - default when you first launched Jalview)
  4. -
  5. Select Calculations→Execute Groovy - Script from the alignment window's menu bar to run the script on - the current view. -
  6. -
- http://www.jalview.org/examples/groovy/featureCounter.groovy - - rendered with hilite.me - -
-
-/*
- * Jalview - A Sequence Alignment Editor and Viewer (Version 2.10)
- * Copyright (C) 2016 The Jalview Authors
- * 
- * This file is part of Jalview.
- * 
- * Jalview is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License 
- * as published by the Free Software Foundation, either version 3
- * of the License, or (at your option) any later version.
- *  
- * Jalview is distributed in the hope that it will be useful, but 
- * WITHOUT ANY WARRANTY; without even the implied warranty 
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
- * PURPOSE.  See the GNU General Public License for more details.
- * 
- * You should have received a copy of the GNU General Public License
- * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
- * The Jalview Authors are detailed in the 'AUTHORS' file.
- */
- 
-import jalview.workers.FeatureCounterI;
-import jalview.workers.AlignmentAnnotationFactory;
-
-/*
- * Example script that registers two alignment annotation calculators
- * - one that counts residues in a column with Pfam annotation
- * - one that counts only charged residues with Pfam annotation
- *
- * To try:
- * 1. load uniref50.fa from the examples folder
- * 2. load features onto it from from examples/exampleFeatures.txt
- * 3. Open this script in the Groovy console.
- * 4. Either execute this script from the console, or via Calculate->Run Groovy Script
- 
- * To explore further, try changing this script to count other kinds of occurrences of 
- * residue and sequence features at columns in an alignment.
- */
-
-/*
- * A closure that returns true for any Charged residue
- */
-def isCharged = { residue ->
-    switch(residue) {
-        case ['D', 'd', 'E', 'e', 'H', 'h', 'K', 'k', 'R', 'r']:
-            return true
-    }
-    false
-} 
-
-/*
- * A closure that returns 1 if sequence features include type 'Pfam', else 0
- * Argument should be a list of SequenceFeature 
- */
-def hasPfam = { features -> 
-    for (sf in features)
-    {
-        /*
-         * Here we inspect the type of the sequence feature.
-         * You can also test sf.description, sf.score, sf.featureGroup,
-         * sf.strand, sf.phase, sf.begin, sf.end
-         * or sf.getValue(attributeName) for GFF 'column 9' properties
-         */
-        if ("Pfam".equals(sf.type))
-        {
-            return true
-        }
-    }
-    false
-}
-
-/*
- * Closure that computes an annotation based on 
- * presence of particular residues and features
- * Parameters are
- * - the name (label) for the alignment annotation
- * - the description (tooltip) for the annotation
- * - a closure (groovy function) that tests whether to include a residue
- * - a closure that tests whether to increment count based on sequence features  
- */
-def getColumnCounter = { name, desc, acceptResidue, acceptFeatures ->
-    [
-     getName: { name }, 
-     getDescription: { desc },
-     getMinColour: { [0, 255, 255] }, // cyan
-     getMaxColour: { [0, 0, 255] }, // blue
-     count: 
-         { res, feats -> 
-            def c = 0
-            if (acceptResidue.call(res))
-            {
-                if (acceptFeatures.call(feats))
-                {
-                    c++
-                }
-            }
-            c
-         }
-     ] as FeatureCounterI
-}
-
-/*
- * Define an annotation row that counts any residue with Pfam domain annotation
- */
-def pfamAnnotation = getColumnCounter("Pfam", "Count of residues with Pfam domain annotation", {true}, hasPfam)
-
-/*
- * Define an annotation row that counts charged residues with Pfam domain annotation
- */
-def chargedPfamAnnotation = getColumnCounter("Pfam charged", "Count of charged residues with Pfam domain annotation", isCharged, hasPfam)
-
-/*
- * Register the annotations
- */
-AlignmentAnnotationFactory.newCalculator(pfamAnnotation) 
-AlignmentAnnotationFactory.newCalculator(chargedPfamAnnotation)
-
-
- - diff --git a/help/html/groovy/featuresCounter.html b/help/html/groovy/featuresCounter.html new file mode 100644 index 0000000..3b6705b --- /dev/null +++ b/help/html/groovy/featuresCounter.html @@ -0,0 +1,123 @@ + + + +Extending Jalview with Groovy - Feature Counter Example + + +

+ Extending Jalview with Groovy - A customisable + feature counter

The groovy script below shows how to + add a new calculation track to a Jalview alignment window. +

+

As currently written, it will add two tracks to a protein + alignment view which count Pfam features in each column, and ones + where a charge residue also occur.

+

To try it for yourself:

+
    +
  1. Copy and paste it into the groovy script console
  2. +
  3. Load the example Feredoxin project (the one that opens by + default when you first launched Jalview)
  4. +
  5. Select Calculations→Execute Groovy + Script from the alignment window's menu bar to run the script on + the current view. +
  6. +
+ Please note: The 2.10.2 feature counting interface is not compatible with earlier versions.

+ http://www.jalview.org/examples/groovy/featuresCounter.groovy + - rendered with hilite.me +
/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+
+import jalview.workers.AlignmentAnnotationFactory;
+import jalview.workers.FeatureSetCounterI;
+
+/*
+ * Example script to compute two alignment annotations
+ * - count of Phosphorylation features
+ * - count of Turn features
+ * To try this, first load example file uniref50.fa and load on features file
+ * exampleFeatures.txt, before running this script
+ *
+ * The script only needs to be run once - it will be registered by Jalview
+ * and recalculated automatically when the alignment changes.
+ * 
+ * Note: The feature api provided by 2.10.2 is not compatible with scripts
+ * that worked with earlier Jalview versions. Apologies for the inconvenience.
+ */
+ 
+def annotator = 
+    [
+     getNames: { ['Phosphorylation', 'Turn'] as String[] }, 
+     getDescriptions:  { ['Count of Phosphorylation features', 'Count of Turn features'] as String[] },
+     getMinColour: { [0, 255, 255] as int[] }, // cyan
+     getMaxColour: { [0, 0, 255] as int[] }, // blue
+     count: 
+         { res, feats -> 
+                int phos
+                int turn
+                for (sf in feats)
+                {
+ 		          /*
+		           * Here we inspect the type of the sequence feature.
+		           * You can also test sf.description, sf.score, sf.featureGroup,
+		           * sf.strand, sf.phase, sf.begin, sf.end
+		           * or sf.getValue(attributeName) for GFF 'column 9' properties
+		           */
+		           if (sf.type.contains('TURN'))
+                   {
+                      turn++
+                   }
+                   if (sf.type.contains('PHOSPHORYLATION'))
+                   {
+                      phos++
+                   }
+                }
+                [phos, turn] as int[]
+         }
+     ] as FeatureSetCounterI
+    
+/*
+ * Register the annotation calculator with Jalview
+ */
+AlignmentAnnotationFactory.newCalculator(annotator) 
+
+ + diff --git a/resources/lang/Messages.properties b/resources/lang/Messages.properties index b571a5b..0a93e7a 100644 --- a/resources/lang/Messages.properties +++ b/resources/lang/Messages.properties @@ -381,10 +381,8 @@ label.remove_from_default_list = Remove from default list? label.remove_user_defined_colour = Remove user defined colour label.you_must_select_least_two_sequences = You must select at least 2 sequences. label.invalid_selection = Invalid Selection -label.principal_component_analysis_must_take_least_four_input_sequences = Principal component analysis must take\nat least 4 input sequences. label.sequence_selection_insufficient = Sequence selection insufficient -label.you_need_more_two_sequences_selected_build_tree = You need to have more than two sequences selected to build a tree! -label.you_need_more_than_n_sequences = You need to have more than {0} sequences +label.you_need_at_least_n_sequences = You need to select at least {0} sequences label.not_enough_sequences = Not enough sequences label.selected_region_to_tree_may_only_contain_residues_or_gaps = The selected region to create a tree may\nonly contain residues or gaps.\nTry using the Pad function in the edit menu,\nor one of the multiple sequence alignment web services. label.sequences_selection_not_aligned = Sequences in selection are not aligned diff --git a/resources/lang/Messages_es.properties b/resources/lang/Messages_es.properties index 5932698..563684b 100644 --- a/resources/lang/Messages_es.properties +++ b/resources/lang/Messages_es.properties @@ -177,7 +177,7 @@ label.score_model_pid = % Identidad label.score_model_blosum62 = BLOSUM62 label.score_model_pam250 = PAM 250 label.score_model_smithwatermanscore = Puntuación entre secuencias alineadas por Smith-Waterman con matriz por defecto proteica / nucleotídica -label.score_model_sequencefeaturesimilarity = Medida de distancia por cuenta promedia de características no compartidas at sequence positions +label.score_model_sequencefeaturesimilarity = Medida de distancia por cuenta promedia de características no compartidas en posiciones de secuencia label.score_model_conservation = Conservación de las propiedades físico-químicas label.score_model_enhconservation = Conservación de las propiedades físico-químicas label.status_bar = Barra de estado @@ -349,9 +349,8 @@ label.remove_from_default_list = eliminar de la lista de defectuosos? label.remove_user_defined_colour = Eliminar el color definido por el usuario label.you_must_select_least_two_sequences = Debes seleccionar al menos 2 secuencias. label.invalid_selection = Selección inválida -label.principal_component_analysis_must_take_least_four_input_sequences = El an\u00E1lisis de la componente principal debe tomar\nal menos 4 secuencias de entrada. label.sequence_selection_insufficient = Selección de secuencias insuficiente -label.you_need_more_two_sequences_selected_build_tree = necesitas seleccionar más de dos secuencias para construir un árbol! +label.you_need_at_least_n_sequences = Necesitas seleccionar al menos {0} secuencias label.not_enough_sequences = No suficientes secuencias label.selected_region_to_tree_may_only_contain_residues_or_gaps = La regi\u00F3n seleccionada para construir un \u00E1rbol puede\ncontener s\u00F3lo residuos o espacios.\nPrueba usando la funci\u00F3n Pad en el men\u00FA de edici\u00F3n,\n o uno de los m\u00FAltiples servicios web de alineamiento de secuencias. label.sequences_selection_not_aligned = Las secuencias seleccionadas no están alineadas @@ -1301,4 +1300,10 @@ warn.name_cannot_be_duplicate = Los nombres URL definidos por el usuario deben s label.invalid_name = Nombre inválido ! label.output_seq_details = Seleccionar Detalles de la secuencia para ver todas label.urllinks = Enlaces -label.togglehidden = Show hidden regions \ No newline at end of file +label.quality_descr = Calidad de alineamiento basándose en puntuación Blosum62 +label.conservation_descr = Conservación del alineamiento total menos de {0}% huecos +label.consensus_descr = % Identidad +label.complement_consensus_descr = % Identidad para cDNA +label.strucconsensus_descr = % Identidad para pares de bases +label.occupancy_descr = Número de posiciones alineadas +label.togglehidden = Show hidden regions diff --git a/src/jalview/appletgui/IdPanel.java b/src/jalview/appletgui/IdPanel.java index e47c50a..4cc4a3a 100755 --- a/src/jalview/appletgui/IdPanel.java +++ b/src/jalview/appletgui/IdPanel.java @@ -35,6 +35,7 @@ import java.awt.event.InputEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -225,6 +226,10 @@ public class IdPanel extends Panel implements MouseListener, String id = sq.getName(); // get the default url with the sequence details filled in + if (urlProvider == null) + { + return; + } String url = urlProvider.getPrimaryUrl(id); String target = urlProvider.getPrimaryTarget(id); try @@ -287,8 +292,15 @@ public class IdPanel extends Panel implements MouseListener, // build a new links menu based on the current links + any non-positional // features - List nlinks = urlProvider.getLinksForMenu(); - + List nlinks; + if (urlProvider != null) + { + nlinks = urlProvider.getLinksForMenu(); + } + else + { + nlinks = new ArrayList(); + } SequenceFeature sf[] = sq == null ? null : sq.getSequenceFeatures(); for (int sl = 0; sf != null && sl < sf.length; sl++) { diff --git a/src/jalview/appletgui/SequenceRenderer.java b/src/jalview/appletgui/SequenceRenderer.java index 78ed4a3..38031e4 100755 --- a/src/jalview/appletgui/SequenceRenderer.java +++ b/src/jalview/appletgui/SequenceRenderer.java @@ -115,7 +115,7 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer void getBoxColour(ResidueShaderI shader, SequenceI seq, int i) { - if (shader != null) + if (shader.getColourScheme() != null) { resBoxColour = shader.findColour(seq.getCharAt(i), i, seq); } diff --git a/src/jalview/datamodel/Annotation.java b/src/jalview/datamodel/Annotation.java index 71ebbb3..8de8eb2 100755 --- a/src/jalview/datamodel/Annotation.java +++ b/src/jalview/datamodel/Annotation.java @@ -30,6 +30,12 @@ import java.awt.Color; */ public class Annotation { + /** + * the empty annotation - proxy for null entries in annotation row + */ + public static final Annotation EMPTY_ANNOTATION = new Annotation("", "", + ' ', 0f); + /** Character label - also shown below histogram */ public String displayCharacter = ""; @@ -192,4 +198,18 @@ public class Annotation } return sb.toString(); } + + /** + * @return true if annot is 'whitespace' annotation (zero score, whitespace or + * zero length display character, label, description + */ + public boolean isWhitespace() + { + return ((value == 0f) + && ((description == null) || (description.trim() + .length() == 0)) + && ((displayCharacter == null) || (displayCharacter + .trim().length() == 0)) + && (secondaryStructure == '\0' || (secondaryStructure == ' ')) && colour == null); + } } diff --git a/src/jalview/gui/AlignFrame.java b/src/jalview/gui/AlignFrame.java index 2becfea..4073e3e 100644 --- a/src/jalview/gui/AlignFrame.java +++ b/src/jalview/gui/AlignFrame.java @@ -3591,19 +3591,6 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, if (viewport.getSelectionGroup() != null && viewport.getSelectionGroup().getSize() > 0) { - if (viewport.getSelectionGroup().getSize() < 3) - { - JvOptionPane - .showMessageDialog( - Desktop.desktop, - MessageManager - .getString("label.you_need_more_two_sequences_selected_build_tree"), - MessageManager - .getString("label.not_enough_sequences"), - JvOptionPane.WARNING_MESSAGE); - return; - } - SequenceGroup sg = viewport.getSelectionGroup(); /* Decide if the selection is a column region */ diff --git a/src/jalview/gui/CalculationChooser.java b/src/jalview/gui/CalculationChooser.java index 05f1fba..8a95594 100644 --- a/src/jalview/gui/CalculationChooser.java +++ b/src/jalview/gui/CalculationChooser.java @@ -25,6 +25,7 @@ import jalview.analysis.scoremodels.ScoreModels; import jalview.analysis.scoremodels.SimilarityParams; import jalview.api.analysis.ScoreModelI; import jalview.api.analysis.SimilarityParamsI; +import jalview.datamodel.SequenceGroup; import jalview.util.MessageManager; import java.awt.BorderLayout; @@ -74,6 +75,10 @@ public class CalculationChooser extends JPanel private static final Font VERDANA_11PT = new Font("Verdana", 0, 11); + private static final int MIN_TREE_SELECTION = 3; + + private static final int MIN_PCA_SELECTION = 4; + AlignFrame af; JRadioButton pca; @@ -84,7 +89,7 @@ public class CalculationChooser extends JPanel JComboBox modelNames; - JButton ok; + JButton calculate; private JInternalFrame frame; @@ -96,6 +101,10 @@ public class CalculationChooser extends JPanel private JCheckBox shorterSequence; + final ComboBoxTooltipRenderer renderer = new ComboBoxTooltipRenderer(); + + List tips = new ArrayList(); + /** * Constructor * @@ -143,6 +152,7 @@ public class CalculationChooser extends JPanel pca.setOpaque(false); neighbourJoining = new JRadioButton( MessageManager.getString("label.tree_calc_nj")); + neighbourJoining.setSelected(true); averageDistance = new JRadioButton( MessageManager.getString("label.tree_calc_av")); neighbourJoining.setOpaque(false); @@ -167,7 +177,6 @@ public class CalculationChooser extends JPanel pcaBorderless.add(pca, FlowLayout.LEFT); calcChoicePanel.add(pcaBorderless, FlowLayout.LEFT); - treePanel.add(neighbourJoining); treePanel.add(averageDistance); @@ -189,6 +198,7 @@ public class CalculationChooser extends JPanel pca.addActionListener(calcChanged); neighbourJoining.addActionListener(calcChanged); averageDistance.addActionListener(calcChanged); + /* * score models drop-down - with added tooltips! */ @@ -216,30 +226,30 @@ public class CalculationChooser extends JPanel /* * OK / Cancel buttons */ - ok = new JButton(MessageManager.getString("action.calculate")); - ok.setFont(VERDANA_11PT); - ok.addActionListener(new java.awt.event.ActionListener() + calculate = new JButton(MessageManager.getString("action.calculate")); + calculate.setFont(VERDANA_11PT); + calculate.addActionListener(new java.awt.event.ActionListener() { @Override public void actionPerformed(ActionEvent e) { - ok_actionPerformed(); + calculate_actionPerformed(); } }); - JButton cancel = new JButton(MessageManager.getString("action.close")); - cancel.setFont(VERDANA_11PT); - cancel.addActionListener(new java.awt.event.ActionListener() + JButton close = new JButton(MessageManager.getString("action.close")); + close.setFont(VERDANA_11PT); + close.addActionListener(new java.awt.event.ActionListener() { @Override public void actionPerformed(ActionEvent e) { - cancel_actionPerformed(e); + close_actionPerformed(); } }); JPanel actionPanel = new JPanel(); actionPanel.setOpaque(false); - actionPanel.add(ok); - actionPanel.add(cancel); + actionPanel.add(calculate); + actionPanel.add(close); boolean includeParams = false; this.add(calcChoicePanel, BorderLayout.CENTER); @@ -260,9 +270,7 @@ public class CalculationChooser extends JPanel title = title + " (" + af.getViewport().viewName + ")"; } - Desktop.addInternalFrame(frame, - title, width, - height, false); + Desktop.addInternalFrame(frame, title, width, height, false); calcChoicePanel.doLayout(); revalidate(); /* @@ -290,18 +298,27 @@ public class CalculationChooser extends JPanel { size = af.getViewport().getSelectionGroup().getSize(); } - if (!(checkEnabled(pca, size, 4) - | checkEnabled(neighbourJoining, size, 3) | checkEnabled( - averageDistance, size, 3))) + + /* + * disable calc options for which there is insufficient input data + * return value of true means enabled and selected + */ + boolean checkPca = checkEnabled(pca, size, MIN_PCA_SELECTION); + boolean checkNeighbourJoining = checkEnabled(neighbourJoining, size, + MIN_TREE_SELECTION); + boolean checkAverageDistance = checkEnabled(averageDistance, size, + MIN_TREE_SELECTION); + + if (checkPca || checkNeighbourJoining || checkAverageDistance) { - ok.setToolTipText(null); - ok.setEnabled(true); + calculate.setToolTipText(null); + calculate.setEnabled(true); } else { - ok.setEnabled(false); + calculate.setEnabled(false); } - updateScoreModels(comboBox, tips); + updateScoreModels(modelNames, tips); } /** @@ -314,12 +331,12 @@ public class CalculationChooser extends JPanel * - size of input to calculation * @param minsize * - minimum size for calculation - * @return true if size < minsize *and* calc.isSelected + * @return true if size >= minsize and calc.isSelected */ private boolean checkEnabled(JRadioButton calc, int size, int minsize) { String ttip = MessageManager.formatMessage( - "label.you_need_more_than_n_sequences", minsize); + "label.you_need_at_least_n_sequences", minsize); calc.setEnabled(size >= minsize); if (!calc.isEnabled()) @@ -333,22 +350,18 @@ public class CalculationChooser extends JPanel if (calc.isSelected()) { modelNames.setEnabled(calc.isEnabled()); - if (!calc.isEnabled()) + if (calc.isEnabled()) { - ok.setEnabled(false); - ok.setToolTipText(ttip); return true; } + else + { + calculate.setToolTipText(ttip); + } } return false; } - final JComboBox comboBox = new JComboBox(); - - final ComboBoxTooltipRenderer renderer = new ComboBoxTooltipRenderer(); - - List tips = new ArrayList(); - /** * A rather elaborate helper method (blame Swing, not me) that builds a * drop-down list of score models (by name) with descriptions as tooltips. @@ -357,7 +370,8 @@ public class CalculationChooser extends JPanel */ protected JComboBox buildModelOptionsList() { - comboBox.setRenderer(renderer); + final JComboBox scoreModelsCombo = new JComboBox(); + scoreModelsCombo.setRenderer(renderer); /* * show tooltip on mouse over the combobox @@ -369,35 +383,36 @@ public class CalculationChooser extends JPanel @Override public void mouseEntered(MouseEvent e) { - comboBox.setToolTipText(tips.get(comboBox.getSelectedIndex())); + scoreModelsCombo.setToolTipText(tips.get(scoreModelsCombo.getSelectedIndex())); } @Override public void mouseExited(MouseEvent e) { - comboBox.setToolTipText(null); + scoreModelsCombo.setToolTipText(null); } }; - for (Component c : comboBox.getComponents()) + for (Component c : scoreModelsCombo.getComponents()) { c.addMouseListener(mouseListener); } - updateScoreModels(comboBox, tips); + updateScoreModels(scoreModelsCombo, tips); /* * set the list of tooltips on the combobox's renderer */ renderer.setTooltips(tips); - return comboBox; + return scoreModelsCombo; } - private void updateScoreModels(JComboBox comboBox, List tips) + private void updateScoreModels(JComboBox comboBox, + List toolTips) { Object curSel = comboBox.getSelectedItem(); - tips.clear(); - DefaultComboBoxModel model = new DefaultComboBoxModel(); + toolTips.clear(); + DefaultComboBoxModel model = new DefaultComboBoxModel(); /* * now we can actually add entries to the combobox, @@ -427,7 +442,7 @@ public class CalculationChooser extends JPanel tooltip = MessageManager.getStringOrReturn("label.score_model_", sm.getName()); } - tips.add(tooltip); + toolTips.add(tooltip); } } if (selectedIsPresent) @@ -441,7 +456,7 @@ public class CalculationChooser extends JPanel /** * Open and calculate the selected tree or PCA on 'OK' */ - protected void ok_actionPerformed() + protected void calculate_actionPerformed() { boolean doPCA = pca.isSelected(); String modelName = modelNames.getSelectedItem().toString(); @@ -467,6 +482,26 @@ public class CalculationChooser extends JPanel */ protected void openTreePanel(String modelName, SimilarityParamsI params) { + /* + * gui validation shouldn't allow insufficient sequences here, but leave + * this check in in case this method gets exposed programmatically in future + */ + AlignViewport viewport = af.getViewport(); + SequenceGroup sg = viewport.getSelectionGroup(); + if (sg != null && sg.getSize() < MIN_TREE_SELECTION) + { + JvOptionPane + .showMessageDialog( + Desktop.desktop, + MessageManager + .formatMessage("label.you_need_at_least_n_sequences", + MIN_TREE_SELECTION), + MessageManager + .getString("label.not_enough_sequences"), + JvOptionPane.WARNING_MESSAGE); + return; + } + String treeType = neighbourJoining.isSelected() ? TreeBuilder.NEIGHBOUR_JOINING : TreeBuilder.AVERAGE_DISTANCE; af.newTreePanel(treeType, modelName, params); @@ -481,19 +516,21 @@ public class CalculationChooser extends JPanel protected void openPcaPanel(String modelName, SimilarityParamsI params) { AlignViewport viewport = af.getViewport(); + + /* + * gui validation shouldn't allow insufficient sequences here, but leave + * this check in in case this method gets exposed programmatically in future + */ if (((viewport.getSelectionGroup() != null) - && (viewport.getSelectionGroup().getSize() < 4) && (viewport + && (viewport.getSelectionGroup().getSize() < MIN_PCA_SELECTION) && (viewport .getSelectionGroup().getSize() > 0)) - || (viewport.getAlignment().getHeight() < 4)) + || (viewport.getAlignment().getHeight() < MIN_PCA_SELECTION)) { - JvOptionPane - .showInternalMessageDialog( - this, - MessageManager - .getString("label.principal_component_analysis_must_take_least_four_input_sequences"), - MessageManager - .getString("label.sequence_selection_insufficient"), - JvOptionPane.WARNING_MESSAGE); + JvOptionPane.showInternalMessageDialog(this, MessageManager + .formatMessage("label.you_need_at_least_n_sequences", + MIN_PCA_SELECTION), MessageManager + .getString("label.sequence_selection_insufficient"), + JvOptionPane.WARNING_MESSAGE); return; } new PCAPanel(af.alignPanel, modelName, params); @@ -542,11 +579,9 @@ public class CalculationChooser extends JPanel } /** - * Closes dialog on cancel - * - * @param e + * Closes dialog on Close button press */ - protected void cancel_actionPerformed(ActionEvent e) + protected void close_actionPerformed() { try { diff --git a/src/jalview/gui/SeqPanel.java b/src/jalview/gui/SeqPanel.java index c2f4c75..ab2e66b 100644 --- a/src/jalview/gui/SeqPanel.java +++ b/src/jalview/gui/SeqPanel.java @@ -397,7 +397,7 @@ public class SeqPanel extends JPanel implements MouseListener, { ap.scrollUp(true); } - while (seqCanvas.cursorY + 1 > av.getRanges().getEndSeq()) + while (seqCanvas.cursorY > av.getRanges().getEndSeq()) { ap.scrollUp(false); } diff --git a/src/jalview/gui/SequenceRenderer.java b/src/jalview/gui/SequenceRenderer.java index 2a2a0cf..36825ea 100755 --- a/src/jalview/gui/SequenceRenderer.java +++ b/src/jalview/gui/SequenceRenderer.java @@ -138,7 +138,7 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer */ void getBoxColour(ResidueShaderI shader, SequenceI seq, int i) { - if (shader != null) + if (shader.getColourScheme() != null) { resBoxColour = shader.findColour(seq.getCharAt(i), i, seq); diff --git a/src/jalview/io/StockholmFile.java b/src/jalview/io/StockholmFile.java index a47e1ea..e4d9f60 100644 --- a/src/jalview/io/StockholmFile.java +++ b/src/jalview/io/StockholmFile.java @@ -45,7 +45,6 @@ import java.util.Hashtable; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.StringTokenizer; import java.util.Vector; import com.stevesoft.pat.Regex; @@ -78,8 +77,8 @@ public class StockholmFile extends AlignFile private static final Regex CLOSE_PAREN = new Regex("(>|\\])", ")"); - private static final Regex DETECT_BRACKETS = new Regex( - "(<|>|\\[|\\]|\\(|\\))"); + public static final Regex DETECT_BRACKETS = new Regex( + "(<|>|\\[|\\]|\\(|\\)|\\{|\\})"); StringBuffer out; // output buffer @@ -366,6 +365,11 @@ public class StockholmFile extends AlignFile // add alignment annotation for this feature String key = type2id(type); + + /* + * have we added annotation rows for this type ? + */ + boolean annotsAdded = false; if (key != null) { if (accAnnotations != null @@ -374,6 +378,7 @@ public class StockholmFile extends AlignFile Vector vv = (Vector) accAnnotations.get(key); for (int ii = 0; ii < vv.size(); ii++) { + annotsAdded = true; AlignmentAnnotation an = (AlignmentAnnotation) vv .elementAt(ii); seqO.addAlignmentAnnotation(an); @@ -386,6 +391,11 @@ public class StockholmFile extends AlignFile while (j.hasMoreElements()) { String desc = j.nextElement().toString(); + if ("annotations".equals(desc) && annotsAdded) + { + // don't add features if we already added an annotation row + continue; + } String ns = content.get(desc).toString(); char[] byChar = ns.toCharArray(); for (int k = 0; k < byChar.length; k++) @@ -572,22 +582,11 @@ public class StockholmFile extends AlignFile { String acc = s.stringMatched(1); String type = s.stringMatched(2); - String seq = new String(s.stringMatched(3)); - String description = null; - // Check for additional information about the current annotation - // We use a simple string tokenizer here for speed - StringTokenizer sep = new StringTokenizer(seq, " \t"); - description = sep.nextToken(); - if (sep.hasMoreTokens()) - { - seq = sep.nextToken(); - } - else - { - seq = description; - description = new String(); - } - // sequence id with from-to fields + String oseq = s.stringMatched(3); + /* + * copy of annotation field that may be processed into whitespace chunks + */ + String seq = new String(oseq); Hashtable ann; // Get an object with all the annotations for this sequence @@ -602,8 +601,12 @@ public class StockholmFile extends AlignFile ann = new Hashtable(); seqAnn.put(acc, ann); } + + // // start of block for appending annotation lines for wrapped + // stokchholm file // TODO test structure, call parseAnnotationRow with vector from // hashtable for specific sequence + Hashtable features; // Get an object with all the content for an annotation if (ann.containsKey("features")) @@ -631,15 +634,18 @@ public class StockholmFile extends AlignFile content = new Hashtable(); features.put(this.id2type(type), content); } - String ns = (String) content.get(description); + String ns = (String) content.get("annotation"); + if (ns == null) { ns = ""; } + // finally, append the annotation line ns += seq; - content.put(description, ns); + content.put("annotation", ns); + // // end of wrapped annotation block. + // // Now a new row is created with the current set of data - // if(type.equals("SS")){ Hashtable strucAnn; if (seqAnn.containsKey(acc)) { @@ -656,7 +662,8 @@ public class StockholmFile extends AlignFile { alan.visible = false; } - // annotations.addAll(newStruc); + // new annotation overwrites any existing annotation... + strucAnn.put(type, newStruc); seqAnn.put(acc, strucAnn); } @@ -831,12 +838,12 @@ public class StockholmFile extends AlignFile if (DETECT_BRACKETS.search(pos)) { ann.secondaryStructure = Rna.getRNASecStrucState(pos).charAt(0); + ann.displayCharacter = "" + pos.charAt(0); } else { ann.secondaryStructure = ResidueProperties.getDssp3state(pos) .charAt(0); - } if (ann.secondaryStructure == pos.charAt(0)) { @@ -846,6 +853,7 @@ public class StockholmFile extends AlignFile { ann.displayCharacter = " " + ann.displayCharacter; } + } } } @@ -966,46 +974,39 @@ public class StockholmFile extends AlignFile // output annotations while (i < s.length && s[i] != null) { - if (s[i].getDatasetSequence() != null) + AlignmentAnnotation[] alAnot = s[i].getAnnotation(); + if (alAnot != null) { - SequenceI ds = s[i].getDatasetSequence(); - AlignmentAnnotation[] alAnot; Annotation[] ann; - Annotation annot; - alAnot = s[i].getAnnotation(); - String feature = ""; - if (alAnot != null) + for (int j = 0; j < alAnot.length; j++) { - for (int j = 0; j < alAnot.length; j++) - { - String key = type2id(alAnot[j].label); - boolean isrna = alAnot[j].isValidStruc(); + String key = type2id(alAnot[j].label); + boolean isrna = alAnot[j].isValidStruc(); - if (isrna) - { - // hardwire to secondary structure if there is RNA secondary - // structure on the annotation - key = "SS"; - } - if (key == null) - { + if (isrna) + { + // hardwire to secondary structure if there is RNA secondary + // structure on the annotation + key = "SS"; + } + if (key == null) + { - continue; - } + continue; + } - // out.append("#=GR "); - out.append(new Format("%-" + maxid + "s").form("#=GR " - + printId(s[i], jvSuffix) + " " + key + " ")); - ann = alAnot[j].annotations; - String seq = ""; - for (int k = 0; k < ann.length; k++) - { - seq += outputCharacter(key, k, isrna, ann, s[i]); - } - out.append(seq); - out.append(newline); + // out.append("#=GR "); + out.append(new Format("%-" + maxid + "s").form("#=GR " + + printId(s[i], jvSuffix) + " " + key + " ")); + ann = alAnot[j].annotations; + String seq = ""; + for (int k = 0; k < ann.length; k++) + { + seq += outputCharacter(key, k, isrna, ann, s[i]); } + out.append(seq); + out.append(newline); } } @@ -1091,8 +1092,8 @@ public class StockholmFile extends AlignFile { if (annot == null) { - // sensible gap character if one is available or make one up - return sequenceI == null ? '-' : sequenceI.getCharAt(k); + // sensible gap character + return ' '; } else { diff --git a/src/jalview/workers/AlignmentAnnotationFactory.java b/src/jalview/workers/AlignmentAnnotationFactory.java index beee1eb..b0392d4 100644 --- a/src/jalview/workers/AlignmentAnnotationFactory.java +++ b/src/jalview/workers/AlignmentAnnotationFactory.java @@ -48,35 +48,17 @@ public class AlignmentAnnotationFactory * @param counter * provider of feature counts per alignment position */ - public static void newCalculator(FeatureCounterI counter) + public static void newCalculator(FeatureSetCounterI counter) { - // TODO need an interface for AlignFrame by which to access - // its AlignViewportI and AlignmentViewPanel AlignmentViewPanel currentAlignFrame = Jalview.getCurrentAlignFrame().alignPanel; - if (currentAlignFrame != null) - { - newCalculator(currentAlignFrame.getAlignViewport(), - currentAlignFrame, counter); - } - else + if (currentAlignFrame == null) { System.err .println("Can't register calculator as no alignment window has focus"); + return; } - } - - /** - * Constructs and registers a new alignment annotation worker - * - * @param viewport - * @param panel - * @param counter - * provider of feature counts per alignment position - */ - public static void newCalculator(AlignViewportI viewport, - AlignmentViewPanel panel, FeatureCounterI counter) - { - new ColumnCounterWorker(viewport, panel, counter); + new ColumnCounterSetWorker(currentAlignFrame.getAlignViewport(), + currentAlignFrame, counter); } /** @@ -92,8 +74,8 @@ public class AlignmentAnnotationFactory AlignFrame currentAlignFrame = Jalview.getCurrentAlignFrame(); if (currentAlignFrame != null) { - newCalculator(currentAlignFrame.getViewport(), currentAlignFrame - .getAlignPanels().get(0), calculator); + new AnnotationWorker(currentAlignFrame.getViewport(), + currentAlignFrame.getAlignPanels().get(0), calculator); } else { diff --git a/src/jalview/workers/ColumnCounterWorker.java b/src/jalview/workers/ColumnCounterSetWorker.java similarity index 61% rename from src/jalview/workers/ColumnCounterWorker.java rename to src/jalview/workers/ColumnCounterSetWorker.java index dd56aaf..6c5707d 100644 --- a/src/jalview/workers/ColumnCounterWorker.java +++ b/src/jalview/workers/ColumnCounterSetWorker.java @@ -36,16 +36,16 @@ import java.util.ArrayList; import java.util.List; /** - * A class to compute an alignment annotation with column counts of any - * properties of interest of positions in an alignment.
+ * A class to compute alignment annotations with column counts for a set of + * properties of interest on positions in an alignment.
* This is designed to be extensible, by supplying to the constructor an object - * that computes a count for each residue position, based on the residue value - * and any sequence features at that position. + * that computes a vector of counts for each residue position, based on the + * residue and and sequence features at that position. * */ -class ColumnCounterWorker extends AlignCalcWorker +class ColumnCounterSetWorker extends AlignCalcWorker { - FeatureCounterI counter; + FeatureSetCounterI counter; /** * Constructor registers the annotation for the given alignment frame @@ -53,8 +53,8 @@ class ColumnCounterWorker extends AlignCalcWorker * @param af * @param counter */ - public ColumnCounterWorker(AlignViewportI viewport, - AlignmentViewPanel panel, FeatureCounterI counter) + public ColumnCounterSetWorker(AlignViewportI viewport, + AlignmentViewPanel panel, FeatureSetCounterI counter) { super(viewport, panel); ourAnnots = new ArrayList(); @@ -69,6 +69,7 @@ class ColumnCounterWorker extends AlignCalcWorker @Override public void run() { + boolean annotationAdded = false; try { calcMan.notifyStart(this); @@ -93,7 +94,7 @@ class ColumnCounterWorker extends AlignCalcWorker { try { - computeAnnotations(); + annotationAdded = computeAnnotations(); } catch (IndexOutOfBoundsException x) { // probable race condition. just finish and return without any fuss. @@ -111,7 +112,10 @@ class ColumnCounterWorker extends AlignCalcWorker if (ap != null) { - ap.adjustAnnotationHeight(); + if (annotationAdded) + { + ap.adjustAnnotationHeight(); + } ap.paintAlignment(true); } @@ -120,8 +124,10 @@ class ColumnCounterWorker extends AlignCalcWorker /** * Scan each column of the alignment to calculate a count by feature type. Set * the count as the value of the alignment annotation for that feature type. + * + * @return */ - void computeAnnotations() + boolean computeAnnotations() { FeatureRenderer fr = new FeatureRenderer(alignViewport); // TODO use the commented out code once JAL-2075 is fixed @@ -130,56 +136,90 @@ class ColumnCounterWorker extends AlignCalcWorker // AlignmentView alignmentView = alignViewport.getAlignmentView(false); // AlignmentI alignment = alignmentView.getVisibleAlignment(' '); - // int width = alignmentView.getWidth(); + int rows = counter.getNames().length; + int width = alignment.getWidth(); int height = alignment.getHeight(); - int[] counts = new int[width]; - int max = 0; + int[][] counts = new int[width][rows]; + int max[] = new int[rows]; + for (int crow = 0; crow < rows; crow++) + { + max[crow] = 0; + } + + int[] minC = counter.getMinColour(); + int[] maxC = counter.getMaxColour(); + Color minColour = new Color(minC[0], minC[1], minC[2]); + Color maxColour = new Color(maxC[0], maxC[1], maxC[2]); for (int col = 0; col < width; col++) { - int count = 0; + int[] count = counts[col]; + for (int crow = 0; crow < rows; crow++) + { + count[crow] = 0; + } for (int row = 0; row < height; row++) { - count += countFeaturesAt(alignment, col, row, fr); + int[] colcount = countFeaturesAt(alignment, col, row, fr); + if (colcount != null) + { + for (int crow = 0; crow < rows; crow++) + { + count[crow] += colcount[crow]; + } + } } counts[col] = count; - max = Math.max(count, max); + for (int crow = 0; crow < rows; crow++) + { + max[crow] = Math.max(count[crow], max[crow]); + } } - Annotation[] anns = new Annotation[width]; - /* - * add non-zero counts as annotations - */ - for (int i = 0; i < counts.length; i++) + boolean annotationAdded = false; + + for (int anrow = 0; anrow < rows; anrow++) { - int count = counts[i]; - if (count > 0) + Annotation[] anns = new Annotation[width]; + /* + * add non-zero counts as annotations + */ + for (int i = 0; i < counts.length; i++) { - Color color = ColorUtils.getGraduatedColour(count, 0, Color.cyan, - max, Color.blue); - String str = String.valueOf(count); - anns[i] = new Annotation(str, str, '0', count, color); + int count = counts[i][anrow]; + if (count > 0) + { + Color color = ColorUtils.getGraduatedColour(count, 0, minColour, + max[anrow], maxColour); + String str = String.valueOf(count); + anns[i] = new Annotation(str, str, '0', count, color); + } } - } - /* - * construct or update the annotation - */ - AlignmentAnnotation ann = alignViewport.getAlignment() - .findOrCreateAnnotation(counter.getName(), - counter.getDescription(), false, null, null); - ann.description = counter.getDescription(); - ann.showAllColLabels = true; - ann.scaleColLabel = true; - ann.graph = AlignmentAnnotation.BAR_GRAPH; - ann.annotations = anns; - setGraphMinMax(ann, anns); - ann.validateRangeAndDisplay(); - if (!ourAnnots.contains(ann)) - { - ourAnnots.add(ann); + /* + * construct or update the annotation + */ + String description = counter.getDescriptions()[anrow]; + if (!alignment.findAnnotation(description).iterator().hasNext()) + { + annotationAdded = true; + } + AlignmentAnnotation ann = alignment.findOrCreateAnnotation( + counter.getNames()[anrow], description, false, null, null); + ann.description = description; + ann.showAllColLabels = true; + ann.scaleColLabel = true; + ann.graph = AlignmentAnnotation.BAR_GRAPH; + ann.annotations = anns; + setGraphMinMax(ann, anns); + ann.validateRangeAndDisplay(); + if (!ourAnnots.contains(ann)) + { + ourAnnots.add(ann); + } } + return annotationAdded; } /** @@ -191,22 +231,22 @@ class ColumnCounterWorker extends AlignCalcWorker * @param row * @param fr */ - int countFeaturesAt(AlignmentI alignment, int col, int row, + int[] countFeaturesAt(AlignmentI alignment, int col, int row, FeatureRenderer fr) { SequenceI seq = alignment.getSequenceAt(row); if (seq == null) { - return 0; + return null; } if (col >= seq.getLength()) { - return 0;// sequence doesn't extend this far + return null;// sequence doesn't extend this far } char res = seq.getCharAt(col); if (Comparison.isGap(res)) { - return 0; + return null; } int pos = seq.findPosition(col); @@ -216,7 +256,7 @@ class ColumnCounterWorker extends AlignCalcWorker // NB have to adjust pos if using AlignmentView.getVisibleAlignment // see JAL-2075 List features = fr.findFeaturesAtRes(seq, pos); - int count = this.counter.count(String.valueOf(res), features); + int[] count = this.counter.count(String.valueOf(res), features); return count; } diff --git a/src/jalview/workers/FeatureCounterI.java b/src/jalview/workers/FeatureSetCounterI.java similarity index 76% rename from src/jalview/workers/FeatureCounterI.java rename to src/jalview/workers/FeatureSetCounterI.java index 3a080ec..e14952f 100644 --- a/src/jalview/workers/FeatureCounterI.java +++ b/src/jalview/workers/FeatureSetCounterI.java @@ -18,6 +18,7 @@ * along with Jalview. If not, see . * The Jalview Authors are detailed in the 'AUTHORS' file. */ + package jalview.workers; import jalview.datamodel.SequenceFeature; @@ -25,15 +26,16 @@ import jalview.datamodel.SequenceFeature; import java.util.List; /** - * An interface for a type that returns counts of any value of interest at a - * sequence position that can be determined from the sequence character and any - * features present at that position + * An interface for a type that returns counts (per computed annotation type) of + * any value of interest at a sequence position that can be determined from the + * sequence character and any features present at that position * */ -public interface FeatureCounterI +public interface FeatureSetCounterI { /** - * Returns a count of some property of interest, for example + * Returns counts (per annotation type) of some properties of interest, for + * example *
    *
  • the number of variant features at the position
  • *
  • the number of Cath features of status 'True Positive'
  • @@ -46,22 +48,22 @@ public interface FeatureCounterI * @param a * list of any sequence features which include the position */ - int count(String residue, List features); + int[] count(String residue, List features); /** - * Returns a name for the annotation that this is counting, for use as the - * displayed label + * Returns names for the annotations that this is counting, for use as the + * displayed labels * * @return */ - String getName(); + String[] getNames(); /** - * Returns a description for the annotation, for display as a tooltip + * Returns descriptions for the annotations, for display as tooltips * * @return */ - String getDescription(); + String[] getDescriptions(); /** * Returns the colour (as [red, green, blue] values in the range 0-255) to use diff --git a/test/jalview/gui/SequenceRendererTest.java b/test/jalview/gui/SequenceRendererTest.java index 29a9a52..c80b830 100644 --- a/test/jalview/gui/SequenceRendererTest.java +++ b/test/jalview/gui/SequenceRendererTest.java @@ -58,6 +58,24 @@ public class SequenceRendererTest assertEquals(Color.magenta, sr.getResidueBoxColour(seq, 5)); // G assertEquals(Color.orange, sr.getResidueBoxColour(seq, 12)); // F } + + @Test(groups = { "Functional" }) + public void testGetResidueBoxColour_none() + { + SequenceI seq = new Sequence("name", "MA--TVLGSPRAPAFF"); + AlignmentI al = new Alignment(new SequenceI[] { seq }); + final AlignViewport av = new AlignViewport(al); + SequenceRenderer sr = new SequenceRenderer(av); + + assertEquals(Color.white, sr.getResidueBoxColour(seq, 0)); + assertEquals(Color.white, sr.getResidueBoxColour(seq, 2)); + + // set for overview + sr.forOverview = true; + assertEquals(Color.lightGray, sr.getResidueBoxColour(seq, 0)); + assertEquals(Color.white, sr.getResidueBoxColour(seq, 2)); + } + // TODO more tests for getResidueBoxColour covering groups, feature rendering, // gaps, overview... diff --git a/test/jalview/io/StockholmFileTest.java b/test/jalview/io/StockholmFileTest.java index e7f1435..1a18fb9 100644 --- a/test/jalview/io/StockholmFileTest.java +++ b/test/jalview/io/StockholmFileTest.java @@ -33,10 +33,13 @@ import jalview.datamodel.SequenceI; import jalview.gui.JvOptionPane; import java.io.File; +import java.util.Arrays; import java.util.BitSet; import java.util.HashMap; +import java.util.List; import java.util.Map; +import org.testng.Assert; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; @@ -177,10 +180,12 @@ public class StockholmFileTest public static void testAlignmentEquivalence(AlignmentI al, AlignmentI al_input, boolean ignoreFeatures) { + testAlignmentEquivalence(al, al_input, ignoreFeatures, false, false); } /** - * assert alignment equivalence + * assert alignment equivalence - uses special comparators for RNA structure + * annotation rows. * * @param al * 'original' @@ -193,10 +198,15 @@ public class StockholmFileTest * @param ignoreRowVisibility * when true, do not fail if there are differences in the visibility * of annotation rows + * @param allowNullAnnotation + * when true, positions in alignment annotation that are null will be + * considered equal to positions containing annotation where + * Annotation.isWhitespace() returns true. + * */ public static void testAlignmentEquivalence(AlignmentI al, AlignmentI al_input, boolean ignoreFeatures, - boolean ignoreRowVisibility) + boolean ignoreRowVisibility, boolean allowNullAnnotation) { assertNotNull("Original alignment was null", al); assertNotNull("Generated alignment was null", al_input); @@ -227,7 +237,7 @@ public class StockholmFileTest { assertEqualSecondaryStructure( "Different alignment annotation at position " + i, - aa_original[i], aa_new[i]); + aa_original[i], aa_new[i], allowNullAnnotation); // compare graphGroup or graph properties - needed to verify JAL-1299 assertEquals("Graph type not identical.", aa_original[i].graph, aa_new[i].graph); @@ -341,7 +351,7 @@ public class StockholmFileTest annot_new = al_input.getSequenceAt(in).getAnnotation()[j]; assertEqualSecondaryStructure( "Different annotation elements", annot_original, - annot_new); + annot_new, allowNullAnnotation); } } } @@ -363,9 +373,26 @@ public class StockholmFileTest } } + /** + * compare two annotation rows, with special support for secondary structure + * comparison. With RNA, only the value and the secondaryStructure symbols are + * compared, displayCharacter and description are ignored. Annotations where + * Annotation.isWhitespace() is true are always considered equal. + * + * @param message + * - not actually used yet.. + * @param annot_or + * - the original annotation + * @param annot_new + * - the one compared to the original annotation + * @param allowNullEquivalence + * when true, positions in alignment annotation that are null will be + * considered equal to non-null positions for which + * Annotation.isWhitespace() is true. + */ private static void assertEqualSecondaryStructure(String message, - AlignmentAnnotation annot_or, - AlignmentAnnotation annot_new) + AlignmentAnnotation annot_or, AlignmentAnnotation annot_new, + boolean allowNullEqivalence) { // TODO: test to cover this assert behaves correctly for all allowed // variations of secondary structure annotation row equivalence @@ -388,7 +415,8 @@ public class StockholmFileTest if (isRna) { if (an_or.secondaryStructure != an_new.secondaryStructure - || an_or.value != an_new.value) + || ((Float.isNaN(an_or.value) != Float + .isNaN(an_new.value)) || an_or.value != an_new.value)) { fail("Different RNA secondary structure at column " + i + " expected: [" + annot_or.annotations[i].toString() @@ -399,7 +427,8 @@ public class StockholmFileTest else { // not RNA secondary structure, so expect all elements to match... - if (!an_or.displayCharacter.trim().equals( + if ((an_or.isWhitespace() != an_new.isWhitespace()) + || !an_or.displayCharacter.trim().equals( an_new.displayCharacter.trim()) || !("" + an_or.secondaryStructure).trim().equals( ("" + an_new.secondaryStructure).trim()) @@ -407,7 +436,9 @@ public class StockholmFileTest .trim().length() == 0) || (an_new.description == null && an_or.description .trim().length() == 0) || an_or.description - .trim().equals(an_new.description.trim())))) + .trim().equals(an_new.description.trim()))) + || !((Float.isNaN(an_or.value) && Float + .isNaN(an_new.value)) || an_or.value == an_new.value)) { fail("Annotation Element Mismatch\nElement " + i + " in original: " + annot_or.annotations[i].toString() @@ -423,17 +454,202 @@ public class StockholmFileTest } else { - fail("Annotation Element Mismatch\nElement " - + i - + " in original: " - + (annot_or.annotations[i] == null ? "is null" - : annot_or.annotations[i].toString()) - + "\nElement " - + i - + " in new: " - + (annot_new.annotations[i] == null ? "is null" - : annot_new.annotations[i].toString())); + if (allowNullEqivalence) + { + if (an_or != null && an_or.isWhitespace()) + + { + continue; + } + if (an_new != null && an_new.isWhitespace()) + { + continue; + } + } + // need also to test for null in one, non-SS annotation in other... + fail("Annotation Element Mismatch\nElement " + i + " in original: " + + (an_or == null ? "is null" : an_or.toString()) + + "\nElement " + i + " in new: " + + (an_new == null ? "is null" : an_new.toString())); + } + } + } + + /** + * @see assertEqualSecondaryStructure - test if two secondary structure + * annotations are not equal + * @param message + * @param an_orig + * @param an_new + * @param allowNullEquivalence + */ + public static void assertNotEqualSecondaryStructure(String message, + AlignmentAnnotation an_orig, AlignmentAnnotation an_new, + boolean allowNullEquivalence) + { + boolean thrown = false; + try + { + assertEqualSecondaryStructure("", an_orig, an_new, + allowNullEquivalence); + } catch (AssertionError af) + { + thrown = true; + } + if (!thrown) + { + fail("Expected difference for [" + an_orig + "] and [" + an_new + "]"); + } + } + private AlignmentAnnotation makeAnnot(Annotation ae) + { + return new AlignmentAnnotation("label", "description", new Annotation[] + { ae }); + } + + @Test(groups={"Functional"}) + public void testAnnotationEquivalence() + { + AlignmentAnnotation one = makeAnnot(new Annotation("", "", ' ', 1)); + AlignmentAnnotation anotherOne = makeAnnot(new Annotation("", "", ' ', + 1)); + AlignmentAnnotation sheet = makeAnnot(new Annotation("","",'E',0f)); + AlignmentAnnotation anotherSheet = makeAnnot(new Annotation("","",'E',0f)); + AlignmentAnnotation sheetWithLabel = makeAnnot(new Annotation("1", "", + 'E', 0f)); + AlignmentAnnotation anotherSheetWithLabel = makeAnnot(new Annotation( + "1", "", 'E', 0f)); + AlignmentAnnotation rnaNoDC = makeAnnot(new Annotation("","",'<',0f)); + AlignmentAnnotation anotherRnaNoDC = makeAnnot(new Annotation("","",'<',0f)); + AlignmentAnnotation rnaWithDC = makeAnnot(new Annotation("B", "", '<', + 0f)); + AlignmentAnnotation anotherRnaWithDC = makeAnnot(new Annotation("B", + "", '<', 0f)); + + // check self equivalence + for (boolean allowNull : new boolean[] { true, false }) + { + assertEqualSecondaryStructure("Should be equal", one, anotherOne, + allowNull); + assertEqualSecondaryStructure("Should be equal", sheet, anotherSheet, + allowNull); + assertEqualSecondaryStructure("Should be equal", sheetWithLabel, + anotherSheetWithLabel, allowNull); + assertEqualSecondaryStructure("Should be equal", rnaNoDC, + anotherRnaNoDC, allowNull); + assertEqualSecondaryStructure("Should be equal", rnaWithDC, + anotherRnaWithDC, allowNull); + // display character doesn't matter for RNA structure (for 2.10.2) + assertEqualSecondaryStructure("Should be equal", rnaWithDC, rnaNoDC, + allowNull); + assertEqualSecondaryStructure("Should be equal", rnaNoDC, rnaWithDC, + allowNull); + } + + // verify others are different + List aaSet = Arrays.asList(one, sheet, + sheetWithLabel, rnaWithDC); + for (int p = 0; p < aaSet.size(); p++) + { + for (int q = 0; q < aaSet.size(); q++) + { + if (p != q) + { + assertNotEqualSecondaryStructure("Should be different", + aaSet.get(p), aaSet.get(q), false); + } + else + { + assertEqualSecondaryStructure("Should be same", aaSet.get(p), + aaSet.get(q), false); + assertEqualSecondaryStructure("Should be same", aaSet.get(p), + aaSet.get(q), true); + assertNotEqualSecondaryStructure( + "Should be different to empty anot", aaSet.get(p), + makeAnnot(Annotation.EMPTY_ANNOTATION), false); + assertNotEqualSecondaryStructure( + "Should be different to empty annot", + makeAnnot(Annotation.EMPTY_ANNOTATION), aaSet.get(q), + true); + assertNotEqualSecondaryStructure("Should be different to null", + aaSet.get(p), makeAnnot(null), false); + assertNotEqualSecondaryStructure("Should be different to null", + makeAnnot(null), aaSet.get(q), true); + } } } + + // test null + + } + + String aliFile = ">Dm\nAAACCCUUUUACACACGGGAAAGGG"; + String annFile = "JALVIEW_ANNOTATION\n# Created: Thu May 04 11:16:52 BST 2017\n\n" + + "SEQUENCE_REF\tDm\nNO_GRAPH\tsecondary structure\tsecondary structure\t" + + "(|(|(|(|, .|, .|, .|, .|)|)|)|)|\t0.0\nROWPROPERTIES\t" + + "secondary structure\tscaletofit=true\tshowalllabs=true\tcentrelabs=false"; + + String annFileCurlyWuss = "JALVIEW_ANNOTATION\n# Created: Thu May 04 11:16:52 BST 2017\n\n" + + "SEQUENCE_REF\tDm\nNO_GRAPH\tsecondary structure\tsecondary structure\t" + + "(|(|(|(||{|{||{|{||)|)|)|)||}|}|}|}|\t0.0\nROWPROPERTIES\t" + + "secondary structure\tscaletofit=true\tshowalllabs=true\tcentrelabs=false"; + String annFileFullWuss = "JALVIEW_ANNOTATION\n# Created: Thu May 04 11:16:52 BST 2017\n\n" + + "SEQUENCE_REF\tDm\nNO_GRAPH\tsecondary structure\tsecondary structure\t" + + "(|(|(|(||{|{||[|[||)|)|)|)||}|}|]|]|\t0.0\nROWPROPERTIES\t" + + "secondary structure\tscaletofit=true\tshowalllabs=true\tcentrelabs=false"; + + @Test(groups = { "Functional" }) + public void secondaryStructureForRNASequence() throws Exception + { + roundTripSSForRNA(aliFile, annFile); + } + + @Test(groups = { "Functional" }) + public void curlyWUSSsecondaryStructureForRNASequence() throws Exception + { + roundTripSSForRNA(aliFile, annFileCurlyWuss); + } + + @Test(groups = { "Functional" }) + public void fullWUSSsecondaryStructureForRNASequence() throws Exception + { + roundTripSSForRNA(aliFile, annFileFullWuss); + } + + @Test(groups = { "Functional" }) + public void detectWussBrackets() + { + for (char ch : new char[] { '{', '}', '[', ']', '(', ')', '<', '>' }) + { + Assert.assertTrue(StockholmFile.DETECT_BRACKETS.matchAt("" + ch, 0), + "Didn't recognise " + ch + " as a WUSS bracket"); + } + for (char ch : new char[] { '@', '!', 'V', 'Q', '*', ' ', '-', '.' }) + { + Assert.assertFalse(StockholmFile.DETECT_BRACKETS.matchAt("" + ch, 0), + "Shouldn't recognise " + ch + " as a WUSS bracket"); + } + } + private static void roundTripSSForRNA(String aliFile, String annFile) + throws Exception + { + AlignmentI al = new AppletFormatAdapter().readFile(aliFile, + DataSourceType.PASTE, jalview.io.FileFormat.Fasta); + AnnotationFile aaf = new AnnotationFile(); + aaf.readAnnotationFile(al, annFile, DataSourceType.PASTE); + al.getAlignmentAnnotation()[0].visible = true; + + // TODO: create a better 'save as ' pattern + StockholmFile sf = new StockholmFile(al); + + String stockholmFile = sf.print(al.getSequencesArray(), true); + + AlignmentI newAl = new AppletFormatAdapter().readFile(stockholmFile, + DataSourceType.PASTE, jalview.io.FileFormat.Stockholm); + // AlignmentUtils.showOrHideSequenceAnnotations(newAl.getViewport() + // .getAlignment(), Arrays.asList("Secondary Structure"), newAl + // .getViewport().getAlignment().getSequences(), true, true); + testAlignmentEquivalence(al, newAl, true, true, true); + } }