From b3027d9c432a8242755a37c34267e80678f40b9a Mon Sep 17 00:00:00 2001 From: Renia Correya Date: Fri, 12 Jul 2024 16:12:53 +0100 Subject: [PATCH] JAL-4392 Implemented show/hide group consensus secondary structure Secondary structure provider options are made available in the pop up menu for sequence groups. User can set hide/show group consensus secondary structure for each group and providers separately. --- resources/lang/Messages.properties | 2 + src/jalview/analysis/AlignmentAnnotationUtils.java | 81 ++++++++++ src/jalview/analysis/AlignmentUtils.java | 79 ++++++---- src/jalview/datamodel/SequenceGroup.java | 90 ++++++----- src/jalview/gui/AlignFrame.java | 4 + src/jalview/gui/PopupMenu.java | 163 +++++++++++++++++++- src/jalview/viewmodel/AlignmentViewport.java | 6 + 7 files changed, 355 insertions(+), 70 deletions(-) diff --git a/resources/lang/Messages.properties b/resources/lang/Messages.properties index 507abb2..cf79798 100644 --- a/resources/lang/Messages.properties +++ b/resources/lang/Messages.properties @@ -238,6 +238,8 @@ label.average_distance_blosum62 = Average Distance Using BLOSUM62 label.neighbour_blosum62 = Neighbour Joining Using BLOSUM62 label.show_annotations = Show annotations label.hide_annotations = Hide annotations +label.group_show_auto_calculated_annotations = Show auto calculated annotations +label.group_hide_auto_calculated_annotations = Hide auto calculated annotations label.show_all_seq_annotations = Show sequence related label.hide_all_seq_annotations = Hide sequence related label.show_all_al_annotations = Show alignment related diff --git a/src/jalview/analysis/AlignmentAnnotationUtils.java b/src/jalview/analysis/AlignmentAnnotationUtils.java index f5626ce..cf2f5bb 100644 --- a/src/jalview/analysis/AlignmentAnnotationUtils.java +++ b/src/jalview/analysis/AlignmentAnnotationUtils.java @@ -21,8 +21,10 @@ package jalview.analysis; import jalview.datamodel.AlignmentAnnotation; +import jalview.datamodel.SequenceGroup; import jalview.datamodel.SequenceI; import jalview.renderer.AnnotationRenderer; +import jalview.util.Constants; import java.util.ArrayList; import java.util.Arrays; @@ -31,6 +33,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; public class AlignmentAnnotationUtils { @@ -195,6 +198,84 @@ public class AlignmentAnnotationUtils } /** + * Updates the lists of shown and hidden secondary structure types based on + * the selected sequence group. + * + * @param shownTypes + * A list that will be populated with the providers of secondary + * structures that are shown. + * @param hiddenTypes + * A list that will be populated with the providers of secondary + * structures that are hidden. + * @param annotations + * A list of AlignmentAnnotation objects. + * @param selectedSequenceGroup + * The sequence group selected by the user. + */ + public static void getShownHiddenSecondaryStructureProvidersForGroup( + List shownTypes, List hiddenTypes, + List annotations, + SequenceGroup selectedSequenceGroup) + { + // Return if the selected sequence group or annotations are null + if (selectedSequenceGroup == null || annotations == null) + { + return; + } + + // Get the secondary structure sources of the selected sequence group + List ssSourcesForSelectedGroup = selectedSequenceGroup + .getSecondaryStructureSources(); + + // Return if there are no secondary structure sources for the selected group + if (ssSourcesForSelectedGroup == null + || ssSourcesForSelectedGroup.isEmpty()) + { + return; + } + + // Iterate through each annotation + for (AlignmentAnnotation aa : annotations) + { + /* Skip to the next annotation if the annotation, the annotation's group + * reference is null, or the annotation's group reference does not match + * the selected group + */ + if (aa.annotations == null || aa.groupRef == null + || selectedSequenceGroup != aa.groupRef + || !aa.label.startsWith( + Constants.SECONDARY_STRUCTURE_CONSENSUS_LABEL)) + { + continue; + } + + /* Find a provider from the secondary structure sources that matches + * the annotation's label. This is to exclude secondary structure + * providers which has no secondary structure data for the selected group. + */ + Optional provider = ssSourcesForSelectedGroup.stream() + .filter(aa.label::contains).findFirst() + .map(substring -> aa.label.substring(0, + aa.label.indexOf(substring) + substring.length())); + + // If a matching provider is found and the annotation is visible, add + // the provider to the shown types list (if not already in shownTypes). + // If the annotation is not visible, add it to hiddenTypes list. + provider.ifPresent(p -> { + if (aa.visible && !shownTypes.contains(p)) + { + shownTypes.add(p); + } + else if (!aa.visible && !shownTypes.contains(p)) + { + hiddenTypes.add(p); + } + }); + } + } + + + /** * Returns a BitSet (possibly empty) of those graphGroups for line graph * annotations, which have at least one member annotation row marked visible. *

diff --git a/src/jalview/analysis/AlignmentUtils.java b/src/jalview/analysis/AlignmentUtils.java index 99d3d6e..40cf903 100644 --- a/src/jalview/analysis/AlignmentUtils.java +++ b/src/jalview/analysis/AlignmentUtils.java @@ -1645,7 +1645,52 @@ public class AlignmentUtils } } } + + /** + * Shows or hides auto calculated annotations for a sequence group. + * + * @param al + * The alignment object with the annotations. + * @param type + * The type of annotation to show or hide. + * @param selectedGroup + * The sequence group for which the annotations should be shown or + * hidden. + * @param anyType + * If true, all types of annotations will be shown/hidden. + * @param doShow + * If true, the annotations will be shown; if false, annotations will + * be hidden. + */ + public static void showOrHideAutoCalculatedAnnotationsForGroup( + AlignmentI al, String type, SequenceGroup selectedGroup, + boolean anyType, boolean doShow) + { + // Get all alignment annotations + AlignmentAnnotation[] anns = al.getAlignmentAnnotation(); + if (anns != null) + { + for (AlignmentAnnotation aa : anns) + { + // Check if anyType is true or if the annotation's label contains the + // specified type (currently for secondary structure consensus) + if ((anyType && aa.label + .startsWith(Constants.SECONDARY_STRUCTURE_CONSENSUS_LABEL)) + || aa.label.startsWith(type)) + { + // If the annotation's group reference is not null and matches the + // selected group, update its visibility. + if (aa.groupRef != null && selectedGroup == aa.groupRef) + { + aa.visible = doShow; + } + } + } + } + } + + public static AlignmentAnnotation getFirstSequenceAnnotationOfType( AlignmentI al, int graphType) { @@ -2853,29 +2898,7 @@ public class AlignmentUtils } return true; } - - - public static List getSecondaryStructureSources( - AlignmentAnnotation[] annotations) - { - - List ssSources = new ArrayList<>(); - Set 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; @@ -2994,7 +3017,7 @@ public class AlignmentUtils } // For JPred - if (aa.label.equals(Constants.SS_ANNOTATION_FROM_JPRED_LABEL)) + if (Constants.SS_ANNOTATION_FROM_JPRED_LABEL.equals(aa.label)) { return (Constants.SECONDARY_STRUCTURE_LABELS.get(aa.label)); @@ -3002,8 +3025,8 @@ public class AlignmentUtils } // For input with secondary structure - if (aa.label.equals(Constants.SS_ANNOTATION_LABEL) - && aa.description != null && aa.description.equals(Constants.SS_ANNOTATION_LABEL)) + if (Constants.SS_ANNOTATION_LABEL.equals(aa.label) + && aa.description != null && Constants.SS_ANNOTATION_LABEL.equals(aa.description)) { return (Constants.SECONDARY_STRUCTURE_LABELS.get(aa.label)); @@ -3082,7 +3105,7 @@ public class AlignmentUtils if (aa != null) { - if (ssSource.equals(Constants.SS_ALL_PROVIDERS)) + if (Constants.SS_ALL_PROVIDERS.equals(ssSource)) { ssAnnots.addAll(Arrays.asList(aa)); continue; @@ -3133,7 +3156,7 @@ public class AlignmentUtils if (label.equals(aa.label)) { - if (selectedSSSource.equals(Constants.SS_ALL_PROVIDERS)) + if (Constants.SS_ALL_PROVIDERS.equals(selectedSSSource)) { ssAlignmentAnnotationForSequences .computeIfAbsent(aa.sequenceRef.getDatasetSequence(), diff --git a/src/jalview/datamodel/SequenceGroup.java b/src/jalview/datamodel/SequenceGroup.java index 7a33baf..abadbac 100755 --- a/src/jalview/datamodel/SequenceGroup.java +++ b/src/jalview/datamodel/SequenceGroup.java @@ -38,7 +38,6 @@ import jalview.renderer.ResidueShader; import jalview.renderer.ResidueShaderI; import jalview.schemes.ColourSchemeI; import jalview.util.Constants; -import jalview.util.MessageManager; /** * Collects a set contiguous ranges on a set of sequences @@ -139,8 +138,6 @@ public class SequenceGroup implements AnnotatedCollectionI private boolean showSequenceLogo = false; - private boolean showSequenceSSLogo = false; - /** * flag indicating if logo should be rendered normalised */ @@ -160,13 +157,13 @@ public class SequenceGroup implements AnnotatedCollectionI List ssConsensus = null; + + List secondaryStructureSources = null; AlignmentAnnotation conservation = null; private boolean showConsensusHistogram; - private boolean showSSConsensusHistogram; - private AnnotatedCollectionI context; public Map hSSConsensusProfileMap; @@ -244,7 +241,6 @@ 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; @@ -422,6 +418,17 @@ public class SequenceGroup implements AnnotatedCollectionI } return tmp.toArray(new SequenceI[tmp.size()]); } + + public List getSecondaryStructureSources() + { + return secondaryStructureSources; + } + + public void setSecondaryStructureSources( + List secondaryStructureSources) + { + this.secondaryStructureSources = secondaryStructureSources; + } /** * DOCUMENT ME! @@ -748,48 +755,71 @@ public class SequenceGroup implements AnnotatedCollectionI // ignoreGapsInConsensusCalculation); } - public ProfilesI ssConsensusData = null; - + + /** + * Updates the secondary structure consensus row based on the provided profiles map and the number of sequences. + * + * @param hSSConsensusProfileMap A map containing secondary structure consensus profiles for each providers. + * @param nseq The number of sequences. + */ private void _updateSSConsensusRow(Map hSSConsensusProfileMap, long nseq) { + // Get a list of secondary structure sources from the profile map keys List ssSources = new ArrayList<>(hSSConsensusProfileMap.keySet()); + secondaryStructureSources = new ArrayList(); + // Sort the secondary structure sources alphabetically Collections.sort(ssSources); + + // Initialize ssConsensus if it is null if (ssConsensus == null) { getSSConsensus(ssSources); } + + // Iterate through each alignment annotation in the ssConsensus list for (AlignmentAnnotation aa : ssConsensus) { ProfilesI profile = null; String ssSource = null; + + // Find the matching profile for the current annotation based on its description for(String source : ssSources) { if(aa.description.startsWith(source)) { profile = hSSConsensusProfileMap.get(source); ssSource = source; } } + + // If no matching profile is found, continue to the next annotation if(profile == null) { continue; } + // Update the label and description of the annotation with the source/provider aa.label = Constants.SECONDARY_STRUCTURE_CONSENSUS_LABEL + " " + ssSource + " " + getName(); aa.description = ssSource + Constants.SECONDARY_STRUCTURE_CONSENSUS_LABEL +" for " + getName(); - ssConsensusData = profile; - // preserve width if already set + + // Get the width of the annotations array 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 + aa.annotations = new Annotation[aWidth]; - AAFrequency.completeSSConsensus(aa, profile, startRes, endRes + 1, - ignoreGapsInConsensus, showSequenceLogo, nseq); // TODO: setting - // container + // Complete the secondary structure consensus + AAFrequency.completeSSConsensus(aa, profile, startRes, endRes + 1, + ignoreGapsInConsensus, showSequenceLogo, nseq); + + //Add the provider to the list if the no of sequences + //contributed to the secondary structure consensus is + //more than 0. + if(aa.getNoOfSequencesIncluded()>0 && !Constants.SS_ALL_PROVIDERS.equals(ssSource)) + { + // Remove "All" from the hidden types list{ + secondaryStructureSources.add(ssSource); + } } - // for - // ignoreGapsInConsensusCalculation); } /** @@ -1265,6 +1295,9 @@ public class SequenceGroup implements AnnotatedCollectionI for(String ssSource : ssSources) { AlignmentAnnotation aa = new AlignmentAnnotation("", "", new Annotation[1], 0f, 100f, AlignmentAnnotation.BAR_GRAPH); + // Setting the annotation visibility to true of the provider is "All" + // and false otherwise. + aa.visible = Constants.SS_ALL_PROVIDERS.equals(ssSource); aa.hasText = true; aa.autoCalculated = true; aa.groupRef = this; @@ -1380,18 +1413,6 @@ 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; - } - /** * * @param showConsHist @@ -1408,17 +1429,6 @@ 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 diff --git a/src/jalview/gui/AlignFrame.java b/src/jalview/gui/AlignFrame.java index 93d6424..4da845d 100644 --- a/src/jalview/gui/AlignFrame.java +++ b/src/jalview/gui/AlignFrame.java @@ -5800,6 +5800,10 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, for (AlignmentAnnotation aa: annotations) { + if(aa.groupRef!=null) { + continue; + } + boolean isSSConsensus = aa.label.startsWith(MessageManager.getString("label.ssconsensus_label")); boolean matchesSSSourceSelection = aa.description.startsWith(ssSourceSelection); diff --git a/src/jalview/gui/PopupMenu.java b/src/jalview/gui/PopupMenu.java index 07fe819..aa5121a 100644 --- a/src/jalview/gui/PopupMenu.java +++ b/src/jalview/gui/PopupMenu.java @@ -176,7 +176,11 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener JMenuItem textColour = new JMenuItem(); - JMenu editGroupMenu = new JMenu(); + JMenu editGroupMenu = new JMenu(); + + JMenu groupShowAutoCalculatedAnnotations = new JMenu(); + + JMenu groupHideAutoCalculatedAnnotations = new JMenu(); JMenuItem chooseStructure = new JMenuItem(); @@ -451,11 +455,18 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener final List selectedGroup = (alignPanel.av .getSelectionGroup() == null ? Collections. emptyList() - : alignPanel.av.getSelectionGroup().getSequences()); + : alignPanel.av.getSelectionGroup().getSequences()); buildAnnotationTypesMenus(groupShowAnnotationsMenu, groupHideAnnotationsMenu, selectedGroup); configureReferenceAnnotationsMenu(groupAddReferenceAnnotations, selectedGroup); + + //If a sequence group is selected, build show/hide auto calculated annotations menu + SequenceGroup selectedSequenceGroup = alignPanel.av.getSelectionGroup(); + if(selectedSequenceGroup != null) { + buildAutoCalculatedAnnotationsMenu(groupShowAutoCalculatedAnnotations, + groupHideAutoCalculatedAnnotations, selectedSequenceGroup); + } try { @@ -725,12 +736,28 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener unGroupMenuItem.setVisible(false); editGroupMenu .setText(MessageManager.getString("action.edit_new_group")); + + // Hide show/hide auto calculated annotations menus for group if a sequence + // group is not defined. + groupShowAutoCalculatedAnnotations.setVisible(false); + groupHideAutoCalculatedAnnotations.setVisible(false); } else { createGroupMenuItem.setVisible(false); unGroupMenuItem.setVisible(true); editGroupMenu.setText(MessageManager.getString("action.edit_group")); + + // Show show/hide auto calculated annotations menus for group if a sequence + // group is defined and showGroupSSConsensus is true. Hide the menus otherwise. + if(alignPanel.av.isShowGroupSSConsensus()) { + groupShowAutoCalculatedAnnotations.setVisible(true); + groupHideAutoCalculatedAnnotations.setVisible(true); + } + else { + groupShowAutoCalculatedAnnotations.setVisible(false); + groupHideAutoCalculatedAnnotations.setVisible(false); + } } if (!forIdPanel) @@ -1047,6 +1074,132 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener // grey out 'hide annotations' if none are shown hideMenu.setEnabled(!shownTypes.isEmpty()); } + + /** + * Builds the menu for showing and hiding auto calculated annotations for a + * selected sequence group. It clears existing menu items and adds menu items + * for showing/hiding annotations. The menu is populated based on the current + * visible status. + * + * @param groupShowAutoCalculatedAnnotations + * The menu for showing auto calculated annotations. + * @param groupHideAutoCalculatedAnnotations + * The menu for hiding auto calculated annotations. + * @param selectedSequenceGroup + * The sequence group selected by the user. + */ + protected void buildAutoCalculatedAnnotationsMenu( + JMenu groupShowAutoCalculatedAnnotations, + JMenu groupHideAutoCalculatedAnnotations, + SequenceGroup selectedSequenceGroup) + { + + // Clear all existing items from the menus + groupShowAutoCalculatedAnnotations.removeAll(); + groupHideAutoCalculatedAnnotations.removeAll(); + + // Add "All" menu item to both show and hide menus. + final String all = MessageManager.getString("label.all"); + addAutoCalculatedAnnotationTypeToShowHide( + groupShowAutoCalculatedAnnotations, selectedSequenceGroup, all, + true, true); + addAutoCalculatedAnnotationTypeToShowHide( + groupHideAutoCalculatedAnnotations, selectedSequenceGroup, all, + true, false); + + // Add separators after "All" + groupShowAutoCalculatedAnnotations.addSeparator(); + groupHideAutoCalculatedAnnotations.addSeparator(); + + // Get the alignment annotations + final AlignmentAnnotation[] annotations = ap.getAlignment() + .getAlignmentAnnotation(); + + // Lists to hold shown and hidden annotation types + List shownTypes = new ArrayList<>(); + List hiddenTypes = new ArrayList<>(); + + // Populate the lists of shown and hidden annotation types based on the + // current visible status for the selected sequence group + AlignmentAnnotationUtils + .getShownHiddenSecondaryStructureProvidersForGroup(shownTypes, + hiddenTypes, + AlignmentAnnotationUtils.asList(annotations), + selectedSequenceGroup); + + // Add code if additional auto calculated annotation types (like residue consensus, + // conservation, etc.) are needed for a selected group + + // Add currently hidden types to the show menu + for (String shownType : shownTypes) + { + addAutoCalculatedAnnotationTypeToShowHide( + groupHideAutoCalculatedAnnotations, selectedSequenceGroup, + shownType, false, false); + } + + // Add currently shown types to the hide menu + for (String hiddenType : hiddenTypes) + { + addAutoCalculatedAnnotationTypeToShowHide( + groupShowAutoCalculatedAnnotations, selectedSequenceGroup, + hiddenType, false, true); + } + + // Enable or disable the menus based on whether there are any hidden + // or shown types + groupShowAutoCalculatedAnnotations.setEnabled(!hiddenTypes.isEmpty()); + groupHideAutoCalculatedAnnotations.setEnabled(!shownTypes.isEmpty()); + } + + + /** + * Adds a menu item to the provided menu for showing or hiding a specific type + * of auto calculated annotation + * + * @param showOrHideAutoCalculatedAnnotationsMenu + * The menu to which the item should be added. + * @param selectedSequenceGroup + * The selected sequence group for which the action will be + * performed. + * @param type + * The type of annotation to show or hide. + * @param allTypes + * Indicates if the action should apply to all annotation types. + * True if user selects "All". + * @param actionIsShow + * Indicates if the item is for showing (true) or hiding (false) the + * annotation type. + */ + protected void addAutoCalculatedAnnotationTypeToShowHide( + JMenu showOrHideAutoCalculatedAnnotationsMenu, + final SequenceGroup selectedSequenceGroup, final String type, + final boolean allTypes, final boolean actionIsShow) + { + + // Create a new menu item with the type + final JMenuItem item = new JMenuItem(type); + + // Add an action listener to the menu item + item.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + // Show or hide the auto calculated annotations for the selected group + AlignmentUtils.showOrHideAutoCalculatedAnnotationsForGroup( + ap.getAlignment(), type, selectedSequenceGroup, allTypes, + actionIsShow); + + refresh(); + } + }); + + // Add the menu item to the menu + showOrHideAutoCalculatedAnnotationsMenu.add(item); + } + + /** * Returns a list of sequences - either the current selection group (if there @@ -1469,6 +1622,10 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener .setText(MessageManager.getString("label.show_annotations")); groupHideAnnotationsMenu .setText(MessageManager.getString("label.hide_annotations")); + groupShowAutoCalculatedAnnotations + .setText(MessageManager.getString("label.group_show_auto_calculated_annotations")); + groupHideAutoCalculatedAnnotations + .setText(MessageManager.getString("label.group_hide_auto_calculated_annotations")); JMenuItem sequenceFeature = new JMenuItem( MessageManager.getString("label.create_sequence_feature")); sequenceFeature.addActionListener(new ActionListener() @@ -1556,6 +1713,8 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener groupMenu.add(groupShowAnnotationsMenu); groupMenu.add(groupHideAnnotationsMenu); groupMenu.add(groupAddReferenceAnnotations); + groupMenu.add(groupShowAutoCalculatedAnnotations); + groupMenu.add(groupHideAutoCalculatedAnnotations); groupMenu.add(editMenu); groupMenu.add(outputMenu); groupMenu.add(sequenceFeature); diff --git a/src/jalview/viewmodel/AlignmentViewport.java b/src/jalview/viewmodel/AlignmentViewport.java index 7a5e47a..dc07645 100644 --- a/src/jalview/viewmodel/AlignmentViewport.java +++ b/src/jalview/viewmodel/AlignmentViewport.java @@ -2414,6 +2414,12 @@ public abstract class AlignmentViewport List ssAa = sg.getSSConsensus(secondaryStructureSources); if(ssAa != null) { for(AlignmentAnnotation aa : ssAa) { + // Setting annotation visibility to true for the secondary + // structure consensus for all providers + if (aa.label.contains(Constants.SS_ALL_PROVIDERS)) + { + aa.visible = true; + } alignment.addAnnotation(aa, 0); } } -- 1.7.10.2