JAL-4386 Drop down for secondary structure sources in calculate window
authorRenia Correya <rcorreya001@dundee.ac.uk>
Fri, 26 Apr 2024 09:27:28 +0000 (10:27 +0100)
committerRenia Correya <rcorreya001@dundee.ac.uk>
Fri, 26 Apr 2024 09:27:28 +0000 (10:27 +0100)
src/jalview/analysis/scoremodels/SecondaryStructureDistanceModel.java
src/jalview/gui/CalculationChooser.java

index 3a719d8..cd09805 100644 (file)
@@ -45,21 +45,35 @@ public class SecondaryStructureDistanceModel extends DistanceScoreModel
 {
   private static final String NAME = "Secondary Structure Similarity";
   
+  //label in secondary structure annotation data model from 3d structures
   private static final String SS_ANNOTATION_LABEL = "Secondary Structure";
   
+  //label in secondary structure annotation data model from JPred
   private static final String SS_ANNOTATION_FROM_JPRED_LABEL = "jnetpred";
 
+  //label in secondary structure annotation data model from JPred
+  private static final String JPRED_WEBSERVICE = "JPred";
+  
   private String description;
   
-  //maximum distance score is defined as 2 as the possible number of unique ss is 2. 
+  //maximum distance score is defined as 2 as the possible number of unique secondary structure is 2. 
   private static final int MAX_SCORE = 2;
   
-  //minimum distance score is defined as 2 as the possible number of unique ss is 2. 
+  //minimum distance score is defined as 2 as the possible number of unique secondary structure is 2. 
   private static final int MIN_SCORE = 0;
    
+  //character used to represent coil in secondary structure
   private static final char COIL = 'C';
   
   FeatureRenderer fr;
+  
+  /*
+   * Annotation label for available sources of secondary structure
+   */
+  private static final String[] SS_ANNOTATION_LABELS = {
+      SS_ANNOTATION_LABEL, 
+      SS_ANNOTATION_FROM_JPRED_LABEL 
+  };
 
   /**
    * Constructor
@@ -137,25 +151,48 @@ public class SecondaryStructureDistanceModel extends DistanceScoreModel
     int noseqs = seqs.length; //no of sequences
     int cpwidth = 0; // = seqData.getWidth();
     double[][] distances = new double[noseqs][noseqs]; //matrix to store distance score
-      
+    //secondary structure source parameter selected by the user from the drop down.
+    String ssSource = params.getSecondaryStructureSource(); 
+    
+    //defining the default value for secondary structure source as 3d structures 
+    //or JPred if user selected JPred
+    String selectedSSSource = SS_ANNOTATION_LABEL;
+    if(ssSource.equals(JPRED_WEBSERVICE))
+      selectedSSSource = SS_ANNOTATION_FROM_JPRED_LABEL;
+        
     // need to get real position for view position
     int[] viscont = seqData.getVisibleContigs();
-    Map<String, HashSet<String>> calcIdMapInAlignmentAnnotation = new HashMap<String,HashSet<String>>();
     
-    AlignmentAnnotation[] alignAnnotList = fr.getViewport().getAlignment().getAlignmentAnnotation();
-    if(alignAnnotList.length > 0) {
-      
+    /*
+     * Add secondary structure annotations that are added to the annotation track
+     * to the map
+     */
+    Map<String, HashSet<String>> ssAlignmentAnnotationForSequences 
+      = new HashMap<String,HashSet<String>>();    
+    
+    AlignmentAnnotation[] alignAnnotList = fr.getViewport().getAlignment()
+            .getAlignmentAnnotation();   
+    
+    if(alignAnnotList.length > 0) {      
       for (AlignmentAnnotation aa: alignAnnotList) {
-        if (SS_ANNOTATION_LABEL.equals(aa.label) || SS_ANNOTATION_FROM_JPRED_LABEL.equals(aa.label)) {
-            calcIdMapInAlignmentAnnotation.computeIfAbsent(aa.getCalcId(), k -> new HashSet<>()).add(aa.description);
-        }
-        
+        if (selectedSSSource.equals(aa.label)) {          
+          ssAlignmentAnnotationForSequences.computeIfAbsent(
+                  aa.sequenceRef.getName(), k -> new HashSet<>())
+          .add(aa.description);
+        }        
       }      
     }
     
-    
-    Set<SeqCigar> seqsWithUndefinedSS = findSeqsWithUndefinedSS(seqs, calcIdMapInAlignmentAnnotation);
-
+    /*
+     * Get the set of sequences which are not considered for the calculation.
+     * Following sequences are added:
+     * 1. Sequences without a defined secondary structure from the selected 
+     * source.
+     * 2. Sequences whose secondary structure annotations are not added to 
+     * the annotation track
+     */
+    Set<SeqCigar> seqsWithUndefinedSS 
+        = findSeqsWithUndefinedSS(seqs, ssAlignmentAnnotationForSequences);
 
     /*
      * scan each column, compute and add to each distance[i, j]
@@ -214,14 +251,14 @@ public class SecondaryStructureDistanceModel extends DistanceScoreModel
             
             //secondary structure is fetched only if the current column is not 
             //gap for the sequence
-            if(!gap1 && !undefinedSS1) {               
-               secondaryStructure1.addAll(
-                               findSSAnnotationForGivenSeqAndCol(seqs[i], cpos));              
+            if(!gap1 && !undefinedSS1) {              
+              secondaryStructure1.addAll(
+                  findSSAnnotationForGivenSeqAndCol(seqs[i], cpos));              
             }
             
-            if(!gap2 && !undefinedSS2) {               
-               secondaryStructure2.addAll(
-                               findSSAnnotationForGivenSeqAndCol(seqs[j], cpos));              
+            if(!gap2 && !undefinedSS2) {              
+              secondaryStructure2.addAll(
+                  findSSAnnotationForGivenSeqAndCol(seqs[j], cpos));              
             }           
 
             /*
@@ -232,7 +269,7 @@ public class SecondaryStructureDistanceModel extends DistanceScoreModel
             if ((!gap1 && !gap2) || params.includeGaps())
             {
               int seqDistance = SetUtils.countDisjunction(
-                         secondaryStructure1, secondaryStructure2);
+                  secondaryStructure1, secondaryStructure2);
               distances[i][j] += seqDistance;
             }
           }
@@ -278,43 +315,61 @@ public class SecondaryStructureDistanceModel extends DistanceScoreModel
         /*
          * position is not a gap
          */        
-         seqsWithoutGapAtCol.add(seq);
+        seqsWithoutGapAtCol.add(seq);
       }
     }
     return seqsWithoutGapAtCol;
   }
+
   
   /**
-   * Builds and returns a set containing sequences (SeqCigar) which have
-   * no secondary structures defined
-   * 
+   * Builds and returns a set containing sequences (SeqCigar) which
+   * are not considered for the similarity calculation.
+   * Following sequences are added:
+   * 1. Sequences without a defined secondary structure from the selected 
+   * source.
+   * 2. Sequences whose secondary structure annotations are not added to 
+   * the annotation track
    * @param seqs
-   *          (0..)
+   * @param ssAlignmentAnnotationForSequences         
    * @return
    */
-  private static final String[] SS_ANNOTATION_LABELS = {
-      SS_ANNOTATION_LABEL, 
-      SS_ANNOTATION_FROM_JPRED_LABEL 
-  };
-
-  protected Set<SeqCigar> findSeqsWithUndefinedSS(SeqCigar[] seqs, Map<String, HashSet<String>> calcIdMapInAlignmentAnnotation) {
+  protected Set<SeqCigar> findSeqsWithUndefinedSS(SeqCigar[] seqs,
+          Map<String, HashSet<String>> ssAlignmentAnnotationForSequences) {
       Set<SeqCigar> seqsWithUndefinedSS = new HashSet<>();
       for (SeqCigar seq : seqs) {
-          if (isSSUndefinedOrNotAdded(seq, calcIdMapInAlignmentAnnotation)) {
+          if (isSSUndefinedOrNotAdded(seq, ssAlignmentAnnotationForSequences)) {
               seqsWithUndefinedSS.add(seq);
           }
       }
       return seqsWithUndefinedSS;
   }
-
-  private boolean isSSUndefinedOrNotAdded(SeqCigar seq, Map<String, HashSet<String>> calcIdMapInAlignmentAnnotation) {
+  
+  
+  /**
+   * Returns true if a sequence (SeqCigar) should not be
+   * considered for the similarity calculation.
+   * Following conditions are checked:
+   * 1. Sequence without a defined secondary structure from the selected 
+   * source.
+   * 2. Sequences whose secondary structure annotations are not added to 
+   * the annotation track
+   * @param seq
+   * @param ssAlignmentAnnotationForSequences
+   * @return
+   */
+  private boolean isSSUndefinedOrNotAdded(SeqCigar seq, 
+          Map<String, HashSet<String>> ssAlignmentAnnotationForSequences) {
       for (String label : SS_ANNOTATION_LABELS) {
           AlignmentAnnotation[] annotations = seq.getRefSeq().getAnnotation(label);
           if (annotations != null) {
               for (AlignmentAnnotation annotation : annotations) {                
-                HashSet<String> descriptionList = calcIdMapInAlignmentAnnotation.get(annotation.getCalcId());
-                  if (descriptionList.contains(annotation.description)) {
-                      // Secondary structure annotation is present and added to the track, no need to add seq
+                HashSet<String> descriptionSet = ssAlignmentAnnotationForSequences
+                        .get(annotation.sequenceRef.getName());
+                if (descriptionSet != null)
+                  if (descriptionSet.contains(annotation.description)) {
+                      // Secondary structure annotation is present and 
+                      //added to the track, no need to add seq
                       return false;
                   }
               }
@@ -335,39 +390,39 @@ public class SecondaryStructureDistanceModel extends DistanceScoreModel
    * @return
    */
   private Set<String> findSSAnnotationForGivenSeqAndCol(
-                 SeqCigar seq, int columnPosition) 
+      SeqCigar seq, int columnPosition) 
   {
-         Set<String> secondaryStructure = new HashSet<String>();
+    Set<String> secondaryStructure = new HashSet<String>();
       
-         char ss; 
-         
-         //fetch the position in sequence for the column and finds the
-         //corresponding secondary structure annotation
-         //TO DO - consider based on priority
-         int seqPosition = seq.findPosition(columnPosition);
-         AlignmentAnnotation[] aa = seq.getRefSeq().getAnnotation(SS_ANNOTATION_LABEL);
-         
-         if(aa == null) {
-           aa = seq.getRefSeq().getAnnotation(SS_ANNOTATION_FROM_JPRED_LABEL);
-         }
-         
-         if (aa != null) {
-           if (aa[0].getAnnotationForPosition(seqPosition) != null) {
-             Annotation a = aa[0].getAnnotationForPosition(seqPosition);
-             ss = a.secondaryStructure;
-             
-             //There is no representation for coil and it can be either ' ' or null. 
-             if (ss == ' ' || ss == '-') {
-               ss = COIL; 
-             }
-           }
-           else {
-             ss = COIL;
-           }
-           secondaryStructure.add(String.valueOf(ss));             
-         }
-         
-         return secondaryStructure;
+    char ss; 
+    
+    //fetch the position in sequence for the column and finds the
+    //corresponding secondary structure annotation
+    //TO DO - consider based on priority
+    int seqPosition = seq.findPosition(columnPosition);
+    AlignmentAnnotation[] aa = seq.getRefSeq().getAnnotation(SS_ANNOTATION_LABEL);
+    
+    if(aa == null) {
+      aa = seq.getRefSeq().getAnnotation(SS_ANNOTATION_FROM_JPRED_LABEL);
+    }
+    
+    if (aa != null) {
+      if (aa[0].getAnnotationForPosition(seqPosition) != null) {
+        Annotation a = aa[0].getAnnotationForPosition(seqPosition);
+        ss = a.secondaryStructure;
+        
+        //There is no representation for coil and it can be either ' ' or null. 
+        if (ss == ' ' || ss == '-') {
+          ss = COIL; 
+        }
+      }
+      else {
+        ss = COIL;
+      }
+      secondaryStructure.add(String.valueOf(ss));           
+    }
+    
+    return secondaryStructure;
   }
   
 
@@ -404,6 +459,7 @@ public class SecondaryStructureDistanceModel extends DistanceScoreModel
   @Override
   public String toString()
   {
-    return "Score between sequences based on hamming distance between binary vectors marking secondary structure displayed at each column";
+    return "Score between sequences based on hamming distance between binary "
+            + "vectors marking secondary structure displayed at each column";
   }
 }
\ No newline at end of file
index b7bb58f..d126cc3 100644 (file)
@@ -60,7 +60,6 @@ import jalview.api.analysis.SimilarityParamsI;
 import jalview.bin.Cache;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.SequenceGroup;
-import jalview.datamodel.SequenceI;
 import jalview.util.MessageManager;
 
 /**
@@ -81,6 +80,10 @@ public class CalculationChooser extends JPanel
   private static final int MIN_TREE_SELECTION = 3;
 
   private static final int MIN_PCA_SELECTION = 4;
+  
+  private static final String SS_ANNOTATION_LABEL = "Secondary Structure";
+  
+  private static final String SS_ANNOTATION_FROM_JPRED_LABEL = "jnetpred";
 
   AlignFrame af;
 
@@ -91,6 +94,8 @@ public class CalculationChooser extends JPanel
   JRadioButton averageDistance;
 
   JComboBox<String> modelNames;
+  
+  JComboBox<String> ssSourceDropdown;
 
   JButton calculate;
 
@@ -210,16 +215,34 @@ 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("Secondary Structure Similarity")) {
+              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
      */
@@ -234,7 +257,7 @@ public class CalculationChooser extends JPanel
     paramsPanel.add(matchGaps);
     paramsPanel.add(includeGappedColumns);
     paramsPanel.add(shorterSequence);
-
+    
     /*
      * OK / Cancel buttons
      */
@@ -272,7 +295,7 @@ public class CalculationChooser extends JPanel
     }
     this.add(actionPanel, BorderLayout.SOUTH);
 
-    int width = 350;
+    int width = 365;
     int height = includeParams ? 420 : 240;
 
     setMinimumSize(new Dimension(325, height - 10));
@@ -420,7 +443,38 @@ 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();
+
+    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)
   {
@@ -531,6 +585,31 @@ public class CalculationChooser extends JPanel
     return filtered;
   }
 
+  
+  protected List<String> getApplicableSecondaryStructureSources()
+  {
+    List<String> ssSources = new ArrayList<>();
+    
+    AlignmentAnnotation[] annotations = af.getViewport().getAlignment().getAlignmentAnnotation();
+    boolean has3DStructure = false, hasJPred = false;
+    if(annotations.length > 0) {
+      
+      for (AlignmentAnnotation annotation : annotations) {
+        has3DStructure |= SS_ANNOTATION_LABEL.equals(annotation.label);
+        hasJPred |= SS_ANNOTATION_FROM_JPRED_LABEL.equals(annotation.label);
+        
+        if (has3DStructure && hasJPred)
+          break;
+      }
+    }
+    if(has3DStructure)
+      ssSources.add("3D Structures");
+    if(hasJPred)
+      ssSources.add("JPred");
+                 
+    return ssSources;
+  }
+  
   /**
    * Open and calculate the selected tree or PCA on 'OK'
    */
@@ -538,8 +617,10 @@ public class CalculationChooser extends JPanel
   {
     boolean doPCA = pca.isSelected();
     String modelName = modelNames.getSelectedItem().toString();
-    SimilarityParamsI params = getSimilarityParameters(doPCA);
-
+    String ssSource = ssSourceDropdown.getSelectedItem().toString();
+    SimilarityParams params = getSimilarityParameters(doPCA);
+    if(ssSource.length()>0)
+      params.setSecondaryStructureSource(ssSource);
     if (doPCA)
     {
       openPcaPanel(modelName, params);
@@ -640,7 +721,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(