JAL-1601 Implement sec str pred for single sequences
authorMateusz Warowny <mmzwarowny@dundee.ac.uk>
Thu, 19 Oct 2023 13:45:38 +0000 (15:45 +0200)
committerMateusz Warowny <mmzwarowny@dundee.ac.uk>
Thu, 19 Oct 2023 13:45:38 +0000 (15:45 +0200)
src/jalview/ws2/actions/secstructpred/SecStructPredAction.java
src/jalview/ws2/actions/secstructpred/SecStructPredPDBSearchTask.java [new file with mode: 0644]
src/jalview/ws2/client/jpred4/JPred4WSDiscoverer.java

index f625f79..b623945 100644 (file)
@@ -8,6 +8,7 @@ import jalview.datamodel.AlignmentI;
 import jalview.viewmodel.AlignmentViewport;
 import jalview.ws.params.ArgumentI;
 import jalview.ws2.actions.BaseAction;
+import jalview.ws2.actions.BaseTask;
 import jalview.ws2.api.Credentials;
 import jalview.ws2.client.api.SecStructPredWebServiceClientI;
 import jalview.ws2.client.api.WebServiceClientI;
@@ -18,6 +19,8 @@ public class SecStructPredAction extends BaseAction<AlignmentI>
   {
     protected SecStructPredWebServiceClientI client;
     
+    protected boolean msaMode;
+    
     private Builder(SecStructPredWebServiceClientI client)
     {
       super();
@@ -25,6 +28,11 @@ public class SecStructPredAction extends BaseAction<AlignmentI>
       this.client = client;
     }
     
+    public void msaMode(boolean msa)
+    {
+      this.msaMode = msa;
+    }
+    
     public SecStructPredAction build()
     {
       return new SecStructPredAction(this);
@@ -38,16 +46,22 @@ public class SecStructPredAction extends BaseAction<AlignmentI>
   
   protected final SecStructPredWebServiceClientI client;
   
+  protected boolean msaMode;
+  
   public SecStructPredAction(Builder builder)
   {
     super(builder);
     client = builder.client;
+    msaMode = builder.msaMode;
   }
   
-  public SecStructPredMsaTask createTask(AlignViewportI viewport,
+  public BaseTask<?, AlignmentI> createTask(AlignViewportI viewport,
       List<ArgumentI> args, Credentials credentials)
   {
-    return new SecStructPredMsaTask(client, args, credentials, viewport);
+    if (msaMode)
+      return new SecStructPredMsaTask(client, args, credentials, viewport);
+    else
+      return new SecStructPredPDBSearchTask(client, args, credentials, viewport);
   }
 
   @Override
diff --git a/src/jalview/ws2/actions/secstructpred/SecStructPredPDBSearchTask.java b/src/jalview/ws2/actions/secstructpred/SecStructPredPDBSearchTask.java
new file mode 100644 (file)
index 0000000..0cb5150
--- /dev/null
@@ -0,0 +1,197 @@
+package jalview.ws2.actions.secstructpred;
+
+import java.io.IOException;
+import java.util.List;
+
+import jalview.analysis.AlignSeq;
+import jalview.analysis.AlignmentAnnotationUtils;
+import jalview.analysis.SeqsetUtils;
+import jalview.analysis.SeqsetUtils.SequenceInfo;
+import jalview.api.AlignViewportI;
+import jalview.bin.Console;
+import jalview.commands.RemoveGapsCommand;
+import jalview.datamodel.Alignment;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.AlignmentView;
+import jalview.datamodel.SeqCigar;
+import jalview.datamodel.SequenceI;
+import jalview.io.JPredFile;
+import jalview.io.JnetAnnotationMaker;
+import jalview.util.Comparison;
+import jalview.util.MessageManager;
+import jalview.ws.params.ArgumentI;
+import jalview.ws2.actions.BaseJob;
+import jalview.ws2.actions.BaseTask;
+import jalview.ws2.actions.ServiceInputInvalidException;
+import jalview.ws2.api.Credentials;
+import jalview.ws2.api.JobStatus;
+import jalview.ws2.client.api.SecStructPredWebServiceClientI;
+
+public class SecStructPredPDBSearchTask extends
+        BaseTask<SecStructPredPDBSearchTask.SecStructPredJob, AlignmentI>
+{
+  private final SecStructPredWebServiceClientI client;
+
+  private final AlignmentView alignmentView;
+  private final AlignmentI currentView;
+  private final char gapChar;
+
+  SecStructPredPDBSearchTask(SecStructPredWebServiceClientI client,
+          List<ArgumentI> args, Credentials credentials,
+          AlignViewportI viewport)
+  {
+    super(client, args, credentials);
+    this.client = client;
+    this.alignmentView = viewport.getAlignmentView(true);
+    this.currentView = viewport.getAlignment();
+    this.gapChar = viewport.getGapCharacter();
+  }
+
+  @Override
+  protected List<SecStructPredJob> prepareJobs()
+          throws ServiceInputInvalidException
+  {
+    SeqCigar[] msf = alignmentView.getSequences();
+    SequenceI seq = msf[0].getSeq('-');
+    int[] delMap = alignmentView.getVisibleContigMapFor(seq.gapMap());
+    if (msf.length > 1)
+      throw new ServiceInputInvalidException(MessageManager.getString(
+              "error.implementation_error_multiple_single_sequence_prediction_jobs_not_supported"));
+    var seqInfo = SeqsetUtils.SeqCharacterHash(seq);
+    seq.setSequence(AlignSeq.extractGaps(Comparison.GapChars,
+            alignmentView.getASequenceString('-', 0)));
+    if (seq.getLength() < 20)
+      throw new ServiceInputInvalidException(
+              "Sequence is too short to predict with JPred - need at least 20 amino acids.");
+    var job = new SecStructPredJob(seq, delMap, seqInfo);
+    job.setStatus(JobStatus.READY);
+    return List.of(job);
+  }
+  
+  public static final int MSA_INDEX = 0;
+
+  @Override
+  protected AlignmentI collectResult(List<SecStructPredJob> jobs)
+          throws IOException
+  {
+    var job = jobs.get(0);  // There should be exactly one job
+    var status = job.getStatus();
+    Console.info(
+            String.format("sec str pred job \"%s\" finished with status %s",
+                    job.getServerJob().getJobId(), status));
+    JPredFile predictionFile = client.getPredictionFile(job.getServerJob());
+    SequenceI[] preds = predictionFile.getSeqsAsArray();
+    Alignment aln = new Alignment(preds);
+    int queryPosition = predictionFile.getQuerySeqPosition();
+    SequenceI profileSeq = aln.getSequenceAt(queryPosition);
+    if (job.delMap != null)
+    {
+      SequenceI[] seqs = (SequenceI[]) alignmentView.getAlignmentAndHiddenColumns(gapChar)[0];
+      if (MSA_INDEX >= seqs.length)
+        throw new Error(MessageManager.getString(
+                "error.implementation_error_invalid_msa_index_for_job"));
+      new RemoveGapsCommand(
+              MessageManager.getString("label.remove_gaps"),
+              new SequenceI[] { seqs[MSA_INDEX] }, currentView);
+      profileSeq.setSequence(seqs[MSA_INDEX].getSequenceAsString());
+    }
+    if (!SeqsetUtils.SeqCharacterUnhash(aln.getSequenceAt(queryPosition), job.info))
+      throw new IOException(MessageManager.getString("exception.couldnt_recover_sequence_props_for_jnet_query"));
+    aln.setDataset(currentView.getDataset());
+    try
+    {
+      JnetAnnotationMaker.add_annotation(predictionFile, aln, queryPosition, true,
+              job.delMap);
+    } catch (Exception e)
+    {
+      throw new IOException(e);
+    }
+    alignToProfileSequence(aln, profileSeq);
+    if (job.delMap != null)
+      aln.setHiddenColumns(aln.propagateInsertions(profileSeq, alignmentView));
+
+    for (AlignmentAnnotation alnAnnot : aln.getAlignmentAnnotation())
+    {
+      if (alnAnnot.sequenceRef != null)
+      {
+        AlignmentAnnotationUtils.replaceAnnotationOnAlignmentWith(alnAnnot,
+                alnAnnot.label, getClass().getSimpleName());
+      }
+    }
+    aln.setSeqrep(profileSeq);
+    return aln;
+  }
+  
+  /**
+   * Given an alignment where all other sequences except profileseq are
+   * aligned to the ungapped profileseq, insert gaps in the other sequences to
+   * realign them with the residues in profileseq.
+   * 
+   * Shamelessly copied from JPredThread.
+   * 
+   * @param al
+   * @param profileseq
+   */
+  private static void alignToProfileSequence(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];
+    }
+  }
+
+  public static class SecStructPredJob extends BaseJob
+  {
+    private final SequenceInfo info;
+
+    private final int[] delMap;
+
+    SecStructPredJob(SequenceI querySequence, int[] delMap,
+            SequenceInfo info)
+    {
+      super(List.of(querySequence));
+      this.delMap = delMap;
+      this.info = info;
+    }
+
+    @Override
+    public boolean isInputValid()
+    {
+      return true;
+    }
+  }
+
+}
index 6cb1576..ada0d2f 100644 (file)
@@ -89,12 +89,14 @@ public class JPred4WSDiscoverer extends AbstractWebServiceDiscoverer
     var actionBuilder = SecStructPredAction.newBuilder(
         new JPred4WSClient(client, false));
     actionBuilder.webService(webService);
-    actionBuilder.name("JPred4");
+    actionBuilder.msaMode(false);
+    actionBuilder.name("JPred4 (Single sequence)");
     webService.addAction(actionBuilder.build());
     
     var actionBuilderMsa = SecStructPredAction.newBuilder(
         new JPred4WSClient(client, true));
     actionBuilderMsa.webService(webService);
+    actionBuilderMsa.msaMode(true);
     actionBuilderMsa.name("JPred4 (MSA)");
     webService.addAction(actionBuilderMsa.build());