1 package jalview.ws2.actions.annotation;
3 import java.io.IOException;
4 import java.util.ArrayList;
5 import java.util.Collections;
6 import java.util.HashMap;
10 import jalview.analysis.AlignmentAnnotationUtils;
11 import jalview.api.AlignCalcManagerI2;
12 import jalview.api.AlignCalcWorkerI;
13 import jalview.api.AlignViewportI;
14 import jalview.api.FeatureColourI;
15 import jalview.api.PollableAlignCalcWorkerI;
16 import jalview.bin.Cache;
17 import jalview.bin.Console;
18 import jalview.datamodel.AlignmentAnnotation;
19 import jalview.datamodel.AlignmentI;
20 import jalview.datamodel.AnnotatedCollectionI;
21 import jalview.datamodel.Annotation;
22 import jalview.datamodel.ContiguousI;
23 import jalview.datamodel.Mapping;
24 import jalview.datamodel.SequenceI;
25 import jalview.datamodel.features.FeatureMatcherSetI;
26 import jalview.schemes.FeatureSettingsAdapter;
27 import jalview.util.ArrayUtils;
28 import jalview.util.MapList;
29 import jalview.util.MathUtils;
30 import jalview.util.Pair;
31 import jalview.workers.AlignCalcWorker;
32 import jalview.ws.params.ArgumentI;
33 import jalview.ws2.actions.BaseJob;
34 import jalview.ws2.actions.BaseTask;
35 import jalview.ws2.actions.ServiceInputInvalidException;
36 import jalview.ws2.actions.api.JobI;
37 import jalview.ws2.actions.api.TaskEventListener;
38 import jalview.ws2.actions.api.TaskI;
39 import jalview.ws2.api.Credentials;
40 import jalview.ws2.api.JobStatus;
41 import jalview.ws2.api.WebServiceJobHandle;
42 import jalview.ws2.client.api.AnnotationWebServiceClientI;
43 import jalview.ws2.helpers.DelegateJobEventListener;
44 import jalview.ws2.helpers.TaskEventSupport;
46 public class AnnotationTask extends BaseTask<AnnotationJob, AnnotationResult>
48 private AnnotationWebServiceClientI client;
50 private final AnnotationAction action;
52 private final AlignViewportI viewport;
54 private JobStatus taskStatus = null;
56 private AlignCalcWorkerAdapter worker = null;
58 private DelegateJobEventListener<AnnotationResult> jobEventHandler;
60 public AnnotationTask(AnnotationWebServiceClientI client,
61 AnnotationAction action, List<ArgumentI> args, Credentials credentials,
62 AlignViewportI viewport)
64 super(client, args, credentials);
67 this.viewport = viewport;
70 // public void start(AlignCalcManagerI2 calcManager)
72 // if (this.worker != null)
73 // throw new IllegalStateException("task already started");
74 // this.worker = new AlignCalcWorkerAdapter(calcManager);
75 // if (taskStatus != JobStatus.CANCELLED)
77 // List<AlignCalcWorkerI> oldWorkers = calcManager.getWorkersOfClass(
78 // AlignCalcWorkerAdapter.class);
79 // for (var worker : oldWorkers)
81 // if (action.getWebService().getName().equalsIgnoreCase(
82 // ((AlignCalcWorkerAdapter) worker).getServiceName()))
84 // // remove interactive workers for the same service.
85 // calcManager.removeWorker(worker);
86 // calcManager.cancelWorker(worker);
89 // if (action.getWebService().isInteractive())
90 // calcManager.registerWorker(worker);
92 // calcManager.startWorker(worker);
97 * Create and return a list of annotation jobs from the current state of the
98 * viewport. Returned job are not started by this method and should be stored
99 * in a field and started separately.
101 * @return list of annotation jobs
102 * @throws ServiceInputInvalidException
103 * input data is not valid
106 public List<AnnotationJob> prepareJobs() throws ServiceInputInvalidException
108 AlignmentI alignment = viewport.getAlignment();
109 if (alignment == null || alignment.getWidth() <= 0 ||
110 alignment.getSequences() == null)
111 throw new ServiceInputInvalidException("Alignment does not contain sequences");
112 if (alignment.isNucleotide() && !action.doAllowNucleotide())
113 throw new ServiceInputInvalidException(
114 action.getFullName() + " does not allow nucleotide sequences");
115 if (!alignment.isNucleotide() && !action.doAllowProtein())
116 throw new ServiceInputInvalidException(
117 action.getFullName() + " does not allow protein sequences");
118 boolean bySequence = !action.isAlignmentAnalysis();
119 AnnotatedCollectionI inputSeqs = bySequence ? viewport.getSelectionGroup() : null;
120 if (inputSeqs == null || inputSeqs.getWidth() <= 0 ||
121 inputSeqs.getSequences() == null || inputSeqs.getSequences().size() < 1)
122 inputSeqs = alignment;
123 boolean submitGaps = action.isAlignmentAnalysis();
124 boolean requireAligned = action.getRequireAlignedSequences();
125 boolean filterSymbols = action.getFilterSymbols();
126 int minSize = action.getMinSequences();
127 AnnotationJob job = AnnotationJob.create(inputSeqs, bySequence,
128 submitGaps, requireAligned, filterSymbols, minSize);
129 if (!job.isInputValid())
131 job.setStatus(JobStatus.INVALID);
132 throw new ServiceInputInvalidException("Annotation job has invalid input");
134 job.setStatus(JobStatus.READY);
139 protected AnnotationResult collectResult(List<AnnotationJob> jobs) throws IOException
141 final Map<String, FeatureColourI> featureColours = new HashMap<>();
142 final Map<String, FeatureMatcherSetI> featureFilters = new HashMap<>();
143 var job = jobs.get(0);
144 List<AlignmentAnnotation> returnedAnnot = client.attachAnnotations(
145 job.getServerJob(), job.getInputSequences(), featureColours,
148 * copy over each annotation row returned and also defined on each
149 * sequence, excluding regions not annotated due to gapMap/column
152 udpateCalcId(returnedAnnot);
153 int graphGroup = viewport.getAlignment().getLastGraphGroup();
154 shiftGraphGroup(returnedAnnot, graphGroup);
155 List<AlignmentAnnotation> annotations = new ArrayList<>();
156 for (AlignmentAnnotation ala : returnedAnnot)
158 SequenceI aseq = null;
159 if (ala.sequenceRef != null) {
160 SequenceI seq = job.seqNames.get(ala.sequenceRef.getName());
161 aseq = seq.getRootDatasetSequence();
163 ala.sequenceRef = aseq;
164 Annotation[] gappedAnnots = createGappedAnnotations(ala.annotations, job.start, job.gapMap);
165 ala.annotations = gappedAnnots;
167 AlignmentAnnotation newAnnot = viewport.getAlignment()
168 .updateFromOrCopyAnnotation(ala);
171 aseq.addAlignmentAnnotation(newAnnot);
172 newAnnot.adjustForAlignment();
173 AlignmentAnnotationUtils.replaceAnnotationOnAlignmentWith(
174 newAnnot, newAnnot.label, newAnnot.getCalcId());
176 annotations.add(newAnnot);
179 boolean hasFeatures = false;
180 for (SequenceI sq : job.getInputSequences())
182 if (!sq.getFeatures().hasFeatures() && (sq.getDBRefs() == null || sq.getDBRefs().isEmpty()))
185 SequenceI seq = job.seqNames.get(sq.getName());
186 SequenceI datasetSeq = seq.getRootDatasetSequence();
187 List<ContiguousI> sourceRange = findContiguousRanges(datasetSeq, job.gapMap, job.start, job.end);
188 int[] sourceStartEnd = ContiguousI.toStartEndArray(sourceRange);
189 Mapping mp = new Mapping(new MapList(
190 sourceStartEnd, new int[]
191 { datasetSeq.getStart(), datasetSeq.getEnd() }, 1, 1));
192 datasetSeq.transferAnnotation(sq, mp);
195 return new AnnotationResult(annotations, hasFeatures, featureColours, featureFilters);
199 * Updates calcId on provided annotations if not already set.
201 public void udpateCalcId(Iterable<AlignmentAnnotation> annotations)
203 for (var annotation : annotations)
205 if (annotation.getCalcId() == null || annotation.getCalcId().isEmpty())
207 annotation.setCalcId(action.getFullName());
209 annotation.autoCalculated = action.isAlignmentAnalysis() &&
210 action.getWebService().isInteractive();
214 private static void shiftGraphGroup(Iterable<AlignmentAnnotation> annotations, int shift)
216 for (AlignmentAnnotation ala : annotations)
218 if (ala.graphGroup > 0)
220 ala.graphGroup += shift;
225 private Annotation[] createGappedAnnotations(Annotation[] annotations, int start, boolean[] gapMap)
227 var size = Math.max(viewport.getAlignment().getWidth(), gapMap.length);
228 Annotation[] gappedAnnotations = new Annotation[size];
229 for (int p = 0, ap = start; ap < size; ap++)
231 if (gapMap != null && gapMap.length > ap && !gapMap[ap])
233 gappedAnnotations[ap] = new Annotation("", "", ' ', Float.NaN);
235 else if (p < annotations.length)
237 gappedAnnotations[ap] = annotations[p++];
240 return gappedAnnotations;
243 private List<ContiguousI> findContiguousRanges(SequenceI seq, boolean[] gapMap, int start, int end)
245 if (gapMap == null || gapMap.length < end)
246 return List.of(seq.findPositions(start, end));
247 List<ContiguousI> ranges = new ArrayList<>();
248 int lastcol = start, col = start;
251 if (col == end || !gapMap[col])
254 ranges.add(seq.findPositions(lastcol, col));
257 } while (++col <= end);
262 public String toString()
264 var status = taskStatus != null ? taskStatus.name() : "UNSET";
265 return String.format("%s(%x, %s)", getClass().getSimpleName(), uid, status);