From 5fe8a7444f60733f195cd812e75d7e873e688880 Mon Sep 17 00:00:00 2001 From: Mateusz Warowny Date: Thu, 19 Oct 2023 15:45:38 +0200 Subject: [PATCH] JAL-1601 Implement sec str pred for single sequences --- .../actions/secstructpred/SecStructPredAction.java | 18 +- .../secstructpred/SecStructPredPDBSearchTask.java | 197 ++++++++++++++++++++ .../ws2/client/jpred4/JPred4WSDiscoverer.java | 4 +- 3 files changed, 216 insertions(+), 3 deletions(-) create mode 100644 src/jalview/ws2/actions/secstructpred/SecStructPredPDBSearchTask.java diff --git a/src/jalview/ws2/actions/secstructpred/SecStructPredAction.java b/src/jalview/ws2/actions/secstructpred/SecStructPredAction.java index f625f79..b623945 100644 --- a/src/jalview/ws2/actions/secstructpred/SecStructPredAction.java +++ b/src/jalview/ws2/actions/secstructpred/SecStructPredAction.java @@ -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 { protected SecStructPredWebServiceClientI client; + protected boolean msaMode; + private Builder(SecStructPredWebServiceClientI client) { super(); @@ -25,6 +28,11 @@ public class SecStructPredAction extends BaseAction 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 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 createTask(AlignViewportI viewport, List 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 index 0000000..0cb5150 --- /dev/null +++ b/src/jalview/ws2/actions/secstructpred/SecStructPredPDBSearchTask.java @@ -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 +{ + private final SecStructPredWebServiceClientI client; + + private final AlignmentView alignmentView; + private final AlignmentI currentView; + private final char gapChar; + + SecStructPredPDBSearchTask(SecStructPredWebServiceClientI client, + List 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 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 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; + } + } + +} diff --git a/src/jalview/ws2/client/jpred4/JPred4WSDiscoverer.java b/src/jalview/ws2/client/jpred4/JPred4WSDiscoverer.java index 6cb1576..ada0d2f 100644 --- a/src/jalview/ws2/client/jpred4/JPred4WSDiscoverer.java +++ b/src/jalview/ws2/client/jpred4/JPred4WSDiscoverer.java @@ -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()); -- 1.7.10.2