From 881dd37ce1077bce230b6b04690112d9d6eafecd Mon Sep 17 00:00:00 2001 From: gmungoc Date: Mon, 10 Aug 2020 16:04:14 +0100 Subject: [PATCH] JAL-3706 handle virtual feature extending to unmapped stop codon --- src/jalview/datamodel/MappedFeatures.java | 25 +++++++++-- src/jalview/datamodel/SequenceFeature.java | 7 ++- src/jalview/gui/PopupMenu.java | 7 ++- src/jalview/io/SequenceAnnotationReport.java | 43 ++++++------------- test/jalview/datamodel/MappedFeaturesTest.java | 47 +++++++++++++++++++-- test/jalview/io/SequenceAnnotationReportTest.java | 15 +++---- 6 files changed, 90 insertions(+), 54 deletions(-) diff --git a/src/jalview/datamodel/MappedFeatures.java b/src/jalview/datamodel/MappedFeatures.java index d652a97..1f672be 100644 --- a/src/jalview/datamodel/MappedFeatures.java +++ b/src/jalview/datamodel/MappedFeatures.java @@ -276,7 +276,8 @@ public class MappedFeatures /** * Answers the mapped ranges (as one or more [start, end] positions) which - * correspond to the given [begin, end] range of the linked sequence. + * correspond to the given [begin, end] range of (some feature on) the linked + * sequence. * *
    * Example: MappedFeatures with CDS features mapped to peptide 
@@ -293,9 +294,27 @@ public class MappedFeatures
    */
   public int[] getMappedPositions(int begin, int end)
   {
+    int[] result = null;
     MapList map = mapping.getMap();
-    return mapping.to == featureSequence ? map.locateInFrom(begin, end)
-            : map.locateInTo(begin, end);
+    if (mapping.to == featureSequence)
+    {
+      result = map.locateInFrom(begin, end);
+      if (result == null)
+      {
+        // fudge for feature (e.g. CDS) extending to a mapped stop codon
+        result = map.locateInFrom(begin, end-3);
+      }
+    }
+    else
+    {
+      result = map.locateInTo(begin, end);
+      if (result == null)
+      {
+        // fudge for feature (e.g. CDS) extending to a mapped stop codon
+        result = map.locateInTo(begin, end-3);
+      }
+    }
+    return result;
   }
 
   /**
diff --git a/src/jalview/datamodel/SequenceFeature.java b/src/jalview/datamodel/SequenceFeature.java
index 6eeba2f..bbf1b45 100755
--- a/src/jalview/datamodel/SequenceFeature.java
+++ b/src/jalview/datamodel/SequenceFeature.java
@@ -612,10 +612,9 @@ public class SequenceFeature implements FeatureLocationI
     String consequence = "";
     if (mf != null)
     {
-      int[] beginRange = mf.getMappedPositions(begin, begin);
-      int[] endRange = mf.getMappedPositions(end, end);
-      int from = beginRange[0];
-      int to = endRange[endRange.length - 1];
+      int[] localRange = mf.getMappedPositions(begin, end);
+      int from = localRange[0];
+      int to = localRange[localRange.length - 1];
       String s = mf.isFromCds() ? "Peptide Location" : "Coding location";
       sb.append(String.format(ROW_DATA, s, seqName, from == to ? from
               : from + (isContactFeature() ? ":" : "-") + to));
diff --git a/src/jalview/gui/PopupMenu.java b/src/jalview/gui/PopupMenu.java
index 568f7f1..eaff5ef 100644
--- a/src/jalview/gui/PopupMenu.java
+++ b/src/jalview/gui/PopupMenu.java
@@ -831,10 +831,9 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
       /*
        * show local rather than linked feature coordinates
        */
-      int[] beginRange = mf.getMappedPositions(start, start);
-      start = beginRange[0];
-      int[] endRange = mf.getMappedPositions(end, end);
-      end = endRange[endRange.length - 1];
+      int[] localRange = mf.getMappedPositions(start, end);
+      start = localRange[0];
+      end = localRange[localRange.length - 1];
     }
     StringBuilder desc = new StringBuilder();
     desc.append(sf.getType()).append(" ").append(String.valueOf(start));
diff --git a/src/jalview/io/SequenceAnnotationReport.java b/src/jalview/io/SequenceAnnotationReport.java
index 27c1652..8b1f7ff 100644
--- a/src/jalview/io/SequenceAnnotationReport.java
+++ b/src/jalview/io/SequenceAnnotationReport.java
@@ -120,21 +120,11 @@ public class SequenceAnnotationReport
     }
   };
 
-  private boolean forTooltip;
-
   /**
-   * Constructor given a flag which affects behaviour
-   * 
-   * 
-   * @param isForTooltip
+   * Constructor
    */
   public SequenceAnnotationReport(boolean isForTooltip)
   {
-    this.forTooltip = isForTooltip;
     if (linkImageURL == null)
     {
       linkImageURL = getClass().getResource("/images/link.gif").toString();
@@ -210,41 +200,32 @@ public class SequenceAnnotationReport
      * if this is a virtual features, convert begin/end to the
      * coordinates of the sequence it is mapped to
      */
-    int[] beginRange = null;
-    int[] endRange = null;
+    int[] localRange = null;
     if (mf != null)
     {
-      beginRange = mf.getMappedPositions(begin, begin);
-      endRange = mf.getMappedPositions(end, end);
-      if (beginRange == null || endRange == null)
+      localRange = mf.getMappedPositions(begin, end);
+      if (localRange == null)
       {
         // something went wrong
         return false;
       }
-      begin = beginRange[0];
-      end = endRange[endRange.length - 1];
+      begin = localRange[0];
+      end = localRange[localRange.length - 1];
     }
 
     StringBuilder sb = new StringBuilder();
     if (feature.isContactFeature())
     {
       /*
-       * include if rpos is at start or end position of [mapped] feature
+       * contact features are rendered slightly differently; note the check for
+       * 'start or end position only' was applied earlier when finding features
        */
-      boolean showContact = (mf == null) && (rpos == begin || rpos == end);
-      boolean showMappedContact = (mf != null) && ((rpos >= beginRange[0]
-              && rpos <= beginRange[beginRange.length - 1])
-              || (rpos >= endRange[0]
-                      && rpos <= endRange[endRange.length - 1]));
-      if (showContact || showMappedContact)
+      if (sb0.length() > 6)
       {
-        if (sb0.length() > 6)
-        {
-          sb.append("
"); - } - sb.append(feature.getType()).append(" ").append(begin).append(":") - .append(end); + sb.append("
"); } + sb.append(feature.getType()).append(" ").append(begin).append(":") + .append(end); return appendText(sb0, sb, maxlength); } diff --git a/test/jalview/datamodel/MappedFeaturesTest.java b/test/jalview/datamodel/MappedFeaturesTest.java index de4ce6c..5d00089 100644 --- a/test/jalview/datamodel/MappedFeaturesTest.java +++ b/test/jalview/datamodel/MappedFeaturesTest.java @@ -2,8 +2,6 @@ package jalview.datamodel; import static org.testng.Assert.assertEquals; -import jalview.util.MapList; - import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -11,6 +9,8 @@ import java.util.Map; import org.testng.annotations.Test; +import jalview.util.MapList; + public class MappedFeaturesTest { @Test(groups = "Functional") @@ -21,7 +21,7 @@ public class MappedFeaturesTest * dna/10-20 aCGTaGctGAa (codons CGT=R, GGA = G) * mapping: 3:1 from [11-13,15,18-19] to peptide/1-2 RG */ - SequenceI from = new Sequence("dna/10-20", "acgTAGCTGAA"); + SequenceI from = new Sequence("dna/10-20", "aCGTaGctGAa"); SequenceI to = new Sequence("peptide", "RG"); MapList map = new MapList(new int[] { 11, 13, 15, 15, 18, 19 }, new int[] @@ -117,4 +117,45 @@ public class MappedFeaturesTest variant = mf.findProteinVariants(sf9); assertEquals(variant, ""); } + + @Test(groups = "Functional") + public void testGetMappedPositions() + { + // CDS including stop codon taa + SequenceI cds = new Sequence("cds/10-21", "ATGcgtGGAtaa"); + SequenceI peptide = new Sequence("peptide", "MRG"); // ATG, CGT, GGA + + /* + * emulate 'map from' range based on CDS _including_ stop codon + */ + MapList map = new MapList(new int[] { 10, 21 }, new int[] { 1, 3 }, 3, + 1); + Mapping mapping = new Mapping(peptide, map); + + MappedFeatures mf = new MappedFeatures(mapping, cds, 2, 'M', null); + + /* + * scenario: sequence_variant feature on CDS at position 14; + * find the corresponding position on peptide + */ + int[] pepPos = mf.getMappedPositions(14, 14); + assertEquals(pepPos[0], 2); + assertEquals(pepPos[1], 2); + + /* + * scenario: exon feature on CDS including stop codon; + */ + pepPos = mf.getMappedPositions(10, 21); + assertEquals(pepPos[0], 1); + assertEquals(pepPos[1], 3); + + /* + * now with the mapping from protein to CDS + */ + mapping = new Mapping(cds, map.getInverse()); + mf = new MappedFeatures(mapping, peptide, 15, 't', null); + int[] cdsPos = mf.getMappedPositions(2, 2); + assertEquals(cdsPos[0], 13); + assertEquals(cdsPos[1], 15); + } } diff --git a/test/jalview/io/SequenceAnnotationReportTest.java b/test/jalview/io/SequenceAnnotationReportTest.java index 772ed2b..57821c9 100644 --- a/test/jalview/io/SequenceAnnotationReportTest.java +++ b/test/jalview/io/SequenceAnnotationReportTest.java @@ -61,23 +61,20 @@ public class SequenceAnnotationReportTest { SequenceAnnotationReport sar = new SequenceAnnotationReport(false); StringBuilder sb = new StringBuilder(); - sb.append("123456"); + sb.append("foo "); SequenceFeature sf = new SequenceFeature("disulfide bond", "desc", 1, 3, 1.2f, "group"); - // residuePos == 2 does not match start or end of feature, nothing done: - sar.appendFeature(sb, 2, null, sf, null, 0); - assertEquals("123456", sb.toString()); - // residuePos == 1 matches start of feature, text appended (but no
) // feature score is not included sar.appendFeature(sb, 1, null, sf, null, 0); - assertEquals("123456disulfide bond 1:3", sb.toString()); + assertEquals("foo disulfide bond 1:3", sb.toString()); - // residuePos == 3 matches end of feature, text appended + // residuePos == 2 doesnt' match end of feature, text appended anyway + // (the test for this is handled by FeatureStore.findContactFeatures()) //
is prefixed once sb.length() > 6 - sar.appendFeature(sb, 3, null, sf, null, 0); - assertEquals("123456disulfide bond 1:3
disulfide bond 1:3", + sar.appendFeature(sb, 2, null, sf, null, 0); + assertEquals("foo disulfide bond 1:3
disulfide bond 1:3", sb.toString()); } -- 1.7.10.2