1 package jalview.ws2.actions.annotation;
3 import java.io.IOException;
4 import java.util.ArrayList;
5 import java.util.HashMap;
9 import jalview.api.AlignViewportI;
10 import jalview.api.FeatureColourI;
11 import jalview.datamodel.AlignmentAnnotation;
12 import jalview.datamodel.AlignmentI;
13 import jalview.datamodel.AnnotatedCollectionI;
14 import jalview.datamodel.Annotation;
15 import jalview.datamodel.ContiguousI;
16 import jalview.datamodel.Mapping;
17 import jalview.datamodel.SequenceI;
18 import jalview.datamodel.features.FeatureMatcherSetI;
19 import jalview.util.MapList;
20 import jalview.ws.params.ArgumentI;
21 import jalview.ws2.actions.BaseTask;
22 import jalview.ws2.actions.ServiceInputInvalidException;
23 import jalview.ws2.api.Credentials;
24 import jalview.ws2.api.JobStatus;
25 import jalview.ws2.client.api.AnnotationWebServiceClientI;
27 public class AnnotationTask
28 extends BaseTask<AnnotationJob, AnnotationResult>
30 private AnnotationWebServiceClientI client;
32 private final AnnotationAction action;
34 private final AlignmentI alignment;
36 private final AnnotatedCollectionI selectionGroup;
38 public AnnotationTask(AnnotationWebServiceClientI client,
39 AnnotationAction action, List<ArgumentI> args,
40 Credentials credentials, AlignViewportI viewport)
42 super(client, args, credentials);
45 this.alignment = viewport.getAlignment();
46 this.selectionGroup = viewport.getSelectionGroup();
50 * Create and return a list of annotation jobs from the current state of the
51 * viewport. Returned job are not started by this method and should be stored
52 * in a field and started separately.
54 * @return list of annotation jobs
55 * @throws ServiceInputInvalidException
56 * input data is not valid
59 public List<AnnotationJob> prepareJobs()
60 throws ServiceInputInvalidException
62 if (alignment == null || alignment.getWidth() <= 0
63 || alignment.getSequences() == null)
64 throw new ServiceInputInvalidException(
65 "Alignment does not contain sequences");
66 if (alignment.isNucleotide() && !action.doAllowNucleotide())
67 throw new ServiceInputInvalidException(action.getFullName()
68 + " does not allow nucleotide sequences");
69 if (!alignment.isNucleotide() && !action.doAllowProtein())
70 throw new ServiceInputInvalidException(
71 action.getFullName() + " does not allow protein sequences");
72 boolean bySequence = !action.isAlignmentAnalysis();
73 AnnotatedCollectionI inputSeqs = bySequence ? selectionGroup : null;
74 if (inputSeqs == null || inputSeqs.getWidth() <= 0
75 || inputSeqs.getSequences() == null
76 || inputSeqs.getSequences().size() < 1)
77 inputSeqs = alignment;
78 boolean submitGaps = action.isAlignmentAnalysis();
79 boolean requireAligned = action.getRequireAlignedSequences();
80 boolean filterSymbols = action.getFilterSymbols();
81 int minSize = action.getMinSequences();
82 AnnotationJob job = AnnotationJob.create(inputSeqs, bySequence,
83 submitGaps, requireAligned, filterSymbols, minSize);
84 if (!job.isInputValid())
86 job.setStatus(JobStatus.INVALID);
87 throw new ServiceInputInvalidException(
88 "Annotation job has invalid input");
90 job.setStatus(JobStatus.READY);
95 protected AnnotationResult collectResult(List<AnnotationJob> jobs)
98 final Map<String, FeatureColourI> featureColours = new HashMap<>();
99 final Map<String, FeatureMatcherSetI> featureFilters = new HashMap<>();
100 var job = jobs.get(0);
101 List<AlignmentAnnotation> returnedAnnot = client.attachAnnotations(
102 job.getServerJob(), job.getInputSequences(), featureColours,
105 * copy over each annotation row returned and also defined on each
106 * sequence, excluding regions not annotated due to gapMap/column
109 udpateCalcId(returnedAnnot);
110 for (AlignmentAnnotation ala : returnedAnnot)
112 SequenceI seq = (ala.sequenceRef == null) ? null
113 : job.seqNames.get(ala.sequenceRef.getName());
114 if (job.gapMap != null && job.gapMap.length > 0)
115 ala.annotations = createGappedAnnotations(ala.annotations,
119 int startRes = seq.findPosition(job.regionStart);
120 ala.createSequenceMapping(seq, startRes, false);
124 boolean hasFeatures = false;
125 for (SequenceI sq : job.getInputSequences())
127 if (!sq.getFeatures().hasFeatures()
128 && (sq.getDBRefs() == null || sq.getDBRefs().isEmpty()))
131 SequenceI seq = job.seqNames.get(sq.getName());
132 SequenceI datasetSeq = seq.getRootDatasetSequence();
133 List<ContiguousI> sourceRange = findContiguousRanges(seq,
134 job.gapMap, job.regionStart, job.regionEnd);
135 int[] sourceStartEnd = ContiguousI.toStartEndArray(sourceRange);
136 Mapping mp = new Mapping(
139 new int[] { datasetSeq.getStart(), datasetSeq.getEnd() },
141 datasetSeq.transferAnnotation(sq, mp);
144 return new AnnotationResult(returnedAnnot, hasFeatures, featureColours,
149 * Updates calcId on provided annotations if not already set.
151 public void udpateCalcId(Iterable<AlignmentAnnotation> annotations)
153 for (var annotation : annotations)
155 if (annotation.getCalcId() == null
156 || annotation.getCalcId().isEmpty())
158 annotation.setCalcId(action.getFullName());
160 annotation.autoCalculated = action.isAlignmentAnalysis()
161 && action.getWebService().isInteractive();
165 // TODO: review and test
166 // may produce wrong output if annotations longer than gapMap
167 private Annotation[] createGappedAnnotations(Annotation[] annotations,
170 var size = Math.max(annotations.length, gapMap.length);
171 Annotation[] gappedAnnotations = new Annotation[size];
172 for (int p = 0, ap = 0; ap < size; ap++)
174 if (ap < gapMap.length && !gapMap[ap])
176 gappedAnnotations[ap] = new Annotation("", "", ' ', Float.NaN);
178 else if (p < annotations.length)
180 gappedAnnotations[ap] = annotations[p++];
183 return gappedAnnotations;
186 // TODO: review ant test!!!
187 private List<ContiguousI> findContiguousRanges(SequenceI seq,
188 boolean[] gapMap, int start, int end)
190 if (gapMap == null || gapMap.length < end)
191 return List.of(seq.findPositions(start + 1, end + 1));
192 List<ContiguousI> ranges = new ArrayList<>();
193 int lastcol = start, col = start;
196 if (col == end || !gapMap[col])
199 ranges.add(seq.findPositions(lastcol, col));
202 } while (++col <= end);