Merge branch 'JAL-1601-direct-jpred4-rest-service' into development/Release_2_12_Branch
[jalview.git] / src / jalview / ws2 / actions / secstructpred / SecStructPredPDBSearchTask.java
1 package jalview.ws2.actions.secstructpred;
2
3 import java.io.IOException;
4 import java.util.List;
5
6 import jalview.analysis.AlignSeq;
7 import jalview.analysis.AlignmentAnnotationUtils;
8 import jalview.analysis.SeqsetUtils;
9 import jalview.analysis.SeqsetUtils.SequenceInfo;
10 import jalview.api.AlignViewportI;
11 import jalview.bin.Console;
12 import jalview.commands.RemoveGapsCommand;
13 import jalview.datamodel.Alignment;
14 import jalview.datamodel.AlignmentAnnotation;
15 import jalview.datamodel.AlignmentI;
16 import jalview.datamodel.AlignmentView;
17 import jalview.datamodel.SeqCigar;
18 import jalview.datamodel.SequenceI;
19 import jalview.io.JPredFile;
20 import jalview.io.JnetAnnotationMaker;
21 import jalview.util.Comparison;
22 import jalview.util.MessageManager;
23 import jalview.ws.params.ArgumentI;
24 import jalview.ws2.actions.BaseJob;
25 import jalview.ws2.actions.BaseTask;
26 import jalview.ws2.actions.ServiceInputInvalidException;
27 import jalview.ws2.api.Credentials;
28 import jalview.ws2.api.JobStatus;
29 import jalview.ws2.client.api.SecStructPredWebServiceClientI;
30
31 public class SecStructPredPDBSearchTask extends
32         BaseTask<SecStructPredPDBSearchTask.SecStructPredJob, AlignmentI>
33 {
34   private final SecStructPredWebServiceClientI client;
35
36   private final AlignmentView alignmentView;
37   private final AlignmentI currentView;
38   private final char gapChar;
39
40   SecStructPredPDBSearchTask(SecStructPredWebServiceClientI client,
41           List<ArgumentI> args, Credentials credentials,
42           AlignViewportI viewport)
43   {
44     super(client, args, credentials);
45     this.client = client;
46     this.alignmentView = viewport.getAlignmentView(true);
47     this.currentView = viewport.getAlignment();
48     this.gapChar = viewport.getGapCharacter();
49   }
50
51   @Override
52   protected List<SecStructPredJob> prepareJobs()
53           throws ServiceInputInvalidException
54   {
55     SeqCigar[] msf = alignmentView.getSequences();
56     if (msf.length > 1)
57       throw new ServiceInputInvalidException(MessageManager.getString(
58               "error.implementation_error_multiple_single_sequence_prediction_jobs_not_supported"));
59     SequenceI seq = msf[0].getSeq('-');
60     if (seq == null)
61       throw new ServiceInputInvalidException("Missing sequence.");
62     int[] delMap = alignmentView.getVisibleContigMapFor(seq.gapMap());
63     var seqInfo = SeqsetUtils.SeqCharacterHash(seq);
64     seq.setSequence(AlignSeq.extractGaps(Comparison.GapChars,
65             alignmentView.getASequenceString('-', 0)));
66     if (seq.getLength() < 20)
67       throw new ServiceInputInvalidException(
68               "Sequence is too short to predict with JPred - need at least 20 amino acids.");
69     var job = new SecStructPredJob(seq, delMap, seqInfo);
70     job.setStatus(JobStatus.READY);
71     return List.of(job);
72   }
73   
74   public static final int MSA_INDEX = 0;
75
76   @Override
77   protected AlignmentI collectResult(List<SecStructPredJob> jobs)
78           throws IOException
79   {
80     var job = jobs.get(0);  // There should be exactly one job
81     var status = job.getStatus();
82     Console.info(
83             String.format("sec str pred job \"%s\" finished with status %s",
84                     job.getServerJob().getJobId(), status));
85     JPredFile predictionFile = client.getPredictionFile(job.getServerJob());
86     SequenceI[] preds = predictionFile.getSeqsAsArray();
87     Alignment aln = new Alignment(preds);
88     int queryPosition = predictionFile.getQuerySeqPosition();
89     SequenceI profileSeq = aln.getSequenceAt(queryPosition);
90     if (job.delMap != null)
91     {
92       SequenceI[] seqs = (SequenceI[]) alignmentView.getAlignmentAndHiddenColumns(gapChar)[0];
93       if (MSA_INDEX >= seqs.length)
94         throw new Error(MessageManager.getString(
95                 "error.implementation_error_invalid_msa_index_for_job"));
96       new RemoveGapsCommand(
97               MessageManager.getString("label.remove_gaps"),
98               new SequenceI[] { seqs[MSA_INDEX] }, currentView);
99       profileSeq.setSequence(seqs[MSA_INDEX].getSequenceAsString());
100     }
101     if (!SeqsetUtils.SeqCharacterUnhash(aln.getSequenceAt(queryPosition), job.info))
102       throw new IOException(MessageManager.getString("exception.couldnt_recover_sequence_props_for_jnet_query"));
103     aln.setDataset(currentView.getDataset());
104     try
105     {
106       JnetAnnotationMaker.add_annotation(predictionFile, aln, queryPosition, true,
107               job.delMap);
108     } catch (Exception e)
109     {
110       throw new IOException(e);
111     }
112     alignToProfileSequence(aln, profileSeq);
113     if (job.delMap != null)
114       aln.setHiddenColumns(aln.propagateInsertions(profileSeq, alignmentView));
115
116     for (AlignmentAnnotation alnAnnot : aln.getAlignmentAnnotation())
117     {
118       if (alnAnnot.sequenceRef != null)
119       {
120         AlignmentAnnotationUtils.replaceAnnotationOnAlignmentWith(alnAnnot,
121                 alnAnnot.label, getClass().getSimpleName());
122       }
123     }
124     aln.setSeqrep(profileSeq);
125     return aln;
126   }
127   
128   /**
129    * Given an alignment where all other sequences except profileseq are
130    * aligned to the ungapped profileseq, insert gaps in the other sequences to
131    * realign them with the residues in profileseq.
132    * 
133    * Shamelessly copied from JPredThread.
134    * 
135    * @param al
136    * @param profileseq
137    */
138   private static void alignToProfileSequence(AlignmentI al, SequenceI profileseq)
139   {
140     char gc = al.getGapCharacter();
141     int[] gapMap = profileseq.gapMap();
142     // insert gaps into profile
143     for (int lp = 0, r = 0; r < gapMap.length; r++)
144     {
145       if (gapMap[r] - lp > 1)
146       {
147         StringBuffer sb = new StringBuffer();
148         for (int s = 0, ns = gapMap[r] - lp; s < ns; s++)
149         {
150           sb.append(gc);
151         }
152         for (int s = 1, ns = al.getHeight(); s < ns; s++)
153         {
154           String sq = al.getSequenceAt(s).getSequenceAsString();
155           int diff = gapMap[r] - sq.length();
156           if (diff > 0)
157           {
158             // pad gaps
159             sq = sq + sb;
160             while ((diff = gapMap[r] - sq.length()) > 0)
161             {
162               sq = sq + ((diff >= sb.length()) ? sb.toString()
163                       : sb.substring(0, diff));
164             }
165             al.getSequenceAt(s).setSequence(sq);
166           }
167           else
168           {
169             al.getSequenceAt(s).setSequence(sq.substring(0, gapMap[r])
170                     + sb.toString() + sq.substring(gapMap[r]));
171           }
172         }
173       }
174       lp = gapMap[r];
175     }
176   }
177
178   public static class SecStructPredJob extends BaseJob
179   {
180     private final SequenceInfo info;
181
182     private final int[] delMap;
183
184     SecStructPredJob(SequenceI querySequence, int[] delMap,
185             SequenceInfo info)
186     {
187       super(List.of(querySequence));
188       this.delMap = delMap;
189       this.info = info;
190     }
191
192     @Override
193     public boolean isInputValid()
194     {
195       return true;
196     }
197   }
198
199 }