JAL-1601 Expand secondary structure prediction tests
[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     SequenceI seq = msf[0].getSeq('-');
57     int[] delMap = alignmentView.getVisibleContigMapFor(seq.gapMap());
58     if (msf.length > 1)
59       throw new ServiceInputInvalidException(MessageManager.getString(
60               "error.implementation_error_multiple_single_sequence_prediction_jobs_not_supported"));
61     var seqInfo = SeqsetUtils.SeqCharacterHash(seq);
62     seq.setSequence(AlignSeq.extractGaps(Comparison.GapChars,
63             alignmentView.getASequenceString('-', 0)));
64     if (seq.getLength() < 20)
65       throw new ServiceInputInvalidException(
66               "Sequence is too short to predict with JPred - need at least 20 amino acids.");
67     var job = new SecStructPredJob(seq, delMap, seqInfo);
68     job.setStatus(JobStatus.READY);
69     return List.of(job);
70   }
71   
72   public static final int MSA_INDEX = 0;
73
74   @Override
75   protected AlignmentI collectResult(List<SecStructPredJob> jobs)
76           throws IOException
77   {
78     var job = jobs.get(0);  // There should be exactly one job
79     var status = job.getStatus();
80     Console.info(
81             String.format("sec str pred job \"%s\" finished with status %s",
82                     job.getServerJob().getJobId(), status));
83     JPredFile predictionFile = client.getPredictionFile(job.getServerJob());
84     SequenceI[] preds = predictionFile.getSeqsAsArray();
85     Alignment aln = new Alignment(preds);
86     int queryPosition = predictionFile.getQuerySeqPosition();
87     SequenceI profileSeq = aln.getSequenceAt(queryPosition);
88     if (job.delMap != null)
89     {
90       SequenceI[] seqs = (SequenceI[]) alignmentView.getAlignmentAndHiddenColumns(gapChar)[0];
91       if (MSA_INDEX >= seqs.length)
92         throw new Error(MessageManager.getString(
93                 "error.implementation_error_invalid_msa_index_for_job"));
94       new RemoveGapsCommand(
95               MessageManager.getString("label.remove_gaps"),
96               new SequenceI[] { seqs[MSA_INDEX] }, currentView);
97       profileSeq.setSequence(seqs[MSA_INDEX].getSequenceAsString());
98     }
99     if (!SeqsetUtils.SeqCharacterUnhash(aln.getSequenceAt(queryPosition), job.info))
100       throw new IOException(MessageManager.getString("exception.couldnt_recover_sequence_props_for_jnet_query"));
101     aln.setDataset(currentView.getDataset());
102     try
103     {
104       JnetAnnotationMaker.add_annotation(predictionFile, aln, queryPosition, true,
105               job.delMap);
106     } catch (Exception e)
107     {
108       throw new IOException(e);
109     }
110     alignToProfileSequence(aln, profileSeq);
111     if (job.delMap != null)
112       aln.setHiddenColumns(aln.propagateInsertions(profileSeq, alignmentView));
113
114     for (AlignmentAnnotation alnAnnot : aln.getAlignmentAnnotation())
115     {
116       if (alnAnnot.sequenceRef != null)
117       {
118         AlignmentAnnotationUtils.replaceAnnotationOnAlignmentWith(alnAnnot,
119                 alnAnnot.label, getClass().getSimpleName());
120       }
121     }
122     aln.setSeqrep(profileSeq);
123     return aln;
124   }
125   
126   /**
127    * Given an alignment where all other sequences except profileseq are
128    * aligned to the ungapped profileseq, insert gaps in the other sequences to
129    * realign them with the residues in profileseq.
130    * 
131    * Shamelessly copied from JPredThread.
132    * 
133    * @param al
134    * @param profileseq
135    */
136   private static void alignToProfileSequence(AlignmentI al, SequenceI profileseq)
137   {
138     char gc = al.getGapCharacter();
139     int[] gapMap = profileseq.gapMap();
140     // insert gaps into profile
141     for (int lp = 0, r = 0; r < gapMap.length; r++)
142     {
143       if (gapMap[r] - lp > 1)
144       {
145         StringBuffer sb = new StringBuffer();
146         for (int s = 0, ns = gapMap[r] - lp; s < ns; s++)
147         {
148           sb.append(gc);
149         }
150         for (int s = 1, ns = al.getHeight(); s < ns; s++)
151         {
152           String sq = al.getSequenceAt(s).getSequenceAsString();
153           int diff = gapMap[r] - sq.length();
154           if (diff > 0)
155           {
156             // pad gaps
157             sq = sq + sb;
158             while ((diff = gapMap[r] - sq.length()) > 0)
159             {
160               sq = sq + ((diff >= sb.length()) ? sb.toString()
161                       : sb.substring(0, diff));
162             }
163             al.getSequenceAt(s).setSequence(sq);
164           }
165           else
166           {
167             al.getSequenceAt(s).setSequence(sq.substring(0, gapMap[r])
168                     + sb.toString() + sq.substring(gapMap[r]));
169           }
170         }
171       }
172       lp = gapMap[r];
173     }
174   }
175
176   public static class SecStructPredJob extends BaseJob
177   {
178     private final SequenceInfo info;
179
180     private final int[] delMap;
181
182     SecStructPredJob(SequenceI querySequence, int[] delMap,
183             SequenceInfo info)
184     {
185       super(List.of(querySequence));
186       this.delMap = delMap;
187       this.info = info;
188     }
189
190     @Override
191     public boolean isInputValid()
192     {
193       return true;
194     }
195   }
196
197 }