From bba9e58f08fc55c0f789f8354d1444e5c5e1b0e6 Mon Sep 17 00:00:00 2001 From: gmungoc Date: Thu, 22 Jun 2017 16:45:53 +0100 Subject: [PATCH] JAL-2481 JAL-2593 JAL-2526 further optimisation of feature/index finding --- src/jalview/datamodel/Sequence.java | 96 +++++++++++++------- src/jalview/datamodel/SequenceI.java | 10 ++ .../renderer/seqfeatures/FeatureRenderer.java | 38 ++++---- test/jalview/datamodel/SequenceTest.java | 55 +++++++++++ 4 files changed, 150 insertions(+), 49 deletions(-) diff --git a/src/jalview/datamodel/Sequence.java b/src/jalview/datamodel/Sequence.java index 783b1bc..96a2fa1 100755 --- a/src/jalview/datamodel/Sequence.java +++ b/src/jalview/datamodel/Sequence.java @@ -984,6 +984,53 @@ public class Sequence extends ASequence implements SequenceI } /** + * {@inheritDoc} + */ + @Override + public Range findPositions(int fromColumn, int toColumn) + { + if (toColumn < fromColumn || fromColumn < 1) + { + return null; + } + + /* + * find the first non-gapped position, if any + */ + int firstPosition = 0; + int col = fromColumn - 1; + int length = sequence.length; + while (col < length && col < toColumn) + { + if (!Comparison.isGap(sequence[col])) + { + firstPosition = findPosition(col++); + break; + } + col++; + } + + if (firstPosition == 0) + { + return null; + } + + /* + * find the last non-gapped position + */ + int lastPosition = firstPosition; + while (col < length && col < toColumn) + { + if (!Comparison.isGap(sequence[col++])) + { + lastPosition++; + } + } + + return new Range(firstPosition, lastPosition); + } + + /** * Returns an int array where indices correspond to each residue in the * sequence and the element value gives its position in the alignment * @@ -1767,28 +1814,16 @@ public class Sequence extends ASequence implements SequenceI String... types) { int startPos = findPosition(fromColumn - 1); // convert base 1 to base 0 - int endPos = findPosition(toColumn - 1); - // to trace / debug behaviour: - // System.out - // .println(String - // .format("%s.findFeatures columns [%d-%d] positions [%d-%d] leaves cursor %s", - // getName(), fromColumn, toColumn, startPos, - // endPos, cursor)); - List result = new ArrayList<>(); - if (datasetSequence != null) - { - result = datasetSequence.getFeatures().findFeatures(startPos, endPos, - types); - } - else - { - result = sequenceFeatureStore.findFeatures(startPos, endPos, types); - } + int endPos = fromColumn == toColumn ? startPos + : findPosition(toColumn - 1); + + List result = getFeatures().findFeatures(startPos, + endPos, types); /* * if the start or end column is gapped, startPos or endPos may be to the * left or right, and we may have included adjacent or enclosing features; - * remove any that are not enclosing, non-contact features + * remove any that are not enclosing features */ if (endPos > this.end || Comparison.isGap(sequence[fromColumn - 1]) || Comparison.isGap(sequence[toColumn - 1])) @@ -1797,22 +1832,21 @@ public class Sequence extends ASequence implements SequenceI while (it.hasNext()) { SequenceFeature sf = it.next(); - int featureStartColumn = findIndex(sf.getBegin()); - int featureEndColumn = findIndex(sf.getEnd()); - boolean noOverlap = featureStartColumn > toColumn - || featureEndColumn < fromColumn; - - /* - * reject an 'enclosing' feature if it is actually a contact feature - */ - if (sf.isContactFeature() && featureStartColumn < fromColumn - && featureEndColumn > toColumn) + int sfBegin = sf.getBegin(); + int sfEnd = sf.getEnd(); + int featureStartColumn = findIndex(sfBegin); + if (featureStartColumn > toColumn) { - noOverlap = true; + it.remove(); } - if (noOverlap) + else if (featureStartColumn < fromColumn) { - it.remove(); + int featureEndColumn = sfEnd == sfBegin ? featureStartColumn + : findIndex(sfEnd); + if (featureEndColumn < fromColumn) + { + it.remove(); + } } } } diff --git a/src/jalview/datamodel/SequenceI.java b/src/jalview/datamodel/SequenceI.java index 6840df8..6e6d1aa 100755 --- a/src/jalview/datamodel/SequenceI.java +++ b/src/jalview/datamodel/SequenceI.java @@ -203,6 +203,16 @@ public interface SequenceI extends ASequenceI public int findPosition(int i); /** + * Returns the from-to sequence positions (start..) for the given column + * positions (1..), or null if no residues are included in the range + * + * @param fromColum + * @param toColumn + * @return + */ + public Range findPositions(int fromColum, int toColumn); + + /** * Returns an int array where indices correspond to each residue in the * sequence and the element value gives its position in the alignment * diff --git a/src/jalview/renderer/seqfeatures/FeatureRenderer.java b/src/jalview/renderer/seqfeatures/FeatureRenderer.java index 8f4f139..541288e 100644 --- a/src/jalview/renderer/seqfeatures/FeatureRenderer.java +++ b/src/jalview/renderer/seqfeatures/FeatureRenderer.java @@ -22,6 +22,7 @@ package jalview.renderer.seqfeatures; import jalview.api.AlignViewportI; import jalview.api.FeatureColourI; +import jalview.datamodel.Range; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; import jalview.util.Comparison; @@ -218,7 +219,11 @@ public class FeatureRenderer extends FeatureRendererModel if (Comparison.isGap(seq.getCharAt(column))) { - return Color.white; + /* + * returning null allows the colour scheme to provide gap colour + * - normally white, but can be customised + */ + return null; } Color renderedColour = null; @@ -264,7 +269,11 @@ public class FeatureRenderer extends FeatureRendererModel final SequenceI seq, int start, int end, int y1, boolean colourOnly) { - if (!seq.getFeatures().hasFeatures()) + /* + * if columns are all gapped, or sequence has no features, nothing to do + */ + Range visiblePositions = seq.findPositions(start+1, end+1); + if (visiblePositions == null || !seq.getFeatures().hasFeatures()) { return null; } @@ -292,29 +301,22 @@ public class FeatureRenderer extends FeatureRendererModel } FeatureColourI fc = getFeatureStyle(type); - List overlaps = seq.findFeatures(start + 1, end + 1, - type); + List overlaps = seq.getFeatures().findFeatures( + visiblePositions.getBegin(), visiblePositions.getEnd(), type); filterFeaturesForDisplay(overlaps, fc); for (SequenceFeature sf : overlaps) { - /* - * a feature type may be flagged as shown but the group - * an instance of it belongs to may be hidden - */ - if (featureGroupNotShown(sf)) - { - continue; - } - Color featureColour = fc.getColor(sf); - boolean isContactFeature = sf.isContactFeature(); - - int featureStartCol = seq.findIndex(sf.begin); + int visibleStart = Math.max(sf.getBegin(), + visiblePositions.getBegin()); + int featureStartCol = seq.findIndex(visibleStart); + int visibleEnd = Math.min(sf.getEnd(), visiblePositions.getEnd()); int featureEndCol = sf.begin == sf.end ? featureStartCol : seq - .findIndex(sf.end); - if (isContactFeature) + .findIndex(visibleEnd); + + if (sf.isContactFeature()) { boolean drawn = renderFeature(g, seq, featureStartCol - 1, featureStartCol - 1, featureColour, start, end, y1, diff --git a/test/jalview/datamodel/SequenceTest.java b/test/jalview/datamodel/SequenceTest.java index e40d1a5..d1f4290 100644 --- a/test/jalview/datamodel/SequenceTest.java +++ b/test/jalview/datamodel/SequenceTest.java @@ -1606,4 +1606,59 @@ public class SequenceTest assertEquals(".K..BCD.EF..", sq.getSequenceAsString()); assertEquals(2, PA.getValue(sq, "changeCount")); } + + @Test(groups = { "Functional" }) + public void testFindPositions() + { + SequenceI sq = new Sequence("test/8-13", "-ABC---DE-F--"); + + /* + * invalid inputs + */ + assertNull(sq.findPositions(6, 5)); + assertNull(sq.findPositions(0, 5)); + assertNull(sq.findPositions(-1, 5)); + + /* + * all gapped ranges + */ + assertNull(sq.findPositions(1, 1)); // 1-based columns + assertNull(sq.findPositions(5, 5)); + assertNull(sq.findPositions(5, 6)); + assertNull(sq.findPositions(5, 7)); + + /* + * all ungapped ranges + */ + assertEquals(new Range(8, 8), sq.findPositions(2, 2)); // A + assertEquals(new Range(8, 9), sq.findPositions(2, 3)); // AB + assertEquals(new Range(8, 10), sq.findPositions(2, 4)); // ABC + assertEquals(new Range(9, 10), sq.findPositions(3, 4)); // BC + + /* + * gap to ungapped range + */ + assertEquals(new Range(8, 10), sq.findPositions(1, 4)); // ABC + assertEquals(new Range(11, 12), sq.findPositions(6, 9)); // DE + + /* + * ungapped to gapped range + */ + assertEquals(new Range(10, 10), sq.findPositions(4, 5)); // C + assertEquals(new Range(9, 13), sq.findPositions(3, 11)); // BCDEF + + /* + * ungapped to ungapped enclosing gaps + */ + assertEquals(new Range(10, 11), sq.findPositions(4, 8)); // CD + assertEquals(new Range(8, 13), sq.findPositions(2, 11)); // ABCDEF + + /* + * gapped to gapped enclosing ungapped + */ + assertEquals(new Range(8, 10), sq.findPositions(1, 5)); // ABC + assertEquals(new Range(11, 12), sq.findPositions(5, 10)); // DE + assertEquals(new Range(8, 13), sq.findPositions(1, 13)); // the lot + assertEquals(new Range(8, 13), sq.findPositions(1, 99)); + } } -- 1.7.10.2