JAL-4392 Fixed failed test cases feature/JAL-4386_calculate_tree_using_secondary_structure_annotation
authorRenia Correya <rcorreya001@dundee.ac.uk>
Wed, 12 Jun 2024 15:01:12 +0000 (16:01 +0100)
committerRenia Correya <rcorreya001@dundee.ac.uk>
Wed, 12 Jun 2024 15:01:12 +0000 (16:01 +0100)
50 files changed:
help/help/html/calculations/tree.html
resources/lang/Messages.properties
resources/scoreModel/secondarystructure.scm [new file with mode: 0644]
src/jalview/analysis/AAFrequency.java
src/jalview/analysis/AlignmentUtils.java
src/jalview/analysis/scoremodels/DistanceScoreModel.java
src/jalview/analysis/scoremodels/FeatureDistanceModel.java
src/jalview/analysis/scoremodels/ScoreModels.java
src/jalview/analysis/scoremodels/SecondaryStructureDistanceModel.java [new file with mode: 0644]
src/jalview/analysis/scoremodels/SimilarityParams.java
src/jalview/api/AlignViewportI.java
src/jalview/api/ViewStyleI.java
src/jalview/api/analysis/ScoreModelI.java
src/jalview/api/analysis/SimilarityParamsI.java
src/jalview/appletgui/AlignFrame.java
src/jalview/bin/Cache.java
src/jalview/datamodel/Alignment.java
src/jalview/datamodel/AlignmentAnnotation.java
src/jalview/datamodel/PDBEntry.java
src/jalview/datamodel/Profile.java
src/jalview/datamodel/ProfileI.java
src/jalview/datamodel/SecondaryStructureCount.java [new file with mode: 0644]
src/jalview/datamodel/SequenceGroup.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/AlignViewport.java
src/jalview/gui/AnnotationLabels.java
src/jalview/gui/CalculationChooser.java
src/jalview/gui/PCAPanel.java
src/jalview/gui/PopupMenu.java
src/jalview/gui/Preferences.java
src/jalview/gui/TreePanel.java
src/jalview/gui/structurechooser/ThreeDBStructureChooserQuerySource.java
src/jalview/jbgui/GAlignFrame.java
src/jalview/jbgui/GPreferences.java
src/jalview/project/Jalview2XML.java
src/jalview/renderer/AnnotationRenderer.java
src/jalview/renderer/ResidueShader.java
src/jalview/renderer/ResidueShaderI.java
src/jalview/schemes/ResidueProperties.java
src/jalview/util/Constants.java [new file with mode: 0644]
src/jalview/viewmodel/AlignmentViewport.java
src/jalview/viewmodel/styles/ViewStyle.java
src/jalview/workers/AlignCalcManager.java
src/jalview/workers/SecondaryStructureConsensusThread.java [new file with mode: 0644]
test/jalview/analysis/AlignmentUtilsTests.java
test/jalview/analysis/scoremodels/ScoreModelsTest.java
test/jalview/analysis/scoremodels/SecondaryStructureDistanceModelTest.java [new file with mode: 0644]
test/jalview/gui/CalculationChooserTest.java
test/jalview/renderer/ResidueShaderTest.java
test/jalview/viewmodel/styles/ViewStyleTest.java

index 95904b6..cc5ca2c 100755 (executable)
       types. Sequences with similar distributions of features of the
       same type will be grouped together in trees computed with this
       metric. <em>This measure was introduced in Jalview 2.9</em></li>
+         
+         <li><strong>Secondary Structure Similarity</strong><br>Trees are 
+         generated using a distance matrix, which is constructed from Jaccard 
+         distances that specifically consider the secondary structure features 
+         observed at each column of the alignment.
+      <ul>
+        <li>For secondary structure similarity analysis, at any given column 
+               <em>i</em>, the range of unique secondary structures is between 0 and 2, 
+               reflecting the presence of helices, sheets, coils and gaps.
+               <br>The similarity at column <em>i</em> = Total 
+               number of unique secondary structures (which can range from 0 to 2) 
+               - Sum of the number of secondary structures in common at column
+               <em>i</em> (which can be either 0 or 1)<br>The similarity scores are 
+               summed across all columns and then divided by the total number of 
+               columns to calculate an average similarity score. 
+        </li>
+      </ul> 
+         Distance calculations are based on the secondary structures 
+         currently displayed. Sequences with similar distributions of secondary 
+         structures will be grouped together in trees.<br>
+         <em>The distance between two sequences is maximum when one 
+         sequence has a defined secondary structure annotation track and the 
+         other does not, indicating complete dissimilarity between them.  
+         Whereas, the distance between two sequences is minimum when both of 
+         the sequences within the comparison do not have a defined secondary 
+         structure annotation track.</em>
+         </li>
   </ul>
   <p>
     <strong>Tree Construction Methods</strong>
index bf88520..f7bdec6 100644 (file)
@@ -201,6 +201,7 @@ label.score_model_sequencefeaturesimilarity = Distance measure of average number
 label.score_model_conservation = Physicochemical property conservation
 label.score_model_enhconservation = Physicochemical property conservation
 label.status_bar = Status bar
+label.sequence_count = No. of sequences:
 label.out_to_textbox = Output to Textbox
 label.occupancy = Occupancy
 # delete Clustal - use FileFormat name instead
@@ -277,12 +278,16 @@ label.all_sequences_columns = All Sequences and Columns
 label.hide_selected_annotations = Hide selected annotations
 label.show_selected_annotations = Show selected annotations
 label.group_consensus = Group Consensus
+label.group_ss_consensus = Group Secondary Structure Consensus
 label.group_conservation = Group Conservation
 label.show_consensus_histogram = Show Consensus Histogram
 label.show_consensus_logo = Show Consensus Logo
+label.show_ssconsensus_logo = Show SS Consensus Logo
 label.norm_consensus_logo = Normalise Consensus Logo
 label.apply_all_groups = Apply to all groups
 label.autocalculated_annotation = Autocalculated Annotation
+label.show_secondary_structure = Show Secondary Structure
+label.show_secondary_structure_consensus = Show Secondary Structure Consensus
 label.show_first = Show first
 label.show_last = Show last
 label.struct_from_pdb = Process secondary structure from PDB
@@ -601,6 +606,7 @@ label.quality = Quality
 label.maximize_window = Maximize Window
 label.conservation = Conservation
 label.consensus = Consensus
+label.ssConsensus = Secondary Structure Consensus
 label.histogram = Histogram
 label.logo = Logo
 label.non_positional_features = List Non-positional Features
@@ -1290,6 +1296,10 @@ label.togglehidden = Show hidden regions
 label.quality_descr = Alignment Quality based on Blosum62 scores
 label.conservation_descr = Conservation of total alignment less than {0}% gaps
 label.consensus_descr = PID
+label.ssconsensus_label = Secondary Structure Consensus
+label.ssconsensus_descr = Secondary Structure Consensus
+option.ss_providers_all = All
+option.ss_providers_none = None
 label.complement_consensus_descr = PID for cDNA
 label.strucconsensus_descr = PID for base pairs
 label.occupancy_descr = Number of aligned positions 
diff --git a/resources/scoreModel/secondarystructure.scm b/resources/scoreModel/secondarystructure.scm
new file mode 100644 (file)
index 0000000..f56986f
--- /dev/null
@@ -0,0 +1,14 @@
+ScoreMatrix SECONDARYSTRUCTURE
+#
+# The SECONDARY STRUCTURE substitution matrix, as in <paper name>
+# The first line declares a ScoreMatrix with the name SECONDARYSTRUCTURE (shown in menus)
+#
+# Scores are not symbol case sensitive, unless column(s) are provided for lower case characters
+# The 'guide symbol' at the start of each row of score values is optional
+# Values may be integer or floating point, delimited by tab, space, comma or combinations
+#
+        E       H       C       * 
+E       1       0       0       0
+H       0       1       0       0
+C       0       0       1       0
+*       0       0       0       1
\ No newline at end of file
index 6967885..9c5ab5f 100755 (executable)
@@ -30,9 +30,12 @@ import jalview.datamodel.Profiles;
 import jalview.datamodel.ProfilesI;
 import jalview.datamodel.ResidueCount;
 import jalview.datamodel.ResidueCount.SymbolCounts;
+import jalview.datamodel.SecondaryStructureCount;
+import jalview.datamodel.SeqCigar;
 import jalview.datamodel.SequenceI;
 import jalview.ext.android.SparseIntArray;
 import jalview.util.Comparison;
+import jalview.util.Constants;
 import jalview.util.Format;
 import jalview.util.MappingUtils;
 import jalview.util.QuickSort;
@@ -191,6 +194,112 @@ public class AAFrequency
     // jalview.bin.Console.outPrintln(elapsed);
   }
 
+  
+  public static final ProfilesI calculateSS(List<SequenceI> list, int start,
+          int end, String source)
+  {
+    return calculateSS(list, start, end, false, source);
+  }
+  
+  public static final ProfilesI calculateSS(List<SequenceI> sequences,
+          int start, int end, boolean profile, String source)
+  {
+    SequenceI[] seqs = new SequenceI[sequences.size()];
+    int width = 0;
+    synchronized (sequences)
+    {
+      for (int i = 0; i < sequences.size(); i++)
+      {
+        seqs[i] = sequences.get(i);
+        int length = seqs[i].getLength();
+        if (length > width)
+        {
+          width = length;
+        }
+      }
+
+      if (end >= width)
+      {
+        end = width;
+      }
+      
+
+      ProfilesI reply = calculateSS(seqs, width, start, end, profile, source);
+      return reply;
+    }
+  }
+  
+  public static final ProfilesI calculateSS(final SequenceI[] sequences,
+          int width, int start, int end, boolean saveFullProfile, String source)
+  {
+
+    int seqCount = sequences.length;
+    
+    int seqWithSSCount = 0;
+    
+    ProfileI[] result = new ProfileI[width];
+
+    for (int column = start; column < end; column++)
+    {
+      
+      int ssCount = 0;
+    
+      SecondaryStructureCount ssCounts = new SecondaryStructureCount();
+
+      for (int row = 0; row < seqCount; row++)
+      {
+        if (sequences[row] == null)
+        {
+          jalview.bin.Console.errPrintln(
+                  "WARNING: Consensus skipping null sequence - possible race condition.");
+          continue;
+        }
+        
+        char c = sequences[row].getCharAt(column);
+        List<AlignmentAnnotation> annots = AlignmentUtils.getAlignmentAnnotationForSource(sequences[row], source);
+        if(annots!=null) {
+          seqWithSSCount++;
+          for(AlignmentAnnotation aa:annots) {
+            if(aa!=null) {
+              ssCount++;
+            }
+            
+            if (sequences[row].getLength() > column && !Comparison.isGap(c) && aa !=null)
+            {
+              
+              int seqPosition = sequences[row].findPosition(column);
+              
+              char ss = AlignmentUtils.findSSAnnotationForGivenSeqposition(
+                      aa, seqPosition); 
+              if(ss == '*') {
+                continue;
+              }        
+              ssCounts.add(ss);                    
+            }
+            else if(Comparison.isGap(c) && aa!=null) {
+              ssCounts.addGap();
+            }
+          }
+        }
+      }
+
+      int maxSSCount = ssCounts.getModalCount();
+      String maxSS = ssCounts.getSSForCount(maxSSCount);
+      int gapCount = ssCounts.getGapCount();
+      ProfileI profile = new Profile(maxSS, ssCount, gapCount, 
+              maxSSCount, seqWithSSCount);
+
+
+      if (saveFullProfile)
+      {
+        profile.setSSCounts(ssCounts);
+      }
+
+      result[column] = profile;
+    }
+    return new Profiles(result);
+  }
+
   /**
    * Make an estimate of the profile size we are going to compute i.e. how many
    * different characters may be present in it. Overestimating has a cost of
@@ -287,6 +396,62 @@ public class AAFrequency
     // long elapsed = System.currentTimeMillis() - now;
     // jalview.bin.Console.outPrintln(-elapsed);
   }
+  
+ public static void completeSSConsensus(AlignmentAnnotation ssConsensus,
+         ProfilesI profiles, int startCol, int endCol, boolean ignoreGaps,
+         boolean showSequenceLogo, long nseq)
+ {
+   // long now = System.currentTimeMillis();
+   if (ssConsensus == null || ssConsensus.annotations == null
+           || ssConsensus.annotations.length < endCol)
+   {
+     /*
+      * called with a bad alignment annotation row 
+      * wait for it to be initialised properly
+      */
+     return;
+   }
+
+   for (int i = startCol; i < endCol; i++)
+   {
+     ProfileI profile = profiles.get(i);
+     if (profile == null)
+     {
+       /*
+        * happens if sequences calculated over were 
+        * shorter than alignment width
+        */
+       ssConsensus.annotations[i] = null;
+       return;
+     }
+     
+     if(ssConsensus.getNoOfSequencesIncluded()<0) {
+       ssConsensus.setNoOfSequencesIncluded(profile.getSeqWithSSCount());
+     }
+     
+     final int dp = getPercentageDp(nseq);
+
+     float value = profile.getSSPercentageIdentity(ignoreGaps);
+
+     String description = getSSTooltip(profile, value, showSequenceLogo,
+             ignoreGaps, dp);
+
+     String modalSS = profile.getModalSS();
+     if ("".equals(modalSS))
+     {
+       modalSS = "-";
+     }
+     else if (modalSS.length() > 1)
+     {
+       modalSS = "+";
+     }
+     ssConsensus.annotations[i] = new Annotation(modalSS, description,
+             ' ', value);
+   }
+   // long elapsed = System.currentTimeMillis() - now;
+   // jalview.bin.Console.outPrintln(-elapsed);
+ }
 
   /**
    * Derive the gap count annotation row.
@@ -392,6 +557,41 @@ public class AAFrequency
     }
     return description;
   }
+  
+  static String getSSTooltip(ProfileI profile, float pid,
+          boolean showSequenceLogo, boolean ignoreGaps, int dp)
+  {
+    SecondaryStructureCount counts = profile.getSSCounts();
+
+    String description = null;
+    if (counts != null && showSequenceLogo)
+    {
+      int normaliseBy = ignoreGaps ? profile.getNonGapped()
+              : profile.getHeight();
+      description = counts.getTooltip(normaliseBy, dp);
+    }
+    else
+    {
+      StringBuilder sb = new StringBuilder(64);
+      String maxSS = profile.getModalSS();
+      if (maxSS.length() > 1)
+      {
+        sb.append("[").append(maxSS).append("]");
+      }
+      else
+      {
+        sb.append(maxSS);
+      }
+      if (maxSS.length() > 0)
+      {
+        sb.append(" ");
+        Format.appendPercentage(sb, pid, dp);
+        sb.append("%");
+      }
+      description = sb.toString();
+    }
+    return description;
+  }
 
   /**
    * Returns the sorted profile for the given consensus data. The returned array
@@ -411,15 +611,30 @@ public class AAFrequency
    */
   public static int[] extractProfile(ProfileI profile, boolean ignoreGaps)
   {
-    ResidueCount counts = profile.getCounts();
-    if (counts == null)
+    char[] symbols;
+    int[] values;
+    
+    if (profile.getCounts() != null)
     {
+      ResidueCount counts = profile.getCounts();
+      SymbolCounts symbolCounts = counts.getSymbolCounts();
+      symbols = symbolCounts.symbols;
+      values = symbolCounts.values;
+      
+    }
+    else if(profile.getSSCounts() != null) 
+    {
+      SecondaryStructureCount counts = profile.getSSCounts();
+      // to do
+      SecondaryStructureCount.SymbolCounts symbolCounts = counts.getSymbolCounts();
+      symbols = symbolCounts.symbols;
+      values = symbolCounts.values;
+    }
+    else {
       return null;
     }
+    
 
-    SymbolCounts symbolCounts = counts.getSymbolCounts();
-    char[] symbols = symbolCounts.symbols;
-    int[] values = symbolCounts.values;
     QuickSort.sort(values, symbols);
     int totalPercentage = 0;
     final int divisor = ignoreGaps ? profile.getNonGapped()
index be5133f..f69864f 100644 (file)
@@ -20,6 +20,7 @@
  */
 package jalview.analysis;
 
+import java.awt.Color;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -36,7 +37,9 @@ import java.util.NoSuchElementException;
 import java.util.Set;
 import java.util.SortedMap;
 import java.util.TreeMap;
+import java.util.Vector;
 
+import jalview.api.AlignCalcWorkerI;
 import jalview.bin.Console;
 import jalview.commands.RemoveGapColCommand;
 import jalview.datamodel.AlignedCodon;
@@ -45,23 +48,30 @@ import jalview.datamodel.AlignedCodonFrame.SequenceToSequenceMapping;
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
+import jalview.datamodel.Annotation;
 import jalview.datamodel.ContactMatrixI;
 import jalview.datamodel.DBRefEntry;
 import jalview.datamodel.GeneLociI;
 import jalview.datamodel.IncompleteCodonException;
 import jalview.datamodel.Mapping;
+import jalview.datamodel.PDBEntry;
+import jalview.datamodel.SeqCigar;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.datamodel.features.SequenceFeatures;
+import jalview.gui.AlignmentPanel;
 import jalview.io.gff.SequenceOntologyI;
 import jalview.schemes.ResidueProperties;
 import jalview.util.Comparison;
+import jalview.util.Constants;
 import jalview.util.DBRefUtils;
 import jalview.util.IntRangeComparator;
 import jalview.util.MapList;
 import jalview.util.MappingUtils;
+import jalview.util.MessageManager;
+import jalview.workers.SecondaryStructureConsensusThread;
 
 /**
  * grab bag of useful alignment manipulation operations Expect these to be
@@ -74,7 +84,7 @@ public class AlignmentUtils
 {
   private static final int CODON_LENGTH = 3;
 
-  private static final String SEQUENCE_VARIANT = "sequence_variant:";
+  private static final String SEQUENCE_VARIANT = "sequence_variant:"; 
 
   /*
    * the 'id' attribute is provided for variant features fetched from
@@ -1532,6 +1542,19 @@ public class AlignmentUtils
       }
     }
   }
+  
+  
+  public static boolean isSSAnnotationPresent( Map<SequenceI, List<AlignmentAnnotation>> annotations) {
+    
+    for (SequenceI seq : annotations.keySet())
+    {
+      if(isSecondaryStructurePresent(annotations.get(seq).toArray(new AlignmentAnnotation[0])))
+      {
+        return true;
+      }
+    }
+    return false;
+  }
 
   /**
    * Make a copy of a reference annotation {@code ann} and add it to an
@@ -2830,4 +2853,305 @@ public class AlignmentUtils
     }
     return true;
   }
+  
+
+  public static List<String> getSecondaryStructureSources(
+          AlignmentAnnotation[] annotations)
+  {
+
+    List<String> ssSources = new ArrayList<>();
+    Set<String> addedLabels = new HashSet<>(); // to keep track of added labels
+
+    for (AlignmentAnnotation annotation : annotations)
+    {
+      String label = annotation.label;
+      if (Constants.SECONDARY_STRUCTURE_LABELS.containsKey(label)
+              && !addedLabels.contains(label))
+      {
+        ssSources.add(Constants.SECONDARY_STRUCTURE_LABELS.get(label));
+        addedLabels.add(label); // Add the label to the set
+      }
+    }
+
+    return ssSources;
+  }
+  
+  public static boolean isSecondaryStructurePresent(AlignmentAnnotation[] annotations)
+  {
+    boolean ssPresent = false;
+
+    for (AlignmentAnnotation aa : annotations)
+    {
+      if (ssPresent)
+      {
+        break;
+      }
+
+      if (Constants.SECONDARY_STRUCTURE_LABELS.containsKey(aa.label))
+      {
+        ssPresent = true;
+        break;
+      }
+    }
+
+    return ssPresent;
+
+  }
+  
+  public static Color getSecondaryStructureAnnotationColour(char symbol)
+  {
+
+    if (symbol == Constants.COIL)
+    {
+      return Color.gray;
+    }
+    if (symbol == Constants.SHEET)
+    {
+      return Color.green;
+    }
+    if (symbol == Constants.HELIX)
+    {
+      return Color.red;
+    }
+
+    return Color.gray;
+  }
+
+  public static char findSSAnnotationForGivenSeqposition(AlignmentAnnotation aa,
+          int seqPosition)
+  {
+    char ss = '*';
+
+    if (aa != null)
+    {
+      if (aa.getAnnotationForPosition(seqPosition) != null)
+      {
+        Annotation a = aa.getAnnotationForPosition(seqPosition);
+        ss = a.secondaryStructure;
+
+        // There is no representation for coil and it can be either ' ' or null.
+        if (ss == ' ' || ss == '-')
+        {
+          ss = Constants.COIL;
+        }
+      }
+      else
+      {
+        ss = Constants.COIL;
+      }
+    }
+
+    return ss;
+  }
+  
+  public static List<String> extractSSSourceInAlignmentAnnotation(
+          AlignmentAnnotation[] annotations)
+  {
+
+    List<String> ssSources = new ArrayList<>();
+    Set<String> addedSources = new HashSet<>(); // to keep track of added
+                                                // sources
+
+    if (annotations == null)
+    {
+      return ssSources;
+    }
+
+    for (AlignmentAnnotation aa : annotations)
+    {
+
+      String ssSource = extractSSSourceFromAnnotationDescription(aa);
+
+      if (ssSource != null && !addedSources.contains(ssSource))
+      {
+        ssSources.add(ssSource);
+        addedSources.add(ssSource);
+      }
+
+    }
+    Collections.sort(ssSources);
+
+    return ssSources;
+
+  }
+  
+  public static String extractSSSourceFromAnnotationDescription(
+          AlignmentAnnotation aa)
+  {
+
+    for (String label : Constants.SECONDARY_STRUCTURE_LABELS.keySet())
+    {
+
+      if (label.equals(aa.label))
+      {
+
+        // For JPred
+        if (aa.label.equals(Constants.SS_ANNOTATION_FROM_JPRED_LABEL))
+        {
+
+          return (Constants.SECONDARY_STRUCTURE_LABELS.get(aa.label));
+
+        }
+
+        // For input with secondary structure
+        if (aa.label.equals(Constants.SS_ANNOTATION_LABEL)
+                && aa.description != null && aa.description.equals(Constants.SS_ANNOTATION_LABEL))
+        {
+
+          return (Constants.SECONDARY_STRUCTURE_LABELS.get(aa.label));
+
+        }
+
+        // For other sources
+        if (aa.sequenceRef == null)
+        {
+          return null;
+        }
+        else if (aa.sequenceRef.getDatasetSequence() == null)
+        {
+          return null;
+        }
+        Vector<PDBEntry> pdbEntries = aa.sequenceRef.getDatasetSequence()
+                .getAllPDBEntries();
+
+        for (PDBEntry entry : pdbEntries)
+        {
+
+          String entryProvider = entry.getProvider();
+          if (entryProvider == null)
+          {
+            entryProvider = "PDB";
+          }
+
+          // Trim the string from first occurrence of colon
+          String entryID = entry.getId();
+          int index = entryID.indexOf(':');
+
+          // Check if colon exists
+          if (index != -1)
+          {
+
+            // Trim the string from first occurrence of colon
+            entryID = entryID.substring(0, index);
+
+          }
+
+          if (entryProvider == "PDB" && aa.description.toLowerCase()
+                  .contains("secondary structure for "
+                          + entryID.toLowerCase()))
+          {
+
+            return entryProvider;
+
+          }
+
+          else if (entryProvider != "PDB" && aa.description.toLowerCase()
+                  .contains(entryID.toLowerCase()))
+          {
+
+            return entryProvider;
+
+          }
+
+        }
+      }
+    }
+
+    return null;
+
+  }
+  
+  //to do set priority for labels
+  public static List<AlignmentAnnotation> getAlignmentAnnotationForSource(
+          SequenceI seq, String ssSource)
+  {
+
+    List<AlignmentAnnotation> ssAnnots = new ArrayList<AlignmentAnnotation>();
+    for (String ssLabel : Constants.SECONDARY_STRUCTURE_LABELS.keySet())
+    {
+
+      AlignmentAnnotation[] aa = seq.getAnnotation(ssLabel);
+      if (aa != null)
+      {
+
+        if (ssSource.equals(MessageManager.getString("option.ss_providers_all")))
+        {
+          ssAnnots.addAll(Arrays.asList(aa));
+          continue;
+        }
+
+        for (AlignmentAnnotation annot : aa)
+        {
+
+          String ssSourceForAnnot = extractSSSourceFromAnnotationDescription(annot);
+          if (ssSourceForAnnot != null && ssSource
+                  .equals(ssSourceForAnnot))
+          {
+            ssAnnots.add(annot);
+          }
+        }
+      }
+    }
+    if (ssAnnots.size() > 0)
+    {
+      return ssAnnots;
+    }
+
+    return null;
+
+  }
+
+  public static Map<SequenceI, ArrayList<AlignmentAnnotation>> getSequenceAssociatedAlignmentAnnotations(
+          AlignmentAnnotation[] alignAnnotList, String selectedSSSource)
+  {
+
+    Map<SequenceI, ArrayList<AlignmentAnnotation>> ssAlignmentAnnotationForSequences 
+             = new HashMap<SequenceI, ArrayList<AlignmentAnnotation>>();
+    if (alignAnnotList == null || alignAnnotList.length == 0)
+    {
+      return ssAlignmentAnnotationForSequences;
+    }
+
+    for (AlignmentAnnotation aa : alignAnnotList)
+    {
+      if (aa.sequenceRef == null)
+      {
+        continue;
+      }
+      
+      for (String label : Constants.SECONDARY_STRUCTURE_LABELS.keySet())
+      {
+
+        if (label.equals(aa.label))
+        {
+
+          if (selectedSSSource.equals(MessageManager.getString("option.ss_providers_all")))
+          {
+            ssAlignmentAnnotationForSequences
+                    .computeIfAbsent(aa.sequenceRef.getDatasetSequence(),
+                            k -> new ArrayList<>())
+                    .add(aa);
+            break;
+          }
+
+          String ssSource = AlignmentUtils
+                  .extractSSSourceFromAnnotationDescription(aa);
+          if (ssSource != null && ssSource.equals(selectedSSSource))
+          {
+
+            ssAlignmentAnnotationForSequences
+                    .computeIfAbsent(aa.sequenceRef.getDatasetSequence(),
+                            k -> new ArrayList<>())
+                    .add(aa);
+            break;
+          }
+        }
+      }
+    }
+
+    return ssAlignmentAnnotationForSequences;
+
+  }
+  
 }
index 3521757..55510bd 100644 (file)
@@ -57,4 +57,5 @@ public abstract class DistanceScoreModel implements ScoreModelI
 
     return similarities;
   }
+  
 }
index bcc0855..df45a72 100644 (file)
@@ -235,7 +235,7 @@ public class FeatureDistanceModel extends DistanceScoreModel
   public boolean isProtein()
   {
     return true;
-  }
+  } 
 
   @Override
   public String toString()
index d9648ba..f591c8e 100644 (file)
@@ -40,6 +40,8 @@ public class ScoreModels
   private final ScoreMatrix PAM250;
 
   private final ScoreMatrix DNA;
+  
+  private final ScoreMatrix SECONDARYSTRUCTURE;
 
   private static ScoreModels instance;
 
@@ -68,7 +70,8 @@ public class ScoreModels
    * <li>PAM250</li>
    * <li>PID</li>
    * <li>DNA</li>
-   * <li>Sequence Feature Similarity</li>
+   * <li>Sequence Feature Similarity</li>   * 
+   * <li>Secondary Structure Similarity</li>
    * </ul>
    */
   private ScoreModels()
@@ -82,6 +85,9 @@ public class ScoreModels
     DNA = loadScoreMatrix("scoreModel/dna.scm");
     registerScoreModel(new PIDModel());
     registerScoreModel(new FeatureDistanceModel());
+    SECONDARYSTRUCTURE = loadScoreMatrix("scoreModel/secondarystructure.scm");
+    registerScoreModel(new SecondaryStructureDistanceModel());   
+
   }
 
   /**
@@ -140,6 +146,9 @@ public class ScoreModels
 
   public void registerScoreModel(ScoreModelI sm)
   {
+    if(sm.getName().equals("SECONDARYSTRUCTURE")) {
+      return;
+    }
     ScoreModelI sm2 = models.get(sm.getName());
     if (sm2 != null)
     {
@@ -178,4 +187,9 @@ public class ScoreModels
   {
     return PAM250;
   }
+  
+  public ScoreMatrix getSecondaryStructureMatrix()
+  {
+    return SECONDARYSTRUCTURE;
+  }
 }
diff --git a/src/jalview/analysis/scoremodels/SecondaryStructureDistanceModel.java b/src/jalview/analysis/scoremodels/SecondaryStructureDistanceModel.java
new file mode 100644 (file)
index 0000000..4e3a3de
--- /dev/null
@@ -0,0 +1,303 @@
+/*
+ * 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.
+ */
+package jalview.analysis.scoremodels;
+
+import jalview.analysis.AlignmentUtils;
+import jalview.api.AlignmentViewPanel;
+import jalview.api.FeatureRenderer;
+import jalview.api.analysis.ScoreModelI;
+import jalview.api.analysis.SimilarityParamsI;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentView;
+import jalview.datamodel.SeqCigar;
+import jalview.datamodel.SequenceI;
+import jalview.math.Matrix;
+import jalview.math.MatrixI;
+import jalview.util.MessageManager;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/* This class contains methods to calculate distance score between 
+ * secondary structure annotations of the sequences. 
+ */
+public class SecondaryStructureDistanceModel extends DistanceScoreModel
+{
+  private static final String NAME = "Secondary Structure Similarity";
+
+  private ScoreMatrix ssRateMatrix;
+  
+  private String description;    
+  
+  FeatureRenderer fr;
+  
+  
+  /**
+   * Constructor
+   */
+  public SecondaryStructureDistanceModel()
+  {
+    
+  }
+  
+  @Override
+  public ScoreModelI getInstance(AlignmentViewPanel view)
+  {
+    SecondaryStructureDistanceModel instance;
+    try
+    {
+      instance = this.getClass().getDeclaredConstructor().newInstance();
+      instance.configureFromAlignmentView(view);
+      return instance;
+    } catch (InstantiationException | IllegalAccessException e)
+    {
+      jalview.bin.Console.errPrintln("Error in " + getClass().getName()
+              + ".getInstance(): " + e.getMessage());
+      return null;
+    } catch (ReflectiveOperationException roe)
+    {
+      return null;
+    }
+  }
+
+  boolean configureFromAlignmentView(AlignmentViewPanel view)
+
+  {
+    fr = view.cloneFeatureRenderer();
+    return true;
+  }
+  
+  /**
+   * Calculates distance score [i][j] between each pair of protein sequences 
+   * based on their secondary structure annotations (H, E, C). 
+   * The final score is normalised by the number of 
+   * alignment columns processed, providing an average similarity score.
+   * <p>
+   * The parameters argument can include settings for handling gap-residue aligned 
+   * positions and may determine if the score calculation is based on the longer or shorter 
+   * sequence in each pair. This can be important for handling partial alignments or 
+   * sequences of significantly different lengths.
+   * 
+   * @param seqData  The aligned sequence data including secondary structure annotations.
+   * @param params   Additional parameters for customising the scoring process, such as gap 
+   *                 handling and sequence length consideration.
+   */
+  @Override
+  public MatrixI findDistances(AlignmentView seqData,
+          SimilarityParamsI params)
+  {   
+    
+    SeqCigar[] seqs = seqData.getSequences();
+    int noseqs = seqs.length; //no of sequences
+    int cpwidth = 0; 
+    double[][] similarities = new double[noseqs][noseqs]; //matrix to store similarity score
+    //secondary structure source parameter selected by the user from the drop down.
+    String ssSource = params.getSecondaryStructureSource(); 
+    if(ssSource == null || ssSource == "") {
+      ssSource = MessageManager.getString("option.ss_providers_all");
+    }
+    ssRateMatrix = ScoreModels.getInstance().getSecondaryStructureMatrix();
+        
+    // need to get real position for view position
+    int[] viscont = seqData.getVisibleContigs();
+    
+    
+    AlignmentAnnotation[] alignAnnotList = fr.getViewport().getAlignment()
+            .getAlignmentAnnotation();   
+    
+
+    /*
+     * Add secondary structure annotations that are added to the annotation track
+     * to the map
+     */
+    Map<SequenceI, ArrayList<AlignmentAnnotation>> ssAlignmentAnnotationForSequences 
+      = AlignmentUtils.getSequenceAssociatedAlignmentAnnotations(alignAnnotList, ssSource); 
+
+    /*
+     * scan each column, compute and add to each similarity[i, j]
+     * the number of secondary structure annotation that seqi 
+     * and seqj do not share
+     */
+    for (int vc = 0; vc < viscont.length; vc += 2)
+    {
+      //Iterates for each column position
+      for (int cpos = viscont[vc]; cpos <= viscont[vc + 1]; cpos++) 
+      {
+        cpwidth++; //used to normalise the similarity score 
+
+        /*
+         * get set of sequences without gap in the current column
+         */
+        Set<SeqCigar> seqsWithoutGapAtCol = findSeqsWithoutGapAtColumn(seqs, cpos);
+
+        /*
+         * calculate similarity score for each secondary structure annotation on i'th and j'th
+         * sequence and add this measure to the similarities matrix 
+         * for [i, j] for j > i
+         */
+        for (int i = 0; i < (noseqs - 1); i++)
+        {
+          //Iterates for each sequences
+          for (int j = i + 1; j < noseqs; j++)
+          {
+                         
+            //check if ss is defined
+            boolean undefinedSS1 = ssAlignmentAnnotationForSequences.get(seqs[i].getRefSeq()) == null;
+            boolean undefinedSS2 = ssAlignmentAnnotationForSequences.get(seqs[j].getRefSeq()) == null;
+
+            // Set similarity to max score if both SS are not defined
+            if (undefinedSS1 && undefinedSS2) {
+                similarities[i][j] += ssRateMatrix.getMaximumScore();
+                continue;
+            } 
+            
+            // Set similarity to minimum score if either one SS is not defined
+            else if(undefinedSS1 || undefinedSS2) {
+                similarities[i][j] += ssRateMatrix.getMinimumScore();
+                continue;
+            }
+            
+            //check if the sequence contains gap in the current column
+            boolean gap1 = !seqsWithoutGapAtCol.contains(seqs[i]);
+            boolean gap2 = !seqsWithoutGapAtCol.contains(seqs[j]);            
+            
+            //Variable to store secondary structure at the current column
+            char ss1 = '*';
+            char ss2 = '*';
+            
+            //secondary structure is fetched only if the current column is not 
+            //gap for the sequence
+            if(!gap1 && !undefinedSS1) {  
+              //fetch the position in sequence for the column and finds the
+              //corresponding secondary structure annotation
+              //TO DO - consider based on priority and displayed
+              int seqPosition = seqs[i].findPosition(cpos);
+              AlignmentAnnotation aa = ssAlignmentAnnotationForSequences.get(seqs[i].getRefSeq()).get(0);
+              if(aa!=null)
+              ss1 = 
+                  AlignmentUtils.findSSAnnotationForGivenSeqposition(aa, seqPosition);              
+            }
+            
+            if(!gap2 && !undefinedSS2) {              
+              int seqPosition = seqs[j].findPosition(cpos);
+              AlignmentAnnotation aa = ssAlignmentAnnotationForSequences.get(seqs[j].getRefSeq()).get(0);
+              if(aa!=null)
+                ss2 = 
+                  AlignmentUtils.findSSAnnotationForGivenSeqposition(aa, seqPosition);               
+            }           
+
+            if ((!gap1 && !gap2) || params.includeGaps())
+            {
+              // Calculate similarity score based on the substitution matrix
+              double similarityScore = ssRateMatrix.getPairwiseScore(ss1, ss2);
+              similarities[i][j] += similarityScore;
+            }
+          }
+        }
+      }
+    }
+
+    /*
+     * normalise the similarity scores (summed over columns) by the
+     * number of visible columns used in the calculation
+     * and fill in the bottom half of the matrix
+     */
+    // TODO JAL-2424 cpwidth may be out by 1 - affects scores but not tree shape
+    
+    for (int i = 0; i < noseqs; i++)
+    {
+      for (int j = i + 1; j < noseqs; j++)
+      {        
+        similarities[i][j] /= cpwidth;
+        similarities[j][i] = similarities[i][j];
+      }
+    }
+    return ssRateMatrix.similarityToDistance(new Matrix(similarities));
+    
+  }
+
+  /**
+   * Builds and returns a set containing sequences (SeqCigar) which do not
+   * have a gap at the given column position.
+   * 
+   * @param seqs
+   * @param columnPosition
+   *          (0..)
+   * @return
+   */
+  private Set<SeqCigar> findSeqsWithoutGapAtColumn(
+          SeqCigar[] seqs, int columnPosition)
+  {
+    Set<SeqCigar> seqsWithoutGapAtCol = new HashSet<>();
+    for (SeqCigar seq : seqs)
+    {
+      int spos = seq.findPosition(columnPosition);
+      if (spos != -1)
+      {
+        /*
+         * position is not a gap
+         */        
+        seqsWithoutGapAtCol.add(seq);
+      }
+    }
+    return seqsWithoutGapAtCol;
+  }
+
+    
+  @Override
+  public String getName()
+  {
+    return NAME;
+  }
+
+  @Override
+  public String getDescription()
+  {
+    return description;
+  }
+
+  @Override
+  public boolean isDNA()
+  {
+    return false; 
+  }
+
+  @Override
+  public boolean isProtein()
+  {
+    return false;
+  }
+  
+  @Override
+  public boolean isSecondaryStructure()
+  {
+    return true;
+  }
+
+  @Override
+  public String toString()
+  {
+    return "Score between sequences based on similarity between binary "
+            + "vectors marking secondary structure displayed at each column";
+  }
+}
\ No newline at end of file
index 5c47703..2a25fa8 100644 (file)
@@ -103,6 +103,8 @@ public class SimilarityParams implements SimilarityParamsI
   private boolean includeGaps;
 
   private boolean denominateByShortestLength;
+  
+  private String secondaryStructureSource;
 
   /**
    * Constructor
@@ -200,4 +202,16 @@ public class SimilarityParams implements SimilarityParamsI
     }
     return true;
   }
+
+  @Override
+  public String getSecondaryStructureSource()
+  {
+    return secondaryStructureSource;
+  }
+
+  @Override
+  public void setSecondaryStructureSource(String secondaryStructureSource)
+  {
+    this.secondaryStructureSource = secondaryStructureSource;
+  }
 }
index e7afee0..2cdb251 100644 (file)
@@ -85,9 +85,9 @@ public interface AlignViewportI extends ViewStyleI
   boolean isValidCharWidth();
 
   boolean isShowConsensusHistogram();
-
+  
   boolean isShowSequenceLogo();
-
+  
   boolean isNormaliseSequenceLogo();
 
   ColourSchemeI getGlobalColourScheme();
@@ -129,6 +129,9 @@ public interface AlignViewportI extends ViewStyleI
    * @return
    */
   AlignmentAnnotation getAlignmentConsensusAnnotation();
+  
+  List<AlignmentAnnotation> getAlignmentSecondaryStructureConsensusAnnotation();
+
 
   /**
    * get the container for alignment gap annotation
@@ -175,6 +178,9 @@ public interface AlignViewportI extends ViewStyleI
    * @param hconsensus
    */
   void setSequenceConsensusHash(ProfilesI hconsensus);
+  
+  void setSequenceSSConsensusHash(Map<String, ProfilesI> hSSConsesnusProfileMap);
+  
 
   /**
    * Set the cDNA complement consensus for the viewport
@@ -577,4 +583,11 @@ public interface AlignViewportI extends ViewStyleI
    * @return contact matrix or NULL
    */
   ContactMatrixI getContactMatrix(AlignmentAnnotation alignmentAnnotation);
+
+  Map<String, ProfilesI> getSequenceSSConsensusHash();
+
+  List<String> getSecondaryStructureSources();
+
+  void setSecondaryStructureSources(List<String> secondaryStructureSources);
+
 }
index a348300..9e87c87 100644 (file)
@@ -21,6 +21,7 @@
 package jalview.api;
 
 import java.awt.Color;
+import java.util.List;
 
 public interface ViewStyleI
 {
@@ -278,4 +279,8 @@ public interface ViewStyleI
    * @return
    */
   void setProteinFontAsCdna(boolean b);
+
+  void setSecondaryStructureSources(List<String> secondaryStructureSources);
+
+  List<String> getSecondaryStructureSources();
 }
index 275cd92..a243c0c 100644 (file)
@@ -62,6 +62,21 @@ public interface ScoreModelI
 
   // TODO getName, isDNA, isProtein can be static methods in Java 8
 
+  
+  default public boolean isSecondaryStructure()
+  {
+    return false;
+  }
+
+  /**
+   * Answers false by default 
+   * Answers true if the data has secondary structure (so should be
+   * shown in menus in that context)
+   * 
+   * @return
+   */
+  
+  
   /**
    * Returns a distance score for the given sequence regions, that is, a matrix
    * whose value [i][j] is the distance of sequence i from sequence j by some
index 8d83a17..43f2866 100644 (file)
@@ -60,4 +60,8 @@ public interface SimilarityParamsI
    * @return
    */
   boolean denominateByShortestLength();
+  
+  String getSecondaryStructureSource();
+
+  void setSecondaryStructureSource(String secondaryStructureSource);
 }
index f502952..55a5364 100644 (file)
@@ -3431,6 +3431,9 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     applyAutoAnnotationSettings.setState(true);
     Menu autoAnnMenu = new Menu(
             MessageManager.getString("label.autocalculated_annotation"));
+
+    Menu selectSS = new Menu(
+            MessageManager.getString("label.select_secondary_structure_source"));
     showGroupConsensus.addItemListener(this);
     showGroupConservation.addItemListener(this);
     showConsensusHistogram.addItemListener(this);
index a64e869..558b859 100755 (executable)
@@ -177,6 +177,8 @@ import jalview.ws.sifts.SiftsSettings;
  * in the alignment.</li>
  * <li>SHOW_CONSENSUS_HISTOGRAM (false) Show consensus annotation row's
  * histogram.</li>
+ * <li>SHOW_SSCONSENSUS_HISTOGRAM (false) Show secondary structure consensus annotation row's
+ * histogram.</li>
  * <li>SHOW_CONSENSUS_LOGO (false) Show consensus annotation row's sequence
  * logo.</li>
  * <li>NORMALISE_CONSENSUS_LOGO (false) Show consensus annotation row's sequence
index 73ccdc5..5073912 100755 (executable)
@@ -71,7 +71,7 @@ public class Alignment implements AlignmentI, AutoCloseable
   public Hashtable alignmentProperties;
 
   private List<AlignedCodonFrame> codonFrameList;
-
+  
   private void initAlignment(SequenceI[] seqs)
   {
     groups = Collections.synchronizedList(new ArrayList<SequenceGroup>());
@@ -82,12 +82,13 @@ public class Alignment implements AlignmentI, AutoCloseable
     nucleotide = Comparison.isNucleotide(seqs);
 
     sequences = Collections.synchronizedList(new ArrayList<SequenceI>());
+    
 
     for (int i = 0; i < seqs.length; i++)
     {
       sequences.add(seqs[i]);
     }
-
+   
   }
 
   /**
@@ -2122,4 +2123,5 @@ public class Alignment implements AlignmentI, AutoCloseable
     cmholder.addContactListFor(annotation, cm);
 
   }
+
 }
index d85c3d8..d660f8f 100755 (executable)
@@ -57,6 +57,8 @@ public class AlignmentAnnotation
   public static final int CDNA_PROFILE = 2;
 
   private static long counter = 0;
+  
+  private long noOfSequencesIncluded = -1;
 
   /**
    * If true, this annotations is calculated every edit, eg consensus, quality
@@ -1779,4 +1781,14 @@ public class AlignmentAnnotation
             showGroups ? "" : "nogroups");
   }
 
+  public long getNoOfSequencesIncluded()
+  {
+    return noOfSequencesIncluded;
+  }
+
+  public void setNoOfSequencesIncluded(long noOfSequencesIncluded)
+  {
+    this.noOfSequencesIncluded = noOfSequencesIncluded;
+  }
+
 }
index ae8523d..c3906f9 100755 (executable)
@@ -560,6 +560,8 @@ public class PDBEntry
 
   private static final String MODELPAGE = "PROVIDERPAGE";
 
+  private static final String PROVIDERCATEGORY = "PROVIDERCATEGORY";
+
   /**
    * Permanent URI for retrieving the original structure data
    * 
@@ -644,4 +646,20 @@ public class PDBEntry
   {
     return sf != null && sf.inFile != null && sf.inFile.exists();
   }
+
+  public void setProviderCategory(String providerCategory)
+  {
+    setProperty(PROVIDERCATEGORY, providerCategory);    
+  }
+  
+  public String getProviderCategory()
+  {
+    return (String) getProperty(PROVIDERCATEGORY);
+  }
+
+  public boolean hasProviderCategory()
+  {
+    return _hasProperty(PROVIDERCATEGORY);
+  }
+  
 }
index 8638896..bc87204 100644 (file)
@@ -32,6 +32,10 @@ public class Profile implements ProfileI
    * an object holding counts of symbols in the profile
    */
   private ResidueCount counts;
+  
+  private SecondaryStructureCount ssCounts;
+  
+  private int seqWithSSCount = -1;
 
   /*
    * the number of sequences (gapped or not) in the profile
@@ -47,12 +51,16 @@ public class Profile implements ProfileI
    * the highest count for any residue in the profile
    */
   private int maxCount;
+  private int maxSSCount;
+  
 
   /*
    * the residue (e.g. K) or residues (e.g. KQW) with the
    * highest count in the profile
    */
   private String modalResidue;
+  
+  private String modalSS;
 
   /**
    * Constructor which allows derived data to be stored without having to store
@@ -74,6 +82,15 @@ public class Profile implements ProfileI
     this.maxCount = max;
     this.modalResidue = modalRes;
   }
+  
+  public Profile(String modalSS, int ssCount, int gaps, int maxSSCount, int seqWithSSCount)
+  {
+    this.height = ssCount;
+    this.gapped = gaps;
+    this.maxSSCount = maxSSCount;
+    this.modalSS = modalSS;
+    this.setSeqWithSSCount(seqWithSSCount);
+  }
 
   /* (non-Javadoc)
    * @see jalview.datamodel.ProfileI#setCounts(jalview.datamodel.ResidueCount)
@@ -83,6 +100,12 @@ public class Profile implements ProfileI
   {
     this.counts = residueCounts;
   }
+  
+  @Override
+  public void setSSCounts(SecondaryStructureCount secondaryStructureCount)
+  {
+    this.ssCounts = secondaryStructureCount;
+  }
 
   /* (non-Javadoc)
    * @see jalview.datamodel.ProfileI#getPercentageIdentity(boolean)
@@ -105,6 +128,25 @@ public class Profile implements ProfileI
     }
     return pid;
   }
+  
+  @Override
+  public float getSSPercentageIdentity(boolean ignoreGaps)
+  {
+    if (height == 0)
+    {
+      return 0f;
+    }
+    float ssPid = 0f;
+    if (ignoreGaps && gapped < height)
+    {
+      ssPid = (maxSSCount * 100f) / (height - gapped);
+    }
+    else
+    {
+      ssPid = (maxSSCount * 100f) / height;
+    }
+    return ssPid;
+  }
 
   /* (non-Javadoc)
    * @see jalview.datamodel.ProfileI#getCounts()
@@ -114,6 +156,12 @@ public class Profile implements ProfileI
   {
     return counts;
   }
+  
+  @Override
+  public SecondaryStructureCount getSSCounts()
+  {
+    return ssCounts;
+  }
 
   /* (non-Javadoc)
    * @see jalview.datamodel.ProfileI#getHeight()
@@ -141,6 +189,12 @@ public class Profile implements ProfileI
   {
     return maxCount;
   }
+  
+  @Override
+  public int getMaxSSCount()
+  {
+    return maxSSCount;
+  }
 
   /* (non-Javadoc)
    * @see jalview.datamodel.ProfileI#getModalResidue()
@@ -150,6 +204,12 @@ public class Profile implements ProfileI
   {
     return modalResidue;
   }
+  
+  @Override
+  public String getModalSS()
+  {
+    return modalSS;
+  }
 
   /* (non-Javadoc)
    * @see jalview.datamodel.ProfileI#getNonGapped()
@@ -159,4 +219,14 @@ public class Profile implements ProfileI
   {
     return height - gapped;
   }
+
+  public int getSeqWithSSCount()
+  {
+    return seqWithSSCount;
+  }
+
+  public void setSeqWithSSCount(int seqWithSSCount)
+  {
+    this.seqWithSSCount = seqWithSSCount;
+  }
 }
index 65a5c0d..9046f23 100644 (file)
@@ -29,6 +29,8 @@ public interface ProfileI
    * @param residueCounts
    */
   public abstract void setCounts(ResidueCount residueCounts);
+  public abstract void setSSCounts(SecondaryStructureCount secondaryStructureCount);
+  
 
   /**
    * Returns the percentage identity of the profile, i.e. the highest proportion
@@ -39,6 +41,8 @@ public interface ProfileI
    * @return
    */
   public abstract float getPercentageIdentity(boolean ignoreGaps);
+  
+  public abstract float getSSPercentageIdentity(boolean ignoreGaps);
 
   /**
    * Returns the full symbol counts for this profile
@@ -53,6 +57,8 @@ public interface ProfileI
    * @return
    */
   public abstract int getHeight();
+  
+  public abstract int getSeqWithSSCount();
 
   /**
    * Returns the number of sequences in the profile which had a gap character
@@ -68,6 +74,7 @@ public interface ProfileI
    * @return
    */
   public abstract int getMaxCount();
+  public abstract int getMaxSSCount();
 
   /**
    * Returns the symbol (or concatenated symbols) which have the highest count
@@ -76,6 +83,7 @@ public interface ProfileI
    * @return
    */
   public abstract String getModalResidue();
+  public abstract String getModalSS();
 
   /**
    * Answers the number of non-gapped sequences in the profile
@@ -83,5 +91,6 @@ public interface ProfileI
    * @return
    */
   public abstract int getNonGapped();
+  SecondaryStructureCount getSSCounts();
 
 }
\ No newline at end of file
diff --git a/src/jalview/datamodel/SecondaryStructureCount.java b/src/jalview/datamodel/SecondaryStructureCount.java
new file mode 100644 (file)
index 0000000..52e9746
--- /dev/null
@@ -0,0 +1,612 @@
+/*
+ * 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.
+ */
+package jalview.datamodel;
+
+import jalview.util.Comparison;
+import jalview.util.Format;
+import jalview.util.QuickSort;
+import jalview.util.SparseCount;
+
+/**
+ * A class to count occurrences of residues in a profile, optimised for speed
+ * and memory footprint.
+ * 
+ * @author gmcarstairs
+ *
+ */
+public class SecondaryStructureCount
+{
+  /**
+   * A data bean to hold the results of counting symbols
+   */
+  public class SymbolCounts
+  {
+    /**
+     * the symbols seen (as char values), in no particular order
+     */
+    public final char[] symbols;
+
+    /**
+     * the counts for each symbol, in the same order as the symbols
+     */
+    public final int[] values;
+
+    SymbolCounts(char[] s, int[] v)
+    {
+      symbols = s;
+      values = v;
+    }
+  }
+
+  private static final int TOUPPERCASE = 'A' - 'a';
+
+  /*
+   * nucleotide symbols to count (including N unknown)
+   */
+  private static final String SS_SYMBOLS = "HEC";
+
+
+  static final int GAP_COUNT = 0;
+
+  /*
+   * fast lookup tables holding the index into our count
+   * arrays of each symbol; index 0 is reserved for gap counting
+   */
+  private static int[] SS_INDEX = new int[26];
+
+  static
+  {
+    for (int i = 0; i < SS_SYMBOLS.length(); i++)
+    {
+      SS_INDEX[SS_SYMBOLS.charAt(i) - 'A'] = i + 1;
+    }
+  }
+
+  /*
+   * counts array, just big enough for the nucleotide or peptide
+   * character set (plus gap counts in position 0)
+   */
+  private short[] counts;
+
+  /*
+   * alternative array of int counts for use if any count 
+   * exceeds the maximum value of short (32767)
+   */
+  private int[] intCounts;
+
+  /*
+   * flag set if we switch from short to int counts
+   */
+  private boolean useIntCounts;
+
+  /*
+   * general-purpose counter, only for use for characters
+   * that are not in the expected alphabet
+   */
+  private SparseCount otherData;
+
+  /*
+   * keeps track of the maximum count value recorded
+   * (if this class ever allows decrements, would need to
+   * calculate this on request instead) 
+   */
+  int maxCount;
+
+  /**
+   * Constructor that allocates an array just big enough for the anticipated
+   * characters, plus one position to count gaps
+   */
+  public SecondaryStructureCount()
+  {
+    //isSS = true;
+    int charsToCount = SS_SYMBOLS.length();
+    counts = new short[charsToCount + 1];
+  }
+
+  /**
+   * Increments the count for the given character. The supplied character may be
+   * upper or lower case but counts are for the upper case only. Gap characters
+   * (space, ., -) are all counted together.
+   * 
+   * @param c
+   * @return the new value of the count for the character
+   */
+  public int add(final char c)
+  {
+    char u = toUpperCase(c);
+    int newValue = 0;
+    int offset = getOffset(u);
+
+    /*
+     * offset 0 is reserved for gap counting, so 0 here means either
+     * an unexpected character, or a gap character passed in error
+     */
+    if (offset == 0)
+    {
+      if (Comparison.isGap(u))
+      {
+        newValue = addGap();
+      }
+      else
+      {
+        newValue = addOtherCharacter(u);
+      }
+    }
+    else
+    {
+      newValue = increment(offset);
+    }
+    return newValue;
+  }
+
+  /**
+   * Increment the count at the specified offset. If this would result in short
+   * overflow, promote to counting int values instead.
+   * 
+   * @param offset
+   * @return the new value of the count at this offset
+   */
+  int increment(int offset)
+  {
+    int newValue = 0;
+    if (useIntCounts)
+    {
+      newValue = intCounts[offset];
+      intCounts[offset] = ++newValue;
+    }
+    else
+    {
+      if (counts[offset] == Short.MAX_VALUE)
+      {
+        handleOverflow();
+        newValue = intCounts[offset];
+        intCounts[offset] = ++newValue;
+      }
+      else
+      {
+        newValue = counts[offset];
+        counts[offset] = (short) ++newValue;
+      }
+    }
+
+    if (offset != GAP_COUNT)
+    {
+      // update modal residue count
+      maxCount = Math.max(maxCount, newValue);
+    }
+    return newValue;
+  }
+
+  /**
+   * Switch from counting in short to counting in int
+   */
+  synchronized void handleOverflow()
+  {
+    intCounts = new int[counts.length];
+    for (int i = 0; i < counts.length; i++)
+    {
+      intCounts[i] = counts[i];
+    }
+    counts = null;
+    useIntCounts = true;
+  }
+
+  /**
+   * Returns this character's offset in the count array
+   * 
+   * @param c
+   * @return
+   */
+  int getOffset(char c)
+  {
+    int offset = 0;
+    if ('A' <= c && c <= 'Z')
+    {
+      offset = SS_INDEX[c - 'A'];
+    }
+    return offset;
+  }
+
+  /**
+   * @param c
+   * @return
+   */
+  protected char toUpperCase(final char c)
+  {
+    char u = c;
+    if ('a' <= c && c <= 'z')
+    {
+      u = (char) (c + TOUPPERCASE);
+    }
+    return u;
+  }
+
+  /**
+   * Increment count for some unanticipated character. The first time this
+   * called, a SparseCount is instantiated to hold these 'extra' counts.
+   * 
+   * @param c
+   * @return the new value of the count for the character
+   */
+  int addOtherCharacter(char c)
+  {
+    if (otherData == null)
+    {
+      otherData = new SparseCount();
+    }
+    int newValue = otherData.add(c, 1);
+    maxCount = Math.max(maxCount, newValue);
+    return newValue;
+  }
+
+  /**
+   * Set count for some unanticipated character. The first time this called, a
+   * SparseCount is instantiated to hold these 'extra' counts.
+   * 
+   * @param c
+   * @param value
+   */
+  void setOtherCharacter(char c, int value)
+  {
+    if (otherData == null)
+    {
+      otherData = new SparseCount();
+    }
+    otherData.put(c, value);
+  }
+
+  /**
+   * Increment count of gap characters
+   * 
+   * @return the new count of gaps
+   */
+  public int addGap()
+  {
+    int newValue = increment(GAP_COUNT);
+    return newValue;
+  }
+
+  /**
+   * Answers true if we are counting ints (only after overflow of short counts)
+   * 
+   * @return
+   */
+  boolean isCountingInts()
+  {
+    return useIntCounts;
+  }
+
+  /**
+   * Sets the count for the given character. The supplied character may be upper
+   * or lower case but counts are for the upper case only.
+   * 
+   * @param c
+   * @param count
+   */
+  public void put(char c, int count)
+  {
+    char u = toUpperCase(c);
+    int offset = getOffset(u);
+
+    /*
+     * offset 0 is reserved for gap counting, so 0 here means either
+     * an unexpected character, or a gap character passed in error
+     */
+    if (offset == 0)
+    {
+      if (Comparison.isGap(u))
+      {
+        set(0, count);
+      }
+      else
+      {
+        setOtherCharacter(u, count);
+        maxCount = Math.max(maxCount, count);
+      }
+    }
+    else
+    {
+      set(offset, count);
+      maxCount = Math.max(maxCount, count);
+    }
+  }
+
+  /**
+   * Sets the count at the specified offset. If this would result in short
+   * overflow, promote to counting int values instead.
+   * 
+   * @param offset
+   * @param value
+   */
+  void set(int offset, int value)
+  {
+    if (useIntCounts)
+    {
+      intCounts[offset] = value;
+    }
+    else
+    {
+      if (value > Short.MAX_VALUE || value < Short.MIN_VALUE)
+      {
+        handleOverflow();
+        intCounts[offset] = value;
+      }
+      else
+      {
+        counts[offset] = (short) value;
+      }
+    }
+  }
+
+  /**
+   * Returns the count for the given character, or zero if no count held
+   * 
+   * @param c
+   * @return
+   */
+  public int getCount(char c)
+  {
+    char u = toUpperCase(c);
+    int offset = getOffset(u);
+    if (offset == 0)
+    {
+      if (!Comparison.isGap(u))
+      {
+        // should have called getGapCount()
+        return otherData == null ? 0 : otherData.get(u);
+      }
+    }
+    return useIntCounts ? intCounts[offset] : counts[offset];
+  }
+
+  public int getGapCount()
+  {
+    return useIntCounts ? intCounts[0] : counts[0];
+  }
+
+  /**
+   * Answers true if this object wraps a counter for unexpected characters
+   * 
+   * @return
+   */
+  boolean isUsingOtherData()
+  {
+    return otherData != null;
+  }
+
+  /**
+   * Returns the character (or concatenated characters) for the symbol(s) with
+   * the given count in the profile. Can be used to get the modal residue by
+   * supplying the modal count value. Returns an empty string if no symbol has
+   * the given count. The symbols are in alphabetic order of standard peptide or
+   * nucleotide characters, followed by 'other' symbols if any.
+   * 
+   * @return
+   */
+  public String getSSForCount(int count)
+  {
+    if (count == 0)
+    {
+      return "";
+    }
+
+    /*
+     * find counts for the given value and append the
+     * corresponding symbol
+     */
+    StringBuilder modal = new StringBuilder();
+    if (useIntCounts)
+    {
+      for (int i = 1; i < intCounts.length; i++)
+      {
+        if (intCounts[i] == count)
+        {
+          modal.append(
+                  SS_SYMBOLS.charAt(i - 1));
+        }
+      }
+    }
+    else
+    {
+      for (int i = 1; i < counts.length; i++)
+      {
+        if (counts[i] == count)
+        {
+          modal.append(
+                  SS_SYMBOLS.charAt(i - 1));
+        }
+      }
+    }
+    if (otherData != null)
+    {
+      for (int i = 0; i < otherData.size(); i++)
+      {
+        if (otherData.valueAt(i) == count)
+        {
+          modal.append((char) otherData.keyAt(i));
+        }
+      }
+    }
+    return modal.toString();
+  }
+
+  /**
+   * Returns the highest count for any symbol(s) in the profile (excluding gap)
+   * 
+   * @return
+   */
+  public int getModalCount()
+  {
+    return maxCount;
+  }
+
+  /**
+   * Returns the number of distinct symbols with a non-zero count (excluding the
+   * gap symbol)
+   * 
+   * @return
+   */
+  public int size()
+  {
+    int size = 0;
+    if (useIntCounts)
+    {
+      for (int i = 1; i < intCounts.length; i++)
+      {
+        if (intCounts[i] > 0)
+        {
+          size++;
+        }
+      }
+    }
+    else
+    {
+      for (int i = 1; i < counts.length; i++)
+      {
+        if (counts[i] > 0)
+        {
+          size++;
+        }
+      }
+    }
+
+    /*
+     * include 'other' characters recorded (even if count is zero
+     * though that would be a strange use case)
+     */
+    if (otherData != null)
+    {
+      size += otherData.size();
+    }
+
+    return size;
+  }
+
+  /**
+   * Returns a data bean holding those symbols that have a non-zero count
+   * (excluding the gap symbol), with their counts.
+   * 
+   * @return
+   */
+  public SymbolCounts getSymbolCounts()
+  {
+    int size = size();
+    char[] symbols = new char[size];
+    int[] values = new int[size];
+    int j = 0;
+
+    if (useIntCounts)
+    {
+      for (int i = 1; i < intCounts.length; i++)
+      {
+        if (intCounts[i] > 0)
+        {
+          char symbol = SS_SYMBOLS.charAt(i - 1);
+          symbols[j] = symbol;
+          values[j] = intCounts[i];
+          j++;
+        }
+      }
+    }
+    else
+    {
+      for (int i = 1; i < counts.length; i++)
+      {
+        if (counts[i] > 0)
+        {
+          char symbol = SS_SYMBOLS.charAt(i - 1);
+          symbols[j] = symbol;
+          values[j] = counts[i];
+          j++;
+        }
+      }
+    }
+    if (otherData != null)
+    {
+      for (int i = 0; i < otherData.size(); i++)
+      {
+        symbols[j] = (char) otherData.keyAt(i);
+        values[j] = otherData.valueAt(i);
+        j++;
+      }
+    }
+
+    return new SymbolCounts(symbols, values);
+  }
+
+  /**
+   * Returns a tooltip string showing residues in descending order of their
+   * percentage frequency in the profile
+   * 
+   * @param normaliseBy
+   *          the divisor for residue counts (may or may not include gapped
+   *          sequence count)
+   * @param percentageDecPl
+   *          the number of decimal places to show in percentages
+   * @return
+   */
+  public String getTooltip(int normaliseBy, int percentageDecPl)
+  {
+    SymbolCounts symbolCounts = getSymbolCounts();
+    char[] ca = symbolCounts.symbols;
+    int[] vl = symbolCounts.values;
+
+    /*
+     * sort characters into ascending order of their counts
+     */
+    QuickSort.sort(vl, ca);
+
+    /*
+     * traverse in reverse order (highest count first) to build tooltip
+     */
+    boolean first = true;
+    StringBuilder sb = new StringBuilder(64);
+    for (int c = ca.length - 1; c >= 0; c--)
+    {
+      final char residue = ca[c];
+      // TODO combine residues which share a percentage
+      // (see AAFrequency.completeCdnaConsensus)
+      float tval = (vl[c] * 100f) / normaliseBy;
+      sb.append(first ? "" : "; ").append(residue).append(" ");
+      Format.appendPercentage(sb, tval, percentageDecPl);
+      sb.append("%");
+      first = false;
+    }
+    return sb.toString();
+  }
+
+  /**
+   * Returns a string representation of the symbol counts, for debug purposes.
+   */
+  @Override
+  public String toString()
+  {
+    StringBuilder sb = new StringBuilder();
+    sb.append("[ ");
+    SymbolCounts sc = getSymbolCounts();
+    for (int i = 0; i < sc.symbols.length; i++)
+    {
+      sb.append(sc.symbols[i]).append(":").append(sc.values[i]).append(" ");
+    }
+    sb.append("]");
+    return sb.toString();
+  }
+}
index 7e53c46..75d4460 100755 (executable)
@@ -26,14 +26,18 @@ import java.beans.PropertyChangeSupport;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
 import jalview.analysis.AAFrequency;
+import jalview.analysis.AlignmentUtils;
 import jalview.analysis.Conservation;
 import jalview.renderer.ResidueShader;
 import jalview.renderer.ResidueShaderI;
 import jalview.schemes.ColourSchemeI;
+import jalview.util.MessageManager;
 
 /**
  * Collects a set contiguous ranges on a set of sequences
@@ -132,6 +136,9 @@ public class SequenceGroup implements AnnotatedCollectionI
    * consensus calculation property
    */
   private boolean showSequenceLogo = false;
+  
+
+  private boolean showSequenceSSLogo = false;
 
   /**
    * flag indicating if logo should be rendered normalised
@@ -149,12 +156,19 @@ public class SequenceGroup implements AnnotatedCollectionI
   private boolean hidecols = false;
 
   AlignmentAnnotation consensus = null;
+  
+
+  List<AlignmentAnnotation> ssConsensus = null;
 
   AlignmentAnnotation conservation = null;
 
   private boolean showConsensusHistogram;
+  
+  private boolean showSSConsensusHistogram;
 
   private AnnotatedCollectionI context;
+  
+  public Map<String, ProfilesI> hSSConsensusProfileMap;
 
   /**
    * Creates a new SequenceGroup object.
@@ -229,6 +243,7 @@ public class SequenceGroup implements AnnotatedCollectionI
       showSequenceLogo = seqsel.showSequenceLogo;
       normaliseSequenceLogo = seqsel.normaliseSequenceLogo;
       showConsensusHistogram = seqsel.showConsensusHistogram;
+      showSSConsensusHistogram = seqsel.showSSConsensusHistogram;
       idColour = seqsel.idColour;
       outlineColour = seqsel.outlineColour;
       seqrep = seqsel.seqrep;
@@ -610,6 +625,38 @@ public class SequenceGroup implements AnnotatedCollectionI
         cs.setConsensus(cnsns);
         upd = true;
       }
+      
+      hSSConsensusProfileMap = new HashMap<String, ProfilesI>();
+      List <String> ssSources = new ArrayList<String>();
+      AnnotatedCollectionI aa = this.getContext();
+      
+      if(aa !=null )
+      {
+        ssSources = AlignmentUtils.extractSSSourceInAlignmentAnnotation(aa.getAlignmentAnnotation());
+      }
+      if(ssSources != null) {
+        ssSources.add(MessageManager.getString("option.ss_providers_all"));
+
+        for(String ssSource : ssSources) {
+          ProfilesI hSSConsensus = AAFrequency.calculateSS(sequences, startRes,  endRes + 1, showSequenceLogo,
+                  ssSource);
+          hSSConsensusProfileMap.put(ssSource, hSSConsensus);
+        }
+      }
+            
+
+      if (ssConsensus != null)
+      {
+        _updateSSConsensusRow(hSSConsensusProfileMap, sequences.size());
+        upd = true;
+      }
+      
+      if (cs != null)
+      {
+        cs.setSSConsensusProfileMap(hSSConsensusProfileMap);
+        upd = true;
+      }
+      
 
       if ((conservation != null)
               || (cs != null && cs.conservationApplied()))
@@ -699,6 +746,50 @@ public class SequenceGroup implements AnnotatedCollectionI
     // for
     // ignoreGapsInConsensusCalculation);
   }
+  
+  public ProfilesI ssConsensusData = null;
+  
+  private void _updateSSConsensusRow(Map<String, ProfilesI> hSSConsensusProfileMap, long nseq)
+  {
+    List<String> ssSources = new ArrayList<>(hSSConsensusProfileMap.keySet());
+
+    Collections.sort(ssSources);
+    if (ssConsensus == null)
+    {
+      getSSConsensus(ssSources);
+    }
+    for (AlignmentAnnotation aa : ssConsensus) {
+      ProfilesI profile = null;
+      String ssSource = null;
+      for(String source : ssSources) {
+        if(aa.description.startsWith(source)) {
+          profile = hSSConsensusProfileMap.get(source);
+          ssSource = source;
+        }
+      }
+      if(profile == null) {
+        continue;
+      }
+
+      aa.label = MessageManager.getString("label.ssconsensus_label") + " " + ssSource + " " + getName();
+      aa.description = ssSource + MessageManager.getString("label.ssconsensus_label") +" for " + getName();
+      ssConsensusData = profile;
+      // preserve width if already set
+      int aWidth = (aa.annotations != null)
+              ? (endRes < aa.annotations.length
+                      ? aa.annotations.length
+                      : endRes + 1)
+              : endRes + 1;
+      aa.annotations = null;
+      aa.annotations = new Annotation[aWidth]; // should be alignment width
+          
+    AAFrequency.completeSSConsensus(aa, profile, startRes, endRes + 1,
+            ignoreGapsInConsensus, showSequenceLogo, nseq); // TODO: setting
+                                                            // container
+    }
+    // for
+    // ignoreGapsInConsensusCalculation);
+  }
 
   /**
    * @param s
@@ -1154,6 +1245,37 @@ public class SequenceGroup implements AnnotatedCollectionI
     }
     return consensus;
   }
+  
+  public List<AlignmentAnnotation> getSSConsensus(List<String> ssSources)
+  {
+    // TODO get or calculate and get consensus annotation row for this group
+    int aWidth = this.getWidth();
+    // pointer
+    // possibility
+    // here.
+    if (aWidth < 0)
+    {
+      return null;
+    }
+    if (ssConsensus == null && ssSources!=null)
+    {
+      ssConsensus = new ArrayList<AlignmentAnnotation>();
+      
+      for(String ssSource : ssSources) {
+        AlignmentAnnotation aa = new AlignmentAnnotation("", "", new Annotation[1], 0f,
+                100f, AlignmentAnnotation.BAR_GRAPH);
+        aa.hasText = true;
+        aa.autoCalculated = true;
+        aa.groupRef = this;
+
+        aa.label = MessageManager.getString("label.ssconsensus_label") + " " + ssSource + " " + getName();
+        aa.description = ssSource + MessageManager.getString("label.ssconsensus_label") +" for " + getName();
+        ssConsensus.add(aa);
+      }
+     
+    }
+    return ssConsensus;
+  }
 
   /**
    * set this alignmentAnnotation object as the one used to render consensus
@@ -1256,6 +1378,18 @@ public class SequenceGroup implements AnnotatedCollectionI
     }
     this.showSequenceLogo = showSequenceLogo;
   }
+  
+  
+  public void setshowSequenceSSLogo(boolean showSequenceSSLogo)
+  {
+    // TODO: decouple calculation from settings update
+    if (this.showSequenceSSLogo != showSequenceSSLogo && ssConsensus != null)
+    {
+      this.showSequenceSSLogo = showSequenceSSLogo;
+      recalcConservation();
+    }
+    this.showSequenceSSLogo = showSequenceSSLogo;
+  }
 
   /**
    * 
@@ -1273,6 +1407,17 @@ public class SequenceGroup implements AnnotatedCollectionI
     }
     this.showConsensusHistogram = showConsHist;
   }
+  
+  public void setShowSSConsensusHistogram(boolean showSSConsHist)
+  {
+
+    if (showSSConsensusHistogram != showSSConsHist && consensus != null)
+    {
+      this.showSSConsensusHistogram = showSSConsHist;
+      recalcConservation();
+    }
+    this.showSSConsensusHistogram = showSSConsHist;
+  }
 
   /**
    * @return the showConsensusHistogram
index 9aa8733..766b35e 100644 (file)
@@ -56,9 +56,11 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Deque;
 import java.util.Enumeration;
+import java.util.HashMap;
 import java.util.Hashtable;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 import java.util.Vector;
 
 import javax.swing.ButtonGroup;
@@ -150,6 +152,7 @@ import jalview.schemes.ColourSchemeI;
 import jalview.schemes.ColourSchemes;
 import jalview.schemes.ResidueColourScheme;
 import jalview.schemes.TCoffeeColourScheme;
+import jalview.util.Constants;
 import jalview.util.HttpUtils;
 import jalview.util.ImageMaker.TYPE;
 import jalview.util.MessageManager;
@@ -802,6 +805,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       }
       ap.av.updateConservation(ap);
       ap.av.updateConsensus(ap);
+      ap.av.updateSecondaryStructureConsensus(ap);
       ap.av.updateStrucConsensus(ap);
     }
   }
@@ -5670,6 +5674,141 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     viewport.setShowUnconserved(showNonconservedMenuItem.getState());
     alignPanel.paintAlignment(false, false);
   }
+  
+  @Override
+  protected void updateShowSecondaryStructureMenu(JMenu showSS, ButtonGroup ssButtonGroup){
+    
+    List<String> ssSources = new ArrayList<String>();
+    AlignmentAnnotation[] anns = alignPanel.getAlignment()
+            .getAlignmentAnnotation();
+    Map<String, JCheckBoxMenuItem> checkboxMap = getCheckboxesInMenu(showSS);
+    
+    ssSources = AlignmentUtils.extractSSSourceInAlignmentAnnotation(anns);
+    
+    if(ssSources == null) {
+      showSS.removeAll();
+      ssButtonGroup.clearSelection();
+      return;
+    }
+    
+    List<String> selectedCheckBoxes = getSelectedOptions(checkboxMap);
+    
+    // Add checkboxes for categories
+    for (String ssSource : ssSources) {
+      
+      if(checkboxMap.get(ssSource)== null) {
+        JCheckBoxMenuItem checkBox = new JCheckBoxMenuItem(ssSource);
+        checkBox.setSelected(false);
+                
+        checkBox.addItemListener(e -> {
+            if (e.getStateChange() == ItemEvent.SELECTED) {
+                
+              showOrHideSecondaryStructureForSource(ssSource, true);
+              
+            } else {                
+
+              showOrHideSecondaryStructureForSource(ssSource, false);
+              
+            }
+        });
+        showSS.add(checkBox);
+      }
+    }
+    // Iterate over the keys of checkboxMap
+    for (String key : checkboxMap.keySet()) {
+        // Check if the key is not in ssSources
+        if (!ssSources.contains(key)) {
+            showSS.remove(checkboxMap.get(key));            
+            checkboxMap.remove(key);
+            selectedCheckBoxes.remove(key);
+        }
+        if(selectedCheckBoxes.contains(key)){
+          checkboxMap.get(key).setSelected(true);
+        }
+        else {
+          checkboxMap.get(key).setSelected(false);
+        }        
+
+        ssButtonGroup.clearSelection();
+    }
+
+  }
+  
+  private List<String> getSelectedOptions(Map<String, JCheckBoxMenuItem> checkboxMap) {
+    List<String> selectedOptions = new ArrayList<>();
+    for (String key : checkboxMap.keySet()) {
+        JCheckBoxMenuItem checkbox = checkboxMap.get(key);
+        if (checkbox.isSelected()) {
+            selectedOptions.add(key);
+        }
+    }
+    return selectedOptions;
+  }
+  
+  private Map<String, JCheckBoxMenuItem> getCheckboxesInMenu(JMenu menu) {
+    Map<String, JCheckBoxMenuItem> checkboxMap = new HashMap<>();
+    for (Component component : menu.getMenuComponents()) {
+        if (component instanceof JCheckBoxMenuItem) {
+            JCheckBoxMenuItem checkbox = (JCheckBoxMenuItem) component;
+            checkboxMap.put(checkbox.getText(), checkbox);
+        }
+    }
+    return checkboxMap;
+}
+  
+  @Override
+  protected void showOrHideSecondaryStructureForSource(String ssSourceSelection, boolean visible){
+    
+    String noneOption = MessageManager.getString("option.ss_providers_none");
+    String allOption = MessageManager.getString("option.ss_providers_all");
+    
+    AlignmentAnnotation[] annotations = alignPanel.getAlignment()
+            .getAlignmentAnnotation();
+    
+    for (AlignmentAnnotation aa: annotations) {
+      
+      boolean isSSConsensus = aa.label.startsWith(MessageManager.getString("label.ssconsensus_label"));
+      boolean matchesSSSourceSelection = aa.description.startsWith(ssSourceSelection);
+      
+      if(isSSConsensus && (matchesSSSourceSelection || ssSourceSelection.equals(noneOption))) {
+        
+        if (ssSourceSelection.equals(allOption)) {
+          aa.visible  = true;
+          break;
+        }
+        
+        if(!aa.description.startsWith(allOption))
+          aa.visible  = visible;
+
+      }
+      
+      for (String label : Constants.SECONDARY_STRUCTURE_LABELS.keySet()) {
+        
+        if (label.equals(aa.label)) { 
+
+            String ssSource = AlignmentUtils.extractSSSourceFromAnnotationDescription(aa);
+
+            if(ssSource != null && (ssSource.equals(ssSourceSelection) || ssSourceSelection.equals(noneOption))) {
+              aa.visible = visible;
+            }           
+        }
+      }
+      
+    }
+    
+      PaintRefresher.Refresh(this, viewport.getSequenceSetId());
+      alignPanel.updateAnnotation();
+      alignPanel.paintAlignment(true, true);
+    
+  }
+  
+  protected void showSSConsensus_actionPerformed(ActionEvent e)
+  {
+    viewport.setShowSSConsensus(showSSConsensus.getState());
+    alignPanel.updateAnnotation(applyAutoAnnotationSettings.getState());
+    
+  }
+
 
   /*
    * (non-Javadoc)
@@ -5686,6 +5825,13 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
 
   }
 
+  @Override
+  protected void showGroupSSConsensus_actionPerformed(ActionEvent e)
+  {
+    viewport.setShowGroupSSConsensus(showGroupSSConsensus.getState());
+    alignPanel.updateAnnotation(applyAutoAnnotationSettings.getState());
+
+  }
   /*
    * (non-Javadoc)
    * 
@@ -5713,7 +5859,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     viewport.setShowConsensusHistogram(showConsensusHistogram.getState());
     alignPanel.updateAnnotation(applyAutoAnnotationSettings.getState());
   }
-
+  
   /*
    * (non-Javadoc)
    * 
index a8bc815..36812f1 100644 (file)
@@ -281,11 +281,13 @@ public class AlignViewport extends AlignmentViewport
       }
       showConsensusHistogram = Cache.getDefault("SHOW_CONSENSUS_HISTOGRAM",
               true);
-      showSequenceLogo = Cache.getDefault("SHOW_CONSENSUS_LOGO", false);
+      showSequenceLogo = Cache.getDefault("SHOW_CONSENSUS_LOGO", true);
       normaliseSequenceLogo = Cache.getDefault("NORMALISE_CONSENSUS_LOGO",
               false);
       showGroupConsensus = Cache.getDefault("SHOW_GROUP_CONSENSUS", false);
       showConsensus = Cache.getDefault("SHOW_IDENTITY", true);
+      
+      showSSConsensus = Cache.getDefault("SHOW_SS_CONSENSUS", false);
 
       showOccupancy = Cache.getDefault(Preferences.SHOW_OCCUPANCY, true);
     }
@@ -314,6 +316,7 @@ public class AlignViewport extends AlignmentViewport
     if (residueShading != null)
     {
       residueShading.setConsensus(hconsensus);
+      residueShading.setSSConsensusProfileMap(hSSConsensusProfileMap);
     }
     setColourAppliesToAllGroups(true);
   }
index 7c5afcd..35ffbf3 100755 (executable)
@@ -40,6 +40,7 @@ import java.awt.geom.AffineTransform;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Locale;
 
 import javax.swing.JCheckBoxMenuItem;
@@ -51,6 +52,7 @@ import javax.swing.ToolTipManager;
 
 import jalview.analysis.AlignSeq;
 import jalview.analysis.AlignmentUtils;
+import jalview.api.AlignCalcWorkerI;
 import jalview.bin.Cache;
 import jalview.bin.Jalview;
 import jalview.datamodel.Alignment;
@@ -65,8 +67,10 @@ import jalview.datamodel.SequenceI;
 import jalview.io.FileFormat;
 import jalview.io.FormatAdapter;
 import jalview.util.Comparison;
+import jalview.util.Constants;
 import jalview.util.MessageManager;
 import jalview.util.Platform;
+import jalview.workers.SecondaryStructureConsensusThread;
 
 /**
  * The panel that holds the labels for alignment annotations, providing
@@ -245,6 +249,15 @@ public class AnnotationLabels extends JPanel
     {
       ap.av.getAlignment().deleteAnnotation(aa[selectedRow]);
       ap.av.getCalcManager().removeWorkerForAnnotation(aa[selectedRow]);
+      
+      
+      List<AlignCalcWorkerI> workers = ap.av.getCalcManager()
+              .getRegisteredWorkersOfClass(SecondaryStructureConsensusThread.class);
+      if (!workers.isEmpty()) {
+          
+        ap.alignFrame.getViewport().getCalcManager().startWorker(workers.remove(0));
+
+      }
     }
     else if (SHOWALL.equals(action))
     {
@@ -869,11 +882,19 @@ public class AnnotationLabels extends JPanel
     {
       msg.append(aa.sequenceRef.getName()).append(" : ");
     }
-
+    
     if (aa.graphGroup == -1)
     {
       msg.append(aa.label);
+      if(aa.getNoOfSequencesIncluded()>=0) 
+      {
+        msg.append(" (");
+        msg.append(MessageManager.getString("label.sequence_count"));
+        msg.append(aa.getNoOfSequencesIncluded());
+        msg.append(")");
+      }
     }
+        
     else if (anns != null)
     {
       boolean first = true;
@@ -916,6 +937,11 @@ public class AnnotationLabels extends JPanel
       // jalview.gui.SeqPanel.mouseMoved(..) that formats sequence feature
       // tooltips
       String desc = aa.getDescription(true).trim();
+      if(aa.getNoOfSequencesIncluded()>=0) {
+        desc += " (" + MessageManager.getString("label.sequence_count")
+                      + aa.getNoOfSequencesIncluded()
+                      + ")";
+      }
       if (!desc.toLowerCase(Locale.ROOT).startsWith(HTML_START_TAG))
       {
         tooltip.append(HTML_START_TAG);
index af41e58..8c8bf21 100644 (file)
@@ -52,12 +52,14 @@ import javax.swing.JRadioButton;
 import javax.swing.event.InternalFrameAdapter;
 import javax.swing.event.InternalFrameEvent;
 
+import jalview.analysis.AlignmentUtils;
 import jalview.analysis.TreeBuilder;
 import jalview.analysis.scoremodels.ScoreModels;
 import jalview.analysis.scoremodels.SimilarityParams;
 import jalview.api.analysis.ScoreModelI;
 import jalview.api.analysis.SimilarityParamsI;
 import jalview.bin.Cache;
+import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.SequenceGroup;
 import jalview.util.MessageManager;
 
@@ -79,6 +81,21 @@ public class CalculationChooser extends JPanel
   private static final int MIN_TREE_SELECTION = 3;
 
   private static final int MIN_PCA_SELECTION = 4;
+  
+  private String secondaryStructureModelName;
+  
+  private void getSecondaryStructureModelName() {
+    
+    ScoreModels scoreModels = ScoreModels.getInstance();
+    for (ScoreModelI sm : scoreModels.getModels())
+    {
+      if (sm.isSecondaryStructure())
+      {
+        secondaryStructureModelName = sm.getName();
+      }
+    }
+    
+  }
 
   AlignFrame af;
 
@@ -89,6 +106,8 @@ public class CalculationChooser extends JPanel
   JRadioButton averageDistance;
 
   JComboBox<String> modelNames;
+  
+  JComboBox<String> ssSourceDropdown;
 
   JButton calculate;
 
@@ -121,6 +140,7 @@ public class CalculationChooser extends JPanel
     this.af = alignFrame;
     init();
     af.alignPanel.setCalculationDialog(this);
+    
   }
 
   /**
@@ -128,6 +148,7 @@ public class CalculationChooser extends JPanel
    */
   void init()
   {
+    getSecondaryStructureModelName();
     setLayout(new BorderLayout());
     frame = new JInternalFrame();
     frame.setFrameIcon(null);
@@ -208,16 +229,37 @@ public class CalculationChooser extends JPanel
     pca.addActionListener(calcChanged);
     neighbourJoining.addActionListener(calcChanged);
     averageDistance.addActionListener(calcChanged);
+    
+    
+    //to do    
+    ssSourceDropdown = buildSSSourcesOptionsList();
+    ssSourceDropdown.setVisible(false); // Initially hide the dropdown
 
     /*
      * score models drop-down - with added tooltips!
      */
     modelNames = buildModelOptionsList();
+    
+    // Step 3: Show or Hide Dropdown Based on Selection
+    modelNames.addActionListener(new ActionListener() {
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            String selectedModel = modelNames.getSelectedItem().toString();
+            
+            if (selectedModel.equals(secondaryStructureModelName)) {
+              ssSourceDropdown.setVisible(true);
+            } else {
+              ssSourceDropdown.setVisible(false);
+            }
+        }
+    });
+
 
     JPanel scoreModelPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
     scoreModelPanel.setOpaque(false);
     scoreModelPanel.add(modelNames);
-
+    scoreModelPanel.add(ssSourceDropdown);
+    
     /*
      * score model parameters
      */
@@ -232,7 +274,7 @@ public class CalculationChooser extends JPanel
     paramsPanel.add(matchGaps);
     paramsPanel.add(includeGappedColumns);
     paramsPanel.add(shorterSequence);
-
+    
     /*
      * OK / Cancel buttons
      */
@@ -270,7 +312,7 @@ public class CalculationChooser extends JPanel
     }
     this.add(actionPanel, BorderLayout.SOUTH);
 
-    int width = 350;
+    int width = 375;
     int height = includeParams ? 420 : 240;
 
     setMinimumSize(new Dimension(325, height - 10));
@@ -418,7 +460,43 @@ public class CalculationChooser extends JPanel
 
     return scoreModelsCombo;
   }
+  
+
+  private JComboBox<String> buildSSSourcesOptionsList()
+  {
+    final JComboBox<String> comboBox = new JComboBox<>();
+    Object curSel = comboBox.getSelectedItem();
+    DefaultComboBoxModel<String> sourcesModel = new DefaultComboBoxModel<>();
+    
+    List<String> ssSources = getApplicableSecondaryStructureSources();
+    
+    if(ssSources == null) {
+      return comboBox;
+    }
+    ssSources.add(0, MessageManager.getString("option.ss_providers_all")); 
 
+    boolean selectedIsPresent = false;
+    for (String source : ssSources)
+    {
+      if (curSel != null && source.equals(curSel))
+      {
+        selectedIsPresent = true;
+        curSel = source;
+      }
+      sourcesModel.addElement(source);
+      
+    }
+
+    if (selectedIsPresent)
+    {
+      sourcesModel.setSelectedItem(curSel);
+    }
+    comboBox.setModel(sourcesModel);
+    
+    return comboBox;
+  }
+  
+  
   private void updateScoreModels(JComboBox<String> comboBox,
           List<String> toolTips)
   {
@@ -430,8 +508,12 @@ public class CalculationChooser extends JPanel
      * select the score models applicable to the alignment type
      */
     boolean nucleotide = af.getViewport().getAlignment().isNucleotide();
-    List<ScoreModelI> models = getApplicableScoreModels(nucleotide,
-            pca.isSelected());
+    AlignmentAnnotation[] alignmentAnnotations = af.getViewport().getAlignment().getAlignmentAnnotation();
+
+    boolean ssPresent = AlignmentUtils.isSecondaryStructurePresent(alignmentAnnotations);
+
+    List<ScoreModelI> models = getApplicableScoreModels(nucleotide, pca.isSelected(),
+            ssPresent);
 
     /*
      * now we can actually add entries to the combobox,
@@ -466,6 +548,7 @@ public class CalculationChooser extends JPanel
     }
     // finally, update the model
     comboBox.setModel(model);
+    
   }
 
   /**
@@ -483,14 +566,15 @@ public class CalculationChooser extends JPanel
    * @return
    */
   protected static List<ScoreModelI> getApplicableScoreModels(
-          boolean nucleotide, boolean forPca)
+          boolean nucleotide, boolean forPca, boolean ssPresent)
   {
     List<ScoreModelI> filtered = new ArrayList<>();
 
     ScoreModels scoreModels = ScoreModels.getInstance();
     for (ScoreModelI sm : scoreModels.getModels())
     {
-      if (!nucleotide && sm.isProtein() || nucleotide && sm.isDNA())
+      if (!nucleotide && sm.isProtein() || nucleotide && sm.isDNA() 
+              || ssPresent && sm.isSecondaryStructure())
       {
         filtered.add(sm);
       }
@@ -505,10 +589,22 @@ public class CalculationChooser extends JPanel
     {
       filtered.add(scoreModels.getBlosum62());
     }
-
+    
     return filtered;
   }
 
+  
+  protected List<String> getApplicableSecondaryStructureSources()
+  {
+    AlignmentAnnotation[] annotations = af.getViewport().getAlignment().getAlignmentAnnotation();
+    
+    //List<String> ssSources = AlignmentUtils.getSecondaryStructureSources(annotations);
+    List<String> ssSources = AlignmentUtils.extractSSSourceInAlignmentAnnotation(annotations);
+    
+                 
+    return ssSources;
+  }
+  
   /**
    * Open and calculate the selected tree or PCA on 'OK'
    */
@@ -516,8 +612,18 @@ public class CalculationChooser extends JPanel
   {
     boolean doPCA = pca.isSelected();
     String modelName = modelNames.getSelectedItem().toString();
-    SimilarityParamsI params = getSimilarityParameters(doPCA);
 
+    String ssSource = null;
+    
+    if (modelName.equals(secondaryStructureModelName)) {
+      Object selectedItem = ssSourceDropdown.getSelectedItem();
+      if (selectedItem != null) {
+          ssSource = selectedItem.toString();
+      }
+    }
+    SimilarityParams params = getSimilarityParameters(doPCA);
+    params.setSecondaryStructureSource(ssSource);
+    
     if (doPCA)
     {
       openPcaPanel(modelName, params);
@@ -618,7 +724,7 @@ public class CalculationChooser extends JPanel
    * @param doPCA
    * @return
    */
-  protected SimilarityParamsI getSimilarityParameters(boolean doPCA)
+  protected SimilarityParams getSimilarityParameters(boolean doPCA)
   {
     // commented out: parameter choices read from gui widgets
     // SimilarityParamsI params = new SimilarityParams(
index 576f3b2..0bcb0da 100644 (file)
@@ -32,8 +32,10 @@ import java.awt.print.PrinterException;
 import java.awt.print.PrinterJob;
 
 import javax.swing.ButtonGroup;
+import javax.swing.JLabel;
 import javax.swing.JMenuItem;
 import javax.swing.JRadioButtonMenuItem;
+import javax.swing.SwingConstants;
 import javax.swing.event.InternalFrameAdapter;
 import javax.swing.event.InternalFrameEvent;
 
@@ -129,6 +131,14 @@ public class PCAPanel extends GPCAPanel
     PaintRefresher.Register(this, av.getSequenceSetId());
 
     setRotatableCanvas(new RotatableCanvas(alignPanel));
+
+    if(params.getSecondaryStructureSource()!=null ) {
+      // Initialize and set subtitle text
+      JLabel subtitleLabel = new JLabel(" Secondary Structure Provider : " 
+                  + params.getSecondaryStructureSource(), SwingConstants.LEFT);
+      this.getContentPane().add(subtitleLabel, BorderLayout.NORTH); 
+    
+    }
     this.getContentPane().add(getRotatableCanvas(), BorderLayout.CENTER);
 
     addKeyListener(getRotatableCanvas());
index 83f424b..9d23474 100644 (file)
@@ -54,6 +54,7 @@ import jalview.analysis.AAFrequency;
 import jalview.analysis.AlignmentAnnotationUtils;
 import jalview.analysis.AlignmentUtils;
 import jalview.analysis.Conservation;
+import jalview.api.AlignCalcWorkerI;
 import jalview.api.AlignViewportI;
 import jalview.bin.Console;
 import jalview.commands.ChangeCaseCommand;
@@ -87,6 +88,7 @@ import jalview.util.Platform;
 import jalview.util.StringUtils;
 import jalview.util.UrlLink;
 import jalview.viewmodel.seqfeatures.FeatureRendererModel;
+import jalview.workers.SecondaryStructureConsensusThread;
 
 /**
  * The popup menu that is displayed on right-click on a sequence id, or in the
@@ -1762,8 +1764,34 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
   {
     final AlignmentI alignment = this.ap.getAlignment();
     AlignmentUtils.addReferenceAnnotations(candidates, alignment, null);
-    refresh();
+        
+    if(AlignmentUtils.isSSAnnotationPresent(candidates)) {
+      
+      restartSSConsensusWorker();
+      ap.validateAnnotationDimensions(true);
+      ap.fontChanged();
+      ap.av.alignmentChanged(ap);
+      ap.adjustAnnotationHeight();
+      restartSSConsensusWorker();
+      //ap.alignFrame.getViewport().getCalcManager().restartWorkers();
+    }
+        
+  }
+  
+  
+  private void restartSSConsensusWorker() {
+    
+    List<AlignCalcWorkerI> workers = ap.alignFrame.getViewport().getCalcManager()
+            .getRegisteredWorkersOfClass(SecondaryStructureConsensusThread.class);
+    if (!workers.isEmpty()) {
+        
+      ap.alignFrame.getViewport().getCalcManager().startWorker(workers.remove(0));
+
+    }
+        
   }
+  
+  
 
   protected void makeReferenceSeq_actionPerformed(ActionEvent actionEvent)
   {
index c4f32c3..4fb013f 100755 (executable)
@@ -292,6 +292,7 @@ public class Preferences extends GPreferences
     conservation.setSelected(Cache.getDefault("SHOW_CONSERVATION", true));
     quality.setSelected(Cache.getDefault("SHOW_QUALITY", true));
     identity.setSelected(Cache.getDefault("SHOW_IDENTITY", true));
+    ssConsensus.setSelected(Cache.getDefault("SHOW_SS_CONSENSUS", false));
     openoverv.setSelected(Cache.getDefault("SHOW_OVERVIEW", false));
     showUnconserved
             .setSelected(Cache.getDefault("SHOW_UNCONSERVED", false));
@@ -781,6 +782,9 @@ public class Preferences extends GPreferences
     Cache.applicationProperties.setProperty("SHOW_IDENTITY",
             Boolean.toString(identity.isSelected()));
 
+    Cache.applicationProperties.setProperty("SHOW_SS_CONSENSUS",
+            Boolean.toString(ssConsensus.isSelected()));
+
     Cache.applicationProperties.setProperty("GAP_SYMBOL",
             gapSymbolCB.getSelectedItem().toString());
 
@@ -1160,6 +1164,7 @@ public class Preferences extends GPreferences
     conservation.setEnabled(annotations.isSelected());
     quality.setEnabled(annotations.isSelected());
     identity.setEnabled(annotations.isSelected());
+    ssConsensus.setEnabled(annotations.isSelected());
     showOccupancy.setEnabled(annotations.isSelected());
     showGroupConsensus.setEnabled(annotations.isSelected());
     showGroupConservation.setEnabled(annotations.isSelected());
index f708c48..24a5547 100755 (executable)
@@ -20,6 +20,7 @@
  */
 package jalview.gui;
 
+import java.awt.BorderLayout;
 import java.awt.Font;
 import java.awt.Graphics;
 import java.awt.event.ActionEvent;
@@ -35,8 +36,11 @@ import java.util.List;
 import java.util.Locale;
 
 import javax.swing.ButtonGroup;
+import javax.swing.JLabel;
 import javax.swing.JMenuItem;
+import javax.swing.JPanel;
 import javax.swing.JRadioButtonMenuItem;
+import javax.swing.SwingConstants;
 import javax.swing.event.InternalFrameAdapter;
 import javax.swing.event.InternalFrameEvent;
 
@@ -96,6 +100,10 @@ public class TreePanel extends GTreePanel
   TreeModel tree;
 
   private AlignViewport av;
+  
+
+  // New JLabel for subtitle
+  private JLabel subtitleLabel;
 
   /**
    * Creates a new TreePanel object.
@@ -181,6 +189,19 @@ public class TreePanel extends GTreePanel
 
     treeCanvas = new TreeCanvas(this, ap, scrollPane);
     scrollPane.setViewportView(treeCanvas);
+    
+    if(this.similarityParams!=null)
+      if(this.similarityParams.getSecondaryStructureSource()!=null ) {
+    
+      subtitleLabel = new JLabel(" Secondary Structure Provider : " 
+                + this.similarityParams.getSecondaryStructureSource(), SwingConstants.LEFT);
+  
+      JPanel panel = new JPanel(new BorderLayout());
+      panel.add(subtitleLabel, BorderLayout.NORTH);
+      panel.add(scrollPane, BorderLayout.CENTER);
+  
+      this.add(panel);
+    }
 
     if (columnWise)
     {
index 76ef85f..333f995 100644 (file)
@@ -387,6 +387,8 @@ public class ThreeDBStructureChooserQuerySource
     int typeColumnIndex = restable.getColumn("Provider").getModelIndex();
     int humanUrl = restable.getColumn("Page URL").getModelIndex();
     int modelformat = restable.getColumn("Model Format").getModelIndex();
+    int idx_mcat = restable.getColumn("Model Category").getModelIndex();
+
     final int up_start_idx = restable.getColumn("Uniprot Start")
             .getModelIndex();
     final int up_end_idx = restable.getColumn("Uniprot End")
@@ -423,6 +425,9 @@ public class ThreeDBStructureChooserQuerySource
               .toString();
       String modelPage = humanUrl < 1 ? null
               : (String) restable.getValueAt(row, humanUrl);
+
+      String modelCategory = idx_mcat < 1 ? null :(String) restable.getValueAt(row,idx_mcat); 
+              
       String strucFormat = restable.getValueAt(row, modelformat).toString();
 
       SequenceI selectedSeq = (SequenceI) restable.getValueAt(row,
@@ -457,6 +462,7 @@ public class ThreeDBStructureChooserQuerySource
         {
           pdbEntry.setProviderPage(modelPage);
         }
+        pdbEntry.setProviderCategory(modelCategory);
         selectedSeq.getDatasetSequence().addPDBId(pdbEntry);
       }
       pdbEntriesToView[count++] = pdbEntry;
index 9b9a44a..a8cdcdd 100755 (executable)
@@ -22,6 +22,7 @@ package jalview.jbgui;
 
 import java.awt.BorderLayout;
 import java.awt.Color;
+import java.awt.Component;
 import java.awt.GridLayout;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
@@ -31,6 +32,7 @@ import java.awt.event.KeyEvent;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import javax.swing.BorderFactory;
@@ -193,12 +195,16 @@ public class GAlignFrame extends JInternalFrame
   protected JMenuItem gatherViews = new JMenuItem();
 
   protected JMenuItem expandViews = new JMenuItem();
+  
+  protected JCheckBoxMenuItem showSSConsensus = new JCheckBoxMenuItem();   
 
+  protected JCheckBoxMenuItem showGroupSSConsensus = new JCheckBoxMenuItem();
+  
   protected JCheckBoxMenuItem showGroupConsensus = new JCheckBoxMenuItem();
 
   protected JCheckBoxMenuItem showGroupConservation = new JCheckBoxMenuItem();
 
-  protected JCheckBoxMenuItem showConsensusHistogram = new JCheckBoxMenuItem();
+  protected JCheckBoxMenuItem showConsensusHistogram = new JCheckBoxMenuItem();  
 
   protected JCheckBoxMenuItem showSequenceLogo = new JCheckBoxMenuItem();
 
@@ -855,6 +861,32 @@ public class GAlignFrame extends JInternalFrame
 
     });
 
+    showSSConsensus
+            .setText(MessageManager.getString("label.show_secondary_structure_consensus"));
+    showSSConsensus.addActionListener(new ActionListener()
+    {
+    
+    @Override
+    public void actionPerformed(ActionEvent e)
+    {
+      showSSConsensus_actionPerformed(e);
+    }
+    
+    });
+    
+    showGroupSSConsensus
+            .setText(MessageManager.getString("label.group_ss_consensus"));
+    showGroupSSConsensus.addActionListener(new ActionListener()
+    {
+    
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        showGroupSSConsensus_actionPerformed(e);
+      }
+    
+    });
+
     showGroupConsensus
             .setText(MessageManager.getString("label.group_consensus"));
     showGroupConsensus.addActionListener(new ActionListener()
@@ -878,7 +910,7 @@ public class GAlignFrame extends JInternalFrame
         showConsensusHistogram_actionPerformed(e);
       }
 
-    });
+    });    
     showSequenceLogo
             .setText(MessageManager.getString("label.show_consensus_logo"));
     showSequenceLogo.addActionListener(new ActionListener()
@@ -1785,11 +1817,61 @@ public class GAlignFrame extends JInternalFrame
     addMenuActionAndAccelerator(keyStroke, copyHighlighted, al);
     copyHighlighted.addActionListener(al);
 
+    ButtonGroup ssButtonGroup = new ButtonGroup();   
+
     JMenu tooltipSettingsMenu = new JMenu(
             MessageManager.getString("label.sequence_id_tooltip"));
     JMenu autoAnnMenu = new JMenu(
             MessageManager.getString("label.autocalculated_annotation"));
 
+    JMenu showSS = new JMenu(
+            MessageManager.getString("label.show_secondary_structure")); 
+    
+    JRadioButtonMenuItem radioButtonAllSS = new JRadioButtonMenuItem(MessageManager.getString("option.ss_providers_all"));
+    radioButtonAllSS.addActionListener(new ActionListener() {
+      @Override
+      public void actionPerformed(ActionEvent e) {
+          showOrHideSecondaryStructureForSource(MessageManager.getString("option.ss_providers_all"), true);
+          // Select all checkboxes if "All" is selected
+          Component[] components = showSS.getMenuComponents();
+          for (Component component : components) {
+              if (component instanceof JCheckBoxMenuItem) {
+                  ((JCheckBoxMenuItem) component).setSelected(true);
+              }
+          }
+      }
+    });
+    ssButtonGroup.add(radioButtonAllSS);
+    showSS.add(radioButtonAllSS);
+    
+    JRadioButtonMenuItem radioButtonNoneSS = new JRadioButtonMenuItem(MessageManager.getString("option.ss_providers_none"));
+    radioButtonNoneSS.addActionListener(new ActionListener() {
+      @Override
+      public void actionPerformed(ActionEvent e) {
+          showOrHideSecondaryStructureForSource(MessageManager.getString("option.ss_providers_none"), false);
+          // Remove selection of all checkboxes if "None" is selected
+          Component[] components = showSS.getMenuComponents();
+          for (Component component : components) {
+              if (component instanceof JCheckBoxMenuItem) {
+                  ((JCheckBoxMenuItem) component).setSelected(false);
+              }
+          }
+      }
+    });
+    ssButtonGroup.add(radioButtonNoneSS);
+    showSS.add(radioButtonNoneSS);
+    showSS.addSeparator();
+    
+    annotationsMenu.addMouseListener(new MouseAdapter() {
+      
+      @Override
+      public void mouseEntered(MouseEvent e) {
+
+        updateShowSecondaryStructureMenu(showSS, ssButtonGroup); // Update radio buttons every time the menu is clicked
+         
+      }
+    });
+    
     JMenu exportImageMenu = new JMenu(
             MessageManager.getString("label.export_image"));
     JMenu fileMenu = new JMenu(MessageManager.getString("action.file"));
@@ -1890,6 +1972,8 @@ public class GAlignFrame extends JInternalFrame
     annotationsMenu.add(sortAnnBySequence);
     annotationsMenu.add(sortAnnByLabel);
     annotationsMenu.addSeparator();
+    annotationsMenu.add(showSS);
+    annotationsMenu.addSeparator();
     autoAnnMenu.add(showAutoFirst);
     autoAnnMenu.add(showAutoLast);
     autoAnnMenu.addSeparator();
@@ -1897,9 +1981,11 @@ public class GAlignFrame extends JInternalFrame
     autoAnnMenu.add(showConsensusHistogram);
     autoAnnMenu.add(showSequenceLogo);
     autoAnnMenu.add(normaliseSequenceLogo);
+    //autoAnnMenu.add(showSSConsensus);
     autoAnnMenu.addSeparator();
     autoAnnMenu.add(showGroupConservation);
     autoAnnMenu.add(showGroupConsensus);
+    autoAnnMenu.add(showGroupSSConsensus);
     annotationsMenu.add(autoAnnMenu);
 
     sort.add(sortIDMenuItem);
@@ -1981,6 +2067,12 @@ public class GAlignFrame extends JInternalFrame
     // selectMenu.add(listenToViewSelections);
   }
 
+  protected void showSSConsensus_actionPerformed(ActionEvent e)
+  {
+    // TODO Auto-generated method stub
+    
+  }
+
   protected void createPNG_actionPerformed(ActionEvent object)
   {
     // TODO Auto-generated method stub
@@ -2247,6 +2339,12 @@ public class GAlignFrame extends JInternalFrame
     // TODO Auto-generated method stub
 
   }
+  
+  protected void showGroupSSConsensus_actionPerformed(ActionEvent e)
+  {
+    // TODO Auto-generated method stub
+
+  }
 
   protected void showGroupConservation_actionPerformed(ActionEvent e)
   {
@@ -2523,10 +2621,6 @@ public class GAlignFrame extends JInternalFrame
 
   }
 
-  protected void jpred_actionPerformed(ActionEvent e)
-  {
-  }
-
   protected void scaleAbove_actionPerformed(ActionEvent e)
   {
   }
@@ -2790,4 +2884,23 @@ public class GAlignFrame extends JInternalFrame
   protected void showComplement_actionPerformed(boolean complement)
   {
   }
+
+  protected List<String> updateShowSSRadioButtons()
+  {
+    // TODO Auto-generated method stub
+    return null;
+  }
+
+  protected void showOrHideSecondaryStructureForSource(String ssSourceSelection, boolean visible)
+  {
+    // TODO Auto-generated method stub
+    
+  }
+
+  protected void updateShowSecondaryStructureMenu(JMenu showSS,
+          ButtonGroup ssButtonGroup)
+  {
+    // TODO Auto-generated method stub
+    
+  }
 }
index 03538ef..e574327 100755 (executable)
@@ -160,7 +160,9 @@ public class GPreferences extends JPanel
 
   protected JCheckBox conservation = new JCheckBox();
 
-  protected JCheckBox identity = new JCheckBox();
+  protected JCheckBox identity = new JCheckBox();  
+
+  protected JCheckBox ssConsensus = new JCheckBox();
 
   protected JCheckBox showGroupConsensus = new JCheckBox();
 
@@ -1873,6 +1875,12 @@ public class GPreferences extends JPanel
     identity.setHorizontalTextPosition(SwingConstants.LEFT);
     identity.setSelected(true);
     identity.setText(MessageManager.getString("label.consensus"));
+    ssConsensus.setEnabled(false);
+    ssConsensus.setFont(LABEL_FONT);
+    ssConsensus.setHorizontalAlignment(SwingConstants.RIGHT);
+    ssConsensus.setHorizontalTextPosition(SwingConstants.LEFT);
+    ssConsensus.setSelected(false);
+    ssConsensus.setText(MessageManager.getString("label.ssConsensus"));
     showOccupancy.setFont(LABEL_FONT);
     showOccupancy.setEnabled(false);
     showOccupancy.setHorizontalAlignment(SwingConstants.RIGHT);
@@ -2067,7 +2075,7 @@ public class GPreferences extends JPanel
     sortAutocalc.setBounds(new Rectangle(290, 285, 165, 21));
 
     JPanel annsettingsPanel = new JPanel();
-    annsettingsPanel.setBounds(new Rectangle(173, 13, 320, 96));
+    annsettingsPanel.setBounds(new Rectangle(173, 13, 320, 101));
     annsettingsPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
     annsettingsPanel.setBorder(new EtchedBorder());
     visualTab.add(annsettingsPanel);
@@ -2077,6 +2085,7 @@ public class GPreferences extends JPanel
     quality.setBorder(jb);
     conservation.setBorder(jb);
     identity.setBorder(jb);
+    ssConsensus.setBorder(jb);
     showConsensbits.setBorder(jb);
     showGroupbits.setBorder(jb);
     showGroupConsensus.setBorder(jb);
@@ -2092,6 +2101,12 @@ public class GPreferences extends JPanel
     // second row of autoannotation box
     autoAnnotSettings = new JPanel();
     annsettingsPanel.add(autoAnnotSettings);
+    autoAnnotSettings.setLayout(new GridLayout(0, 1));
+    autoAnnotSettings.add(ssConsensus);
+
+    // third row of autoannotation box
+    autoAnnotSettings = new JPanel();
+    annsettingsPanel.add(autoAnnotSettings);
 
     autoAnnotSettings.setLayout(new GridLayout(0, 3));
     autoAnnotSettings.add(conservation);
index 983b512..e620c1c 100644 (file)
@@ -5309,6 +5309,8 @@ public class Jalview2XML
             view.isIgnoreGapsinConsensus());
     viewport.getResidueShading()
             .setConsensus(viewport.getSequenceConsensusHash());
+    viewport.getResidueShading()
+    .setSSConsensusProfileMap(viewport.getSequenceSSConsensusHash());
     if (safeBoolean(view.isConservationSelected()) && cs != null)
     {
       viewport.getResidueShading()
index b5dac0d..7c360c2 100644 (file)
@@ -33,11 +33,14 @@ import java.awt.geom.AffineTransform;
 import java.awt.image.ImageObserver;
 import java.util.BitSet;
 import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
 
 import org.jfree.graphics2d.svg.SVGGraphics2D;
 import org.jibble.epsgraphics.EpsGraphics2D;
 
 import jalview.analysis.AAFrequency;
+import jalview.analysis.AlignmentUtils;
 import jalview.analysis.CodingUtils;
 import jalview.analysis.Rna;
 import jalview.analysis.StructureFrequency;
@@ -55,6 +58,7 @@ import jalview.schemes.ColourSchemeI;
 import jalview.schemes.NucleotideColourScheme;
 import jalview.schemes.ResidueProperties;
 import jalview.schemes.ZappoColourScheme;
+import jalview.util.MessageManager;
 import jalview.util.Platform;
 
 public class AnnotationRenderer
@@ -88,6 +92,8 @@ public class AnnotationRenderer
   private HiddenColumns hiddenColumns;
 
   private ProfilesI hconsensus;
+  
+  private Map<String, ProfilesI> hSSconsensus;
 
   private Hashtable<String, Object>[] complementConsensus;
 
@@ -164,6 +170,7 @@ public class AnnotationRenderer
   {
     hiddenColumns = null;
     hconsensus = null;
+    hSSconsensus = null;
     complementConsensus = null;
     hStrucConsensus = null;
     fadedImage = null;
@@ -377,6 +384,7 @@ public class AnnotationRenderer
     columnSelection = av.getColumnSelection();
     hiddenColumns = av.getAlignment().getHiddenColumns();
     hconsensus = av.getSequenceConsensusHash();
+    hSSconsensus = av.getSequenceSSConsensusHash();
     complementConsensus = av.getComplementConsensusHash();
     hStrucConsensus = av.getRnaStructureConsensusHash();
     av_ignoreGapsConsensus = av.isIgnoreGapsConsensus();
@@ -425,8 +433,35 @@ public class AnnotationRenderer
         }
       }
     }
-    else
+    
+    if(aa.autoCalculated && aa.label.startsWith(MessageManager.getString("label.ssconsensus_label"))) 
     {
+      
+      if(aa.groupRef != null && aa.groupRef.hSSConsensusProfileMap != null
+              && aa.groupRef.isShowSequenceLogo()) {
+      for (String source : aa.groupRef.hSSConsensusProfileMap.keySet()) {
+        if(aa.description.startsWith(source)) {
+      
+          return AAFrequency.extractProfile(
+                  aa.groupRef.hSSConsensusProfileMap.get(source).get(column),
+                  aa.groupRef.getIgnoreGapsConsensus());
+        }
+      }
+      }
+      
+      if(hSSconsensus!=null && aa.groupRef == null) {
+      for (String source : hSSconsensus.keySet()) {
+        if(aa.description.startsWith(source)) {
+      
+          return AAFrequency.extractProfile(
+                  hSSconsensus.get(source).get(column),
+                  av_ignoreGapsConsensus);
+        }
+      }
+      }
+      
+    }
+    
       if (aa.autoCalculated && aa.label.startsWith("StrucConsensus"))
       {
         // TODO implement group structure consensus
@@ -448,7 +483,7 @@ public class AnnotationRenderer
                   av_ignoreGapsConsensus);
         }
       }
-    }
+    
     return null;
   }
 
@@ -514,6 +549,8 @@ public class AnnotationRenderer
             .getAlignmentStrucConsensusAnnotation();
     final AlignmentAnnotation complementConsensusAnnot = av
             .getComplementConsensusAnnotation();
+    final List<AlignmentAnnotation> ssConsensusAnnot = av
+            .getAlignmentSecondaryStructureConsensusAnnotation();
 
     BitSet graphGroupDrawn = new BitSet();
     int charOffset = 0; // offset for a label
@@ -533,14 +570,15 @@ public class AnnotationRenderer
       // settings appropriately
       // TODO: generalise this to have render styles for consensus/profile
       // data
-      if (row.groupRef != null && row == row.groupRef.getConsensus())
+      if (row.groupRef != null && 
+              (row == row.groupRef.getConsensus() || row.groupRef.getSSConsensus(null).contains(row)))
       {
         renderHistogram = row.groupRef.isShowConsensusHistogram();
         renderProfile = row.groupRef.isShowSequenceLogo();
         normaliseProfile = row.groupRef.isNormaliseSequenceLogo();
       }
       else if (row == consensusAnnot || row == structConsensusAnnot
-              || row == complementConsensusAnnot)
+              || row == complementConsensusAnnot || (ssConsensusAnnot!=null && ssConsensusAnnot.contains(row)))
       {
         renderHistogram = av_renderHistogram;
         renderProfile = av_renderProfile;
@@ -1519,6 +1557,8 @@ public class AnnotationRenderer
          * {profile type, #values, total count, char1, pct1, char2, pct2...}
          */
         int profl[] = getProfileFor(_aa, column);
+        
+        
 
         // just try to draw the logo if profl is not null
         if (profl != null && profl[2] != 0)
@@ -1600,9 +1640,14 @@ public class AnnotationRenderer
               colour = profcolour.findColour(codonTranslation.charAt(0),
                       column, null);
             }
+            if(_aa.label.startsWith(MessageManager.getString("label.ssconsensus_label"))) {
+              colour = AlignmentUtils.getSecondaryStructureAnnotationColour(dc[0]);              
+            }
             else
             {
+              
               colour = profcolour.findColour(dc[0], column, null);
+
             }
             g.setColor(colour == Color.white ? Color.lightGray : colour);
 
index c031170..9231901 100644 (file)
@@ -61,6 +61,12 @@ public class ResidueShader implements ResidueShaderI
    * the consensus data for each column
    */
   private ProfilesI consensus;
+  
+  /*
+   * the ss consensus data for each column for each source
+   */
+  
+  private Map<String, ProfilesI> ssConsensusProfileMap;
 
   /*
    * if true, apply shading of colour by conservation
@@ -128,6 +134,7 @@ public class ResidueShader implements ResidueShaderI
     this.conservationIncrement = rs.conservationIncrement;
     this.ignoreGaps = rs.ignoreGaps;
     this.pidThreshold = rs.pidThreshold;
+    this.ssConsensusProfileMap = rs.ssConsensusProfileMap;
   }
 
   /**
@@ -137,6 +144,7 @@ public class ResidueShader implements ResidueShaderI
   public void setConsensus(ProfilesI cons)
   {
     consensus = cons;
+    
   }
 
   /**
@@ -261,6 +269,36 @@ public class ResidueShader implements ResidueShaderI
 
     return colour;
   }
+  
+  @Override
+  public Color findSSColour(char symbol, int position, SequenceI seq, String source)
+  {
+    if (colourScheme == null)
+    {
+      return Color.white; // Colour is 'None'
+    }
+
+    /*
+     * get 'base' colour
+     */
+    ProfileI profile = ssConsensusProfileMap.get(source) == null ? null : ssConsensusProfileMap.get(source).get(position);
+    String modalSS = profile == null ? null
+            : profile.getModalSS();
+    float pid = profile == null ? 0f
+            : profile.getSSPercentageIdentity(ignoreGaps);
+    Color colour = colourScheme.findColour(symbol, position, seq,
+            modalSS, pid);
+
+    /*
+     * apply PID threshold and consensus fading if in force
+     */
+    if (!Comparison.isGap(symbol))
+    {
+      colour = adjustColour(symbol, position, colour);
+    }
+
+    return colour;
+  }
 
   /**
    * Adjusts colour by applying thresholding or conservation shading, if in
@@ -415,4 +453,14 @@ public class ResidueShader implements ResidueShaderI
   {
     colourScheme = cs;
   }
+
+  public Map<String, ProfilesI> getSSConsensusProfileMap()
+  {
+    return ssConsensusProfileMap;
+  }
+
+  public void setSSConsensusProfileMap(Map<String, ProfilesI> ssConsensusProfileMap)
+  {
+    this.ssConsensusProfileMap = ssConsensusProfileMap;
+  }
 }
index 4d97171..42f4e2e 100644 (file)
@@ -34,6 +34,8 @@ public interface ResidueShaderI
 {
 
   public abstract void setConsensus(ProfilesI cons);
+  
+  public abstract void setSSConsensusProfileMap(Map<String, ProfilesI> ssConsensusProfileMap);
 
   public abstract boolean conservationApplied();
 
@@ -82,4 +84,7 @@ public interface ResidueShaderI
 
   public abstract void setColourScheme(ColourSchemeI cs);
 
+  Color findSSColour(char symbol, int position, SequenceI seq,
+          String source);
+
 }
\ No newline at end of file
index e297ff6..2fcb95f 100755 (executable)
@@ -41,6 +41,8 @@ public class ResidueProperties
   public static final int[] nucleotideIndex;
 
   public static final int[] purinepyrimidineIndex;
+  
+  public static final int[] secondaryStructureIndex;
 
   public static final Map<String, Integer> aa3Hash = new HashMap<>();
 
@@ -195,6 +197,19 @@ public class ResidueProperties
     purinepyrimidineIndex['N'] = 2;
     purinepyrimidineIndex['n'] = 2;
   }
+  
+  static
+  {
+    secondaryStructureIndex = new int[255];
+    for (int i = 0; i < 255; i++)
+    {
+      secondaryStructureIndex[i] = 3; 
+    }
+
+    secondaryStructureIndex['H'] = 0;
+    secondaryStructureIndex['E'] = 1;
+    secondaryStructureIndex['C'] = 2;
+  }
 
   private static final Integer ONE = Integer.valueOf(1);
 
@@ -383,6 +398,12 @@ public class ResidueProperties
       Color.white, // all other nucleotides
       Color.white // Gap
   };
+  
+  //Secondary structure
+   public static final Color[] secondarystructure = { Color.red, // H
+       Color.green, // E
+       Color.gray  // C
+   };
 
   // Zappo
   public static final Color[] zappo = { Color.pink, // A
diff --git a/src/jalview/util/Constants.java b/src/jalview/util/Constants.java
new file mode 100644 (file)
index 0000000..bced9c8
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+package jalview.util;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A class to hold constants relating to Url links used in Jalview
+ */
+public class Constants
+{
+  
+  //character used to represent secondary structures
+  public static final char HELIX = 'H';
+  public static final char SHEET = 'E';
+  public static final char COIL = 'C';
+
+  //label in secondary structure annotation data model from 3d structures
+  public static final String SS_ANNOTATION_LABEL = "Secondary Structure";
+  
+  //label in secondary structure annotation data model from JPred
+  public static final String SS_ANNOTATION_FROM_JPRED_LABEL = "jnetpred";  
+  
+  public static final Map<String, String> SECONDARY_STRUCTURE_LABELS = new HashMap<>();
+  static {
+      SECONDARY_STRUCTURE_LABELS.put(SS_ANNOTATION_LABEL, "3D Structures");
+      SECONDARY_STRUCTURE_LABELS.put(SS_ANNOTATION_FROM_JPRED_LABEL, "JPred");
+      // Add other secondary structure labels here if needed
+  } 
+}
index de30b28..ee07c3a 100644 (file)
@@ -25,6 +25,7 @@ import java.beans.PropertyChangeSupport;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.BitSet;
+import java.util.Collections;
 import java.util.Deque;
 import java.util.HashMap;
 import java.util.Hashtable;
@@ -33,6 +34,7 @@ import java.util.List;
 import java.util.Map;
 
 import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder;
+import jalview.analysis.AlignmentUtils;
 import jalview.analysis.Conservation;
 import jalview.analysis.TreeModel;
 import jalview.api.AlignCalcManagerI;
@@ -76,6 +78,7 @@ import jalview.viewmodel.styles.ViewStyle;
 import jalview.workers.AlignCalcManager;
 import jalview.workers.ComplementConsensusThread;
 import jalview.workers.ConsensusThread;
+import jalview.workers.SecondaryStructureConsensusThread;
 import jalview.workers.StrucConsensusThread;
 
 /**
@@ -697,6 +700,8 @@ public abstract class AlignmentViewport
   }
 
   protected AlignmentAnnotation consensus;
+  
+  protected List<AlignmentAnnotation> secondaryStructureConsensus;
 
   protected AlignmentAnnotation complementConsensus;
 
@@ -709,6 +714,8 @@ public abstract class AlignmentViewport
   protected AlignmentAnnotation quality;
 
   protected AlignmentAnnotation[] groupConsensus;
+  
+  protected AlignmentAnnotation[] groupSSConsensus;
 
   protected AlignmentAnnotation[] groupConservation;
 
@@ -716,6 +723,10 @@ public abstract class AlignmentViewport
    * results of alignment consensus analysis for visible portion of view
    */
   protected ProfilesI hconsensus = null;
+  
+  protected Map<String, ProfilesI> hSSConsensusProfileMap = null;
+  
+  
 
   /**
    * results of cDNA complement consensus visible portion of view
@@ -735,6 +746,32 @@ public abstract class AlignmentViewport
   {
     hconservation = cons;
   }
+  
+  @Override
+  public List<String> getSecondaryStructureSources()
+  {
+    return viewStyle.getSecondaryStructureSources();
+  }
+
+  @Override
+  public void setSecondaryStructureSources(
+          List<String> secondaryStructureSources)
+  {
+    viewStyle.setSecondaryStructureSources(secondaryStructureSources);
+  }
+  
+  protected void setSecondaryStructureSources(AlignmentAnnotation[] aa)
+  {
+    List<String> sources = null;
+    
+    if(aa!=null) {
+      sources = AlignmentUtils.extractSSSourceInAlignmentAnnotation(aa);
+      if(sources != null) {
+        sources.add(0,MessageManager.getString("option.ss_providers_all"));
+        viewStyle.setSecondaryStructureSources(sources);
+      }
+    }
+  }
 
   /**
    * percentage gaps allowed in a column before all amino acid properties should
@@ -753,7 +790,13 @@ public abstract class AlignmentViewport
   {
     this.hconsensus = hconsensus;
   }
-
+  
+  @Override
+  public void setSequenceSSConsensusHash(Map<String, ProfilesI> hSSConsensusProfileMap)
+  {
+    this.hSSConsensusProfileMap = hSSConsensusProfileMap;
+  }
+    
   @Override
   public void setComplementConsensusHash(
           Hashtable<String, Object>[] hconsensus)
@@ -768,6 +811,13 @@ public abstract class AlignmentViewport
   }
 
   @Override
+  public Map<String, ProfilesI> getSequenceSSConsensusHash()
+  {
+    return hSSConsensusProfileMap;
+  }
+  
+
+  @Override
   public Hashtable<String, Object>[] getComplementConsensusHash()
   {
     return hcomplementConsensus;
@@ -805,6 +855,14 @@ public abstract class AlignmentViewport
     return consensus;
   }
 
+  
+  @Override
+  public List<AlignmentAnnotation> getAlignmentSecondaryStructureConsensusAnnotation()
+  {
+    return secondaryStructureConsensus;
+  }
+  
+
   @Override
   public AlignmentAnnotation getAlignmentGapAnnotation()
   {
@@ -896,6 +954,58 @@ public abstract class AlignmentViewport
       }
     }
   }
+  
+  
+  
+
+  /**
+   * trigger update of Secondary Structure consensus annotation
+   */
+  public void updateSecondaryStructureConsensus(final AlignmentViewPanel ap)
+  {
+    // see note in mantis : issue number 8585
+    if (secondaryStructureConsensus == null || !autoCalculateConsensus)
+    {
+      return;
+    }
+    List<String> ssSources = viewStyle.getSecondaryStructureSources();
+    if (secondaryStructureConsensus.size() != ssSources.size()) {
+      
+      for(String source : ssSources) {      
+        boolean ssConsensusForSourcePresent = false;
+        for(AlignmentAnnotation aa : secondaryStructureConsensus) {
+          if(aa.description.startsWith(source)) {
+            ssConsensusForSourcePresent = true;
+            break;
+          }
+        }
+        
+        if(!ssConsensusForSourcePresent) {
+          AlignmentAnnotation ssConsensus = new AlignmentAnnotation(MessageManager.getString("label.ssconsensus_label") + " "+source,
+                  source + " " + MessageManager.getString("label.ssconsensus_descr"),
+                  new Annotation[1], 0f, 100f, AlignmentAnnotation.BAR_GRAPH);
+
+          ssConsensus.hasText = true;
+          ssConsensus.autoCalculated = true;
+          secondaryStructureConsensus.add(ssConsensus);
+          if (showSSConsensus)
+          {
+            ssConsensus.visible = true;
+            alignment.addAnnotation(ssConsensus);
+            
+          }
+        }
+      }
+    }
+    if (calculator
+            .getRegisteredWorkersOfClass(SecondaryStructureConsensusThread.class) == null)
+    {
+      calculator.registerWorker(new SecondaryStructureConsensusThread(this, ap));
+    }
+    ap.adjustAnnotationHeight();
+
+    
+  }
 
   // --------START Structure Conservation
   public void updateStrucConsensus(final AlignmentViewPanel ap)
@@ -959,6 +1069,7 @@ public abstract class AlignmentViewport
     consensus = null;
     complementConsensus = null;
     strucConsensus = null;
+    secondaryStructureConsensus = null;
     conservation = null;
     quality = null;
     groupConsensus = null;
@@ -1000,12 +1111,15 @@ public abstract class AlignmentViewport
    * should consensus rows be shown for groups
    */
   protected boolean showGroupConsensus = false;
+  
+
+  protected boolean showGroupSSConsensus = false;
 
   /**
    * should consensus profile be rendered by default
    */
   protected boolean showSequenceLogo = false;
-
+  
   /**
    * should consensus profile be rendered normalised to row height
    */
@@ -1015,7 +1129,7 @@ public abstract class AlignmentViewport
    * should consensus histograms be rendered by default
    */
   protected boolean showConsensusHistogram = true;
-
+  
   /**
    * @return the showConsensusProfile
    */
@@ -1024,7 +1138,7 @@ public abstract class AlignmentViewport
   {
     return showSequenceLogo;
   }
-
+  
   /**
    * @param showSequenceLogo
    *          the new value
@@ -1039,10 +1153,14 @@ public abstract class AlignmentViewport
       calculator.updateAnnotationFor(ConsensusThread.class);
       calculator.updateAnnotationFor(ComplementConsensusThread.class);
       calculator.updateAnnotationFor(StrucConsensusThread.class);
+      
+      //to do
+
+      calculator.updateAnnotationFor(SecondaryStructureConsensusThread.class);
     }
     this.showSequenceLogo = showSequenceLogo;
   }
-
+  
   /**
    * @param showConsensusHistogram
    *          the showConsensusHistogram to set
@@ -1076,6 +1194,12 @@ public abstract class AlignmentViewport
   {
     return showGroupConsensus;
   }
+  
+  public boolean isShowGroupSSConsensus()
+  {
+    return showGroupSSConsensus;
+  }
+
 
   /**
    * @param showGroupConsensus
@@ -1085,7 +1209,21 @@ public abstract class AlignmentViewport
   {
     this.showGroupConsensus = showGroupConsensus;
   }
-
+  
+  public void setShowGroupSSConsensus(boolean showGroupSSConsensus)
+  {
+    this.showGroupSSConsensus = showGroupSSConsensus;
+  }
+  
+  /**
+   * @param showSSConsensus
+   *          the showSSConsensus to set
+   */
+  public void setShowSSConsensus(boolean showSSConsensus)
+  {
+    this.showSSConsensus = showSSConsensus;
+  }
+  
   /**
    * 
    * @return flag to indicate if the consensus histogram should be rendered by
@@ -1247,6 +1385,7 @@ public abstract class AlignmentViewport
     if (ap != null)
     {
       updateConsensus(ap);
+      updateSecondaryStructureConsensus(ap);
       if (residueShading != null)
       {
         residueShading.setThreshold(residueShading.getThreshold(),
@@ -1319,7 +1458,9 @@ public abstract class AlignmentViewport
 
   protected boolean showQuality = true;
 
-  protected boolean showConsensus = true;
+  protected boolean showConsensus = true;  
+
+  protected boolean showSSConsensus = true;
 
   protected boolean showOccupancy = true;
 
@@ -1878,6 +2019,7 @@ public abstract class AlignmentViewport
     if (autoCalculateConsensus)
     {
       updateConsensus(ap);
+      updateSecondaryStructureConsensus(ap);
     }
     if (hconsensus != null && autoCalculateConsensus)
     {
@@ -1961,13 +2103,30 @@ public abstract class AlignmentViewport
       consensus = new AlignmentAnnotation("Consensus",
               MessageManager.getString("label.consensus_descr"),
               new Annotation[1], 0f, 100f, AlignmentAnnotation.BAR_GRAPH);
+      setSecondaryStructureSources(alignment.getAlignmentAnnotation());
+      List<String> secondaryStructureSources = getSecondaryStructureSources();
+
+      if(secondaryStructureSources!=null) {
+
+        secondaryStructureConsensus = new ArrayList<AlignmentAnnotation>();
+        for (String ssSource : secondaryStructureSources) {
+        
+          AlignmentAnnotation ssConsensus = new AlignmentAnnotation(MessageManager.getString("label.ssconsensus_label") + " "+ssSource,
+                  ssSource + " " + MessageManager.getString("label.ssconsensus_descr"),
+                  new Annotation[1], 0f, 100f, AlignmentAnnotation.BAR_GRAPH);
+          secondaryStructureConsensus.add(ssConsensus);
+        }
+        
+      }
+      
       initConsensus(consensus);
+      initSSConsensus(secondaryStructureConsensus);
       initGapCounts();
-
       initComplementConsensus();
     }
   }
 
+
   /**
    * If this is a protein alignment and there are mappings to cDNA, adds the
    * cDNA consensus annotation and returns true, else returns false.
@@ -2018,6 +2177,23 @@ public abstract class AlignmentViewport
       alignment.addAnnotation(aa);
     }
   }
+  
+  private void initSSConsensus(List<AlignmentAnnotation> secondaryStructureConsensuses)
+  {
+    if(secondaryStructureConsensuses == null) {
+      return;
+    }
+    for(AlignmentAnnotation aa : secondaryStructureConsensuses) {
+      aa.hasText = true;
+      aa.autoCalculated = true;
+  
+      if (showSSConsensus)
+      {
+        alignment.addAnnotation(aa);
+      }
+      
+    }
+  }
 
   // these should be extracted from the view model - style and settings for
   // derived annotation
@@ -2165,6 +2341,7 @@ public abstract class AlignmentViewport
     boolean updateCalcs = false;
     boolean conv = isShowGroupConservation();
     boolean cons = isShowGroupConsensus();
+    boolean sscons = isShowGroupSSConsensus();
     boolean showprf = isShowSequenceLogo();
     boolean showConsHist = isShowConsensusHistogram();
     boolean normLogo = isNormaliseSequenceLogo();
@@ -2216,6 +2393,19 @@ public abstract class AlignmentViewport
           updateCalcs = true;
           alignment.addAnnotation(sg.getConsensus(), 0);
         }
+        if(sscons) 
+        {        
+          updateCalcs = true;
+          List<String> secondaryStructureSources = getSecondaryStructureSources();
+          if(secondaryStructureSources !=null) {
+            List<AlignmentAnnotation> ssAa = sg.getSSConsensus(secondaryStructureSources);
+            if(ssAa != null) {
+              for(AlignmentAnnotation aa : ssAa) {
+                alignment.addAnnotation(aa, 0);
+              }
+            }
+          }
+        }
         // refresh the annotation rows
         if (updateCalcs)
         {
@@ -3004,6 +3194,42 @@ public abstract class AlignmentViewport
             + ((ignoreGapsInConsensusCalculation) ? " without gaps" : ""));
     return sq;
   }
+  
+  //to do jal-4386
+  public SequenceI getSSConsensusSeq()
+  {
+    if (secondaryStructureConsensus == null)
+    {
+      updateSecondaryStructureConsensus(null);
+    }
+    if (secondaryStructureConsensus == null)
+    {
+      return null;
+    }
+    StringBuffer seqs = new StringBuffer();
+//    for (int i = 0; i < secondaryStructureConsensus.annotations.length; i++)
+//    {
+//      Annotation annotation = secondaryStructureConsensus.annotations[i];
+//      if (annotation != null)
+//      {
+//        String description = annotation.description;
+//        if (description != null && description.startsWith("["))
+//        {
+//          // consensus is a tie - just pick the first one
+//          seqs.append(description.charAt(1));
+//        }
+//        else
+//        {
+//          seqs.append(annotation.displayCharacter);
+//        }
+//      }
+//    }
+
+    SequenceI sq = new Sequence("Sec Str Consensus", seqs.toString());
+    sq.setDescription("Percentage Identity Sec Str Consensus "
+            + ((ignoreGapsInConsensusCalculation) ? " without gaps" : ""));
+    return sq;
+  }
 
   @Override
   public void setCurrentTree(TreeModel tree)
index 715645a..e856262 100644 (file)
@@ -21,6 +21,7 @@
 package jalview.viewmodel.styles;
 
 import java.awt.Color;
+import java.util.List;
 
 import jalview.api.ViewStyleI;
 
@@ -224,6 +225,7 @@ public class ViewStyle implements ViewStyleI
     setUpperCasebold(vs.isUpperCasebold());
     setWrapAlignment(vs.getWrapAlignment());
     setWrappedWidth(vs.getWrappedWidth());
+    setSecondaryStructureSources(vs.getSecondaryStructureSources());
     // ViewStyle.configureFrom(this, viewStyle);
   }
 
@@ -286,7 +288,8 @@ public class ViewStyle implements ViewStyleI
             && getThresholdTextColour() == vs.getThresholdTextColour()
             && isUpperCasebold() == vs.isUpperCasebold()
             && getWrapAlignment() == vs.getWrapAlignment()
-            && getWrappedWidth() == vs.getWrappedWidth());
+            && getWrappedWidth() == vs.getWrappedWidth()
+            && getSecondaryStructureSources() == vs.getSecondaryStructureSources());
     /*
      * and compare non-primitive types; syntax below will match null with null
      * values
@@ -373,6 +376,11 @@ public class ViewStyle implements ViewStyleI
   private boolean showComplementFeatures;
 
   private boolean showComplementFeaturesOnTop;
+  
+  /**
+  * secondary structure annotation rows shown in the view
+  */
+  private List<String> secondaryStructureSources = List.of( new String[0] );
 
   /**
    * GUI state
@@ -1144,4 +1152,16 @@ public class ViewStyle implements ViewStyleI
   {
     return showComplementFeaturesOnTop;
   }
+  
+  @Override
+  public List<String> getSecondaryStructureSources()
+  {
+    return secondaryStructureSources;
+  }
+
+  @Override
+  public void setSecondaryStructureSources(List<String> secondaryStructureSources)
+  {
+    this.secondaryStructureSources = secondaryStructureSources;
+  }
 }
index cdf8ea4..d64bf4d 100644 (file)
  */
 package jalview.workers;
 
-import jalview.api.AlignCalcManagerI;
-import jalview.api.AlignCalcWorkerI;
-import jalview.datamodel.AlignmentAnnotation;
-
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -33,6 +29,10 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import jalview.api.AlignCalcManagerI;
+import jalview.api.AlignCalcWorkerI;
+import jalview.datamodel.AlignmentAnnotation;
+
 public class AlignCalcManager implements AlignCalcManagerI
 {
   /*
diff --git a/src/jalview/workers/SecondaryStructureConsensusThread.java b/src/jalview/workers/SecondaryStructureConsensusThread.java
new file mode 100644 (file)
index 0000000..bd4b78c
--- /dev/null
@@ -0,0 +1,311 @@
+/*
+ * 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.
+ */
+package jalview.workers;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import jalview.analysis.AAFrequency;
+import jalview.analysis.AlignmentUtils;
+import jalview.api.AlignViewportI;
+import jalview.api.AlignmentViewPanel;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.Annotation;
+import jalview.datamodel.ProfilesI;
+import jalview.datamodel.SequenceI;
+import jalview.renderer.ResidueShaderI;
+import jalview.util.MessageManager;
+
+public class SecondaryStructureConsensusThread extends AlignCalcWorker
+{
+  public SecondaryStructureConsensusThread(AlignViewportI alignViewport,
+          AlignmentViewPanel alignPanel)
+  {
+    super(alignViewport, alignPanel);
+  }
+
+  @Override
+  public void run()
+  {
+    if (calcMan.isPending(this))
+    {
+      return;
+    }
+    calcMan.notifyStart(this);
+    // long started = System.currentTimeMillis();
+    try
+    {
+      List<AlignmentAnnotation> ssConsensus = getSSConsensusAnnotation();
+      AlignmentAnnotation gap = getGapAnnotation();
+      if ((ssConsensus == null && gap == null) || calcMan.isPending(this))
+      {
+        calcMan.workerComplete(this);
+        return;
+      }
+      while (!calcMan.notifyWorking(this))
+      {
+        try
+        {
+          if (ap != null)
+          {
+            ap.paintAlignment(false, false);
+          }
+          Thread.sleep(200);
+        } catch (Exception ex)
+        {
+          ex.printStackTrace();
+        }
+      }
+      if (alignViewport.isClosed())
+      {
+        abortAndDestroy();
+        return;
+      }
+      AlignmentI alignment = alignViewport.getAlignment();
+
+      int aWidth = -1;
+
+      if (alignment == null || (aWidth = alignment.getWidth()) < 0)
+      {
+        calcMan.workerComplete(this);
+        return;
+      }
+      
+      setSecondaryStructureSources();
+      eraseSSConsensus(aWidth);
+      computeSSConsensus(alignment);
+      updateResultAnnotation(true);
+
+      if (ap != null)
+      {
+        ap.paintAlignment(true, true);
+      }
+    } catch (OutOfMemoryError error)
+    {
+      calcMan.disableWorker(this);
+      ap.raiseOOMWarning("calculating consensus", error);
+    } finally
+    {
+      /*
+       * e.g. ArrayIndexOutOfBoundsException can happen due to a race condition
+       * - alignment was edited at same time as calculation was running
+       */
+      calcMan.workerComplete(this);
+    }
+  }
+
+  /**
+   * Clear out any existing consensus annotations
+   * 
+   * @param aWidth
+   *          the width (number of columns) of the annotated alignment
+   */
+  protected void eraseSSConsensus(int aWidth)
+  {
+    List<AlignmentAnnotation> ssConsensuses = getSSConsensusAnnotation();
+    for(AlignmentAnnotation ssConsensus : ssConsensuses) {
+      if (ssConsensus != null)
+      {
+        ssConsensus.annotations = new Annotation[aWidth];
+      }
+    }
+    AlignmentAnnotation gap = getGapAnnotation();
+    if (gap != null)
+    {
+      gap.annotations = new Annotation[aWidth];
+    }
+  }
+
+  /**
+   * @param alignment
+   */
+  protected void computeSSConsensus(AlignmentI alignment)
+  {
+
+    SequenceI[] aseqs = getSequences();
+    int width = alignment.getWidth();
+    Map<String, ProfilesI> hSSConsensusProfileMap = new HashMap<String, ProfilesI>();
+    List <String> ssSources = getSecondaryStructureSources();
+    for(String ssSource : ssSources) {
+      ProfilesI hSSConsensus = AAFrequency.calculateSS(aseqs, width, 0, width,
+              true, ssSource);
+      hSSConsensusProfileMap.put(ssSource, hSSConsensus);
+    }
+    
+
+    alignViewport.setSequenceSSConsensusHash(hSSConsensusProfileMap);
+    setColourSchemeConsensus(hSSConsensusProfileMap);
+  }
+
+  /**
+   * @return
+   */
+  protected SequenceI[] getSequences()
+  {
+    return alignViewport.getAlignment().getSequencesArray();
+  }
+
+  /**
+   * @param hconsensus
+   */
+  protected void setColourSchemeConsensus(Map<String, ProfilesI> ssConsensusProfileMap)
+  {
+    ResidueShaderI cs = alignViewport.getResidueShading();
+    if (cs != null)
+    {
+      cs.setSSConsensusProfileMap(ssConsensusProfileMap);
+    }
+  }
+
+  /**
+   * Get the Consensus annotation for the alignment
+   * 
+   * @return
+   */
+  protected List<AlignmentAnnotation> getSSConsensusAnnotation()
+  {
+    return alignViewport.getAlignmentSecondaryStructureConsensusAnnotation();
+  }
+  
+  /**
+   * Get the Consensus annotation for the alignment
+   * 
+   * @return
+   */
+  protected void setSecondaryStructureSources()
+  {
+    List<String> sources = null;
+    AlignmentAnnotation[] aa = alignViewport.getAlignment().getAlignmentAnnotation();
+    if(aa!=null) {
+      sources = AlignmentUtils.extractSSSourceInAlignmentAnnotation(aa);
+      if(sources != null) {
+        sources.add(0, MessageManager.getString("option.ss_providers_all"));
+        alignViewport.setSecondaryStructureSources(sources);
+      }
+    }
+  }
+  
+  protected List<String> getSecondaryStructureSources()
+  {
+    return alignViewport.getSecondaryStructureSources();
+  }
+
+  /**
+   * Get the Gap annotation for the alignment
+   * 
+   * @return
+   */
+  protected AlignmentAnnotation getGapAnnotation()
+  {
+    return alignViewport.getAlignmentGapAnnotation();
+  }
+
+  /**
+   * update the consensus annotation from the sequence profile data using
+   * current visualization settings.
+   */
+  @Override
+  public void updateAnnotation()
+  {
+    updateResultAnnotation(false);
+  }
+
+  public void updateResultAnnotation(boolean immediate)
+  {
+    List<AlignmentAnnotation> ssConsensuses = getSSConsensusAnnotation();
+    Map<String, ProfilesI> ssConsensusProfileMap = getViewportSSConsensus();
+    for(AlignmentAnnotation ssConsensus : ssConsensuses) {
+      ProfilesI ssConsensusProfile = null;
+      for(String source: ssConsensusProfileMap.keySet()) {
+        if(ssConsensus.description.startsWith(source)) {
+          ssConsensusProfile = ssConsensusProfileMap.get(source);
+          break;
+        }
+      }
+    if(ssConsensusProfile==null) {
+      continue;
+    }
+    if (immediate || !calcMan.isWorking(this) && ssConsensus != null
+            && ssConsensusProfile != null)
+    {
+      if(ssConsensusProfile.get(0)!=null)
+      ssConsensus.setNoOfSequencesIncluded(ssConsensusProfile.get(0).getSeqWithSSCount());
+      deriveSSConsensus(ssConsensus, ssConsensusProfile);
+      AlignmentAnnotation gap = getGapAnnotation();
+      if (gap != null)
+      {
+        deriveGap(gap, ssConsensusProfile);
+      }
+    }
+    }
+  }
+
+  /**
+   * Convert the computed consensus data into the desired annotation for
+   * display.
+   * 
+   * @param consensusAnnotation
+   *          the annotation to be populated
+   * @param hconsensus
+   *          the computed consensus data
+   */
+  protected void deriveSSConsensus(AlignmentAnnotation ssConsensus,
+          ProfilesI hSSConsensus)
+  {
+
+    long nseq = getSequences().length;
+    AAFrequency.completeSSConsensus(ssConsensus, hSSConsensus,
+            hSSConsensus.getStartColumn(), hSSConsensus.getEndColumn() + 1,
+            alignViewport.isIgnoreGapsConsensus(),
+            alignViewport.isShowSequenceLogo(), nseq);
+  }
+
+  /**
+   * Convert the computed consensus data into a gap annotation row for display.
+   * 
+   * @param gapAnnotation
+   *          the annotation to be populated
+   * @param hconsensus
+   *          the computed consensus data
+   */
+  protected void deriveGap(AlignmentAnnotation gapAnnotation,
+          ProfilesI hconsensus)
+  {
+    long nseq = getSequences().length;
+    AAFrequency.completeGapAnnot(gapAnnotation, hconsensus,
+            hconsensus.getStartColumn(), hconsensus.getEndColumn() + 1,
+            nseq);
+  }
+
+  /**
+   * Get the consensus data stored on the viewport.
+   * 
+   * @return
+   */
+  protected Map<String, ProfilesI> getViewportSSConsensus()
+  {
+    // TODO convert ComplementConsensusThread to use Profile
+    return alignViewport.getSequenceSSConsensusHash();
+  }
+}
index f017662..d5639fa 100644 (file)
@@ -28,16 +28,21 @@ import static org.testng.AssertJUnit.assertNull;
 import static org.testng.AssertJUnit.assertSame;
 import static org.testng.AssertJUnit.assertTrue;
 
+import java.awt.Color;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.SortedMap;
 import java.util.TreeMap;
 
+import org.testng.Assert;
 import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
 import jalview.datamodel.AlignedCodonFrame;
@@ -78,6 +83,25 @@ public class AlignmentUtilsTests
   {
     JvOptionPane.setInteractiveMode(false);
     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
+    
+    AlignmentAnnotation ann1 = new AlignmentAnnotation("Secondary Structure", "Secondary Structure",
+            new Annotation[] {});
+    AlignmentAnnotation ann2 = new AlignmentAnnotation("jnetpred", "jnetpred",
+            new Annotation[] {});
+    AlignmentAnnotation ann3 = new AlignmentAnnotation("Temp", "Temp",
+            new Annotation[] {});
+    AlignmentAnnotation ann4 = new AlignmentAnnotation("Temp", "Temp",
+            new Annotation[] {});
+    
+    AlignmentAnnotation[] anns1 = new AlignmentAnnotation[] {ann1, ann3, ann4};
+
+    AlignmentAnnotation[] anns2 = new AlignmentAnnotation[] {ann2, ann3, ann4};
+    
+    AlignmentAnnotation[] anns3 = new AlignmentAnnotation[] {ann3, ann4};
+    
+    AlignmentAnnotation[] anns4 = new AlignmentAnnotation[0];
+    
+    AlignmentAnnotation[] anns5 = new AlignmentAnnotation[] {ann1, ann2, ann3, ann4};
   }
 
   @Test(groups = { "Functional" })
@@ -2751,4 +2775,140 @@ public class AlignmentUtilsTests
                     && al.getAlignmentAnnotation().length == 2);
 
   }
+    
+    @Test(groups = "Functional", dataProvider = "SecondaryStructureAnnotations")
+    public void testSecondaryStructurePresentAndSources(AlignmentAnnotation[] annotations, boolean expectedSSPresent, ArrayList<String> expectedSSSources) {
+        Assert.assertEquals(expectedSSPresent, AlignmentUtils.isSecondaryStructurePresent(annotations));
+        Assert.assertEquals(expectedSSSources, AlignmentUtils.getSecondaryStructureSources(annotations));
+    }
+
+    @DataProvider(name = "SecondaryStructureAnnotations")
+    public static Object[][] provideSecondaryStructureAnnotations() {
+        AlignmentAnnotation ann1 = new AlignmentAnnotation("Secondary Structure", "Secondary Structure", new Annotation[]{});
+        AlignmentAnnotation ann2 = new AlignmentAnnotation("jnetpred", "jnetpred", new Annotation[]{});
+        AlignmentAnnotation ann3 = new AlignmentAnnotation("Temp", "Temp", new Annotation[]{});
+        AlignmentAnnotation ann4 = new AlignmentAnnotation("Temp", "Temp", new Annotation[]{});
+
+        List<String> ssSources1 = new ArrayList<>(Arrays.asList("3D Structures"));
+        List<String> ssSources2 = new ArrayList<>(Arrays.asList("JPred"));
+        List<String> ssSources3 = new ArrayList<>(Arrays.asList("3D Structures", "JPred"));
+        List<String> ssSources4 = new ArrayList<>();
+
+        return new Object[][]{
+            {new AlignmentAnnotation[]{ann1, ann3, ann4}, true, ssSources1},
+            {new AlignmentAnnotation[]{ann2, ann3, ann4}, true, ssSources2},
+            {new AlignmentAnnotation[]{ann3, ann4}, false, ssSources4},
+            {new AlignmentAnnotation[]{}, false, ssSources4},
+            {new AlignmentAnnotation[]{ann1, ann2, ann3, ann4}, true, ssSources3}
+        };
+    }
+    
+    @Test(dataProvider = "SecondaryStructureAnnotationColours")
+    public void testSecondaryStructureAnnotationColour(char symbol, Color expectedColor) {
+        Color actualColor = AlignmentUtils.getSecondaryStructureAnnotationColour(symbol);
+        Assert.assertEquals(actualColor, expectedColor);
+    }
+
+    @DataProvider(name = "SecondaryStructureAnnotationColours")
+    public static Object[][] provideSecondaryStructureAnnotationColours() {
+        return new Object[][]{
+            {'C', Color.gray},
+            {'E', Color.green},
+            {'H', Color.red},
+            {'-', Color.gray}
+        };
+    }
+    
+    @Test(dataProvider = "SSAnnotationPresence")
+    public void testIsSSAnnotationPresent(Map<SequenceI, List<AlignmentAnnotation>> annotations, boolean expectedPresence) {
+        boolean actualPresence = AlignmentUtils.isSSAnnotationPresent(annotations);
+        Assert.assertEquals(actualPresence, expectedPresence);
+    }
+
+    @DataProvider(name = "SSAnnotationPresence")
+    public static Object[][] provideSSAnnotationPresence() {
+        Map<SequenceI, List<AlignmentAnnotation>> annotations1 = new HashMap<>();
+        SequenceI seq1 = new Sequence("Seq1", "ASD---ASD---ASD", 37, 45);
+        List<AlignmentAnnotation> annotationsList1 = new ArrayList<>();
+        annotationsList1.add(new AlignmentAnnotation("Secondary Structure", "Secondary Structure", new Annotation[]{}));
+        annotations1.put(seq1, annotationsList1); // Annotation present secondary structure for seq1
+
+        Map<SequenceI, List<AlignmentAnnotation>> annotations2 = new HashMap<>();
+        SequenceI seq2 = new Sequence("Seq2", "ASD---ASD------", 37, 42);
+        List<AlignmentAnnotation> annotationsList2 = new ArrayList<>();
+        annotationsList2.add(new AlignmentAnnotation("Other Annotation", "Other Annotation", new Annotation[]{}));
+        annotations2.put(seq2, annotationsList2); // Annotation not related to any of secondary structure for seq2
+
+        Map<SequenceI, List<AlignmentAnnotation>> annotations3 = new HashMap<>();
+        // Empty annotation map
+        
+        Map<SequenceI, List<AlignmentAnnotation>> annotations4 = new HashMap<>();
+        SequenceI seq4 = new Sequence("Seq4", "ASD---ASD---AS-", 37, 44);
+        List<AlignmentAnnotation> annotationsList4 = new ArrayList<>();
+        annotationsList4.add(new AlignmentAnnotation("jnetpred", "jnetpred", new Annotation[]{}));
+        annotations4.put(seq4, annotationsList4); // Annotation present from JPred for seq4
+
+
+        return new Object[][]{
+            {annotations1, true}, // Annotations present secondary structure present
+            {annotations2, false}, // No annotations related to any of the secondary structure present
+            {annotations3, false},  // Empty annotation map
+            {annotations4, true}, // Annotations present from JPred secondary structure present
+        };
+    }
+    
+    @Test
+    public void testGetSSSourceFromAnnotationDescription(AlignmentAnnotation[] annotations, String expectedSSSource) {
+        List<String> actualSSSource = AlignmentUtils.extractSSSourceInAlignmentAnnotation(annotations);
+        Assert.assertEquals(actualSSSource, expectedSSSource);
+    }
+    
+    @DataProvider(name = "SSSourceFromAnnotationDescription")
+    public static Object[][] provideSSSourceFromAnnotationDescription() {
+        Map<SequenceI, List<AlignmentAnnotation>> annotations1 = new HashMap<>();
+        SequenceI seq1 = new Sequence("Seq1", "ASD---ASD---ASD", 37, 45);
+        List<AlignmentAnnotation> annotationsList1 = new ArrayList<>();
+        annotationsList1.add(new AlignmentAnnotation("jnetpred", "JPred Output", new Annotation[]{}));
+        annotations1.put(seq1, annotationsList1); // Annotation present from JPred for seq1
+
+        Map<SequenceI, List<AlignmentAnnotation>> annotations2 = new HashMap<>();
+        SequenceI seq2 = new Sequence("Seq2", "ASD---ASD------", 37, 42);
+        List<AlignmentAnnotation> annotationsList2 = new ArrayList<>();
+        annotationsList2.add(new AlignmentAnnotation("Secondary Structure", 
+                "Secondary Structure for af-q43517-f1A", new Annotation[]{}));
+        annotations2.put(seq2, annotationsList2); // Annotation present secondary structure from Alphafold for seq2
+
+        Map<SequenceI, List<AlignmentAnnotation>> annotations3 = new HashMap<>();
+        // Empty annotation map
+        
+        Map<SequenceI, List<AlignmentAnnotation>> annotations4 = new HashMap<>();
+        SequenceI seq4 = new Sequence("Seq4", "ASD---ASD---AS-", 37, 44);
+        List<AlignmentAnnotation> annotationsList4 = new ArrayList<>();
+        annotationsList4.add(new AlignmentAnnotation("Secondary Structure", 
+                "Secondary Structure for 4zhpA", new Annotation[]{}));
+        annotations4.put(seq4, annotationsList4); // Annotation present secondary structure from pdb for seq4
+        
+        Map<SequenceI, List<AlignmentAnnotation>> annotations5 = new HashMap<>();
+        SequenceI seq5 = new Sequence("Seq5", "ASD---ASD---AS-", 37, 44);
+        List<AlignmentAnnotation> annotationsList5 = new ArrayList<>();
+        annotationsList5.add(new AlignmentAnnotation("Secondary Structure", 
+                "Secondary Structure for p09911_54-147__3a7wzn.1.p3502557454997462030P", 
+                new Annotation[]{}));
+        annotations5.put(seq5, annotationsList5); // Annotation present secondary structure from Swiss model for seq5
+        
+
+        //JPred Output - JPred
+        //Secondary Structure for af-q43517-f1A - Alphafold
+        //Secondary Structure for 4zhpA - Experimental
+        //Secondary Structure for p09911_54-147__3a7wzn.1.p3502557454997462030P - Swiss Model
+        
+        return new Object[][]{
+            {annotations1, "JPred"}, 
+            {annotations2, "Alphafold"}, 
+            {annotations3, null},  
+            {annotations4, "PDB"},
+            {annotations5, "Swiss Model"}
+        };
+    }   
+
 }
index 0a3af64..d0a8047 100644 (file)
@@ -83,6 +83,12 @@ public class ScoreModelsTest
     assertFalse(sm instanceof PairwiseScoreModelI);
     assertTrue(sm instanceof DistanceScoreModel);
     assertEquals(sm.getName(), "Sequence Feature Similarity");
+    
+    sm = models.next();
+    assertFalse(sm instanceof SimilarityScoreModel);
+    assertFalse(sm instanceof PairwiseScoreModelI);
+    assertTrue(sm instanceof DistanceScoreModel);
+    assertEquals(sm.getName(), "Secondary Structure Similarity");
   }
 
   /**
diff --git a/test/jalview/analysis/scoremodels/SecondaryStructureDistanceModelTest.java b/test/jalview/analysis/scoremodels/SecondaryStructureDistanceModelTest.java
new file mode 100644 (file)
index 0000000..f32be37
--- /dev/null
@@ -0,0 +1,436 @@
+/*
+ * 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.
+ */
+package jalview.analysis.scoremodels;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import jalview.api.analysis.ScoreModelI;
+import jalview.api.analysis.SimilarityParamsI;
+import jalview.datamodel.Alignment;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.AlignmentView;
+import jalview.datamodel.Annotation;
+import jalview.datamodel.Sequence;
+import jalview.datamodel.SequenceFeature;
+import jalview.datamodel.SequenceI;
+import jalview.gui.AlignFrame;
+import jalview.gui.AlignViewport;
+import jalview.gui.JvOptionPane;
+import jalview.io.DataSourceType;
+import jalview.io.FileLoader;
+import jalview.math.MatrixI;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+// This class tests methods in Class SecondaryStructureDistanceModel 
+public class SecondaryStructureDistanceModelTest
+{
+  
+  /**
+   * Verify computed distances of sequences with gap
+   */
+  @Test(groups = "Functional")
+  public void testFindDistances_withGap()
+  {
+    AlignFrame af = setupAlignmentViewWithGap();
+    AlignViewport viewport = af.getViewport();
+    AlignmentView view = viewport.getAlignmentView(false);
+
+    ScoreModelI sm = new SecondaryStructureDistanceModel();
+    sm = ScoreModels.getInstance().getScoreModel(sm.getName(),
+            af.alignPanel);
+    
+    /*
+     * feature distance model always normalises by region width
+     * gap-gap is always included (but scores zero)
+     * the only variable parameter is 'includeGaps'
+     */
+
+    /*
+     * include gaps
+     * score = 0 + 0 + 1 + 0 = 1/4
+     */
+    SimilarityParamsI params = new SimilarityParams(false, true, true, true);
+    params.setSecondaryStructureSource("3D Structures");
+    MatrixI distances = sm.findDistances(view, params);
+    assertEquals(distances.getValue(0, 0), 1d);
+    assertEquals(distances.getValue(1, 1), 1d);
+    assertEquals(distances.getValue(0, 1), 0d); 
+    assertEquals(distances.getValue(1, 0), 0d);
+    
+    /*
+     * exclude gaps
+     * score = 0 + 0 + 0 + 0 = 0/4
+     */
+    
+    SimilarityParamsI params2 = new SimilarityParams(false, true, false, true);
+    params2.setSecondaryStructureSource("3D Structures");
+    MatrixI distances2 = sm.findDistances(view, params2);
+    assertEquals(distances2.getValue(0, 1), 0d); 
+    assertEquals(distances2.getValue(1, 0), 0d);
+  }
+  
+  
+  /**
+   * Verify computed distances of sequences with gap
+   */
+  @Test(groups = "Functional")
+  public void testFindDistances_withSSUndefinedInEitherOneSeq()
+  {
+    AlignFrame af = setupAlignmentViewWithoutSS("either");
+    AlignViewport viewport = af.getViewport();
+    AlignmentView view = viewport.getAlignmentView(false);
+
+    ScoreModelI sm = new SecondaryStructureDistanceModel();
+    sm = ScoreModels.getInstance().getScoreModel(sm.getName(),
+            af.alignPanel);
+    
+    /*
+     * feature distance model always normalises by region width
+     * gap-gap is always included (but scores zero)
+     * the only variable parameter is 'includeGaps'
+     */
+
+    /*
+     * include gaps
+     * score = 0 + 0 + 2 + 2 = 2/4
+     */
+    SimilarityParamsI params = new SimilarityParams(false, true, true, true);
+    params.setSecondaryStructureSource("3D Structures");
+    MatrixI distances = sm.findDistances(view, params);
+    assertEquals(distances.getValue(0, 0), 1d);
+    assertEquals(distances.getValue(1, 1), 1d);
+    assertEquals(distances.getValue(0, 1), 0d); 
+    assertEquals(distances.getValue(1, 0), 0d);
+    
+    /*
+     * exclude gaps
+     * score = 0 + 0 + 2 + 2 = 2/4
+     */
+    
+    SimilarityParamsI params2 = new SimilarityParams(false, true, false, true);
+    params2.setSecondaryStructureSource("3D Structures");
+    MatrixI distances2 = sm.findDistances(view, params2);
+    assertEquals(distances2.getValue(0, 1), 0d); 
+    assertEquals(distances2.getValue(1, 0), 0d);
+  }
+
+  
+  /**
+   * Verify computed distances of sequences with gap
+   */
+  @Test(groups = "Functional")
+  public void testFindDistances_withSSUndefinedInBothSeqs()
+  {
+    AlignFrame af = setupAlignmentViewWithoutSS("both");
+    AlignViewport viewport = af.getViewport();
+    AlignmentView view = viewport.getAlignmentView(false);
+
+    ScoreModelI sm = new SecondaryStructureDistanceModel();
+    sm = ScoreModels.getInstance().getScoreModel(sm.getName(),
+            af.alignPanel);
+    
+    /*
+     * feature distance model always normalises by region width
+     * gap-gap is always included (but scores zero)
+     * the only variable parameter is 'includeGaps'
+     */
+
+    /*
+     * include gaps
+     * score = 0 + 0 + 2 + 2 = 2/4
+     */
+    SimilarityParamsI params = new SimilarityParams(false, true, true, true);
+    params.setSecondaryStructureSource("3D Structures");
+    MatrixI distances = sm.findDistances(view, params);
+    assertEquals(distances.getValue(0, 0), 1d);
+    assertEquals(distances.getValue(1, 1), 1d);
+    assertEquals(distances.getValue(0, 1), 0d); 
+    assertEquals(distances.getValue(1, 0), 0d);
+    
+    /*
+     * exclude gaps
+     * score = 0 + 0 + 2 + 2 = 2/4
+     */
+    
+    SimilarityParamsI params2 = new SimilarityParams(false, true, false, true);
+    params2.setSecondaryStructureSource("3D Structures");
+    MatrixI distances2 = sm.findDistances(view, params2);
+    assertEquals(distances2.getValue(0, 1), 0d); 
+    assertEquals(distances2.getValue(1, 0), 0d);
+  }
+
+
+  
+  /**
+   * <pre>
+   * Set up
+   *   column      1 2 3 4 
+   *        seq s1 F R K S
+   *        
+   *        seq s2 F S J L
+   * </pre>
+   * 
+   * @return
+   */
+  protected AlignFrame setupAlignmentView(String similar)
+  {
+    /*
+     * sequences without gaps
+     */
+    SequenceI s1 = new Sequence("s1", "FRKS");
+    SequenceI s2 = new Sequence("s2", "FSJL");
+
+    s1.addSequenceFeature(
+            new SequenceFeature("chain", null, 1, 4, 0f, null));
+    s1.addSequenceFeature(
+            new SequenceFeature("domain", null, 1, 4, 0f, null));
+    s2.addSequenceFeature(
+            new SequenceFeature("chain", null, 1, 4, 0f, null));
+    s2.addSequenceFeature(
+            new SequenceFeature("metal", null, 1, 4, 0f, null));
+    s2.addSequenceFeature(
+            new SequenceFeature("Pfam", null, 1, 4, 0f, null));
+    
+    
+    /*
+     * Set up secondary structure annotations
+     */
+    Annotation ssE = new Annotation("","",'E',0);
+    Annotation ssH = new Annotation("","",'H',0);
+    Annotation ssC = new Annotation(".","",' ',0);
+    
+    Annotation[] anns1;
+    Annotation[] anns2;
+    
+    /* All secondary structure annotations are similar for each column
+     * Set up
+    *   column      1 2 3 4 
+    *        seq s1 F R K S
+    *           ss E H S E
+    *        
+    *        seq s2 F S J L
+    *           ss E H S E
+    */
+    if(similar == "All Similar") {
+    
+       anns1 = new Annotation[] { ssE, ssH, ssC, ssE};
+       anns2 = new Annotation[] { ssE, ssH, ssC, ssE};
+    
+    }
+    
+    /* All secondary structure annotations are dissimilar for each column
+     * Set up
+     *   column      1 2 3 4 
+     *        seq s1 F R K S
+     *           ss E E C E
+     *        
+     *        seq s2 F S J L
+     *           ss H E E C
+     */
+    else if(similar == "Not Similar") {
+        
+       anns1 = new Annotation[] { ssE, ssE, ssC, ssE};
+       anns2 = new Annotation[] { ssH, ssH, ssE, ssC};
+    
+    }
+    
+    /* All secondary structure annotations are dissimilar for each column
+     * Set up
+     *   column      1 2 3 4 
+     *        seq s1 F R K S
+     *            ss E E C E
+     *        
+     *        seq s2 F S J L
+     *            ss H E E C
+     */
+    else if(similar == "With Coil") {
+        
+      anns1 = new Annotation[] { ssE, ssE, null, ssE};
+      anns2 = new Annotation[] { ssH, ssH, ssE, null};
+    
+    }
+    
+    /*  Set up
+     *   column      1 2 3 4 
+     *        seq s1 F R K S
+     *           ss H E C E
+     *        
+     *        seq s2 F S J L
+     *           ss H E E C
+     */
+    else {
+       
+       anns1 = new Annotation[] { ssH, ssE, ssC, ssE};
+       anns2 = new Annotation[] { ssH, ssE, ssE, ssC};
+    }
+    
+    
+    AlignmentAnnotation ann1 = new AlignmentAnnotation("Secondary Structure",
+            "Secondary Structure", anns1);
+    AlignmentAnnotation ann2 = new AlignmentAnnotation("Secondary Structure",
+            "Secondary Structure", anns2);
+    
+    s1.addAlignmentAnnotation(ann1);
+    s2.addAlignmentAnnotation(ann2);    
+    
+    AlignmentI al = new Alignment(new SequenceI[] { s1, s2 });
+    AlignFrame af = new AlignFrame(al, 300, 300);
+    af.setShowSeqFeatures(true);
+    af.getFeatureRenderer().findAllFeatures(true);
+    return af;
+  }
+  
+
+  /**
+   * <pre>
+   * Set up
+   *   column      1 2 3 4 
+   *        seq s1 F R   S
+   *             SS H E   C
+   *        
+   *        seq s2 F S J L
+   *             ss H E E C
+   * </pre>
+   * 
+   * @return
+   */
+  protected AlignFrame setupAlignmentViewWithGap()
+  {
+    
+    SequenceI s1 = new Sequence("s1", "FR S");
+    SequenceI s2 = new Sequence("s2", "FSJL");
+
+    s1.addSequenceFeature(
+            new SequenceFeature("chain", null, 1, 3, 0f, null));
+    s1.addSequenceFeature(
+            new SequenceFeature("domain", null, 1, 3, 0f, null));
+    s2.addSequenceFeature(
+            new SequenceFeature("chain", null, 1, 4, 0f, null));
+    s2.addSequenceFeature(
+            new SequenceFeature("metal", null, 1, 4, 0f, null));
+    s2.addSequenceFeature(
+            new SequenceFeature("Pfam", null, 1, 4, 0f, null));
+    
+    
+    Annotation ssE = new Annotation("","",'E',0);
+    Annotation ssH = new Annotation("","",'H',0);
+    Annotation ssC = new Annotation(".","",' ',0);
+    
+    Annotation[] anns1;
+    Annotation[] anns2;
+       
+    anns1 = new Annotation[] { ssH, ssE, ssC};
+    anns2 = new Annotation[] { ssH, ssE, ssE, ssC};    
+    
+    AlignmentAnnotation ann1 = new AlignmentAnnotation("Secondary Structure",
+            "Secondary Structure", anns1);
+    AlignmentAnnotation ann2 = new AlignmentAnnotation("Secondary Structure",
+            "Secondary Structure", anns2);
+    
+    s1.addAlignmentAnnotation(ann1);
+    s2.addAlignmentAnnotation(ann2);    
+        
+    AlignmentI al = new Alignment(new SequenceI[] { s1, s2 });
+    AlignFrame af = new AlignFrame(al, 300, 300);
+    af.setShowSeqFeatures(true);
+    af.getFeatureRenderer().findAllFeatures(true);
+    
+    return af;
+  }
+  
+  protected AlignFrame setupAlignmentViewWithoutSS(String type) {
+    
+    SequenceI s1 = new Sequence("s1", "FR S");
+    SequenceI s2 = new Sequence("s2", "FSJL");
+    
+    s1.addSequenceFeature(
+            new SequenceFeature("chain", null, 1, 3, 0f, null));
+    s1.addSequenceFeature(
+            new SequenceFeature("domain", null, 1, 3, 0f, null));
+    s2.addSequenceFeature(
+            new SequenceFeature("chain", null, 1, 4, 0f, null));
+    s2.addSequenceFeature(
+            new SequenceFeature("metal", null, 1, 4, 0f, null));
+    s2.addSequenceFeature(
+            new SequenceFeature("Pfam", null, 1, 4, 0f, null));
+    
+    if(!type.equals("both")) {    
+      Annotation ssE = new Annotation("","",'E',0);
+      Annotation ssH = new Annotation("","",'H',0);
+      Annotation ssC = new Annotation(".","",' ',0);
+      
+      Annotation[] anns1;
+        
+      anns1 = new Annotation[] { ssH, ssE, ssC};
+          
+      AlignmentAnnotation ann1 = new AlignmentAnnotation("Secondary Structure",
+              "Secondary Structure", anns1);    
+  
+      s1.addAlignmentAnnotation(ann1);    
+    }
+    
+    AlignmentI al = new Alignment(new SequenceI[] { s1, s2 });
+    AlignFrame af = new AlignFrame(al, 300, 300);
+    af.setShowSeqFeatures(true);
+    af.getFeatureRenderer().findAllFeatures(true);
+    return af;
+  }
+  
+  
+  @DataProvider(name = "testData")
+  public Object[][] testData() {
+      return new Object[][] {
+              {"All Similar", 1d, 1d, 0d, 0d / 4},
+              {"Partially Similar", 1d, 1d, 0d, 0d},
+              {"Not Similar", 1d, 1d, 0d, 0d},
+              {"With Coil", 1d, 1d, 0d, 0d},
+      };
+  }
+
+  @Test(dataProvider = "testData")
+  public void testFindDistances(String scenario, double expectedValue00, double expectedValue11,
+                                 double expectedValue01, double expectedValue10) {
+      AlignFrame af = setupAlignmentView(scenario);
+      AlignViewport viewport = af.getViewport();
+      AlignmentView view = viewport.getAlignmentView(false);
+
+      ScoreModelI sm = new SecondaryStructureDistanceModel();
+      sm = ScoreModels.getInstance().getScoreModel(sm.getName(),
+              af.alignPanel);
+
+      SimilarityParamsI params = new SimilarityParams(false, true, true, true);
+      params.setSecondaryStructureSource("3D Structures");
+      MatrixI distances = sm.findDistances(view, params);
+
+      assertEquals(distances.getValue(0, 0), expectedValue00);
+      assertEquals(distances.getValue(1, 1), expectedValue11);
+      assertEquals(distances.getValue(0, 1), expectedValue01);
+      assertEquals(distances.getValue(1, 0), expectedValue10);
+  }
+
+  
+}
index 9835189..23538c2 100644 (file)
@@ -55,27 +55,29 @@ public class CalculationChooserTest
      * peptide models for PCA
      */
     List<ScoreModelI> filtered = CalculationChooser
-            .getApplicableScoreModels(false, true);
-    assertEquals(filtered.size(), 4);
+            .getApplicableScoreModels(false, true, true);
+    assertEquals(filtered.size(), 5);
     assertSame(filtered.get(0), blosum62);
     assertSame(filtered.get(1), pam250);
     assertEquals(filtered.get(2).getName(), "PID");
     assertEquals(filtered.get(3).getName(), "Sequence Feature Similarity");
+    assertEquals(filtered.get(4).getName(), "Secondary Structure Similarity");
 
     /*
      * peptide models for Tree are the same
      */
-    filtered = CalculationChooser.getApplicableScoreModels(false, false);
-    assertEquals(filtered.size(), 4);
+    filtered = CalculationChooser.getApplicableScoreModels(false, false, true);
+    assertEquals(filtered.size(), 5);
     assertSame(filtered.get(0), blosum62);
     assertSame(filtered.get(1), pam250);
     assertEquals(filtered.get(2).getName(), "PID");
     assertEquals(filtered.get(3).getName(), "Sequence Feature Similarity");
+    assertEquals(filtered.get(4).getName(), "Secondary Structure Similarity");
 
     /*
      * nucleotide models for PCA
      */
-    filtered = CalculationChooser.getApplicableScoreModels(true, true);
+    filtered = CalculationChooser.getApplicableScoreModels(true, true, false);
     assertEquals(filtered.size(), 3);
     assertSame(filtered.get(0), dna);
     assertEquals(filtered.get(1).getName(), "PID");
@@ -84,7 +86,7 @@ public class CalculationChooserTest
     /*
      * nucleotide models for Tree are the same
      */
-    filtered = CalculationChooser.getApplicableScoreModels(true, false);
+    filtered = CalculationChooser.getApplicableScoreModels(true, false, false);
     assertEquals(filtered.size(), 3);
     assertSame(filtered.get(0), dna);
     assertEquals(filtered.get(1).getName(), "PID");
@@ -99,16 +101,17 @@ public class CalculationChooserTest
     /*
      * nucleotide models for Tree are unchanged
      */
-    filtered = CalculationChooser.getApplicableScoreModels(true, false);
-    assertEquals(filtered.size(), 3);
+    filtered = CalculationChooser.getApplicableScoreModels(true, false, true);
+    assertEquals(filtered.size(), 4);
     assertSame(filtered.get(0), dna);
     assertEquals(filtered.get(1).getName(), "PID");
     assertEquals(filtered.get(2).getName(), "Sequence Feature Similarity");
+    assertEquals(filtered.get(3).getName(), "Secondary Structure Similarity");
 
     /*
      * nucleotide models for PCA add BLOSUM62 as last option
      */
-    filtered = CalculationChooser.getApplicableScoreModels(true, true);
+    filtered = CalculationChooser.getApplicableScoreModels(true, true, false);
     assertEquals(filtered.size(), 4);
     assertSame(filtered.get(0), dna);
     assertEquals(filtered.get(1).getName(), "PID");
index 78f00c8..9ff93e1 100644 (file)
@@ -30,6 +30,7 @@ import jalview.datamodel.ProfileI;
 import jalview.datamodel.Profiles;
 import jalview.datamodel.ProfilesI;
 import jalview.datamodel.ResidueCount;
+import jalview.datamodel.SecondaryStructureCount;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceI;
 import jalview.schemes.ColourSchemeI;
@@ -338,6 +339,49 @@ public class ResidueShaderTest
           {
             return 0;
           }
+
+          @Override
+          public void setSSCounts(
+                  SecondaryStructureCount secondaryStructureCount)
+          {
+            // TODO Auto-generated method stub
+            
+          }
+
+          @Override
+          public float getSSPercentageIdentity(boolean ignoreGaps)
+          {
+            // TODO Auto-generated method stub
+            return 0;
+          }
+
+          @Override
+          public int getMaxSSCount()
+          {
+            // TODO Auto-generated method stub
+            return 0;
+          }
+
+          @Override
+          public String getModalSS()
+          {
+            // TODO Auto-generated method stub
+            return null;
+          }
+
+          @Override
+          public SecondaryStructureCount getSSCounts()
+          {
+            // TODO Auto-generated method stub
+            return null;
+          }
+
+          @Override
+          public int getSeqWithSSCount()
+          {
+            // TODO Auto-generated method stub
+            return 0;
+          }
         };
       }
 
index 3019afa..ba8aa99 100644 (file)
@@ -27,7 +27,10 @@ import static org.testng.AssertJUnit.assertTrue;
 import jalview.gui.JvOptionPane;
 
 import java.awt.Color;
+import java.awt.Container;
 import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Random;
 
 import org.testng.AssertJUnit;
@@ -177,6 +180,13 @@ public class ViewStyleTest
       field.set(vs,
               Color.RED.equals(field.get(vs)) ? Color.BLACK : Color.RED);
     }
+    else if (type.equals(java.util.List.class))
+    {
+      List<?> list = (List<?>) field.get(vs);
+      List<Object> mutableList = new ArrayList<>(list);
+      mutableList.add("All");
+      field.set(vs, mutableList);
+    }
     else
     {
       AssertJUnit.fail("Unhandled field type (add to test): "