From: gmungoc Date: Thu, 1 Jun 2017 13:35:10 +0000 (+0100) Subject: JAL-2754 Sequence.findFeatures(fromCol, toCol) X-Git-Tag: Release_2_10_3b1~218 X-Git-Url: http://source.jalview.org/gitweb/?p=jalview.git;a=commitdiff_plain;h=4c6cd08af580ff73cfce39bec34ae4658a4b4b34 JAL-2754 Sequence.findFeatures(fromCol, toCol) --- diff --git a/src/jalview/analysis/AlignmentSorter.java b/src/jalview/analysis/AlignmentSorter.java index bddf6e3..9943a22 100755 --- a/src/jalview/analysis/AlignmentSorter.java +++ b/src/jalview/analysis/AlignmentSorter.java @@ -755,13 +755,10 @@ public class AlignmentSorter * get sequence residues overlapping column region * and features for residue positions and specified types */ - // TODO new method findPositions(startCol, endCol)? JAL-2544 - int startResidue = seqs[i].findPosition(startCol); - int endResidue = seqs[i].findPosition(endCol); String[] types = featureTypes == null ? null : featureTypes .toArray(new String[featureTypes.size()]); - List sfs = seqs[i].findFeatures(startResidue, - endResidue, types); + List sfs = seqs[i].findFeatures(startCol + 1, + endCol + 1, types); seqScores[i] = 0; scores[i] = 0.0; @@ -772,18 +769,6 @@ public class AlignmentSorter SequenceFeature sf = it.next(); /* - * double-check feature overlaps columns (JAL-2544) - * (could avoid this with a findPositions(fromCol, toCol) method) - * findIndex returns base 1 column values, startCol/endCol are base 0 - */ - if (seqs[i].findIndex(sf.getBegin()) > endCol + 1 - || seqs[i].findIndex(sf.getEnd()) < startCol + 1) - { - it.remove(); - continue; - } - - /* * accept all features with null or empty group, otherwise * check group is one of the currently visible groups */ diff --git a/src/jalview/analysis/scoremodels/FeatureDistanceModel.java b/src/jalview/analysis/scoremodels/FeatureDistanceModel.java index 056ecdb..ddbaf73 100644 --- a/src/jalview/analysis/scoremodels/FeatureDistanceModel.java +++ b/src/jalview/analysis/scoremodels/FeatureDistanceModel.java @@ -177,10 +177,12 @@ public class FeatureDistanceModel extends DistanceScoreModel /** * Builds and returns a map containing a (possibly empty) list (one per * SeqCigar) of visible feature types at the given column position. The map - * has no entry for sequences which are gapped at the column position. + * does not include entries for features which straddle a gapped column + * positions. * * @param seqs * @param columnPosition + * (0..) * @return */ protected Map> findFeatureTypesAtColumn( @@ -192,9 +194,12 @@ public class FeatureDistanceModel extends DistanceScoreModel int spos = seq.findPosition(columnPosition); if (spos != -1) { + /* + * position is not a gap + */ Set types = new HashSet(); - List sfs = fr.findFeaturesAtRes(seq.getRefSeq(), - spos); + List sfs = fr.findFeaturesAtResidue( + seq.getRefSeq(), spos); for (SequenceFeature sf : sfs) { types.add(sf.getType()); diff --git a/src/jalview/api/FeatureRenderer.java b/src/jalview/api/FeatureRenderer.java index edd236b..9d2d7f4 100644 --- a/src/jalview/api/FeatureRenderer.java +++ b/src/jalview/api/FeatureRenderer.java @@ -60,6 +60,7 @@ public interface FeatureRenderer * * @param sequence * @param column + * aligned column position (1..) * @param g * @return */ @@ -147,14 +148,27 @@ public interface FeatureRenderer void setGroupVisibility(String group, boolean visible); /** - * Returns features at the specified position on the given sequence. + * Returns features at the specified aligned column on the given sequence. + * Non-positional features are not included. If the column has a gap, then + * enclosing features are included (but not contact features). + * + * @param sequence + * @param column + * aligned column position (1..) + * @return + */ + List findFeaturesAtColumn(SequenceI sequence, int column); + + /** + * Returns features at the specified residue position on the given sequence. * Non-positional features are not included. * * @param sequence - * @param res + * @param resNo + * residue position (start..) * @return */ - List findFeaturesAtRes(SequenceI sequence, int res); + List findFeaturesAtResidue(SequenceI sequence, int resNo); /** * get current displayed types, in ordering of rendering (on top last) @@ -200,4 +214,5 @@ public interface FeatureRenderer * @return */ float getTransparency(); + } diff --git a/src/jalview/appletgui/SeqPanel.java b/src/jalview/appletgui/SeqPanel.java index fe7f115..9e31efe 100644 --- a/src/jalview/appletgui/SeqPanel.java +++ b/src/jalview/appletgui/SeqPanel.java @@ -56,7 +56,6 @@ import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.util.Collections; import java.util.List; -import java.util.ListIterator; import java.util.Vector; public class SeqPanel extends Panel implements MouseMotionListener, @@ -530,7 +529,7 @@ public class SeqPanel extends Panel implements MouseMotionListener, } int seq = findSeq(evt); - int res = findRes(evt); + int res = findColumn(evt); if (seq < 0 || res < 0) { @@ -566,14 +565,9 @@ public class SeqPanel extends Panel implements MouseMotionListener, av.setSelectionGroup(null); } - int column = findRes(evt); - boolean isGapped = Comparison.isGap(sequence.getCharAt(column)); - List features = findFeaturesAtRes(sequence, - sequence.findPosition(column)); - if (isGapped) - { - removeAdjacentFeatures(features, column + 1, sequence); - } + int column = findColumn(evt); + List features = findFeaturesAtColumn(sequence, + column + 1); if (!features.isEmpty()) { @@ -609,7 +603,14 @@ public class SeqPanel extends Panel implements MouseMotionListener, int wrappedBlock = -1; - int findRes(MouseEvent evt) + /** + * Returns the aligned sequence position (base 0) at the mouse position, or + * the closest visible one + * + * @param evt + * @return + */ + int findColumn(MouseEvent evt) { int res = 0; int x = evt.getX(); @@ -712,7 +713,7 @@ public class SeqPanel extends Panel implements MouseMotionListener, { int seq = findSeq(evt); - int res = findRes(evt); + int res = findColumn(evt); if (seq < av.getAlignment().getHeight() && res < av.getAlignment().getSequenceAt(seq).getLength()) @@ -784,7 +785,7 @@ public class SeqPanel extends Panel implements MouseMotionListener, @Override public void mouseMoved(MouseEvent evt) { - final int column = findRes(evt); + final int column = findColumn(evt); int seq = findSeq(evt); if (seq >= av.getAlignment().getHeight() || seq < 0 || column < 0) @@ -867,12 +868,8 @@ public class SeqPanel extends Panel implements MouseMotionListener, */ if (av.isShowSequenceFeatures()) { - List allFeatures = findFeaturesAtRes(sequence, - respos); - if (isGapped) - { - removeAdjacentFeatures(allFeatures, column + 1, sequence); - } + List allFeatures = findFeaturesAtColumn(sequence, + column + 1); for (SequenceFeature sf : allFeatures) { tooltipText.append(sf.getType() + " " + sf.begin + ":" + sf.end); @@ -904,38 +901,19 @@ public class SeqPanel extends Panel implements MouseMotionListener, } } - List findFeaturesAtRes(SequenceI sequence, int res) - { - return seqCanvas.getFeatureRenderer().findFeaturesAtRes(sequence, res); - } - /** - * Removes from the list of features any that start after, or end before, the - * given column position. This allows us to retain only those features - * adjacent to a gapped position that straddle the position. Contact features - * that 'straddle' the position are also removed, since they are not 'at' the - * position. + * Returns features at the specified aligned column on the given sequence. + * Non-positional features are not included. If the column has a gap, then + * enclosing features are included (but not contact features). * - * @param features - * @param column - * alignment column (1..) * @param sequence + * @param column + * (1..) + * @return */ - protected void removeAdjacentFeatures(List features, - int column, SequenceI sequence) + List findFeaturesAtColumn(SequenceI sequence, int column) { - // TODO should this be an AlignViewController method (shared by gui)? - ListIterator it = features.listIterator(); - while (it.hasNext()) - { - SequenceFeature sf = it.next(); - if (sf.isContactFeature() - || sequence.findIndex(sf.getBegin()) > column - || sequence.findIndex(sf.getEnd()) < column) - { - it.remove(); - } - } + return seqCanvas.getFeatureRenderer().findFeaturesAtColumn(sequence, column); } Tooltip tooltip; @@ -1015,7 +993,7 @@ public class SeqPanel extends Panel implements MouseMotionListener, return; } - int res = findRes(evt); + int res = findColumn(evt); if (res < 0) { @@ -1428,7 +1406,7 @@ public class SeqPanel extends Panel implements MouseMotionListener, scrollThread = null; } - int res = findRes(evt); + int column = findColumn(evt); int seq = findSeq(evt); oldSeq = seq; startWrapBlock = wrappedBlock; @@ -1440,16 +1418,16 @@ public class SeqPanel extends Panel implements MouseMotionListener, SequenceI sequence = av.getAlignment().getSequenceAt(seq); - if (sequence == null || res > sequence.getLength()) + if (sequence == null || column > sequence.getLength()) { return; } stretchGroup = av.getSelectionGroup(); - if (stretchGroup == null || !stretchGroup.contains(sequence, res)) + if (stretchGroup == null || !stretchGroup.contains(sequence, column)) { - stretchGroup = av.getAlignment().findGroup(sequence, res); + stretchGroup = av.getAlignment().findGroup(sequence, column); if (stretchGroup != null) { // only update the current selection if the popup menu has a group to @@ -1461,8 +1439,8 @@ public class SeqPanel extends Panel implements MouseMotionListener, // DETECT RIGHT MOUSE BUTTON IN AWT if ((evt.getModifiers() & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK) { - List allFeatures = findFeaturesAtRes(sequence, - sequence.findPosition(res)); + List allFeatures = findFeaturesAtColumn(sequence, + sequence.findPosition(column + 1)); Vector links = null; for (SequenceFeature sf : allFeatures) @@ -1484,7 +1462,7 @@ public class SeqPanel extends Panel implements MouseMotionListener, if (av.cursorMode) { - seqCanvas.cursorX = findRes(evt); + seqCanvas.cursorX = findColumn(evt); seqCanvas.cursorY = findSeq(evt); seqCanvas.repaint(); return; @@ -1496,8 +1474,8 @@ public class SeqPanel extends Panel implements MouseMotionListener, { // define a new group here SequenceGroup sg = new SequenceGroup(); - sg.setStartRes(res); - sg.setEndRes(res); + sg.setStartRes(column); + sg.setEndRes(column); sg.addSequence(sequence, false); av.setSelectionGroup(sg); stretchGroup = sg; @@ -1555,7 +1533,7 @@ public class SeqPanel extends Panel implements MouseMotionListener, public void doMouseDraggedDefineMode(MouseEvent evt) { - int res = findRes(evt); + int res = findColumn(evt); int y = findSeq(evt); if (wrappedBlock != startWrapBlock) diff --git a/src/jalview/controller/AlignViewController.java b/src/jalview/controller/AlignViewController.java index 33683fd..5c1f403 100644 --- a/src/jalview/controller/AlignViewController.java +++ b/src/jalview/controller/AlignViewController.java @@ -232,72 +232,66 @@ public class AlignViewController implements AlignViewControllerI static int findColumnsWithFeature(String featureType, SequenceCollectionI sqcol, BitSet bs) { - final int startPosition = sqcol.getStartRes() + 1; // converted to base 1 - final int endPosition = sqcol.getEndRes() + 1; + final int startColumn = sqcol.getStartRes() + 1; // converted to base 1 + final int endColumn = sqcol.getEndRes() + 1; List seqs = sqcol.getSequences(); int nseq = 0; for (SequenceI sq : seqs) { if (sq != null) { - int ist = sq.findPosition(sqcol.getStartRes()); - int iend = sq.findPosition(sqcol.getEndRes()); // see JAL-2526 - List sfs = sq.findFeatures(ist, iend, featureType); - boolean overlap = false; - for (SequenceFeature sf : sfs) + // int ist = sq.findPosition(sqcol.getStartRes()); + List sfs = sq.findFeatures(startColumn, + endColumn, featureType); + + if (!sfs.isEmpty()) { - // future functionality - featureType == null means mark columns - // containing all displayed features - if (sf != null && (featureType.equals(sf.getType()))) - { - int sfStartCol = sq.findIndex(sf.getBegin()); - int sfEndCol = sq.findIndex(sf.getEnd()); // inefficient - JAL-2526 + nseq++; + } - if (sf.isContactFeature()) - { - /* - * 'contact' feature - check for 'start' or 'end' - * position within the selected region - */ - if (sfStartCol >= startPosition && sfStartCol <= endPosition) - { - bs.set(sfStartCol - 1); - overlap = true; - } - if (sfEndCol >= startPosition && sfEndCol <= endPosition) - { - bs.set(sfEndCol - 1); - overlap = true; - } - continue; - } + for (SequenceFeature sf : sfs) + { + int sfStartCol = sq.findIndex(sf.getBegin()); + int sfEndCol = sq.findIndex(sf.getEnd()); + if (sf.isContactFeature()) + { /* - * contiguous feature - select feature positions (if any) - * within the selected region + * 'contact' feature - check for 'start' or 'end' + * position within the selected region */ - if (sfStartCol < startPosition) - { - sfStartCol = startPosition; - } - if (sfStartCol < ist) + if (sfStartCol >= startColumn && sfStartCol <= endColumn) { - sfStartCol = ist; + bs.set(sfStartCol - 1); } - if (sfEndCol > endPosition) + if (sfEndCol >= startColumn && sfEndCol <= endColumn) { - sfEndCol = endPosition; - } - for (; sfStartCol <= sfEndCol; sfStartCol++) - { - bs.set(sfStartCol - 1); // convert to base 0 - overlap = true; + bs.set(sfEndCol - 1); } + continue; + } + + /* + * contiguous feature - select feature positions (if any) + * within the selected region + */ + if (sfStartCol < startColumn) + { + sfStartCol = startColumn; + } + // not sure what the point of this is + // if (sfStartCol < ist) + // { + // sfStartCol = ist; + // } + if (sfEndCol > endColumn) + { + sfEndCol = endColumn; + } + for (; sfStartCol <= sfEndCol; sfStartCol++) + { + bs.set(sfStartCol - 1); // convert to base 0 } - } - if (overlap) - { - nseq++; } } } diff --git a/src/jalview/datamodel/Sequence.java b/src/jalview/datamodel/Sequence.java index dd7c24a..0055d0e 100755 --- a/src/jalview/datamodel/Sequence.java +++ b/src/jalview/datamodel/Sequence.java @@ -34,6 +34,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; import java.util.List; +import java.util.ListIterator; import java.util.Vector; import com.stevesoft.pat.Regex; @@ -700,68 +701,6 @@ public class Sequence extends ASequence implements SequenceI } /** - * {@inheritDoc} - */ - @Override - public Range findPositions(int fromCol, int toCol) - { - /* - * count residues before fromCol - */ - int j = 0; - int count = 0; - int seqlen = sequence.length; - while (j < fromCol && j < seqlen) - { - if (!Comparison.isGap(sequence[j])) - { - count++; - } - j++; - } - - /* - * find first and last residues between fromCol and toCol - */ - int firstPos = 0; - int lastPos = 0; - int firstPosCol = 0; - boolean foundFirst = false; - - while (j <= toCol && j < seqlen) - { - if (!Comparison.isGap(sequence[j])) - { - count++; - if (!foundFirst) - { - firstPos = count; - firstPosCol = j; - foundFirst = true; - } - lastPos = count; - } - j++; - } - - if (firstPos == 0) - { - /* - * no residues in this range - */ - return null; - } - - /* - * adjust for sequence start coordinate - */ - firstPos += start - 1; - lastPos += start - 1; - - return new Range(firstPos, lastPos); - } - - /** * Returns an int array where indices correspond to each residue in the * sequence and the element value gives its position in the alignment * @@ -1505,13 +1444,55 @@ public class Sequence extends ASequence implements SequenceI * {@inheritDoc} */ @Override - public List findFeatures(int from, int to, + public List findFeatures(int fromColumn, int toColumn, String... types) { + int startPos = findPosition(fromColumn - 1); // convert base 1 to base 0 + int endPos = findPosition(toColumn - 1); + + List result = new ArrayList<>(); if (datasetSequence != null) { - return datasetSequence.findFeatures(from, to, types); + result = datasetSequence.getFeatures().findFeatures(startPos, endPos, + types); } - return sequenceFeatureStore.findFeatures(from, to, types); + else + { + result = sequenceFeatureStore.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 + */ + if (endPos > this.end || Comparison.isGap(sequence[fromColumn - 1]) + || Comparison.isGap(sequence[toColumn - 1])) + { + ListIterator it = result.listIterator(); + 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) + { + noOverlap = true; + } + if (noOverlap) + { + it.remove(); + } + } + } + + return result; } } diff --git a/src/jalview/datamodel/SequenceI.java b/src/jalview/datamodel/SequenceI.java index dbf3ed3..ec76cb9 100755 --- a/src/jalview/datamodel/SequenceI.java +++ b/src/jalview/datamodel/SequenceI.java @@ -202,30 +202,6 @@ public interface SequenceI extends ASequenceI public int findPosition(int i); /** - * Returns the range of sequence positions included in the given alignment - * position range. If no positions are included (the range is entirely gaps), - * then returns null. - * - *
-   * Example: 
-   * >Seq/8-13
-   * ABC--DE-F
-   * findPositions(1, 4) returns Range(9, 9) // B only
-   * findPositions(3, 4) returns null // all gaps
-   * findPositions(2, 6) returns Range(10, 12) // CDE
-   * findPositions(3, 7) returns Range(11,12) // DE
-   * 
- * - * @param fromCol - * first aligned column position (base 0, inclusive) - * @param toCol - * last aligned column position (base 0, inclusive) - * - * @return - */ - public Range findPositions(int fromCol, int toCol); - - /** * Returns an int array where indices correspond to each residue in the * sequence and the element value gives its position in the alignment * @@ -510,14 +486,18 @@ public interface SequenceI extends ASequenceI public List getPrimaryDBRefs(); /** - * Returns a (possibly empty) list of sequence features that overlap the range - * from-to (inclusive), optionally restricted to one or more specified feature - * types + * Returns a (possibly empty) list of sequence features that overlap the given + * alignment column range, optionally restricted to one or more specified + * feature types. If the range is all gaps, then features which enclose it are + * included (but not contact features). * - * @param from - * @param to + * @param fromCol + * start column of range inclusive (1..) + * @param toCol + * end column of range inclusive (1..) * @param types + * optional feature types to restrict results to * @return */ - List findFeatures(int from, int to, String... types); + List findFeatures(int fromCol, int toCol, String... types); } diff --git a/src/jalview/gui/AlignmentPanel.java b/src/jalview/gui/AlignmentPanel.java index f32dd7b..721f0c1 100644 --- a/src/jalview/gui/AlignmentPanel.java +++ b/src/jalview/gui/AlignmentPanel.java @@ -30,6 +30,7 @@ import jalview.datamodel.SearchResultsI; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceGroup; import jalview.datamodel.SequenceI; +import jalview.io.HTMLOutput; import jalview.jbgui.GAlignmentPanel; import jalview.math.AlignmentDimension; import jalview.schemes.ResidueProperties; @@ -1438,32 +1439,32 @@ public class AlignmentPanel extends GAlignmentPanel implements { try { - int s, sSize = av.getAlignment().getHeight(), res, alwidth = av - .getAlignment().getWidth(), g, gSize, f, fSize, sy; + int sSize = av.getAlignment().getHeight(); + int alwidth = av.getAlignment().getWidth(); PrintWriter out = new PrintWriter(new FileWriter(imgMapFile)); - out.println(jalview.io.HTMLOutput.getImageMapHTML()); + out.println(HTMLOutput.getImageMapHTML()); out.println("" + ""); - for (s = 0; s < sSize; s++) + for (int s = 0; s < sSize; s++) { - sy = s * av.getCharHeight() + scaleHeight; + int sy = s * av.getCharHeight() + scaleHeight; SequenceI seq = av.getAlignment().getSequenceAt(s); SequenceGroup[] groups = av.getAlignment().findAllGroups(seq); - for (res = 0; res < alwidth; res++) + for (int column = 0; column < alwidth; column++) { StringBuilder text = new StringBuilder(512); String triplet = null; if (av.getAlignment().isNucleotide()) { triplet = ResidueProperties.nucleotideName.get(seq - .getCharAt(res) + ""); + .getCharAt(column) + ""); } else { - triplet = ResidueProperties.aa2Triplet.get(seq.getCharAt(res) + triplet = ResidueProperties.aa2Triplet.get(seq.getCharAt(column) + ""); } @@ -1472,23 +1473,23 @@ public class AlignmentPanel extends GAlignmentPanel implements continue; } - int seqPos = seq.findPosition(res); - gSize = groups.length; - for (g = 0; g < gSize; g++) + int seqPos = seq.findPosition(column); + int gSize = groups.length; + for (int g = 0; g < gSize; g++) { if (text.length() < 1) { text.append(" res) + if (groups[g].getStartRes() < column + && groups[g].getEndRes() > column) { text.append("
").append(groups[g].getName()) .append(""); @@ -1498,17 +1499,16 @@ public class AlignmentPanel extends GAlignmentPanel implements if (text.length() < 1) { text.append(" features = seq.findFeatures(seqPos, - seqPos); + List features = seq.findFeatures(column, column); for (SequenceFeature sf : features) { if (sf.isContactFeature()) diff --git a/src/jalview/gui/SeqPanel.java b/src/jalview/gui/SeqPanel.java index 4b24dee..06568ca 100644 --- a/src/jalview/gui/SeqPanel.java +++ b/src/jalview/gui/SeqPanel.java @@ -62,7 +62,6 @@ import java.awt.event.MouseWheelListener; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.ListIterator; import javax.swing.JPanel; import javax.swing.SwingUtilities; @@ -733,6 +732,7 @@ public class SeqPanel extends JPanel implements MouseListener, int seq = findSeq(evt); if (column < 0 || seq < 0 || seq >= av.getAlignment().getHeight()) { + lastMouseSeq = -1; return; } if (column == lastMouseColumn && seq == lastMouseSeq) @@ -794,11 +794,7 @@ public class SeqPanel extends JPanel implements MouseListener, if (av.isShowSequenceFeatures()) { List features = ap.getFeatureRenderer() - .findFeaturesAtRes(sequence.getDatasetSequence(), pos); - if (isGapped) - { - removeAdjacentFeatures(features, column + 1, sequence); - } + .findFeaturesAtColumn(sequence, column + 1); seqARep.appendFeatures(tooltipText, pos, features, this.ap.getSeqPanel().seqCanvas.fr.getMinMax()); } @@ -820,35 +816,6 @@ public class SeqPanel extends JPanel implements MouseListener, } } - /** - * Removes from the list of features any that start after, or end before, the - * given column position. This allows us to retain only those features - * adjacent to a gapped position that straddle the position. Contact features - * that 'straddle' the position are also removed, since they are not 'at' the - * position. - * - * @param features - * @param column - * alignment column (1..) - * @param sequence - */ - protected void removeAdjacentFeatures(List features, - final int column, SequenceI sequence) - { - // TODO should this be an AlignViewController method (and reused by applet)? - ListIterator it = features.listIterator(); - while (it.hasNext()) - { - SequenceFeature sf = it.next(); - if (sf.isContactFeature() - || sequence.findIndex(sf.getBegin()) > column - || sequence.findIndex(sf.getEnd()) < column) - { - it.remove(); - } - } - } - private Point lastp = null; /* @@ -1587,19 +1554,13 @@ public class SeqPanel extends JPanel implements MouseListener, } int column = findColumn(evt); - boolean isGapped = Comparison.isGap(sequence.getCharAt(column)); /* * find features at the position (if not gapped), or straddling * the position (if at a gap) */ List features = seqCanvas.getFeatureRenderer() - .findFeaturesAtRes(sequence.getDatasetSequence(), - sequence.findPosition(column)); - if (isGapped) - { - removeAdjacentFeatures(features, column, sequence); - } + .findFeaturesAtColumn(sequence, column + 1); if (!features.isEmpty()) { @@ -1776,12 +1737,11 @@ public class SeqPanel extends JPanel implements MouseListener, */ void showPopupMenu(MouseEvent evt) { - final int res = findColumn(evt); + final int column = findColumn(evt); final int seq = findSeq(evt); SequenceI sequence = av.getAlignment().getSequenceAt(seq); List allFeatures = ap.getFeatureRenderer() - .findFeaturesAtRes(sequence.getDatasetSequence(), - sequence.findPosition(res)); + .findFeaturesAtColumn(sequence, column + 1); List links = new ArrayList<>(); for (SequenceFeature sf : allFeatures) { diff --git a/src/jalview/renderer/seqfeatures/FeatureColourFinder.java b/src/jalview/renderer/seqfeatures/FeatureColourFinder.java index 1db2004..ba63834 100644 --- a/src/jalview/renderer/seqfeatures/FeatureColourFinder.java +++ b/src/jalview/renderer/seqfeatures/FeatureColourFinder.java @@ -55,7 +55,7 @@ public class FeatureColourFinder * @param defaultColour * @param seq * @param column - * alignment column position (base zero) + * alignment column position (0..) * @return */ public Color findFeatureColour(Color defaultColour, SequenceI seq, @@ -81,7 +81,7 @@ public class FeatureColourFinder } } - Color c = featureRenderer.findFeatureColour(seq, column, g); + Color c = featureRenderer.findFeatureColour(seq, column + 1, g); if (c == null) { return defaultColour; diff --git a/src/jalview/renderer/seqfeatures/FeatureRenderer.java b/src/jalview/renderer/seqfeatures/FeatureRenderer.java index 950d0cd..5e071f9 100644 --- a/src/jalview/renderer/seqfeatures/FeatureRenderer.java +++ b/src/jalview/renderer/seqfeatures/FeatureRenderer.java @@ -21,7 +21,6 @@ package jalview.renderer.seqfeatures; import jalview.api.AlignViewportI; -import jalview.datamodel.Range; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; import jalview.util.Comparison; @@ -227,7 +226,7 @@ public class FeatureRenderer extends FeatureRendererModel /* * simple case - just find the topmost rendered visible feature colour */ - renderedColour = findFeatureColour(seq, seq.findPosition(column)); + renderedColour = findFeatureColour(seq, column); } else { @@ -278,15 +277,6 @@ public class FeatureRenderer extends FeatureRendererModel transparency)); } - /* - * get range of sequence positions within column range - */ - Range seqRange = seq.findPositions(start, end); - if (seqRange == null) - { - return null; - } - Color drawnColour = null; /* @@ -300,8 +290,8 @@ public class FeatureRenderer extends FeatureRendererModel continue; } - List overlaps = seq.findFeatures(seqRange.start, - seqRange.end, type); + List overlaps = seq.findFeatures(start + 1, end + 1, + type); for (SequenceFeature sf : overlaps) { /* @@ -321,14 +311,12 @@ public class FeatureRenderer extends FeatureRendererModel .findIndex(sf.end); if (isContactFeature) { - boolean drawn = renderFeature(g, seq, - featureStartCol - 1, - featureStartCol - 1, featureColour, - start, end, y1, colourOnly); - drawn |= renderFeature(g, seq, - featureEndCol - 1, - featureEndCol - 1, featureColour, - start, end, y1, colourOnly); + boolean drawn = renderFeature(g, seq, featureStartCol - 1, + featureStartCol - 1, featureColour, start, end, y1, + colourOnly); + drawn |= renderFeature(g, seq, featureEndCol - 1, + featureEndCol - 1, featureColour, start, end, y1, + colourOnly); if (drawn) { drawnColour = featureColour; @@ -393,16 +381,21 @@ public class FeatureRenderer extends FeatureRendererModel } /** - * Returns the sequence feature colour rendered at the given sequence - * position, or null if none found. The feature of highest render order (i.e. - * on top) is found, subject to both feature type and feature group being - * visible, and its colour returned. + * Returns the sequence feature colour rendered at the given column position, + * or null if none found. The feature of highest render order (i.e. on top) is + * found, subject to both feature type and feature group being visible, and + * its colour returned. + *

+ * Note this method does not check for a gap in the column so would return the + * colour for features enclosing a gapped column. Check for gap before calling + * if different behaviour is wanted. * * @param seq - * @param pos + * @param column + * (1..) * @return */ - Color findFeatureColour(SequenceI seq, int pos) + Color findFeatureColour(SequenceI seq, int column) { /* * check for new feature added while processing @@ -421,7 +414,8 @@ public class FeatureRenderer extends FeatureRendererModel continue; } - List overlaps = seq.findFeatures(pos, pos, type); + List overlaps = seq.findFeatures(column, column, + type); for (SequenceFeature sequenceFeature : overlaps) { if (!featureGroupNotShown(sequenceFeature)) diff --git a/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java b/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java index 1af491c..a568341 100644 --- a/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java +++ b/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java @@ -262,10 +262,14 @@ public abstract class FeatureRendererModel implements } @Override - public List findFeaturesAtRes(SequenceI sequence, int res) + public List findFeaturesAtColumn(SequenceI sequence, int column) { + /* + * include features at the position provided their feature type is + * displayed, and feature group is null or marked for display + */ List result = new ArrayList(); - if (!av.areFeaturesDisplayed()) + if (!av.areFeaturesDisplayed() || getFeaturesDisplayed() == null) { return result; } @@ -274,12 +278,7 @@ public abstract class FeatureRendererModel implements .getVisibleFeatures(); String[] visibleTypes = visibleFeatures .toArray(new String[visibleFeatures.size()]); - - /* - * include features at the position provided their feature type is - * displayed, and feature group is null or marked for display - */ - List features = sequence.findFeatures(res, res, + List features = sequence.findFeatures(column, column, visibleTypes); for (SequenceFeature sf : features) @@ -966,4 +965,39 @@ public abstract class FeatureRendererModel implements .booleanValue(); } + /** + * {@inheritDoc} + */ + @Override + public List findFeaturesAtResidue(SequenceI sequence, + int resNo) + { + List result = new ArrayList(); + if (!av.areFeaturesDisplayed() || getFeaturesDisplayed() == null) + { + return result; + } + + /* + * include features at the position provided their feature type is + * displayed, and feature group is null or the empty string + * or marked for display + */ + Set visibleFeatures = getFeaturesDisplayed() + .getVisibleFeatures(); + String[] visibleTypes = visibleFeatures + .toArray(new String[visibleFeatures.size()]); + List features = sequence.getFeatures().findFeatures( + resNo, resNo, visibleTypes); + + for (SequenceFeature sf : features) + { + if (!featureGroupNotShown(sf)) + { + result.add(sf); + } + } + return result; + } + } diff --git a/src/jalview/workers/ColumnCounterSetWorker.java b/src/jalview/workers/ColumnCounterSetWorker.java index 6c5707d..ba627a8 100644 --- a/src/jalview/workers/ColumnCounterSetWorker.java +++ b/src/jalview/workers/ColumnCounterSetWorker.java @@ -228,6 +228,7 @@ class ColumnCounterSetWorker extends AlignCalcWorker * * @param alignment * @param col + * (0..) * @param row * @param fr */ @@ -248,14 +249,12 @@ class ColumnCounterSetWorker extends AlignCalcWorker { return null; } - int pos = seq.findPosition(col); /* * compute a count for any displayed features at residue */ - // NB have to adjust pos if using AlignmentView.getVisibleAlignment // see JAL-2075 - List features = fr.findFeaturesAtRes(seq, pos); + List features = fr.findFeaturesAtColumn(seq, col + 1); int[] count = this.counter.count(String.valueOf(res), features); return count; } diff --git a/test/jalview/analysis/scoremodels/FeatureDistanceModelTest.java b/test/jalview/analysis/scoremodels/FeatureDistanceModelTest.java index 263844f..16ca70d 100644 --- a/test/jalview/analysis/scoremodels/FeatureDistanceModelTest.java +++ b/test/jalview/analysis/scoremodels/FeatureDistanceModelTest.java @@ -199,22 +199,22 @@ public class FeatureDistanceModelTest Assert.assertEquals(af.getFeatureRenderer().getDisplayedFeatureTypes() .size(), 1, "Should be just one feature type displayed"); // step through and check for pointwise feature presence/absence - Assert.assertEquals(af.getFeatureRenderer().findFeaturesAtRes(aseq, 1) + Assert.assertEquals(af.getFeatureRenderer().findFeaturesAtColumn(aseq, 1) .size(), 0); // step through and check for pointwise feature presence/absence - Assert.assertEquals(af.getFeatureRenderer().findFeaturesAtRes(aseq, 2) + Assert.assertEquals(af.getFeatureRenderer().findFeaturesAtColumn(aseq, 2) .size(), 1); // step through and check for pointwise feature presence/absence - Assert.assertEquals(af.getFeatureRenderer().findFeaturesAtRes(aseq, 3) + Assert.assertEquals(af.getFeatureRenderer().findFeaturesAtColumn(aseq, 3) .size(), 0); // step through and check for pointwise feature presence/absence - Assert.assertEquals(af.getFeatureRenderer().findFeaturesAtRes(aseq, 4) + Assert.assertEquals(af.getFeatureRenderer().findFeaturesAtColumn(aseq, 4) .size(), 0); // step through and check for pointwise feature presence/absence - Assert.assertEquals(af.getFeatureRenderer().findFeaturesAtRes(aseq, 5) + Assert.assertEquals(af.getFeatureRenderer().findFeaturesAtColumn(aseq, 5) .size(), 1); // step through and check for pointwise feature presence/absence - Assert.assertEquals(af.getFeatureRenderer().findFeaturesAtRes(aseq, 6) + Assert.assertEquals(af.getFeatureRenderer().findFeaturesAtColumn(aseq, 6) .size(), 0); } diff --git a/test/jalview/datamodel/SequenceTest.java b/test/jalview/datamodel/SequenceTest.java index 4da11eb..7d3fdd2 100644 --- a/test/jalview/datamodel/SequenceTest.java +++ b/test/jalview/datamodel/SequenceTest.java @@ -1162,23 +1162,57 @@ public class SequenceTest seq.setDatasetSequence(seq2); } - @Test - public void testFindPositions() + @Test(groups = { "Functional" }) + public void testFindFeatures() { - SequenceI sq = new Sequence("Seq", "ABC--DE-F", 8, 13); - - Range range = sq.findPositions(1, 4); // BC - assertEquals(new Range(9, 10), range); - - range = sq.findPositions(2, 4); // C - assertEquals(new Range(10, 10), range); - - assertNull(sq.findPositions(3, 4)); // all gaps + SequenceI sq = new Sequence("test/8-16", "-ABC--DEF--GHI--"); + sq.createDatasetSequence(); - range = sq.findPositions(2, 6); // CDE - assertEquals(new Range(10, 12), range); + assertTrue(sq.findFeatures(1, 99).isEmpty()); - range = sq.findPositions(3, 7); // DE - assertEquals(new Range(11, 12), range); + // add non-positional feature + SequenceFeature sf0 = new SequenceFeature("Cath", "desc", 0, 0, 2f, + null); + sq.addSequenceFeature(sf0); + // add feature on BCD + SequenceFeature sf1 = new SequenceFeature("Cath", "desc", 9, 11, 2f, + null); + sq.addSequenceFeature(sf1); + // add feature on DE + SequenceFeature sf2 = new SequenceFeature("Cath", "desc", 11, 12, 2f, + null); + sq.addSequenceFeature(sf2); + // add contact feature at [B, H] + SequenceFeature sf3 = new SequenceFeature("Disulphide bond", "desc", 9, + 15, 2f, + null); + sq.addSequenceFeature(sf3); + // add contact feature at [F, G] + SequenceFeature sf4 = new SequenceFeature("Disulfide Bond", "desc", 13, + 14, 2f, + null); + sq.addSequenceFeature(sf4); + + // no features in columns 1-2 (-A) + List found = sq.findFeatures(1, 2); + assertTrue(found.isEmpty()); + + // columns 1-6 (-ABC--) includes BCD and B/H feature but not DE + found = sq.findFeatures(1, 6); + assertEquals(2, found.size()); + assertTrue(found.contains(sf1)); + assertTrue(found.contains(sf3)); + + // columns 5-6 (--) includes (enclosing) BCD but not (contact) B/H feature + found = sq.findFeatures(5, 6); + assertEquals(1, found.size()); + assertTrue(found.contains(sf1)); + + // columns 7-10 (DEF-) includes BCD, DE, F/G but not B/H feature + found = sq.findFeatures(7, 10); + assertEquals(3, found.size()); + assertTrue(found.contains(sf1)); + assertTrue(found.contains(sf2)); + assertTrue(found.contains(sf4)); } } diff --git a/test/jalview/ext/rbvi/chimera/JalviewChimeraView.java b/test/jalview/ext/rbvi/chimera/JalviewChimeraView.java index 29fd092..734f7eb 100644 --- a/test/jalview/ext/rbvi/chimera/JalviewChimeraView.java +++ b/test/jalview/ext/rbvi/chimera/JalviewChimeraView.java @@ -39,6 +39,7 @@ import jalview.gui.JvOptionPane; import jalview.gui.Preferences; import jalview.gui.StructureViewer; import jalview.gui.StructureViewer.ViewerType; +import jalview.io.DataSourceType; import jalview.io.FileLoader; import jalview.structure.StructureMapping; import jalview.structure.StructureSelectionManager; @@ -50,7 +51,6 @@ import java.io.File; import java.io.IOException; import java.util.List; import java.util.Vector; -import jalview.io.DataSourceType; import org.testng.annotations.AfterClass; import org.testng.annotations.AfterMethod; @@ -440,15 +440,18 @@ public class JalviewChimeraView binding.copyStructureAttributesToFeatures("phi", af.getViewport() .getAlignPanel()); fr.setVisible("phi"); - List fs = fr.findFeaturesAtRes(fer2Arath, 54); + List fs = fer2Arath.getFeatures().findFeatures(54, 54); assertEquals(fs.size(), 3); - assertEquals(fs.get(0).getType(), "RESNUM"); - assertEquals(fs.get(1).getType(), "phi"); - assertEquals(fs.get(2).getType(), "phi"); - assertEquals(fs.get(1).getDescription(), "A"); // chain - assertEquals(fs.get(2).getDescription(), "B"); - assertEquals(fs.get(1).getScore(), -131.0713f, 0.001f); - assertEquals(fs.get(2).getScore(), -127.39512, 0.001f); + /* + * order of returned features is not guaranteed + */ + assertTrue("RESNUM".equals(fs.get(0).getType()) + || "RESNUM".equals(fs.get(1).getType()) + || "RESNUM".equals(fs.get(2).getType())); + assertTrue(fs.contains(new SequenceFeature("phi", "A", 54, 54, + -131.0713f, "Chimera"))); + assertTrue(fs.contains(new SequenceFeature("phi", "B", 54, 54, + -127.39512f, "Chimera"))); /* * tear down - also in AfterMethod @@ -470,7 +473,8 @@ public class JalviewChimeraView int res, String featureType) { String where = "at position " + res; - List fs = fr.findFeaturesAtRes(seq, res); + List fs = seq.getFeatures().findFeatures(res, res); + assertEquals(fs.size(), 2, where); assertEquals(fs.get(0).getType(), "RESNUM", where); SequenceFeature sf = fs.get(1); diff --git a/test/jalview/renderer/seqfeatures/FeatureRendererTest.java b/test/jalview/renderer/seqfeatures/FeatureRendererTest.java index ab5c137..31348c6 100644 --- a/test/jalview/renderer/seqfeatures/FeatureRendererTest.java +++ b/test/jalview/renderer/seqfeatures/FeatureRendererTest.java @@ -138,9 +138,9 @@ public class FeatureRendererTest } @Test(groups = "Functional") - public void testFindFeaturesAtRes() + public void testFindFeaturesAtColumn() { - String seqData = ">s1\nabcdefghijklmnopqrstuvwxyz\n"; + String seqData = ">s1/4-29\n-ab--cdefghijklmnopqrstuvwxyz\n"; AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData, DataSourceType.PASTE); AlignViewportI av = af.getViewport(); @@ -150,7 +150,7 @@ public class FeatureRendererTest /* * with no features */ - List features = fr.findFeaturesAtRes(seq, 3); + List features = fr.findFeaturesAtColumn(seq, 3); assertTrue(features.isEmpty()); /* @@ -159,34 +159,43 @@ public class FeatureRendererTest SequenceFeature sf1 = new SequenceFeature("Type1", "Desc", 0, 0, 1f, "Group"); // non-positional seq.addSequenceFeature(sf1); - SequenceFeature sf2 = new SequenceFeature("Type2", "Desc", 5, 15, 1f, + SequenceFeature sf2 = new SequenceFeature("Type2", "Desc", 8, 18, 1f, "Group1"); seq.addSequenceFeature(sf2); - SequenceFeature sf3 = new SequenceFeature("Type3", "Desc", 5, 15, 1f, + SequenceFeature sf3 = new SequenceFeature("Type3", "Desc", 8, 18, 1f, "Group2"); seq.addSequenceFeature(sf3); - SequenceFeature sf4 = new SequenceFeature("Type3", "Desc", 5, 15, 1f, + SequenceFeature sf4 = new SequenceFeature("Type3", "Desc", 8, 18, 1f, null); // null group is always treated as visible seq.addSequenceFeature(sf4); /* * add contact features */ - SequenceFeature sf5 = new SequenceFeature("Disulphide Bond", "Desc", 4, - 12, 1f, "Group1"); + SequenceFeature sf5 = new SequenceFeature("Disulphide Bond", "Desc", 7, + 15, 1f, "Group1"); seq.addSequenceFeature(sf5); - SequenceFeature sf6 = new SequenceFeature("Disulphide Bond", "Desc", 4, - 12, 1f, "Group2"); + SequenceFeature sf6 = new SequenceFeature("Disulphide Bond", "Desc", 7, + 15, 1f, "Group2"); seq.addSequenceFeature(sf6); - SequenceFeature sf7 = new SequenceFeature("Disulphide Bond", "Desc", 4, - 12, 1f, null); + SequenceFeature sf7 = new SequenceFeature("Disulphide Bond", "Desc", 7, + 15, 1f, null); seq.addSequenceFeature(sf7); + // feature spanning B--C + SequenceFeature sf8 = new SequenceFeature("Type1", "Desc", 5, 6, 1f, + "Group"); + seq.addSequenceFeature(sf8); + // contact feature B/C + SequenceFeature sf9 = new SequenceFeature("Disulphide Bond", "Desc", 5, + 6, 1f, "Group"); + seq.addSequenceFeature(sf9); + /* * let feature renderer discover features (and make visible) */ fr.findAllFeatures(true); - features = fr.findFeaturesAtRes(seq, 12); // all positional + features = fr.findFeaturesAtColumn(seq, 15); // all positional assertEquals(features.size(), 6); assertTrue(features.contains(sf2)); assertTrue(features.contains(sf3)); @@ -198,7 +207,7 @@ public class FeatureRendererTest /* * at a non-contact position */ - features = fr.findFeaturesAtRes(seq, 11); + features = fr.findFeaturesAtColumn(seq, 14); assertEquals(features.size(), 3); assertTrue(features.contains(sf2)); assertTrue(features.contains(sf3)); @@ -214,7 +223,8 @@ public class FeatureRendererTest data[2] = new Object[] { "Type3", colour, true }; data[3] = new Object[] { "Disulphide Bond", colour, true }; fr.setFeaturePriority(data); - features = fr.findFeaturesAtRes(seq, 12); + + features = fr.findFeaturesAtColumn(seq, 15); assertEquals(features.size(), 5); // no sf2 assertTrue(features.contains(sf3)); assertTrue(features.contains(sf4)); @@ -226,10 +236,20 @@ public class FeatureRendererTest * make "Group2" not displayed */ fr.setGroupVisibility("Group2", false); - features = fr.findFeaturesAtRes(seq, 12); + + features = fr.findFeaturesAtColumn(seq, 15); assertEquals(features.size(), 3); // no sf2, sf3, sf6 assertTrue(features.contains(sf4)); assertTrue(features.contains(sf5)); assertTrue(features.contains(sf7)); + + // features 'at' a gap between b and c + // - returns enclosing feature BC but not contact feature B/C + features = fr.findFeaturesAtColumn(seq, 4); + assertEquals(features.size(), 1); + assertTrue(features.contains(sf8)); + features = fr.findFeaturesAtColumn(seq, 5); + assertEquals(features.size(), 1); + assertTrue(features.contains(sf8)); } }