Merge branch 'features/JAL-2446NCList' into
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Mon, 5 Jun 2017 07:57:27 +0000 (08:57 +0100)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Mon, 5 Jun 2017 07:57:27 +0000 (08:57 +0100)
features/JAL-2574findFeaturesByColumn

Conflicts:
src/jalview/datamodel/SequenceI.java

1  2 
src/jalview/datamodel/Sequence.java
src/jalview/datamodel/SequenceI.java
src/jalview/workers/ColumnCounterSetWorker.java
test/jalview/datamodel/SequenceTest.java

@@@ -31,10 -31,10 +31,11 @@@ import jalview.util.StringUtils
  
  import java.util.ArrayList;
  import java.util.Arrays;
+ import java.util.BitSet;
  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;
@@@ -701,6 -701,68 +702,6 @@@ public class Sequence extends ASequenc
    }
  
    /**
 -   * {@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
     * 
    }
  
    @Override
+   public BitSet getInsertionsAsBits()
+   {
+     BitSet map = new BitSet();
+     int lastj = -1, j = 0;
+     int pos = start;
+     int seqlen = sequence.length;
+     while ((j < seqlen))
+     {
+       if (jalview.util.Comparison.isGap(sequence[j]))
+       {
+         if (lastj == -1)
+         {
+           lastj = j;
+         }
+       }
+       else
+       {
+         if (lastj != -1)
+         {
+           map.set(lastj, j);
+           lastj = -1;
+         }
+       }
+       j++;
+     }
+     if (lastj != -1)
+     {
+       map.set(lastj, j);
+       lastj = -1;
+     }
+     return map;
+   }
+   @Override
    public void deleteChars(int i, int j)
    {
      int newstart = start, newend = end;
     * {@inheritDoc}
     */
    @Override
 -  public List<SequenceFeature> findFeatures(int from, int to,
 +  public List<SequenceFeature> findFeatures(int fromColumn, int toColumn,
            String... types)
    {
 +    int startPos = findPosition(fromColumn - 1); // convert base 1 to base 0
 +    int endPos = findPosition(toColumn - 1);
 +
 +    List<SequenceFeature> 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<SequenceFeature> 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;
    }
  }
@@@ -22,6 -22,7 +22,7 @@@ package jalview.datamodel
  
  import jalview.datamodel.features.SequenceFeaturesI;
  
+ import java.util.BitSet;
  import java.util.List;
  import java.util.Vector;
  
@@@ -202,6 -203,30 +203,6 @@@ public interface SequenceI extends ASeq
    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.
 -   * 
 -   * <pre>
 -   * 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
 -   * </pre>
 -   * 
 -   * @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
     * 
    public List<DBRefEntry> 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<SequenceFeature> findFeatures(int from, int to, String... types);
 +  List<SequenceFeature> findFeatures(int fromCol, int toCol, String... types);
+   
+   /**
+    * 
+    * @return BitSet corresponding to index [0,length) where Comparison.isGap()
+    *         returns true.
+    */
+   BitSet getInsertionsAsBits();
  }
@@@ -182,19 -182,19 +182,19 @@@ class ColumnCounterSetWorker extends Al
      for (int anrow = 0; anrow < rows; anrow++)
      {
        Annotation[] anns = new Annotation[width];
+       long rmax = 0;
        /*
-        * add non-zero counts as annotations
+        * add counts as annotations. zeros are needed since select-by-annotation ignores empty annotation positions
         */
        for (int i = 0; i < counts.length; i++)
        {
          int count = counts[i][anrow];
-         if (count > 0)
-         {
-           Color color = ColorUtils.getGraduatedColour(count, 0, minColour,
-                   max[anrow], maxColour);
-           String str = String.valueOf(count);
-           anns[i] = new Annotation(str, str, '0', count, color);
-         }
+         Color color = ColorUtils.getGraduatedColour(count, 0, minColour,
+                 max[anrow], maxColour);
+         String str = String.valueOf(count);
+         anns[i] = new Annotation(str, str, '0', count, color);
+         rmax = Math.max(count, rmax);
        }
  
        /*
        ann.scaleColLabel = true;
        ann.graph = AlignmentAnnotation.BAR_GRAPH;
        ann.annotations = anns;
-       setGraphMinMax(ann, anns);
+       ann.graphMin = 0f; // minimum always zero count
+       ann.graphMax = rmax; // maximum count from loop over feature columns
        ann.validateRangeAndDisplay();
        if (!ourAnnots.contains(ann))
        {
     * 
     * @param alignment
     * @param col
 +   *          (0..)
     * @param row
     * @param fr
     */
      {
        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<SequenceFeature> features = fr.findFeaturesAtRes(seq, pos);
 +    List<SequenceFeature> features = fr.findFeaturesAtColumn(seq, col + 1);
      int[] count = this.counter.count(String.valueOf(res), features);
      return count;
    }
@@@ -35,6 -35,7 +35,7 @@@ import jalview.util.MapList
  import java.io.File;
  import java.util.ArrayList;
  import java.util.Arrays;
+ import java.util.BitSet;
  import java.util.List;
  import java.util.Vector;
  
@@@ -76,6 -77,18 +77,18 @@@ public class SequenceTes
      assertEquals("Gap interval 1 end wrong", 4, gapInt.get(0)[1]);
      assertEquals("Gap interval 2 start wrong", 6, gapInt.get(1)[0]);
      assertEquals("Gap interval 2 end wrong", 8, gapInt.get(1)[1]);
+     BitSet gapfield = aseq.getInsertionsAsBits();
+     BitSet expectedgaps = new BitSet();
+     expectedgaps.set(2, 5);
+     expectedgaps.set(6, 9);
+     assertEquals(6, expectedgaps.cardinality());
+     assertEquals("getInsertionsAsBits didn't mark expected number of gaps",
+             6, gapfield.cardinality());
+     assertEquals("getInsertionsAsBits not correct.", expectedgaps, gapfield);
    }
  
    @Test(groups = ("Functional"))
      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<SequenceFeature> 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));
    }
  }