From: Jim Procter Date: Tue, 21 May 2024 14:19:13 +0000 (+0100) Subject: JAL-4356 release date for 2.11.3.3 is 22nd May 2025 X-Git-Url: http://source.jalview.org/gitweb/?p=jalview.git;a=commitdiff_plain;h=refs%2Fheads%2Fdevelop;hp=26ac9a5b81dff731ffb988fdb7e7df2f8ee99747 JAL-4356 release date for 2.11.3.3 is 22nd May 2025 --- diff --git a/RELEASE b/RELEASE index a44846c..fcd9828 100644 --- a/RELEASE +++ b/RELEASE @@ -1,2 +1,2 @@ -jalview.release=releases/Release_2_11_3_Branch -jalview.version=2.11.3.3 +jalview.release=releases/Release_2_11_4_Branch +jalview.version=2.11.4.0 diff --git a/build.gradle b/build.gradle index 3ed6c56..437c43a 100644 --- a/build.gradle +++ b/build.gradle @@ -1769,6 +1769,7 @@ task testTask0(type: Test) { preserveOrder true useDefaultListeners=true } + timeout = Duration.ofMinutes(15) } /* separated tests */ @@ -1781,6 +1782,7 @@ task testTask1(type: Test) { preserveOrder true useDefaultListeners=true } + timeout = Duration.ofMinutes(5) } task testTask2(type: Test) { @@ -1792,6 +1794,7 @@ task testTask2(type: Test) { preserveOrder true useDefaultListeners=true } + timeout = Duration.ofMinutes(5) } task testTask3(type: Test) { group = "Verification" @@ -1802,6 +1805,7 @@ task testTask3(type: Test) { preserveOrder true useDefaultListeners=true } + timeout = Duration.ofMinutes(5) } /* insert more testTaskNs here -- change N to next digit or other string */ @@ -1818,6 +1822,7 @@ task testTaskN(type: Test) { } */ + /* * adapted from https://medium.com/@wasyl/pretty-tests-summary-in-gradle-744804dd676c * to summarise test results from all Test tasks @@ -2603,6 +2608,14 @@ task getdownArchiveBuild() { } } + // the wrapper scripts dir + if ( file("${getdownAppBaseDir}/${getdown_wrapper_script_dir}").exists() ) { + copy { + from "${getdownAppBaseDir}/${getdown_wrapper_script_dir}" + into "${getdownFullArchiveDir}/${getdown_wrapper_script_dir}" + } + } + getdownArchiveTxt.write(getdownArchiveTextLines.join("\n")) def vLaunchJvl = file(getdownVersionLaunchJvl) diff --git a/help/help/html/calculations/tree.html b/help/help/html/calculations/tree.html index 95904b6..cc5ca2c 100755 --- a/help/help/html/calculations/tree.html +++ b/help/help/html/calculations/tree.html @@ -79,6 +79,33 @@ types. Sequences with similar distributions of features of the same type will be grouped together in trees computed with this metric. This measure was introduced in Jalview 2.9 + +
  • Secondary Structure Similarity
    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. + + Distance calculations are based on the secondary structures + currently displayed. Sequences with similar distributions of secondary + structures will be grouped together in trees.
    + 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. +
  • Tree Construction Methods diff --git a/help/help/html/menus/alwedit.html b/help/help/html/menus/alwedit.html index 7384cda..a5bc366 100755 --- a/help/help/html/menus/alwedit.html +++ b/help/help/html/menus/alwedit.html @@ -108,6 +108,10 @@ one of the sequences (the shorter) is discarded. Press the "Apply" button to remove redundant sequences. The "Undo" button will undo the last redundancy deletion. +

  • Left Justify and Right + Justify
    +
    Moves all gaps to either right or left of the sequences in + the alignment or the currently selected region.
  • Pad Gaps
    When selected, the alignment will be kept at minimal width (so there are no empty columns before or after the first or last diff --git a/help/markdown/releases/release-2_11_3_0.md b/help/markdown/releases/release-2_11_3_0.md index 35ff569..c732130 100644 --- a/help/markdown/releases/release-2_11_3_0.md +++ b/help/markdown/releases/release-2_11_3_0.md @@ -16,7 +16,7 @@ channel: "release" - Use selected columns for superposition - Highlight aligned positions on all associated structures when mousing over a column - sequence descriptions are updated from database reference sources if not already defined -- Visible adjuster marks to grab and adjust annotation panel height and id width +- Visible adjuster marks to grab and adjust annotation panel height and id width - Adjustable ID margin when alignment is wrapped - Command line options and configurable bitmap export (via preferences file) for height, width and scale factor - Show or hide ligands in a Jmol structure view via View Ligands submenu diff --git a/help/markdown/releases/release-2_11_3_3.md b/help/markdown/releases/release-2_11_3_3.md index dcbe8e8..3401e13 100644 --- a/help/markdown/releases/release-2_11_3_3.md +++ b/help/markdown/releases/release-2_11_3_3.md @@ -1,22 +1,35 @@ --- version: 2.11.3.3 -date: 2024-01-24 +date: 2024-05-22 channel: "release" --- ## New Features +- Left/Right justify the alignment or current selection (with Undo) - Minimum versions of Jalview's build toolchain are explicitly specified in doc/building.md +### development and deployment + +- timeout for test tasks to prevent hanging builds + ## Issues Resolved +- Secondary structure not available after dragging a 3D structure onto a sequence to associate it when 'Process secondary structure' flag is true in preferences - Clustal colourscheme documentation not consistent with implementation - Handles to adjust ID panel width or annotation panel height can be dragged out of the Alignment window - Trackpad scrolling not proportional to gesture velocity - Cannot output individual images of more than one structure view attached to one open alignment - --jvmempc and --jvmemmax don't work for jalview installed via conda or jar - User's default colour scheme is erroneously applied to alignments in Jalview projects opened via command line when no --colour argument supplied -- Headless alignment export with structure annotations doesn't include secondary structure and temperature factor +- Headless alignment export with structure annotations doesn't include secondary structure and temperature factor (Known defect since 2.11.3.0) - Uniprot record retrieval not working in JalviewJS (affects all versions) - bio.tools record version number updated - Miscellaneous patches to fix hangs in 2.11.3 test suite +- Jalview archive build getdown doesn't include wrapper scripts + +### New Known Issues + +- Protein structure derived annotation tracks not available as reference annotation unless 'add to alignment' preference is enabled +- Enabling just "Add Secondary Structure" Structure preference when importing structure via CLI results in temperature factor being shown in alignment + diff --git a/help/markdown/whatsnew/whatsnew-2_11_3_3.md b/help/markdown/whatsnew/whatsnew-2_11_3_3.md index de5e441..5dbeb43 100644 --- a/help/markdown/whatsnew/whatsnew-2_11_3_3.md +++ b/help/markdown/whatsnew/whatsnew-2_11_3_3.md @@ -1 +1 @@ -The 2.11.3.3 release features minor updates to documentation, and addresses bugs affecting export of multiple images for structure views. +The 2.11.3.3 release features minor updates to documentation, and addresses bugs affecting export of multiple images for structure views, trackpad scrolling, sequence ID margin width, and extraction of secondary structure for imported models. It also includes two new alignment editing operations - Left and Right justify, which can be applied to the whole alignment or current selection. diff --git a/resources/lang/Messages.properties b/resources/lang/Messages.properties index c3c8589..8256e06 100644 --- a/resources/lang/Messages.properties +++ b/resources/lang/Messages.properties @@ -66,8 +66,10 @@ action.remove_left = Remove left action.remove_right = Remove right action.remove_empty_columns = Remove Empty Columns action.remove_all_gaps = Remove All Gaps -action.left_justify_alignment = Left Justify Alignment -action.right_justify_alignment = Right Justify Alignment +tooltip.left_justify = Left justify whole alignment or selected region +tooltip.right_justify = Right justify whole alignment or selected region +action.left_justify = Left Justify +action.right_justify = Right Justify action.boxes = Boxes action.text = Text action.by_pairwise_id = By Pairwise Identity @@ -277,10 +279,13 @@ 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 +label.show_secondary_structure = Show Secondary Structure label.show_first = Show first label.show_last = Show last label.struct_from_pdb = Process secondary structure from PDB @@ -599,6 +604,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 @@ -1288,6 +1294,8 @@ 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 = 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/resources/scoreModel/secondarystructure.scm b/resources/scoreModel/secondarystructure.scm new file mode 100644 index 0000000..f56986f --- /dev/null +++ b/resources/scoreModel/secondarystructure.scm @@ -0,0 +1,14 @@ +ScoreMatrix SECONDARYSTRUCTURE +# +# The SECONDARY STRUCTURE substitution matrix, as in +# 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 diff --git a/src/jalview/analysis/AAFrequency.java b/src/jalview/analysis/AAFrequency.java index 6967885..707110a 100755 --- a/src/jalview/analysis/AAFrequency.java +++ b/src/jalview/analysis/AAFrequency.java @@ -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,103 @@ 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) + { + + int seqCount = sequences.length; + + 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); + AlignmentAnnotation aa = AlignmentUtils.getDisplayedAlignmentAnnotation(sequences[row]); + 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); + + 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 +387,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 +544,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 +598,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..7d0ccdb 100644 --- a/src/jalview/analysis/AlignmentUtils.java +++ b/src/jalview/analysis/AlignmentUtils.java @@ -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,29 @@ 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.workers.SecondaryStructureConsensusThread; /** * grab bag of useful alignment manipulation operations Expect these to be @@ -74,7 +83,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 +1541,19 @@ public class AlignmentUtils } } } + + + public static boolean isSSAnnotationPresent( Map> 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 +2852,195 @@ 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; + + 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 extractSSSourceInAlignmentAnnotation(AlignmentAnnotation[] annotations) { + + List ssSources = new ArrayList<>(); + Set addedSources = new HashSet<>(); // to keep track of added sources + + + 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.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 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 AlignmentAnnotation getDisplayedAlignmentAnnotation(SequenceI seq){ + + for(String ssLabel : Constants.SECONDARY_STRUCTURE_LABELS.keySet()) { + + AlignmentAnnotation[] aa = seq.getAnnotation(ssLabel); + if(aa!=null) { + + for (AlignmentAnnotation annot: aa) { + if(annot.visible) { + return annot; + } + } + } + } + + return null; + + } + } diff --git a/src/jalview/analysis/scoremodels/DistanceScoreModel.java b/src/jalview/analysis/scoremodels/DistanceScoreModel.java index 3521757..55510bd 100644 --- a/src/jalview/analysis/scoremodels/DistanceScoreModel.java +++ b/src/jalview/analysis/scoremodels/DistanceScoreModel.java @@ -57,4 +57,5 @@ public abstract class DistanceScoreModel implements ScoreModelI return similarities; } + } diff --git a/src/jalview/analysis/scoremodels/FeatureDistanceModel.java b/src/jalview/analysis/scoremodels/FeatureDistanceModel.java index bcc0855..df45a72 100644 --- a/src/jalview/analysis/scoremodels/FeatureDistanceModel.java +++ b/src/jalview/analysis/scoremodels/FeatureDistanceModel.java @@ -235,7 +235,7 @@ public class FeatureDistanceModel extends DistanceScoreModel public boolean isProtein() { return true; - } + } @Override public String toString() diff --git a/src/jalview/analysis/scoremodels/ScoreModels.java b/src/jalview/analysis/scoremodels/ScoreModels.java index d9648ba..f591c8e 100644 --- a/src/jalview/analysis/scoremodels/ScoreModels.java +++ b/src/jalview/analysis/scoremodels/ScoreModels.java @@ -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 *
  • PAM250
  • *
  • PID
  • *
  • DNA
  • - *
  • Sequence Feature Similarity
  • + *
  • Sequence Feature Similarity
  • * + *
  • Secondary Structure Similarity
  • * */ 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 index 0000000..6cce5b4 --- /dev/null +++ b/src/jalview/analysis/scoremodels/SecondaryStructureDistanceModel.java @@ -0,0 +1,390 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ +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.Annotation; +import jalview.datamodel.SeqCigar; +import jalview.math.Matrix; +import jalview.math.MatrixI; +import jalview.util.Constants; +import jalview.util.SetUtils; + +import java.util.HashMap; +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. + *

    + * 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; // = seqData.getWidth(); + 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(); + ssRateMatrix = ScoreModels.getInstance().getSecondaryStructureMatrix(); + + //defining the default value for secondary structure source as 3d structures + //or JPred if user selected JPred + String selectedSSSource = Constants.SS_ANNOTATION_LABEL; + if(ssSource.equals(Constants.SECONDARY_STRUCTURE_LABELS.get(Constants.SS_ANNOTATION_FROM_JPRED_LABEL))) + { + selectedSSSource = Constants.SS_ANNOTATION_FROM_JPRED_LABEL; + } + + // need to get real position for view position + int[] viscont = seqData.getVisibleContigs(); + + /* + * Add secondary structure annotations that are added to the annotation track + * to the map + */ + Map> ssAlignmentAnnotationForSequences + = new HashMap>(); + + AlignmentAnnotation[] alignAnnotList = fr.getViewport().getAlignment() + .getAlignmentAnnotation(); + + if(alignAnnotList.length > 0) { + for (AlignmentAnnotation aa: alignAnnotList) { + if (selectedSSSource.equals(aa.label)) { + ssAlignmentAnnotationForSequences.computeIfAbsent( + aa.sequenceRef.getName(), k -> new HashSet<>()) + .add(aa.description); + } + } + } + + /* + * 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 seqsWithUndefinedSS + = findSeqsWithUndefinedSS(seqs, ssAlignmentAnnotationForSequences); + + /* + * 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 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++) + { + SeqCigar sc1 = seqs[i]; + SeqCigar sc2 = seqs[j]; + + + //check if ss is defined + boolean undefinedSS1 = seqsWithUndefinedSS.contains(sc1); + boolean undefinedSS2 = seqsWithUndefinedSS.contains(sc2); + + // 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(sc1); + boolean gap2 = !seqsWithoutGapAtCol.contains(sc2); + + //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 = seqs[i].getRefSeq().getAnnotation(selectedSSSource); + if(aa!=null) + ss1 = + AlignmentUtils.findSSAnnotationForGivenSeqposition(aa[0], seqPosition); + } + + if(!gap2 && !undefinedSS2) { + int seqPosition = seqs[j].findPosition(cpos); + AlignmentAnnotation[] aa = seqs[j].getRefSeq().getAnnotation(selectedSSSource); + if(aa!=null) + ss2 = + AlignmentUtils.findSSAnnotationForGivenSeqposition(aa[0], 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 findSeqsWithoutGapAtColumn( + SeqCigar[] seqs, int columnPosition) + { + Set 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; + } + + + /** + * 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 + * @param ssAlignmentAnnotationForSequences + * @return + */ + private Set findSeqsWithUndefinedSS(SeqCigar[] seqs, + Map> ssAlignmentAnnotationForSequences) { + Set seqsWithUndefinedSS = new HashSet<>(); + for (SeqCigar seq : seqs) { + if (isSSUndefinedOrNotAdded(seq, ssAlignmentAnnotationForSequences)) { + seqsWithUndefinedSS.add(seq); + } + } + return seqsWithUndefinedSS; + } + + + /** + * 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> ssAlignmentAnnotationForSequences) { + for (String label : Constants.SECONDARY_STRUCTURE_LABELS.keySet()) { + AlignmentAnnotation[] annotations = seq.getRefSeq().getAnnotation(label); + if (annotations != null) { + for (AlignmentAnnotation annotation : annotations) { + HashSet 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; + } + } + } + } + } + // Either annotations are undefined or not added to the track + return true; + } + + @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 diff --git a/src/jalview/analysis/scoremodels/SimilarityParams.java b/src/jalview/analysis/scoremodels/SimilarityParams.java index 5c47703..2a25fa8 100644 --- a/src/jalview/analysis/scoremodels/SimilarityParams.java +++ b/src/jalview/analysis/scoremodels/SimilarityParams.java @@ -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; + } } diff --git a/src/jalview/api/AlignViewControllerI.java b/src/jalview/api/AlignViewControllerI.java index 3e689d1..25e4873 100644 --- a/src/jalview/api/AlignViewControllerI.java +++ b/src/jalview/api/AlignViewControllerI.java @@ -119,4 +119,10 @@ public interface AlignViewControllerI */ boolean copyHighlightedRegionsToClipboard(); + /** + * Justify alignment or currently selected region left or right + * @param left - true - means justify left + * @return + */ + boolean justify_Region(boolean left); } diff --git a/src/jalview/api/AlignViewportI.java b/src/jalview/api/AlignViewportI.java index 0cfd03d..9624f83 100644 --- a/src/jalview/api/AlignViewportI.java +++ b/src/jalview/api/AlignViewportI.java @@ -20,6 +20,13 @@ */ package jalview.api; +import java.awt.Color; +import java.awt.Font; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + import jalview.analysis.Conservation; import jalview.analysis.TreeModel; import jalview.datamodel.AlignmentAnnotation; @@ -38,13 +45,6 @@ import jalview.renderer.ResidueShaderI; import jalview.schemes.ColourSchemeI; import jalview.viewmodel.ViewportRanges; -import java.awt.Color; -import java.awt.Font; -import java.util.Hashtable; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - /** * @author jimp * @@ -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 @@ -565,6 +575,19 @@ public interface AlignViewportI extends ViewStyleI * @return */ Iterator getViewAsVisibleContigs(boolean selectedRegionOnly); + /** + * notify all concerned that the alignment data has changed and derived data + * needs to be recalculated + */ + public void notifyAlignmentChanged(); + /** + * retrieve a matrix associated with the view's alignment's annotation + * @param alignmentAnnotation + * @return contact matrix or NULL + */ ContactMatrixI getContactMatrix(AlignmentAnnotation alignmentAnnotation); + + ProfilesI getSequenceSSConsensusHash(); + } diff --git a/src/jalview/api/analysis/ScoreModelI.java b/src/jalview/api/analysis/ScoreModelI.java index 275cd92..a243c0c 100644 --- a/src/jalview/api/analysis/ScoreModelI.java +++ b/src/jalview/api/analysis/ScoreModelI.java @@ -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 diff --git a/src/jalview/api/analysis/SimilarityParamsI.java b/src/jalview/api/analysis/SimilarityParamsI.java index 8d83a17..43f2866 100644 --- a/src/jalview/api/analysis/SimilarityParamsI.java +++ b/src/jalview/api/analysis/SimilarityParamsI.java @@ -60,4 +60,8 @@ public interface SimilarityParamsI * @return */ boolean denominateByShortestLength(); + + String getSecondaryStructureSource(); + + void setSecondaryStructureSource(String secondaryStructureSource); } diff --git a/src/jalview/appletgui/AlignFrame.java b/src/jalview/appletgui/AlignFrame.java index f502952..55a5364 100644 --- a/src/jalview/appletgui/AlignFrame.java +++ b/src/jalview/appletgui/AlignFrame.java @@ -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); 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/commands/JustifyLeftOrRightCommand.java b/src/jalview/commands/JustifyLeftOrRightCommand.java new file mode 100644 index 0000000..2559662 --- /dev/null +++ b/src/jalview/commands/JustifyLeftOrRightCommand.java @@ -0,0 +1,98 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ +package jalview.commands; + +import java.util.List; + +import jalview.datamodel.AlignmentI; +import jalview.datamodel.ContiguousI; +import jalview.datamodel.SequenceI; +import jalview.util.Comparison; + +public class JustifyLeftOrRightCommand extends EditCommand +{ + /** + * Constructs and performs a trim alignment command + * + * @param description + * (to show in Undo/Redo menu) + * @param justifyLeft + * if true left justify, otherwise right + * @param seqs + * the sequences to justify + * @param start + * - leftmost column (base 0) to justify + * + * @param end + * - rightmost column (base 0) to justify + * + * @param al + */ + public JustifyLeftOrRightCommand(String description, boolean left, + List seqs, int from, int to, AlignmentI al) + { + this.description = description; + + for (SequenceI seq : seqs) + { + ContiguousI cont = seq.findPositions(from + 1, to + 1); + if (cont == null) + { + continue; + } + char[] range = seq.getSequence(from, to+1); + if (range==null || range.length==0) + { + continue; + } + int dsstart = seq.getDatasetSequence().getStart(); + char[] sqchar = seq.getDatasetSequence().getSequence( + -dsstart + cont.getBegin(), -dsstart + cont.getEnd() + 1); + // debug + // println sqchar; + char[] alseq = new char[to - from + 1]; + int sqstart = left ? 0 : alseq.length - sqchar.length; + int gaps = alseq.length - sqchar.length; + int gapstart = left ? sqchar.length : 0; + char gc = al.getGapCharacter(); + for (int gp = 0; gp < gaps; gp++) + { + alseq[gapstart + gp] = gc; + } + + for (int sqp = 0,insp=0; sqp seqs; + + from = 0; + to = al.getWidth() - 1; + seqs = al.getSequences(); + if (reg != null) + { + seqs = reg.getSequences(); + from = reg.getStartRes(); + to = reg.getEndRes(); + } + + if ((to - from) < 1) + { + return false; + } + + al.padGaps(); + jalview.commands.JustifyLeftOrRightCommand finalEdit = new jalview.commands.JustifyLeftOrRightCommand( + "Justify " + (left ? "Left" : "Right"), left, seqs, from, to, + al); + avcg.addHistoryItem(finalEdit); + viewport.notifyAlignmentChanged(); + return true; + } } diff --git a/src/jalview/datamodel/PDBEntry.java b/src/jalview/datamodel/PDBEntry.java index ae8523d..c3906f9 100755 --- a/src/jalview/datamodel/PDBEntry.java +++ b/src/jalview/datamodel/PDBEntry.java @@ -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); + } + } 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/SecondaryStructureCount.java b/src/jalview/datamodel/SecondaryStructureCount.java new file mode 100644 index 0000000..52e9746 --- /dev/null +++ b/src/jalview/datamodel/SecondaryStructureCount.java @@ -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 . + * 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(); + } +} 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..1aca4d4 100644 --- a/src/jalview/gui/AlignFrame.java +++ b/src/jalview/gui/AlignFrame.java @@ -61,7 +61,9 @@ import java.util.List; import java.util.Locale; import java.util.Vector; +import javax.swing.AbstractButton; import javax.swing.ButtonGroup; +import javax.swing.ButtonModel; import javax.swing.JCheckBoxMenuItem; import javax.swing.JComponent; import javax.swing.JEditorPane; @@ -71,6 +73,7 @@ import javax.swing.JLayeredPane; import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.JPanel; +import javax.swing.JRadioButtonMenuItem; import javax.swing.JScrollPane; import javax.swing.SwingUtilities; @@ -82,6 +85,7 @@ import jalview.analysis.Dna; import jalview.analysis.GeneticCodeI; import jalview.analysis.ParseProperties; import jalview.analysis.SequenceIdMatcher; +import jalview.api.AlignCalcWorkerI; import jalview.api.AlignExportSettingsI; import jalview.api.AlignViewControllerGuiI; import jalview.api.AlignViewControllerI; @@ -150,6 +154,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; @@ -157,6 +162,7 @@ import jalview.util.Platform; import jalview.util.imagemaker.BitmapImageSizing; import jalview.viewmodel.AlignmentViewport; import jalview.viewmodel.ViewportRanges; +import jalview.workers.SecondaryStructureConsensusThread; import jalview.ws.DBRefFetcher; import jalview.ws.DBRefFetcher.FetchFinishedListenerI; import jalview.ws.jws1.Discoverer; @@ -802,6 +808,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, } ap.av.updateConservation(ap); ap.av.updateConsensus(ap); + ap.av.updateSecondaryStructureConsensus(ap); ap.av.updateStrucConsensus(ap); } } @@ -5638,9 +5645,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, @Override protected void justifyLeftMenuItem_actionPerformed(ActionEvent e) { - AlignmentI al = viewport.getAlignment(); - al.justify(false); - viewport.firePropertyChange("alignment", null, al); + avc.justify_Region(true); } /** @@ -5649,9 +5654,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, @Override protected void justifyRightMenuItem_actionPerformed(ActionEvent e) { - AlignmentI al = viewport.getAlignment(); - al.justify(true); - viewport.firePropertyChange("alignment", null, al); + avc.justify_Region(false); } @Override @@ -5674,6 +5677,133 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, viewport.setShowUnconserved(showNonconservedMenuItem.getState()); alignPanel.paintAlignment(false, false); } + + @Override + protected void updateShowSSRadioButtons(JMenu showSS, ButtonGroup ssButtonGroup){ + + List ssSources = new ArrayList(); + AlignmentAnnotation[] anns = alignPanel.getAlignment() + .getAlignmentAnnotation(); + + ssSources = AlignmentUtils.extractSSSourceInAlignmentAnnotation(anns); + + // Get the currently selected radio button + String selectedButtonModelName = getSelectedRadioButtonDisplayString(ssButtonGroup); + boolean selectedButtonModel = false; + + // Clear existing radio buttons + showSS.removeAll(); + JRadioButtonMenuItem radioButtonAllSS = new JRadioButtonMenuItem("All"); + radioButtonAllSS.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + showSS_actionPerformed("All"); + } + }); + ssButtonGroup.add(radioButtonAllSS); + showSS.add(radioButtonAllSS); + + JRadioButtonMenuItem radioButtonNoneSS = new JRadioButtonMenuItem("None"); + radioButtonNoneSS.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + showSS_actionPerformed("None"); + } + }); + ssButtonGroup.add(radioButtonNoneSS); + showSS.add(radioButtonNoneSS); + showSS.addSeparator(); + + for(String ssSource : ssSources) { + + JRadioButtonMenuItem radioButton = new JRadioButtonMenuItem(ssSource); + radioButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + showSS_actionPerformed(ssSource); + } + }); + ssButtonGroup.add(radioButton); + showSS.add(radioButton); + + // Check if this radio button's name matches the selected radio button's name + if (ssSource.equals(selectedButtonModelName)) { + radioButton.setSelected(true); // Select this radio button + selectedButtonModel = true; + } + + } + + if (selectedButtonModelName == "None") { + // If no radio button was previously selected, select "All" + ssButtonGroup.setSelected(radioButtonNoneSS.getModel(), true); + selectedButtonModel = true; + } + if (!selectedButtonModel) { + // If no radio button was previously selected, select "All" + ssButtonGroup.setSelected(radioButtonAllSS.getModel(), true); + } + } + + @Override + protected void showSS_actionPerformed(String ssSourceSelection){ + + + AlignmentAnnotation[] annotations = alignPanel.getAlignment() + .getAlignmentAnnotation(); + + for (AlignmentAnnotation aa: annotations) { + + for (String label : Constants.SECONDARY_STRUCTURE_LABELS.keySet()) { + + if (label.equals(aa.label)) { + + aa.visible = false; + + if(ssSourceSelection == "All") { + aa.visible = true; + } + + else { + String ssSource = AlignmentUtils.extractSSSourceFromAnnotationDescription(aa); + if(ssSource.equals(ssSourceSelection)) { + aa.visible = true; + } + + + } + } + } + + } + + + List workers = viewport.getCalcManager() + .getRegisteredWorkersOfClass(SecondaryStructureConsensusThread.class); + if (!workers.isEmpty()) { + + viewport.getCalcManager().startWorker(workers.remove(0)); + + } + + PaintRefresher.Refresh(this, viewport.getSequenceSetId()); + alignPanel.updateAnnotation(); + alignPanel.paintAlignment(true, true); + + + + } + + protected String getSelectedRadioButtonDisplayString(ButtonGroup ssButtonGroup) { + Enumeration buttons = ssButtonGroup.getElements(); + while (buttons.hasMoreElements()) { + AbstractButton button = buttons.nextElement(); + if (button.isSelected()) { + return button.getText(); + } + } + return null; // No radio button is selected +} /* * (non-Javadoc) @@ -5717,6 +5847,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..4e1fcab 100644 --- a/src/jalview/gui/AlignViewport.java +++ b/src/jalview/gui/AlignViewport.java @@ -281,11 +281,15 @@ 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); showConsensus = Cache.getDefault("SHOW_IDENTITY", true); + + showSSConsensus = Cache.getDefault("SHOW_SS_CONSENSUS", false); showOccupancy = Cache.getDefault(Preferences.SHOW_OCCUPANCY, true); } @@ -314,6 +318,7 @@ public class AlignViewport extends AlignmentViewport if (residueShading != null) { residueShading.setConsensus(hconsensus); + residueShading.setSsConsensus(hSSConsensus); } setColourAppliesToAllGroups(true); } diff --git a/src/jalview/gui/AssociatePdbFileWithSeq.java b/src/jalview/gui/AssociatePdbFileWithSeq.java index c9951f5..b2689be 100644 --- a/src/jalview/gui/AssociatePdbFileWithSeq.java +++ b/src/jalview/gui/AssociatePdbFileWithSeq.java @@ -49,7 +49,7 @@ public class AssociatePdbFileWithSeq StructureSelectionManagerProvider ssmp) { return associatePdbWithSeq(choice, file, sequence, prompt, ssmp, - TFType.DEFAULT, null, true); + TFType.DEFAULT, null, false); } public PDBEntry associatePdbWithSeq(String choice, DataSourceType file, @@ -97,9 +97,9 @@ public class AssociatePdbFileWithSeq { entry.setFile(choice); sequence.getDatasetSequence().addPDBId(entry); + entry.setStructureFile(pdbfile); StructureSelectionManager.getStructureSelectionManager(ssmp) .registerPDBEntry(entry); - entry.setStructureFile(pdbfile); } if (tft != null) entry.setProperty("TFType", tft.name()); diff --git a/src/jalview/gui/CalculationChooser.java b/src/jalview/gui/CalculationChooser.java index af41e58..4e22a74 100644 --- a/src/jalview/gui/CalculationChooser.java +++ b/src/jalview/gui/CalculationChooser.java @@ -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 modelNames; + + JComboBox 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 = 365; int height = includeParams ? 420 : 240; setMinimumSize(new Dimension(325, height - 10)); @@ -418,7 +460,38 @@ public class CalculationChooser extends JPanel return scoreModelsCombo; } + + + private JComboBox buildSSSourcesOptionsList() + { + final JComboBox comboBox = new JComboBox<>(); + Object curSel = comboBox.getSelectedItem(); + DefaultComboBoxModel sourcesModel = new DefaultComboBoxModel<>(); + List 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 comboBox, List toolTips) { @@ -430,8 +503,12 @@ public class CalculationChooser extends JPanel * select the score models applicable to the alignment type */ boolean nucleotide = af.getViewport().getAlignment().isNucleotide(); - List models = getApplicableScoreModels(nucleotide, - pca.isSelected()); + AlignmentAnnotation[] alignmentAnnotations = af.getViewport().getAlignment().getAlignmentAnnotation(); + + boolean ssPresent = AlignmentUtils.isSecondaryStructurePresent(alignmentAnnotations); + + List models = getApplicableScoreModels(nucleotide, pca.isSelected(), + ssPresent); /* * now we can actually add entries to the combobox, @@ -466,6 +543,7 @@ public class CalculationChooser extends JPanel } // finally, update the model comboBox.setModel(model); + } /** @@ -483,14 +561,15 @@ public class CalculationChooser extends JPanel * @return */ protected static List getApplicableScoreModels( - boolean nucleotide, boolean forPca) + boolean nucleotide, boolean forPca, boolean ssPresent) { List 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 +584,22 @@ public class CalculationChooser extends JPanel { filtered.add(scoreModels.getBlosum62()); } - + return filtered; } + + protected List getApplicableSecondaryStructureSources() + { + AlignmentAnnotation[] annotations = af.getViewport().getAlignment().getAlignmentAnnotation(); + + List ssSources = AlignmentUtils.getSecondaryStructureSources(annotations); + //List ssSources = AlignmentUtils.extractSSSourceInAlignmentAnnotation(annotations); + + + return ssSources; + } + /** * Open and calculate the selected tree or PCA on 'OK' */ @@ -516,8 +607,16 @@ public class CalculationChooser extends JPanel { boolean doPCA = pca.isSelected(); String modelName = modelNames.getSelectedItem().toString(); - SimilarityParamsI params = getSimilarityParameters(doPCA); - + String ssSource = ""; + Object selectedItem = ssSourceDropdown.getSelectedItem(); + if (selectedItem != null) { + ssSource = selectedItem.toString(); + } + SimilarityParams params = getSimilarityParameters(doPCA); + if(ssSource.length()>0) + { + params.setSecondaryStructureSource(ssSource); + } if (doPCA) { openPcaPanel(modelName, params); @@ -618,7 +717,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( diff --git a/src/jalview/gui/PopupMenu.java b/src/jalview/gui/PopupMenu.java index 8875288..09cd9a0 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 @@ -1386,6 +1388,29 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener cut_actionPerformed(); } }); + JMenuItem justifyLeftMenuItem = new JMenuItem( + MessageManager.getString("action.left_justify")); + justifyLeftMenuItem.addActionListener(new ActionListener() + { + + @Override + public void actionPerformed(ActionEvent e) + { + ap.alignFrame.avc.justify_Region(true); + } + }); + JMenuItem justifyRightMenuItem = new JMenuItem( + MessageManager.getString("action.right_justify")); + justifyRightMenuItem.addActionListener(new ActionListener() + { + + @Override + public void actionPerformed(ActionEvent e) + { + ap.alignFrame.avc.justify_Region(false); + } + }); + upperCase.setText(MessageManager.getString("label.to_upper_case")); upperCase.addActionListener(new ActionListener() { @@ -1534,6 +1559,8 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener editMenu.add(copy); editMenu.add(cut); + editMenu.add(justifyLeftMenuItem); + editMenu.add(justifyRightMenuItem); editMenu.add(editSequence); editMenu.add(upperCase); editMenu.add(lowerCase); @@ -1737,8 +1764,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/gui/Preferences.java b/src/jalview/gui/Preferences.java index c4f32c3..4fb013f 100755 --- a/src/jalview/gui/Preferences.java +++ b/src/jalview/gui/Preferences.java @@ -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()); diff --git a/src/jalview/gui/StructureChooser.java b/src/jalview/gui/StructureChooser.java index 6132908..0e834d4 100644 --- a/src/jalview/gui/StructureChooser.java +++ b/src/jalview/gui/StructureChooser.java @@ -1344,10 +1344,10 @@ public class StructureChooser extends GStructureChooser selectedSequence = userSelectedSeq; } String pdbFilename = selectedPdbFileName; - + // TODO - tidy up this ugly hack so we call launchStructureViewer too StructureChooser.openStructureFileForSequence(ssm, sc, ap, - selectedSequence, true, pdbFilename, tft, paeFilename, - true); + selectedSequence, true, pdbFilename, tft, paeFilename,false, + true,false,getTargetedStructureViewer(ssm).getViewerType()); } SwingUtilities.invokeLater(new Runnable() { @@ -1806,6 +1806,22 @@ public class StructureChooser extends GStructureChooser paeFilename, false, true, doXferSettings, null); } + /** + * + * @param ssm + * @param sc + * @param ap + * @param seq + * @param prompt + * @param sFilename + * @param tft + * @param paeFilename + * @param forceHeadless + * @param showRefAnnotations + * @param doXferSettings + * @param viewerType - when not null means the viewer will be opened, providing forceHeadless/headless is not true + * @return + */ public static StructureViewer openStructureFileForSequence( StructureSelectionManager ssm, StructureChooser sc, AlignmentPanel ap, SequenceI seq, boolean prompt, @@ -1828,7 +1844,7 @@ public class StructureChooser extends GStructureChooser ssm = ap.getStructureSelectionManager(); StructureSelectionManager.doConfigureStructurePrefs(ssm); } - + PDBEntry fileEntry = new AssociatePdbFileWithSeq().associatePdbWithSeq( sFilename, DataSourceType.FILE, seq, prompt, Desktop.instance, tft, paeFilename, doXferSettings); @@ -1840,10 +1856,13 @@ public class StructureChooser extends GStructureChooser sv = sc.launchStructureViewer(ssm, new PDBEntry[] { fileEntry }, ap, new SequenceI[] { seq }, viewerType); + // foo + sv.getJalviewStructureDisplay().raiseViewer(); } sc.mainFrame.dispose(); + // TODO should honor preferences - only show reference annotation that is requested - JAL-4415 JAL-3124 if (showRefAnnotations) { showReferenceAnnotationsForSequence(ap.alignFrame, seq); diff --git a/src/jalview/gui/structurechooser/ThreeDBStructureChooserQuerySource.java b/src/jalview/gui/structurechooser/ThreeDBStructureChooserQuerySource.java index 76ef85f..333f995 100644 --- a/src/jalview/gui/structurechooser/ThreeDBStructureChooserQuerySource.java +++ b/src/jalview/gui/structurechooser/ThreeDBStructureChooserQuerySource.java @@ -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; diff --git a/src/jalview/jbgui/GAlignFrame.java b/src/jalview/jbgui/GAlignFrame.java index 7daeb37..2a79908 100755 --- a/src/jalview/jbgui/GAlignFrame.java +++ b/src/jalview/jbgui/GAlignFrame.java @@ -23,6 +23,7 @@ package jalview.jbgui; import java.awt.BorderLayout; import java.awt.Color; import java.awt.GridLayout; +import java.awt.Menu; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusAdapter; @@ -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; @@ -54,10 +56,13 @@ import jalview.analysis.GeneticCodeI; import jalview.analysis.GeneticCodes; import jalview.api.SplitContainerI; import jalview.bin.Cache; +import jalview.bin.Console; import jalview.gui.JvSwingUtils; import jalview.gui.Preferences; import jalview.io.FileFormats; +import jalview.log.JLoggerLog4j; import jalview.schemes.ResidueColourScheme; +import jalview.util.Log4j; import jalview.util.MessageManager; import jalview.util.Platform; @@ -193,12 +198,18 @@ public class GAlignFrame extends JInternalFrame protected JMenuItem gatherViews = new JMenuItem(); protected JMenuItem expandViews = new JMenuItem(); + + protected JCheckBoxMenuItem threeDStructure = new JCheckBoxMenuItem(); + + protected JCheckBoxMenuItem jPred = new JCheckBoxMenuItem(); protected JCheckBoxMenuItem showGroupConsensus = new JCheckBoxMenuItem(); 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(); @@ -441,7 +452,9 @@ public class GAlignFrame extends JInternalFrame addMenuActionAndAccelerator(keyStroke, removeAllGapsMenuItem, al); JMenuItem justifyLeftMenuItem = new JMenuItem( - MessageManager.getString("action.left_justify_alignment")); + MessageManager.getString("action.left_justify")); + justifyLeftMenuItem.setToolTipText( + MessageManager.getString("tooltip.left_justify")); justifyLeftMenuItem.addActionListener(new ActionListener() { @Override @@ -451,7 +464,10 @@ public class GAlignFrame extends JInternalFrame } }); JMenuItem justifyRightMenuItem = new JMenuItem( - MessageManager.getString("action.right_justify_alignment")); + MessageManager.getString("action.right_justify")); + justifyRightMenuItem.setToolTipText( + MessageManager.getString("action.left_justify")); + justifyRightMenuItem.addActionListener(new ActionListener() { @Override @@ -873,6 +889,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")); @@ -1779,12 +1807,61 @@ public class GAlignFrame extends JInternalFrame }; addMenuActionAndAccelerator(keyStroke, copyHighlighted, al); copyHighlighted.addActionListener(al); + + + + ButtonGroup ssButtonGroup = new ButtonGroup(); + final JRadioButtonMenuItem showAll = new JRadioButtonMenuItem( + MessageManager.getString("label.show_first")); + final JRadioButtonMenuItem showjPred = new JRadioButtonMenuItem( + MessageManager.getString("label.show_last")); + final JRadioButtonMenuItem show3DSS = new JRadioButtonMenuItem( + MessageManager.getString("label.show_last")); + ssButtonGroup.add(showAll); + ssButtonGroup.add(showjPred); + ssButtonGroup.add(show3DSS); + final boolean autoFirst1 = Cache + .getDefault(Preferences.SHOW_AUTOCALC_ABOVE, false); + showAll.setSelected(autoFirst1); + setShowAutoCalculatedAbove(autoFirst); + showAutoFirst.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + setShowAutoCalculatedAbove(showAutoFirst.isSelected()); + sortAnnotations_actionPerformed(); + } + }); + showAutoLast.setSelected(!showAutoFirst.isSelected()); + showAutoLast.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + setShowAutoCalculatedAbove(!showAutoLast.isSelected()); + sortAnnotations_actionPerformed(); + } + }); 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")); + + showSS.addMouseListener(new MouseAdapter() { + + @Override + public void mouseEntered(MouseEvent e) { + + updateShowSSRadioButtons(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")); @@ -1840,11 +1917,11 @@ public class GAlignFrame extends JInternalFrame editMenu.add(removeAllGapsMenuItem); editMenu.add(removeRedundancyMenuItem); editMenu.addSeparator(); - // dont add these yet in the CVS build - they cannot be undone! - // Excluded from Jalview 2.5 release - undo needs to be implemented. - // editMenu.add(justifyLeftMenuItem); - // editMenu.add(justifyRightMenuItem); - // editMenu.addSeparator(); + + editMenu.add(justifyLeftMenuItem); + editMenu.add(justifyRightMenuItem); + + editMenu.addSeparator(); editMenu.add(padGapsMenuitem); showMenu.add(showAllColumns); @@ -1885,11 +1962,14 @@ 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(); autoAnnMenu.add(applyAutoAnnotationSettings); autoAnnMenu.add(showConsensusHistogram); + autoAnnMenu.add(showSSConsensusHistogram); autoAnnMenu.add(showSequenceLogo); autoAnnMenu.add(normaliseSequenceLogo); autoAnnMenu.addSeparator(); @@ -2224,6 +2304,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) { @@ -2785,4 +2871,22 @@ public class GAlignFrame extends JInternalFrame protected void showComplement_actionPerformed(boolean complement) { } + + protected List updateShowSSRadioButtons() + { + // TODO Auto-generated method stub + return null; + } + + protected void updateShowSSRadioButtons(JMenu showSS, + ButtonGroup ssButtonGroup) + { + // TODO Auto-generated method stub + } + + protected void showSS_actionPerformed(String ssSourceSelection) + { + // TODO Auto-generated method stub + + } } diff --git a/src/jalview/jbgui/GPreferences.java b/src/jalview/jbgui/GPreferences.java index 03538ef..e574327 100755 --- a/src/jalview/jbgui/GPreferences.java +++ b/src/jalview/jbgui/GPreferences.java @@ -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); 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..4e9f669 100644 --- a/src/jalview/renderer/AnnotationRenderer.java +++ b/src/jalview/renderer/AnnotationRenderer.java @@ -38,6 +38,7 @@ 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 +56,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 +90,8 @@ public class AnnotationRenderer private HiddenColumns hiddenColumns; private ProfilesI hconsensus; + + private ProfilesI hSSconsensus; private Hashtable[] complementConsensus; @@ -164,6 +168,7 @@ public class AnnotationRenderer { hiddenColumns = null; hconsensus = null; + hSSconsensus = null; complementConsensus = null; hStrucConsensus = null; fadedImage = null; @@ -377,6 +382,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 +431,15 @@ public class AnnotationRenderer } } } + + else if(aa.autoCalculated && aa.label.startsWith(MessageManager.getString("label.ssconsensus_label"))) + { + return AAFrequency.extractProfile( + hSSconsensus.get(column), + av_ignoreGapsConsensus); + + } + else { if (aa.autoCalculated && aa.label.startsWith("StrucConsensus")) @@ -514,6 +529,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 +557,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 +1536,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 +1619,14 @@ public class AnnotationRenderer colour = profcolour.findColour(codonTranslation.charAt(0), column, null); } + if(_aa.label == 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); 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/schemes/ResidueProperties.java b/src/jalview/schemes/ResidueProperties.java index e297ff6..2fcb95f 100755 --- a/src/jalview/schemes/ResidueProperties.java +++ b/src/jalview/schemes/ResidueProperties.java @@ -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 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 index 0000000..bced9c8 --- /dev/null +++ b/src/jalview/util/Constants.java @@ -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 . + * 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 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 + } +} diff --git a/src/jalview/viewmodel/AlignmentViewport.java b/src/jalview/viewmodel/AlignmentViewport.java index 3e1bc63..712c63b 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(), @@ -1319,7 +1403,9 @@ public abstract class AlignmentViewport protected boolean showQuality = true; - protected boolean showConsensus = true; + protected boolean showConsensus = true; + + protected boolean showSSConsensus = false; protected boolean showOccupancy = true; @@ -1377,6 +1463,12 @@ public abstract class AlignmentViewport changeSupport.firePropertyChange(prop, oldvalue, newvalue); } + @Override + public void notifyAlignmentChanged() + { + firePropertyChange("alignment", null, alignment); + } + // common hide/show column stuff public void hideSelectedColumns() @@ -1872,6 +1964,7 @@ public abstract class AlignmentViewport if (autoCalculateConsensus) { updateConsensus(ap); + updateSecondaryStructureConsensus(ap); } if (hconsensus != null && autoCalculateConsensus) { @@ -1955,13 +2048,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(MessageManager.getString("label.ssconsensus_label"), + 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 +2111,17 @@ public abstract class AlignmentViewport alignment.addAnnotation(aa); } } + + private void initSSConsensus(AlignmentAnnotation aa) + { + 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 @@ -2160,7 +2270,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 +2309,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 +2323,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 +3113,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 { /* diff --git a/src/jalview/workers/SecondaryStructureConsensusThread.java b/src/jalview/workers/SecondaryStructureConsensusThread.java new file mode 100644 index 0000000..04cefe5 --- /dev/null +++ b/src/jalview/workers/SecondaryStructureConsensusThread.java @@ -0,0 +1,258 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ +package jalview.workers; + +import jalview.analysis.AAFrequency; +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; + +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 + { + 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; + } + + 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) + { + AlignmentAnnotation ssConsensus = getSSConsensusAnnotation(); + 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(); + ProfilesI hSSConsensus = AAFrequency.calculateSS(aseqs, width, 0, width, + true); + + alignViewport.setSequenceSSConsensusHash(hSSConsensus); + setColourSchemeConsensus(hSSConsensus); + } + + /** + * @return + */ + protected SequenceI[] getSequences() + { + return alignViewport.getAlignment().getSequencesArray(); + } + + /** + * @param hconsensus + */ + protected void setColourSchemeConsensus(ProfilesI hSSconsensus) + { + ResidueShaderI cs = alignViewport.getResidueShading(); + if (cs != null) + { + cs.setSsConsensus(hSSconsensus); + } + } + + /** + * Get the Consensus annotation for the alignment + * + * @return + */ + protected AlignmentAnnotation getSSConsensusAnnotation() + { + return alignViewport.getAlignmentSecondaryStructureConsensusAnnotation(); + } + + /** + * 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) + { + AlignmentAnnotation ssConsensus = getSSConsensusAnnotation(); + ProfilesI hSSConsensus = (ProfilesI) getViewportSSConsensus(); + if (immediate || !calcMan.isWorking(this) && ssConsensus != null + && hSSConsensus != null) + { + deriveSSConsensus(ssConsensus, hSSConsensus); + AlignmentAnnotation gap = getGapAnnotation(); + if (gap != null) + { + deriveGap(gap, hSSConsensus); + } + } + } + + /** + * 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 ssConsensusAnnotation, + ProfilesI hSSConsensus) + { + + long nseq = getSequences().length; + AAFrequency.completeSSConsensus(ssConsensusAnnotation, 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 Object getViewportSSConsensus() + { + // TODO convert ComplementConsensusThread to use Profile + return alignViewport.getSequenceSSConsensusHash(); + } +} diff --git a/test/jalview/analysis/AlignmentUtilsTests.java b/test/jalview/analysis/AlignmentUtilsTests.java index f017662..d5639fa 100644 --- a/test/jalview/analysis/AlignmentUtilsTests.java +++ b/test/jalview/analysis/AlignmentUtilsTests.java @@ -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 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 ssSources1 = new ArrayList<>(Arrays.asList("3D Structures")); + List ssSources2 = new ArrayList<>(Arrays.asList("JPred")); + List ssSources3 = new ArrayList<>(Arrays.asList("3D Structures", "JPred")); + List 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> annotations, boolean expectedPresence) { + boolean actualPresence = AlignmentUtils.isSSAnnotationPresent(annotations); + Assert.assertEquals(actualPresence, expectedPresence); + } + + @DataProvider(name = "SSAnnotationPresence") + public static Object[][] provideSSAnnotationPresence() { + Map> annotations1 = new HashMap<>(); + SequenceI seq1 = new Sequence("Seq1", "ASD---ASD---ASD", 37, 45); + List annotationsList1 = new ArrayList<>(); + annotationsList1.add(new AlignmentAnnotation("Secondary Structure", "Secondary Structure", new Annotation[]{})); + annotations1.put(seq1, annotationsList1); // Annotation present secondary structure for seq1 + + Map> annotations2 = new HashMap<>(); + SequenceI seq2 = new Sequence("Seq2", "ASD---ASD------", 37, 42); + List 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> annotations3 = new HashMap<>(); + // Empty annotation map + + Map> annotations4 = new HashMap<>(); + SequenceI seq4 = new Sequence("Seq4", "ASD---ASD---AS-", 37, 44); + List 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 actualSSSource = AlignmentUtils.extractSSSourceInAlignmentAnnotation(annotations); + Assert.assertEquals(actualSSSource, expectedSSSource); + } + + @DataProvider(name = "SSSourceFromAnnotationDescription") + public static Object[][] provideSSSourceFromAnnotationDescription() { + Map> annotations1 = new HashMap<>(); + SequenceI seq1 = new Sequence("Seq1", "ASD---ASD---ASD", 37, 45); + List annotationsList1 = new ArrayList<>(); + annotationsList1.add(new AlignmentAnnotation("jnetpred", "JPred Output", new Annotation[]{})); + annotations1.put(seq1, annotationsList1); // Annotation present from JPred for seq1 + + Map> annotations2 = new HashMap<>(); + SequenceI seq2 = new Sequence("Seq2", "ASD---ASD------", 37, 42); + List 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> annotations3 = new HashMap<>(); + // Empty annotation map + + Map> annotations4 = new HashMap<>(); + SequenceI seq4 = new Sequence("Seq4", "ASD---ASD---AS-", 37, 44); + List 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> annotations5 = new HashMap<>(); + SequenceI seq5 = new Sequence("Seq5", "ASD---ASD---AS-", 37, 44); + List 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"} + }; + } + } diff --git a/test/jalview/analysis/scoremodels/ScoreModelsTest.java b/test/jalview/analysis/scoremodels/ScoreModelsTest.java index 0a3af64..d0a8047 100644 --- a/test/jalview/analysis/scoremodels/ScoreModelsTest.java +++ b/test/jalview/analysis/scoremodels/ScoreModelsTest.java @@ -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 index 0000000..f32be37 --- /dev/null +++ b/test/jalview/analysis/scoremodels/SecondaryStructureDistanceModelTest.java @@ -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 . + * 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); + } + + + + /** + *
    +   * Set up
    +   *   column      1 2 3 4 
    +   *        seq s1 F R K S
    +   *        
    +   *        seq s2 F S J L
    +   * 
    + * + * @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; + } + + + /** + *
    +   * 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
    +   * 
    + * + * @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); + } + + +} diff --git a/test/jalview/commands/EditCommandTest.java b/test/jalview/commands/EditCommandTest.java index 8e04cdd..1255c58 100644 --- a/test/jalview/commands/EditCommandTest.java +++ b/test/jalview/commands/EditCommandTest.java @@ -36,10 +36,13 @@ import jalview.datamodel.SequenceI; import jalview.datamodel.features.SequenceFeatures; import jalview.gui.JvOptionPane; +import java.nio.charset.Charset; +import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; +import java.util.spi.LocaleServiceProvider; import org.testng.Assert; import org.testng.annotations.BeforeClass; @@ -1179,4 +1182,63 @@ public class EditCommandTest assertEquals(10, sf.getBegin()); assertEquals(11, sf.getEnd()); } + private SequenceI mkDs(SequenceI as) + { + SequenceI ds = as.createDatasetSequence(); + ds.setSequence(ds.getSequenceAsString().toUpperCase(Locale.ROOT)); + return ds; + } + /** + * Test that mimics 'remove all gapped columns' action. This generates a + * series Delete Gap edits that each act on all sequences that share a gapped + * column region. + */ + @Test(groups = { "Functional" }) + public void testLeftRight_Justify_and_preserves_gaps() + { + EditCommand command = new EditCommand(); + String original1 = "--ABc--DEF"; + String original2 = "-G-Hi--J"; + String original3 = "-M-No--PQ"; + + /* + * Set up the sequence array for operations + */ + SequenceI seq1 = new Sequence("sq1", original1); + SequenceI ds1 = mkDs(seq1); + /* + * and check we are preserving data - if the calls below fail, something has broken the Jalview dataset derivation process + */ + assertEquals("ABCDEF", seq1.getDatasetSequence().getSequenceAsString()); + assertEquals(original1,seq1.getSequenceAsString()); + SequenceI seq2 = new Sequence("sq2",original2); + SequenceI ds2 = mkDs(seq2); + SequenceI seq3 = new Sequence("sq3", original3); + SequenceI ds3 = mkDs(seq3); + List sqs = Arrays.asList( seq1, seq2, seq3 ); + Alignment al = new Alignment(sqs.toArray(new SequenceI[0])); + EditCommand lefj = new JustifyLeftOrRightCommand("Left J", true, sqs, 1, 7, al); + String exp = "-ABcD---EF"; + // check without case conservation + assertEquals(exp.toUpperCase(Locale.ROOT),seq1.getSequenceAsString().toUpperCase(Locale.ROOT)); + // check case + assertEquals(exp,seq1.getSequenceAsString()); + // and other seqs + assertEquals("-GHiJ---",seq2.getSequenceAsString()); + assertEquals("-MNoP---Q",seq3.getSequenceAsString()); + lefj.undoCommand(new AlignmentI[] { al}); + assertEquals(original3,seq3.getSequenceAsString()); + assertEquals(original1,seq1.getSequenceAsString()); + assertEquals(original2,seq2.getSequenceAsString()); + + EditCommand righj = new JustifyLeftOrRightCommand("Right J", false, sqs, 2, 7, al); + assertEquals("----ABcDEF",seq1.getSequenceAsString()); + assertEquals("-G---HiJ",seq2.getSequenceAsString()); + assertEquals("-M---NoPQ",seq3.getSequenceAsString()); + righj.undoCommand(new AlignmentI[] { al}); + assertEquals(original3,seq3.getSequenceAsString()); + assertEquals(original1,seq1.getSequenceAsString()); + assertEquals(original2,seq2.getSequenceAsString()); + + } } diff --git a/test/jalview/gui/AssociatePDBFileTest.java b/test/jalview/gui/AssociatePDBFileTest.java new file mode 100644 index 0000000..0e6791b --- /dev/null +++ b/test/jalview/gui/AssociatePDBFileTest.java @@ -0,0 +1,142 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ +package jalview.gui; + + +import java.awt.Color; +import java.io.File; +import java.util.Iterator; + +import org.junit.Assert; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import jalview.analysis.AlignmentAnnotationUtils; +import jalview.api.FeatureColourI; +import jalview.bin.Cache; +import jalview.bin.Jalview; +import jalview.datamodel.Alignment; +import jalview.datamodel.AlignmentI; +import jalview.datamodel.HiddenColumns; +import jalview.datamodel.PDBEntry; +import jalview.datamodel.Sequence; +import jalview.datamodel.SequenceFeature; +import jalview.datamodel.SequenceGroup; +import jalview.datamodel.SequenceI; +import jalview.io.DataSourceType; +import jalview.io.FileLoader; +import jalview.project.Jalview2xmlTests; +import jalview.renderer.ResidueShaderI; +import jalview.schemes.BuriedColourScheme; +import jalview.schemes.FeatureColour; +import jalview.schemes.HelixColourScheme; +import jalview.schemes.JalviewColourScheme; +import jalview.schemes.StrandColourScheme; +import jalview.schemes.TurnColourScheme; +import jalview.util.MessageManager; + +public class AssociatePDBFileTest +{ + AlignFrame af; + + @BeforeClass(alwaysRun = true) + public static void setUpBeforeClass() throws Exception + { + setUpJvOptionPane(); + /* + * use read-only test properties file + */ + Cache.loadProperties("test/jalview/io/testProps.jvprops"); + Jalview.main(new String[] { "--nonews" }); + } + + @AfterMethod(alwaysRun = true) + public void tearDown() + { + if (Desktop.instance != null) + Desktop.instance.closeAll_actionPerformed(null); + } + + /** + * configure (read-only) properties for test to ensure Consensus is computed + * for colour Above PID testing + */ + @BeforeMethod(alwaysRun = true) + public void setUp() + { + Cache.loadProperties("test/jalview/io/testProps.jvprops"); + Cache.applicationProperties.setProperty("SHOW_IDENTITY", + Boolean.TRUE.toString()); + af = new FileLoader().LoadFileWaitTillLoaded(">1GAQ|A/19-314\n" + + "ESKKQEEGVVTNLYKPKEPYVGRCLLNTKITGDDAPGETWHMVFSTEGKIPYREGQSIGVIADGVDKNGKPH\n" + + "KVRLYSIASSAIGDFGDSKTVSLCVKRLIYTNDAGEIVKGVCSNFLCDLQPGDNVQITGPVGKEMLMPKDPN\n" + + "ATIIMLATGTGIAPFRSFLWKMFFEKHDDYKFNGLGWLFLGVPTSSSLLYKEEFGKMKERAPENFRVDYAVS\n" + + "REQTNAAGERMYIQTRMAEYKEELWELLKKDNTYVYMCGLKGMEKGIDDIMVSLAEKDGIDWFDYKKQLKRG\n" + + "DQWNVEVY\n" + + ">1GAQ|B/1-98\n" + + "ATYNVKLITPEGEVELQVPDDVYILDQAEEDGIDLPYSCRAGSCSSCAGKVVSGSVDQSDQSYLDDGQIADG\n" + + "WVLTCHAYPTSDVVIETHKEEELTGA\n" + + ">1GAQ|C/19-314\n" + + "ESKKQEEGVVTNLYKPKEPYVGRCLLNTKITGDDAPGETWHMVFSTEGKIPYREGQSIGVIADGVDKNGKPH\n" + + "KVRLYSIASSAIGDFGDSKTVSLCVKRLIYTNDAGEIVKGVCSNFLCDLQPGDNVQITGPVGKEMLMPKDPN\n" + + "ATIIMLATGTGIAPFRSFLWKMFFEKHDDYKFNGLGWLFLGVPTSSSLLYKEEFGKMKERAPENFRVDYAVS\n" + + "REQTNAAGERMYIQTRMAEYKEELWELLKKDNTYVYMCGLKGMEKGIDDIMVSLAEKDGIDWFDYKKQLKRG\n" + + "DQWNVEVY\n" + , + DataSourceType.PASTE); + + /* + * wait for Consensus thread to complete + */ + do + { + try + { + Thread.sleep(50); + } catch (InterruptedException x) + { + } + } while (af.getViewport().getCalcManager().isWorking()); + } + + public static void setUpJvOptionPane() + { + JvOptionPane.setInteractiveMode(false); + JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION); + } + + @Test(groups = "Functional") + public void testAssociatePDBFile() + { + String assoc_file="examples/1gaq.txt"; + for (SequenceI toassoc:af.getViewport().getAlignment().getSequences()) + { + PDBEntry pe = new AssociatePdbFileWithSeq() + .associatePdbWithSeq(assoc_file, + DataSourceType.FILE, toassoc, false, + Desktop.instance); + Assert.assertNotNull(pe); + Assert.assertNotEquals(toassoc.getDatasetSequence().getAnnotation().length,0); + } + } +} diff --git a/test/jalview/gui/CalculationChooserTest.java b/test/jalview/gui/CalculationChooserTest.java index 9835189..23538c2 100644 --- a/test/jalview/gui/CalculationChooserTest.java +++ b/test/jalview/gui/CalculationChooserTest.java @@ -55,27 +55,29 @@ public class CalculationChooserTest * peptide models for PCA */ List 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"); diff --git a/test/jalview/renderer/ResidueShaderTest.java b/test/jalview/renderer/ResidueShaderTest.java index 78f00c8..081272a 100644 --- a/test/jalview/renderer/ResidueShaderTest.java +++ b/test/jalview/renderer/ResidueShaderTest.java @@ -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,42 @@ 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; + } }; }