From b254ce17e47c43d19804efa129367291a79b1315 Mon Sep 17 00:00:00 2001 From: Renia Correya Date: Wed, 24 Apr 2024 15:05:44 +0100 Subject: [PATCH] JAL-4392 Consensus secondary structure: Display Secondary structure consensus for the alignment. --- resources/lang/Messages.properties | 3 + src/jalview/analysis/AAFrequency.java | 239 +++++++++++++++++++++++++- src/jalview/analysis/AlignmentUtils.java | 22 ++- src/jalview/api/AlignViewportI.java | 13 ++ src/jalview/bin/Cache.java | 2 + src/jalview/datamodel/Profile.java | 57 ++++++ src/jalview/datamodel/ProfileI.java | 7 + src/jalview/datamodel/SequenceGroup.java | 98 +++++++++++ src/jalview/gui/AlignFrame.java | 8 + src/jalview/gui/AlignViewport.java | 5 +- src/jalview/gui/PopupMenu.java | 22 +++ src/jalview/jbgui/GAlignFrame.java | 23 ++- src/jalview/project/Jalview2XML.java | 2 + src/jalview/renderer/AnnotationRenderer.java | 21 ++- src/jalview/renderer/ResidueShader.java | 48 ++++++ src/jalview/renderer/ResidueShaderI.java | 4 + src/jalview/viewmodel/AlignmentViewport.java | 146 +++++++++++++++- src/jalview/workers/AlignCalcManager.java | 8 +- 18 files changed, 713 insertions(+), 15 deletions(-) diff --git a/resources/lang/Messages.properties b/resources/lang/Messages.properties index c3c8589..195eb1f 100644 --- a/resources/lang/Messages.properties +++ b/resources/lang/Messages.properties @@ -277,7 +277,9 @@ label.show_selected_annotations = Show selected annotations label.group_consensus = Group Consensus label.group_conservation = Group Conservation label.show_consensus_histogram = Show Consensus Histogram +label.show_ssconsensus_histogram = Show SS 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 @@ -1288,6 +1290,7 @@ 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_descr = SS Consensus label.complement_consensus_descr = PID for cDNA label.strucconsensus_descr = PID for base pairs label.occupancy_descr = Number of aligned positions diff --git a/src/jalview/analysis/AAFrequency.java b/src/jalview/analysis/AAFrequency.java index 6967885..796625a 100755 --- a/src/jalview/analysis/AAFrequency.java +++ b/src/jalview/analysis/AAFrequency.java @@ -30,6 +30,7 @@ import jalview.datamodel.Profiles; import jalview.datamodel.ProfilesI; import jalview.datamodel.ResidueCount; import jalview.datamodel.ResidueCount.SymbolCounts; +import jalview.datamodel.SecondaryStructureCount; import jalview.datamodel.SequenceI; import jalview.ext.android.SparseIntArray; import jalview.util.Comparison; @@ -54,6 +55,8 @@ import java.util.List; public class AAFrequency { public static final String PROFILE = "P"; + private static final String SS_ANNOTATION_LABEL = "Secondary Structure"; + private static final char COIL = 'C'; /* * Quick look-up of String value of char 'A' to 'Z' @@ -191,6 +194,130 @@ public class AAFrequency // jalview.bin.Console.outPrintln(elapsed); } + + public static final ProfilesI calculateSS(List list, int start, + int end) + { + return calculateSS(list, start, end, false); + } + + public static final ProfilesI calculateSS(List sequences, + int start, int end, boolean profile) + { + 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); + return reply; + } + } + + public static final ProfilesI calculateSS(final SequenceI[] sequences, + int width, int start, int end, boolean saveFullProfile) + { + // long now = System.currentTimeMillis(); + int seqCount = sequences.length; + + ProfileI[] result = new ProfileI[width]; + + for (int column = start; column < end; column++) + { + /* + * Apply a heuristic to detect nucleotide data (which can + * be counted in more compact arrays); here we test for + * more than 90% nucleotide; recheck every 10 columns in case + * of misleading data e.g. highly conserved Alanine in peptide! + * Mistakenly guessing nucleotide has a small performance cost, + * as it will result in counting in sparse arrays. + * Mistakenly guessing peptide has a small space cost, + * as it will use a larger than necessary array to hold counts. + */ + + 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); + + if (sequences[row].getLength() > column && !Comparison.isGap(c)) + { + + AlignmentAnnotation[] aa = sequences[row].getAnnotation(SS_ANNOTATION_LABEL); + if(aa == null) { + continue; + } + int seqPosition = sequences[row].findPosition(column); + char ss; + if (aa[0].getAnnotationForPosition(seqPosition) != null) { + ss = aa[0].getAnnotationForPosition(seqPosition).secondaryStructure; + + //There is no representation for coil and it can be either ' ' or null. + if (ss == ' ') { + ss = COIL; + } + } + else { + ss = COIL; + } + + //secondaryStructures[row][column] = ss; + + ssCounts.add(ss); + ssCount++; + + } + else + { + /* + * count a gap if the sequence doesn't reach this column + */ + ssCounts.addGap(); + } + } + + int maxSSCount = ssCounts.getModalCount(); + String maxSS = ssCounts.getSSForCount(maxSSCount); + int gapCount = ssCounts.getGapCount(); + ProfileI profile = new Profile(maxSS, ssCount, gapCount, + maxSSCount); + + if (saveFullProfile) + { + profile.setSSCounts(ssCounts); + } + + result[column] = profile; + } + return new Profiles(result); + // long elapsed = System.currentTimeMillis() - now; + // jalview.bin.Console.outPrintln(elapsed); + } + /** * 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 +414,58 @@ 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; + } + + 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 +571,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 +625,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() diff --git a/src/jalview/analysis/AlignmentUtils.java b/src/jalview/analysis/AlignmentUtils.java index be5133f..0772d54 100644 --- a/src/jalview/analysis/AlignmentUtils.java +++ b/src/jalview/analysis/AlignmentUtils.java @@ -37,6 +37,7 @@ import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; +import jalview.api.AlignCalcWorkerI; import jalview.bin.Console; import jalview.commands.RemoveGapColCommand; import jalview.datamodel.AlignedCodon; @@ -55,6 +56,7 @@ 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; @@ -62,6 +64,7 @@ import jalview.util.DBRefUtils; import jalview.util.IntRangeComparator; import jalview.util.MapList; import jalview.util.MappingUtils; +import jalview.workers.SecondaryStructureConsensusThread; /** * grab bag of useful alignment manipulation operations Expect these to be @@ -74,7 +77,9 @@ 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:"; + + private static final String SS_ANNOTATION_LABEL = "Secondary Structure"; /* * the 'id' attribute is provided for variant features fetched from @@ -1532,6 +1537,21 @@ public class AlignmentUtils } } } + + + public static boolean isSSAnnotationPresent( Map> annotations) { + + for (SequenceI seq : annotations.keySet()) + { + for (AlignmentAnnotation ann : annotations.get(seq)) + { + if(ann.getDescription(false).startsWith(SS_ANNOTATION_LABEL)) { + return true; + } + } + } + return false; + } /** * Make a copy of a reference annotation {@code ann} and add it to an diff --git a/src/jalview/api/AlignViewportI.java b/src/jalview/api/AlignViewportI.java index 0cfd03d..b7747f5 100644 --- a/src/jalview/api/AlignViewportI.java +++ b/src/jalview/api/AlignViewportI.java @@ -85,8 +85,12 @@ public interface AlignViewportI extends ViewStyleI boolean isValidCharWidth(); boolean isShowConsensusHistogram(); + + boolean isShowSSConsensusHistogram(); boolean isShowSequenceLogo(); + + boolean isShowSequenceSSLogo(); boolean isNormaliseSequenceLogo(); @@ -129,6 +133,9 @@ public interface AlignViewportI extends ViewStyleI * @return */ AlignmentAnnotation getAlignmentConsensusAnnotation(); + + AlignmentAnnotation getAlignmentSecondaryStructureConsensusAnnotation(); + /** * get the container for alignment gap annotation @@ -175,6 +182,9 @@ public interface AlignViewportI extends ViewStyleI * @param hconsensus */ void setSequenceConsensusHash(ProfilesI hconsensus); + + void setSequenceSSConsensusHash(ProfilesI hSSConsensus); + /** * Set the cDNA complement consensus for the viewport @@ -567,4 +577,7 @@ public interface AlignViewportI extends ViewStyleI Iterator getViewAsVisibleContigs(boolean selectedRegionOnly); ContactMatrixI getContactMatrix(AlignmentAnnotation alignmentAnnotation); + + ProfilesI getSequenceSSConsensusHash(); + } diff --git a/src/jalview/bin/Cache.java b/src/jalview/bin/Cache.java index a64e869..558b859 100755 --- a/src/jalview/bin/Cache.java +++ b/src/jalview/bin/Cache.java @@ -177,6 +177,8 @@ import jalview.ws.sifts.SiftsSettings; * in the alignment. *
  • SHOW_CONSENSUS_HISTOGRAM (false) Show consensus annotation row's * histogram.
  • + *
  • SHOW_SSCONSENSUS_HISTOGRAM (false) Show secondary structure consensus annotation row's + * histogram.
  • *
  • SHOW_CONSENSUS_LOGO (false) Show consensus annotation row's sequence * logo.
  • *
  • NORMALISE_CONSENSUS_LOGO (false) Show consensus annotation row's sequence diff --git a/src/jalview/datamodel/Profile.java b/src/jalview/datamodel/Profile.java index 8638896..35f1429 100644 --- a/src/jalview/datamodel/Profile.java +++ b/src/jalview/datamodel/Profile.java @@ -32,6 +32,8 @@ public class Profile implements ProfileI * an object holding counts of symbols in the profile */ private ResidueCount counts; + + private SecondaryStructureCount ssCounts; /* * the number of sequences (gapped or not) in the profile @@ -47,12 +49,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 +80,14 @@ public class Profile implements ProfileI this.maxCount = max; this.modalResidue = modalRes; } + + public Profile(String modalSS, int ssCount, int gaps, int maxSSCount) + { + this.height = ssCount; + this.gapped = gaps; + this.maxSSCount = maxSSCount; + this.modalSS = modalSS; + } /* (non-Javadoc) * @see jalview.datamodel.ProfileI#setCounts(jalview.datamodel.ResidueCount) @@ -83,6 +97,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 +125,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 +153,12 @@ public class Profile implements ProfileI { return counts; } + + @Override + public SecondaryStructureCount getSSCounts() + { + return ssCounts; + } /* (non-Javadoc) * @see jalview.datamodel.ProfileI#getHeight() @@ -141,6 +186,12 @@ public class Profile implements ProfileI { return maxCount; } + + @Override + public int getMaxSSCount() + { + return maxSSCount; + } /* (non-Javadoc) * @see jalview.datamodel.ProfileI#getModalResidue() @@ -150,6 +201,12 @@ public class Profile implements ProfileI { return modalResidue; } + + @Override + public String getModalSS() + { + return modalSS; + } /* (non-Javadoc) * @see jalview.datamodel.ProfileI#getNonGapped() diff --git a/src/jalview/datamodel/ProfileI.java b/src/jalview/datamodel/ProfileI.java index 65a5c0d..511095e 100644 --- a/src/jalview/datamodel/ProfileI.java +++ b/src/jalview/datamodel/ProfileI.java @@ -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 @@ -68,6 +72,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 +81,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 +89,6 @@ public interface ProfileI * @return */ public abstract int getNonGapped(); + SecondaryStructureCount getSSCounts(); } \ No newline at end of file diff --git a/src/jalview/datamodel/SequenceGroup.java b/src/jalview/datamodel/SequenceGroup.java index 7e53c46..b5048a0 100755 --- a/src/jalview/datamodel/SequenceGroup.java +++ b/src/jalview/datamodel/SequenceGroup.java @@ -132,6 +132,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,10 +152,15 @@ public class SequenceGroup implements AnnotatedCollectionI private boolean hidecols = false; AlignmentAnnotation consensus = null; + + + AlignmentAnnotation ssConsensus = null; AlignmentAnnotation conservation = null; private boolean showConsensusHistogram; + + private boolean showSSConsensusHistogram; private AnnotatedCollectionI context; @@ -229,6 +237,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 +619,21 @@ public class SequenceGroup implements AnnotatedCollectionI cs.setConsensus(cnsns); upd = true; } + + + ProfilesI ssCnsns = AAFrequency.calculateSS(sequences, startRes, + endRes + 1, showSequenceLogo); + if (ssConsensus != null) + { + _updateSSConsensusRow(ssCnsns, sequences.size()); + upd = true; + } + if (cs != null) + { + cs.setSsConsensus(ssCnsns); + upd = true; + } + if ((conservation != null) || (cs != null && cs.conservationApplied())) @@ -699,6 +723,33 @@ public class SequenceGroup implements AnnotatedCollectionI // for // ignoreGapsInConsensusCalculation); } + + public ProfilesI ssConsensusData = null; + + private void _updateSSConsensusRow(ProfilesI ssCnsns, long nseq) + { + if (ssConsensus == null) + { + getSSConsensus(); + } + ssConsensus.label = "Sec Str Consensus for " + getName(); + ssConsensus.description = "Percent Identity"; + ssConsensusData = ssCnsns; + // preserve width if already set + int aWidth = (ssConsensus.annotations != null) + ? (endRes < ssConsensus.annotations.length + ? ssConsensus.annotations.length + : endRes + 1) + : endRes + 1; + ssConsensus.annotations = null; + ssConsensus.annotations = new Annotation[aWidth]; // should be alignment width + + AAFrequency.completeSSConsensus(ssConsensus, ssCnsns, startRes, endRes + 1, + ignoreGapsInConsensus, showSequenceLogo, nseq); // TODO: setting + // container + // for + // ignoreGapsInConsensusCalculation); + } /** * @param s @@ -1154,6 +1205,30 @@ public class SequenceGroup implements AnnotatedCollectionI } return consensus; } + + public AlignmentAnnotation getSSConsensus() + { + // 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) + { + ssConsensus = new AlignmentAnnotation("", "", new Annotation[1], 0f, + 100f, AlignmentAnnotation.BAR_GRAPH); + ssConsensus.hasText = true; + ssConsensus.autoCalculated = true; + ssConsensus.groupRef = this; + ssConsensus.label = "Sec Str Consensus for " + getName(); + ssConsensus.description = "Percent Identity"; + } + return ssConsensus; + } /** * set this alignmentAnnotation object as the one used to render consensus @@ -1256,6 +1331,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 +1360,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 diff --git a/src/jalview/gui/AlignFrame.java b/src/jalview/gui/AlignFrame.java index c49626b..1bf2529 100644 --- a/src/jalview/gui/AlignFrame.java +++ b/src/jalview/gui/AlignFrame.java @@ -802,6 +802,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, } ap.av.updateConservation(ap); ap.av.updateConsensus(ap); + ap.av.updateSecondaryStructureConsensus(ap); ap.av.updateStrucConsensus(ap); } } @@ -5717,6 +5718,13 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, viewport.setShowConsensusHistogram(showConsensusHistogram.getState()); alignPanel.updateAnnotation(applyAutoAnnotationSettings.getState()); } + + @Override + protected void showSSConsensusHistogram_actionPerformed(ActionEvent e) + { + viewport.setShowSSConsensusHistogram(showSSConsensusHistogram.getState()); + alignPanel.updateAnnotation(applyAutoAnnotationSettings.getState()); + } /* * (non-Javadoc) diff --git a/src/jalview/gui/AlignViewport.java b/src/jalview/gui/AlignViewport.java index a8bc815..037f120 100644 --- a/src/jalview/gui/AlignViewport.java +++ b/src/jalview/gui/AlignViewport.java @@ -281,7 +281,9 @@ public class AlignViewport extends AlignmentViewport } showConsensusHistogram = Cache.getDefault("SHOW_CONSENSUS_HISTOGRAM", true); - showSequenceLogo = Cache.getDefault("SHOW_CONSENSUS_LOGO", false); + showSSConsensusHistogram = Cache.getDefault("SHOW_SSCONSENSUS_HISTOGRAM", + true); + showSequenceLogo = Cache.getDefault("SHOW_CONSENSUS_LOGO", true); normaliseSequenceLogo = Cache.getDefault("NORMALISE_CONSENSUS_LOGO", false); showGroupConsensus = Cache.getDefault("SHOW_GROUP_CONSENSUS", false); @@ -314,6 +316,7 @@ public class AlignViewport extends AlignmentViewport if (residueShading != null) { residueShading.setConsensus(hconsensus); + residueShading.setSsConsensus(hSSConsensus); } setColourAppliesToAllGroups(true); } diff --git a/src/jalview/gui/PopupMenu.java b/src/jalview/gui/PopupMenu.java index 8875288..d2c00eb 100644 --- a/src/jalview/gui/PopupMenu.java +++ b/src/jalview/gui/PopupMenu.java @@ -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 @@ -1737,8 +1739,28 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener { final AlignmentI alignment = this.ap.getAlignment(); AlignmentUtils.addReferenceAnnotations(candidates, alignment, null); + + if(AlignmentUtils.isSSAnnotationPresent(candidates)) { + restartSSConsensusWorker(); + } + refresh(); } + + + private void restartSSConsensusWorker() { + + List 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) { diff --git a/src/jalview/jbgui/GAlignFrame.java b/src/jalview/jbgui/GAlignFrame.java index 7daeb37..da35bd4 100755 --- a/src/jalview/jbgui/GAlignFrame.java +++ b/src/jalview/jbgui/GAlignFrame.java @@ -198,7 +198,9 @@ public class GAlignFrame extends JInternalFrame protected JCheckBoxMenuItem showGroupConservation = new JCheckBoxMenuItem(); - protected JCheckBoxMenuItem showConsensusHistogram = new JCheckBoxMenuItem(); + protected JCheckBoxMenuItem showConsensusHistogram = new JCheckBoxMenuItem(); + + protected JCheckBoxMenuItem showSSConsensusHistogram = new JCheckBoxMenuItem(); protected JCheckBoxMenuItem showSequenceLogo = new JCheckBoxMenuItem(); @@ -873,6 +875,18 @@ public class GAlignFrame extends JInternalFrame showConsensusHistogram_actionPerformed(e); } + }); + showSSConsensusHistogram.setText( + MessageManager.getString("label.show_ssconsensus_histogram")); + showSSConsensusHistogram.addActionListener(new ActionListener() + { + + @Override + public void actionPerformed(ActionEvent e) + { + showConsensusHistogram_actionPerformed(e); + } + }); showSequenceLogo .setText(MessageManager.getString("label.show_consensus_logo")); @@ -1890,6 +1904,7 @@ public class GAlignFrame extends JInternalFrame autoAnnMenu.addSeparator(); autoAnnMenu.add(applyAutoAnnotationSettings); autoAnnMenu.add(showConsensusHistogram); + autoAnnMenu.add(showSSConsensusHistogram); autoAnnMenu.add(showSequenceLogo); autoAnnMenu.add(normaliseSequenceLogo); autoAnnMenu.addSeparator(); @@ -2224,6 +2239,12 @@ public class GAlignFrame extends JInternalFrame // TODO Auto-generated method stub } + + protected void showSSConsensusHistogram_actionPerformed(ActionEvent e) + { + // TODO Auto-generated method stub + + } protected void showSequenceLogo_actionPerformed(ActionEvent e) { diff --git a/src/jalview/project/Jalview2XML.java b/src/jalview/project/Jalview2XML.java index 983b512..2f4052a 100644 --- a/src/jalview/project/Jalview2XML.java +++ b/src/jalview/project/Jalview2XML.java @@ -5309,6 +5309,8 @@ public class Jalview2XML view.isIgnoreGapsinConsensus()); viewport.getResidueShading() .setConsensus(viewport.getSequenceConsensusHash()); + viewport.getResidueShading() + .setSsConsensus(viewport.getSequenceSSConsensusHash()); if (safeBoolean(view.isConservationSelected()) && cs != null) { viewport.getResidueShading() diff --git a/src/jalview/renderer/AnnotationRenderer.java b/src/jalview/renderer/AnnotationRenderer.java index b5dac0d..015db5c 100644 --- a/src/jalview/renderer/AnnotationRenderer.java +++ b/src/jalview/renderer/AnnotationRenderer.java @@ -88,6 +88,8 @@ public class AnnotationRenderer private HiddenColumns hiddenColumns; private ProfilesI hconsensus; + + private ProfilesI hSSconsensus; private Hashtable[] complementConsensus; @@ -164,6 +166,7 @@ public class AnnotationRenderer { hiddenColumns = null; hconsensus = null; + hSSconsensus = null; complementConsensus = null; hStrucConsensus = null; fadedImage = null; @@ -377,6 +380,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,6 +429,15 @@ public class AnnotationRenderer } } } + + else if(aa.autoCalculated && aa.label.startsWith("SecondaryStructureConsensus")) + { + return AAFrequency.extractProfile( + hSSconsensus.get(column), + av_ignoreGapsConsensus); + + } + else { if (aa.autoCalculated && aa.label.startsWith("StrucConsensus")) @@ -514,6 +527,8 @@ public class AnnotationRenderer .getAlignmentStrucConsensusAnnotation(); final AlignmentAnnotation complementConsensusAnnot = av .getComplementConsensusAnnotation(); + final AlignmentAnnotation ssConsensusAnnot = av + .getAlignmentSecondaryStructureConsensusAnnotation(); BitSet graphGroupDrawn = new BitSet(); int charOffset = 0; // offset for a label @@ -540,7 +555,7 @@ public class AnnotationRenderer normaliseProfile = row.groupRef.isNormaliseSequenceLogo(); } else if (row == consensusAnnot || row == structConsensusAnnot - || row == complementConsensusAnnot) + || row == complementConsensusAnnot || row == ssConsensusAnnot) { renderHistogram = av_renderHistogram; renderProfile = av_renderProfile; @@ -1519,6 +1534,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) @@ -1602,7 +1619,9 @@ public class AnnotationRenderer } else { + colour = profcolour.findColour(dc[0], column, null); + } g.setColor(colour == Color.white ? Color.lightGray : colour); diff --git a/src/jalview/renderer/ResidueShader.java b/src/jalview/renderer/ResidueShader.java index c031170..b914c65 100644 --- a/src/jalview/renderer/ResidueShader.java +++ b/src/jalview/renderer/ResidueShader.java @@ -61,6 +61,22 @@ public class ResidueShader implements ResidueShaderI * the consensus data for each column */ private ProfilesI consensus; + + /* + * the consensus data for each column + */ + private ProfilesI ssConsensus; + + + public ProfilesI getSsConsensus() + { + return ssConsensus; + } + + public void setSsConsensus(ProfilesI ssConsensus) + { + this.ssConsensus = ssConsensus; + } /* * if true, apply shading of colour by conservation @@ -128,6 +144,7 @@ public class ResidueShader implements ResidueShaderI this.conservationIncrement = rs.conservationIncrement; this.ignoreGaps = rs.ignoreGaps; this.pidThreshold = rs.pidThreshold; + this.ssConsensus = rs.ssConsensus; } /** @@ -137,6 +154,7 @@ public class ResidueShader implements ResidueShaderI public void setConsensus(ProfilesI cons) { consensus = cons; + } /** @@ -261,6 +279,36 @@ public class ResidueShader implements ResidueShaderI return colour; } + + @Override + public Color findSSColour(char symbol, int position, SequenceI seq) + { + if (colourScheme == null) + { + return Color.white; // Colour is 'None' + } + + /* + * get 'base' colour + */ + ProfileI profile = ssConsensus == null ? null : ssConsensus.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 diff --git a/src/jalview/renderer/ResidueShaderI.java b/src/jalview/renderer/ResidueShaderI.java index 4d97171..0412d21 100644 --- a/src/jalview/renderer/ResidueShaderI.java +++ b/src/jalview/renderer/ResidueShaderI.java @@ -34,6 +34,8 @@ public interface ResidueShaderI { public abstract void setConsensus(ProfilesI cons); + + public abstract void setSsConsensus(ProfilesI cons); public abstract boolean conservationApplied(); @@ -82,4 +84,6 @@ public interface ResidueShaderI public abstract void setColourScheme(ColourSchemeI cs); + Color findSSColour(char symbol, int position, SequenceI seq); + } \ No newline at end of file diff --git a/src/jalview/viewmodel/AlignmentViewport.java b/src/jalview/viewmodel/AlignmentViewport.java index 3e1bc63..ccf6379 100644 --- a/src/jalview/viewmodel/AlignmentViewport.java +++ b/src/jalview/viewmodel/AlignmentViewport.java @@ -76,6 +76,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 +698,8 @@ public abstract class AlignmentViewport } protected AlignmentAnnotation consensus; + + protected AlignmentAnnotation secondaryStructureConsensus; protected AlignmentAnnotation complementConsensus; @@ -709,6 +712,8 @@ public abstract class AlignmentViewport protected AlignmentAnnotation quality; protected AlignmentAnnotation[] groupConsensus; + + protected AlignmentAnnotation[] groupSSConsensus; protected AlignmentAnnotation[] groupConservation; @@ -716,6 +721,10 @@ public abstract class AlignmentViewport * results of alignment consensus analysis for visible portion of view */ protected ProfilesI hconsensus = null; + + protected ProfilesI hSSConsensus = null; + + /** * results of cDNA complement consensus visible portion of view @@ -753,7 +762,13 @@ public abstract class AlignmentViewport { this.hconsensus = hconsensus; } - + + @Override + public void setSequenceSSConsensusHash(ProfilesI hSSConsensus) + { + this.hSSConsensus = hSSConsensus; + } + @Override public void setComplementConsensusHash( Hashtable[] hconsensus) @@ -766,6 +781,12 @@ public abstract class AlignmentViewport { return hconsensus; } + + @Override + public ProfilesI getSequenceSSConsensusHash() + { + return hSSConsensus; + } @Override public Hashtable[] getComplementConsensusHash() @@ -805,6 +826,14 @@ public abstract class AlignmentViewport return consensus; } + + @Override + public AlignmentAnnotation getAlignmentSecondaryStructureConsensusAnnotation() + { + return secondaryStructureConsensus; + } + + @Override public AlignmentAnnotation getAlignmentGapAnnotation() { @@ -896,6 +925,26 @@ public abstract class AlignmentViewport } } } + + + + + /** + * trigger update of consensus annotation + */ + public void updateSecondaryStructureConsensus(final AlignmentViewPanel ap) + { + // see note in mantis : issue number 8585 + if (secondaryStructureConsensus == null || !autoCalculateConsensus) + { + return; + } + if (calculator + .getRegisteredWorkersOfClass(SecondaryStructureConsensusThread.class) == null) + { + calculator.registerWorker(new SecondaryStructureConsensusThread(this, ap)); + } + } // --------START Structure Conservation public void updateStrucConsensus(final AlignmentViewPanel ap) @@ -959,6 +1008,7 @@ public abstract class AlignmentViewport consensus = null; complementConsensus = null; strucConsensus = null; + secondaryStructureConsensus = null; conservation = null; quality = null; groupConsensus = null; @@ -1005,6 +1055,8 @@ public abstract class AlignmentViewport * should consensus profile be rendered by default */ protected boolean showSequenceLogo = false; + + protected boolean showSequenceSSLogo = false; /** * should consensus profile be rendered normalised to row height @@ -1015,6 +1067,13 @@ public abstract class AlignmentViewport * should consensus histograms be rendered by default */ protected boolean showConsensusHistogram = true; + + protected boolean showSSConsensusHistogram = true; + + public void setShowSSConsensusHistogram(boolean showSSConsensusHistogram) + { + this.showSSConsensusHistogram = showSSConsensusHistogram; + } /** * @return the showConsensusProfile @@ -1024,6 +1083,12 @@ public abstract class AlignmentViewport { return showSequenceLogo; } + + @Override + public boolean isShowSequenceSSLogo() + { + return showSequenceSSLogo; + } /** * @param showSequenceLogo @@ -1042,6 +1107,18 @@ public abstract class AlignmentViewport } this.showSequenceLogo = showSequenceLogo; } + + public void setShowSequenceSSLogo(boolean showSequenceSSLogo) + { + if (showSequenceSSLogo != this.showSequenceSSLogo) + { + // TODO: decouple settings setting from calculation when refactoring + // annotation update method from alignframe to viewport + this.showSequenceSSLogo = showSequenceSSLogo; + calculator.updateAnnotationFor(SecondaryStructureConsensusThread.class); + } + this.showSequenceSSLogo = showSequenceSSLogo; + } /** * @param showConsensusHistogram @@ -1096,6 +1173,12 @@ public abstract class AlignmentViewport { return this.showConsensusHistogram; } + + @Override + public boolean isShowSSConsensusHistogram() + { + return this.showSSConsensusHistogram; + } /** * when set, updateAlignment will always ensure sequences are of equal length @@ -1247,6 +1330,7 @@ public abstract class AlignmentViewport if (ap != null) { updateConsensus(ap); + updateSecondaryStructureConsensus(ap); if (residueShading != null) { residueShading.setThreshold(residueShading.getThreshold(), @@ -1872,6 +1956,7 @@ public abstract class AlignmentViewport if (autoCalculateConsensus) { updateConsensus(ap); + updateSecondaryStructureConsensus(ap); } if (hconsensus != null && autoCalculateConsensus) { @@ -1955,13 +2040,19 @@ public abstract class AlignmentViewport consensus = new AlignmentAnnotation("Consensus", MessageManager.getString("label.consensus_descr"), new Annotation[1], 0f, 100f, AlignmentAnnotation.BAR_GRAPH); + + secondaryStructureConsensus = new AlignmentAnnotation("SecondaryStructureConsensus", + MessageManager.getString("label.ssconsensus_descr"), + new Annotation[1], 0f, 100f, AlignmentAnnotation.BAR_GRAPH); + 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. @@ -2012,6 +2103,17 @@ public abstract class AlignmentViewport alignment.addAnnotation(aa); } } + + private void initSSConsensus(AlignmentAnnotation aa) + { + aa.hasText = true; + aa.autoCalculated = true; + + if (showConsensus) + { + alignment.addAnnotation(aa); + } + } // these should be extracted from the view model - style and settings for // derived annotation @@ -2160,7 +2262,9 @@ public abstract class AlignmentViewport boolean conv = isShowGroupConservation(); boolean cons = isShowGroupConsensus(); boolean showprf = isShowSequenceLogo(); + boolean showSSprf = isShowSequenceSSLogo(); boolean showConsHist = isShowConsensusHistogram(); + boolean showSSConsHist = isShowSSConsensusHistogram(); boolean normLogo = isNormaliseSequenceLogo(); /** @@ -2197,7 +2301,9 @@ public abstract class AlignmentViewport { // set defaults for this group's conservation/consensus sg.setshowSequenceLogo(showprf); + sg.setshowSequenceSSLogo(showSSprf); sg.setShowConsensusHistogram(showConsHist); + sg.setShowSSConsensusHistogram(showSSConsHist); sg.setNormaliseSequenceLogo(normLogo); } if (conv) @@ -2209,6 +2315,7 @@ public abstract class AlignmentViewport { updateCalcs = true; alignment.addAnnotation(sg.getConsensus(), 0); + alignment.addAnnotation(sg.getSSConsensus(), 0); } // refresh the annotation rows if (updateCalcs) @@ -2998,6 +3105,41 @@ public abstract class AlignmentViewport + ((ignoreGapsInConsensusCalculation) ? " without gaps" : "")); return sq; } + + 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) diff --git a/src/jalview/workers/AlignCalcManager.java b/src/jalview/workers/AlignCalcManager.java index cdf8ea4..d64bf4d 100644 --- a/src/jalview/workers/AlignCalcManager.java +++ b/src/jalview/workers/AlignCalcManager.java @@ -20,10 +20,6 @@ */ 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 { /* -- 1.7.10.2