JAL-3807 - Add single sequence capabilities to jws2.JPredClient
authorMateusz Warowny <mmzwarowny@dundee.ac.uk>
Fri, 12 Feb 2021 16:19:54 +0000 (17:19 +0100)
committerMateusz Warowny <mmzwarowny@dundee.ac.uk>
Mon, 22 Feb 2021 17:53:53 +0000 (18:53 +0100)
src/jalview/ws/api/JPredServiceI.java [moved from src/jalview/ws/api/JPredMutlipleAlignmentServiceI.java with 66% similarity]
src/jalview/ws/jws2/JPredClient.java
src/jalview/ws/jws2/JPredThread.java
src/jalview/ws/slivkaws/SlivkaJPredServiceInstance.java

@@ -6,9 +6,9 @@ import jalview.datamodel.AlignmentI;
 import jalview.datamodel.SequenceI;
 import jalview.io.JPredFile;
 
-public interface JPredMutlipleAlignmentServiceI extends JalviewWebServiceI
+public interface JPredServiceI extends JalviewWebServiceI
 {
-  public JobId align(List<SequenceI> sequences) throws Throwable;
+  public JobId predict(List<SequenceI> sequences, boolean msa) throws Throwable;
 
   public AlignmentI getAlignment(JobId jobId) throws Exception;
 
index 68bcb6c..1413dfe 100644 (file)
@@ -9,17 +9,17 @@ import jalview.datamodel.SequenceI;
 import jalview.gui.AlignFrame;
 import jalview.ws.WSClient;
 import jalview.ws.WSMenuEntryProviderI;
-import jalview.ws.api.JPredMutlipleAlignmentServiceI;
+import jalview.ws.api.JPredServiceI;
 import jalview.ws.api.ServiceWithParameters;
 
 public class JPredClient extends WSClient
 {
-  JPredMutlipleAlignmentServiceI server;
+  JPredServiceI server;
 
   public JPredClient(ServiceWithParameters sh, String title,
       AlignmentView alView, AlignFrame alFrame, boolean viewOnly)
   {
-    server = (JPredMutlipleAlignmentServiceI) sh.getEndpoint();
+    server = (JPredServiceI) sh.getEndpoint();
     wsInfo = setWebService(sh, false);
     startClient(title, alView, alFrame, viewOnly);
   }
@@ -29,16 +29,23 @@ public class JPredClient extends WSClient
   {
     var msf = view.getSequences();
     var seq = msf[0].getSeq('-');
-    if (msf.length <= 1)
-      throw new RuntimeException("You need more than one sequence.");
+    int[] delMap = null;
+    if (viewOnly)
+      delMap = view.getVisibleContigMapFor(seq.gapMap());
     var aln = new SequenceI[msf.length];
     for (int i = 0; i < msf.length; i++)
     {
       aln[i] = msf[i].getSeq('-');
     }
-    int[] delMap = viewOnly ?
-      view.getVisibleContigMapFor(seq.gapMap()) : null;
-    var sequenceInfo = SeqsetUtils.uniquify(aln, true);
+    var sequenceInfo = msf.length > 1 ? SeqsetUtils.uniquify(aln, true)
+            : SeqsetUtils.SeqCharacterHash(seq);
+    if (viewOnly)
+    {
+      String seqs[] = view.getSequenceStrings('-');
+      for (int i = 0; i < msf.length; i++)
+        aln[i].setSequence(seqs[i]);
+      seq.setSequence(seqs[0]);
+    }
     var thread = new JPredThread(wsInfo, title, server, sequenceInfo, aln,
         delMap, view, frame, WsURL);
     wsInfo.setthisService(thread);
@@ -54,14 +61,7 @@ public class JPredClient extends WSClient
       mi.setToolTipText(sh.getHostURL());
       mi.addActionListener((event) -> {
         var view = frame.gatherSeqOrMsaForSecStrPrediction();
-        if (view.getSequences().length > 1)
-        {
-          new JPredClient(sh, frame.getTitle(), view, frame, true);
-        }
-        else
-        {
-          Cache.log.error("Single sequence not supported");
-        }
+        new JPredClient(sh, frame.getTitle(), view, frame, true);
       });
       menu.add(mi);
     };
index 087e9d4..d10ca8a 100644 (file)
@@ -7,6 +7,7 @@ import java.util.List;
 
 import jalview.analysis.SeqsetUtils;
 import jalview.bin.Cache;
+import jalview.commands.RemoveGapsCommand;
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
@@ -24,51 +25,56 @@ import jalview.ws.AWsJob;
 import jalview.ws.JobStateSummary;
 import jalview.ws.WSClientI;
 import jalview.ws.api.CancellableI;
-import jalview.ws.api.JPredMutlipleAlignmentServiceI;
+import jalview.ws.api.JPredServiceI;
 import jalview.ws.gui.WsJob;
 import jalview.ws.gui.WsJob.JobState;
 
 
-class JPredJob extends WsJob
+public class JPredThread extends AWSThread implements WSClientI
 {
-  Hashtable<?, ?> sequenceInfo;
-  List<SequenceI> msf;
-  int[] delMap;
-  AlignmentI alignment = null;
-  HiddenColumns hiddenCols = null;
 
-  public JPredJob(Hashtable<?, ?> sequenceInfo, SequenceI[] msf, int[] delMap)
+  private static class JPredJob extends WsJob
   {
-    this.sequenceInfo = sequenceInfo;
-    this.msf = List.of(msf);
-    this.delMap = delMap;
-  }
+    private final Hashtable<?, ?> sequenceInfo;
+    private final List<SequenceI> msf;
+    private final int[] delMap;
+    private AlignmentI alignment = null;
+    private HiddenColumns hiddenCols = null;
 
-  @Override
-  public boolean hasValidInput()
-  {
-    return true;
-  }
+    private JPredJob(Hashtable<?, ?> sequenceInfo, SequenceI[] msf, int[] delMap)
+    {
+      this.sequenceInfo = sequenceInfo;
+      this.msf = List.of(msf);
+      this.delMap = delMap;
+    }
 
-  @Override
-  public boolean hasResults()
-  {
-    return (isSubjobComplete() && alignment != null);
-  }
-}
+    @Override
+    public boolean hasValidInput()
+    {
+      return true;
+    }
 
+    @Override
+    public boolean hasResults()
+    {
+      return (isSubjobComplete() && alignment != null);
+    }
+
+    public boolean isMSA()
+    {
+      return msf.size() > 1;
+    }
+  }
 
-public class JPredThread extends AWSThread implements WSClientI
-{
 
-  private JPredMutlipleAlignmentServiceI server;
+  private JPredServiceI server;
   private String title;
   private Hashtable<?, ?> sequenceInfo;
   private SequenceI[] msf;
   private int[] delMap;
 
   public JPredThread(WebserviceInfo wsInfo, String title,
-      JPredMutlipleAlignmentServiceI server, Hashtable<?, ?> sequenceInfo,
+      JPredServiceI server, Hashtable<?, ?> sequenceInfo,
       SequenceI[] msf, int[] delMap, AlignmentView view, AlignFrame frame,
       String wsURL)
   {
@@ -122,7 +128,7 @@ public class JPredThread extends AWSThread implements WSClientI
     try {
       try
       {
-        var jobHandle = server.align(job.msf);
+        var jobHandle = server.predict(job.msf, job.isMSA());
         if (jobHandle != null)
           job.setJobHandle(jobHandle);
       }
@@ -229,6 +235,8 @@ public class JPredThread extends AWSThread implements WSClientI
     wsInfo.removeProgressBar(progbar);
   }
 
+  static final int msaIndex = 0;
+
   private void prepareJobResult(JPredJob job) throws Exception
   {
     HiddenColumns hiddenCols = null;
@@ -237,38 +245,70 @@ public class JPredThread extends AWSThread implements WSClientI
     var prediction = server.getPrediction(job.getJobHandle());
     var preds = prediction.getSeqsAsArray();
 
-    if (job.delMap != null)
+    if (job.msf.size() > 1)
     {
-      Object[] alandcolsel = input
-              .getAlignmentAndHiddenColumns(getGapChar());
-      alignment = new Alignment((SequenceI[]) alandcolsel[0]);
-      hiddenCols = (HiddenColumns) alandcolsel[1];
+      if (job.delMap != null)
+      {
+        Object[] alandcolsel = input
+                .getAlignmentAndHiddenColumns(getGapChar());
+        alignment = new Alignment((SequenceI[]) alandcolsel[0]);
+        hiddenCols = (HiddenColumns) alandcolsel[1];
+      }
+      else
+      {
+        alignment = server.getAlignment(job.getJobHandle());
+        var seqs = new SequenceI[alignment.getHeight()];
+        for (int i = 0; i < alignment.getHeight(); i++)
+        {
+          seqs[i] = alignment.getSequenceAt(i);
+        }
+        if (!SeqsetUtils.deuniquify(sequenceInfo, seqs))
+        {
+          throw (new Exception(MessageManager.getString(
+                  "exception.couldnt_recover_sequence_properties_for_alignment")));
+        }
+      }
+      firstSeq = 0;
+      if (currentView.getDataset() != null)
+      {
+        alignment.setDataset(currentView.getDataset());
+      }
+      else
+      {
+        alignment.setDataset(null);
+      }
+      JnetAnnotationMaker.add_annotation(prediction, alignment, firstSeq, false,
+              job.delMap);
     }
     else
     {
-      alignment = server.getAlignment(job.getJobHandle());
-      var seqs = new SequenceI[alignment.getHeight()];
-      for (int i = 0; i < alignment.getHeight(); i++)
+      alignment = new Alignment(preds);
+      firstSeq = prediction.getQuerySeqPosition();
+      if (job.delMap != null)
       {
-        seqs[i] = alignment.getSequenceAt(i);
+        Object[] alanndcolsel = input.getAlignmentAndHiddenColumns(getGapChar());
+        SequenceI[] seqs = (SequenceI[]) alanndcolsel[0];
+        new RemoveGapsCommand(MessageManager.getString("label.remove_gaps"),
+                new SequenceI[] {seqs[msaIndex]}, currentView);
+        SequenceI profileSeq = alignment.getSequenceAt(firstSeq);
+        profileSeq.setSequence(seqs[msaIndex].getSequenceAsString());
       }
-      if (!SeqsetUtils.deuniquify(sequenceInfo, seqs))
+      if (!SeqsetUtils.SeqCharacterUnhash(
+              alignment.getSequenceAt(firstSeq), sequenceInfo))
       {
-        throw (new Exception(MessageManager.getString(
-                "exception.couldnt_recover_sequence_properties_for_alignment")));
+        throw new Exception(MessageManager.getString(
+                "exception.couldnt_recover_sequence_props_for_jnet_query"));
       }
-    }
-    firstSeq = 0;
-    if (currentView.getDataset() != null)
-    {
       alignment.setDataset(currentView.getDataset());
+      JnetAnnotationMaker.add_annotation(prediction, alignment, firstSeq, true,
+              job.delMap);
+      SequenceI profileSeq = alignment.getSequenceAt(0);
+      alignToProfileSeq(alignment, profileSeq);
+      if (job.delMap != null)
+      {
+        hiddenCols = alignment.propagateInsertions(profileSeq, input);
+      }
     }
-    else
-    {
-      alignment.setDataset(null);
-    }
-    JnetAnnotationMaker.add_annotation(prediction, alignment, firstSeq, false,
-            job.delMap);
 
     for (var annot : alignment.getAlignmentAnnotation())
     {
@@ -306,6 +346,46 @@ public class JPredThread extends AWSThread implements WSClientI
     dssan.adjustForAlignment();
   }
 
+  private static void alignToProfileSeq(AlignmentI al, SequenceI profileseq)
+  {
+    char gc = al.getGapCharacter();
+    int[] gapMap = profileseq.gapMap();
+    // insert gaps into profile
+    for (int lp = 0, r = 0; r < gapMap.length; r++)
+    {
+      if (gapMap[r] - lp > 1)
+      {
+        StringBuffer sb = new StringBuffer();
+        for (int s = 0, ns = gapMap[r] - lp; s < ns; s++)
+        {
+          sb.append(gc);
+        }
+        for (int s = 1, ns = al.getHeight(); s < ns; s++)
+        {
+          String sq = al.getSequenceAt(s).getSequenceAsString();
+          int diff = gapMap[r] - sq.length();
+          if (diff > 0)
+          {
+            // pad gaps
+            sq = sq + sb;
+            while ((diff = gapMap[r] - sq.length()) > 0)
+            {
+              sq = sq + ((diff >= sb.length()) ? sb.toString()
+                      : sb.substring(0, diff));
+            }
+            al.getSequenceAt(s).setSequence(sq);
+          }
+          else
+          {
+            al.getSequenceAt(s).setSequence(sq.substring(0, gapMap[r])
+                    + sb.toString() + sq.substring(gapMap[r]));
+          }
+        }
+      }
+      lp = gapMap[r];
+    }
+  }
+
   private void displayResults(boolean newWindow)
   {
     if (jobs == null || jobs.length == 0)
@@ -315,9 +395,8 @@ public class JPredThread extends AWSThread implements WSClientI
     var job = (JPredJob) jobs[0];
     if (job.hasResults() && newWindow)
     {
-      AlignFrame frame;
       job.alignment.setSeqrep(job.alignment.getSequenceAt(0));
-      frame = new AlignFrame(job.alignment, job.hiddenCols,
+      AlignFrame frame = new AlignFrame(job.alignment, job.hiddenCols,
               AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
       Desktop.addInternalFrame(frame, title, AlignFrame.DEFAULT_WIDTH,
               AlignFrame.DEFAULT_HEIGHT);
index b8e4d2f..462be28 100644 (file)
@@ -8,16 +8,41 @@ import jalview.datamodel.AlignmentI;
 import jalview.datamodel.SequenceI;
 import jalview.io.DataSourceType;
 import jalview.io.JPredFile;
-import jalview.ws.api.JPredMutlipleAlignmentServiceI;
+import jalview.ws.api.JPredServiceI;
 import jalview.ws.api.JobId;
+import jalview.ws.params.ArgumentI;
 import uk.ac.dundee.compbio.slivkaclient.RemoteFile;
 import uk.ac.dundee.compbio.slivkaclient.SlivkaClient;
 import uk.ac.dundee.compbio.slivkaclient.SlivkaService;
 
 public class SlivkaJPredServiceInstance extends SlivkaWSInstance
-    implements JPredMutlipleAlignmentServiceI
+    implements JPredServiceI
 {
 
+  private class InputFormatParameter implements ArgumentI
+  {
+    String value = "";
+
+    @Override
+    public String getName()
+    {
+      return "format";
+    }
+
+    @Override
+    public String getValue()
+    {
+      return value;
+    }
+
+    @Override
+    public void setValue(String selectedItem)
+    {
+      value = selectedItem;
+    }
+  }
+
+
   public SlivkaJPredServiceInstance(SlivkaClient client,
       SlivkaService service, String action)
   {
@@ -26,9 +51,13 @@ public class SlivkaJPredServiceInstance extends SlivkaWSInstance
   }
 
   @Override
-  public JobId align(List<SequenceI> sequences) throws Throwable
+  public JobId predict(List<SequenceI> sequences, boolean msa) throws Throwable
   {
-    return super.submit(sequences, null, null);
+    // Hack allowing to send both single and msa jobs
+    // until msa and single sequence services are separated.
+    var arg = new InputFormatParameter();
+    arg.setValue(msa ? "fasta" : "seq");
+    return super.submit(sequences, null, List.of(arg));
   }
 
   @Override