2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
21 package jalview.ws.jws2;
23 import jalview.analysis.AlignSeq;
24 import jalview.analysis.AlignmentAnnotationUtils;
25 import jalview.analysis.SeqsetUtils;
26 import jalview.api.AlignViewportI;
27 import jalview.api.AlignmentViewPanel;
28 import jalview.api.FeatureColourI;
29 import jalview.api.PollableAlignCalcWorkerI;
30 import jalview.bin.Cache;
31 import jalview.datamodel.AlignmentAnnotation;
32 import jalview.datamodel.AlignmentI;
33 import jalview.datamodel.AnnotatedCollectionI;
34 import jalview.datamodel.Annotation;
35 import jalview.datamodel.ContiguousI;
36 import jalview.datamodel.Mapping;
37 import jalview.datamodel.SequenceI;
38 import jalview.datamodel.features.FeatureMatcherSetI;
39 import jalview.gui.AlignFrame;
40 import jalview.gui.Desktop;
41 import jalview.gui.IProgressIndicator;
42 import jalview.gui.IProgressIndicatorHandler;
43 import jalview.gui.JvOptionPane;
44 import jalview.gui.WebserviceInfo;
45 import jalview.schemes.FeatureSettingsAdapter;
46 import jalview.schemes.ResidueProperties;
47 import jalview.util.MapList;
48 import jalview.util.MessageManager;
49 import jalview.workers.AlignCalcWorker;
50 import jalview.ws.JobStateSummary;
51 import jalview.ws.api.CancellableI;
52 import jalview.ws.api.JalviewServiceEndpointProviderI;
53 import jalview.ws.api.JobId;
54 import jalview.ws.api.SequenceAnnotationServiceI;
55 import jalview.ws.api.ServiceWithParameters;
56 import jalview.ws.api.WSAnnotationCalcManagerI;
57 import jalview.ws.gui.AnnotationWsJob;
58 import jalview.ws.jws2.dm.AAConSettings;
59 import jalview.ws.params.ArgumentI;
60 import jalview.ws.params.WsParamSetI;
62 import java.util.ArrayList;
63 import java.util.HashMap;
64 import java.util.List;
67 public class SeqAnnotationServiceCalcWorker extends AlignCalcWorker
68 implements WSAnnotationCalcManagerI, PollableAlignCalcWorkerI
71 protected ServiceWithParameters service;
73 protected WsParamSetI preset;
75 protected List<ArgumentI> arguments;
77 protected IProgressIndicator guiProgress;
79 protected boolean submitGaps = true;
82 * by default, we filter out non-standard residues before submission
84 protected boolean filterNonStandardResidues = true;
87 * Recover any existing parameters for this service
89 protected void initViewportParams()
91 if (getCalcId() != null)
93 ((jalview.gui.AlignViewport) alignViewport).setCalcIdSettingsFor(
95 new AAConSettings(true, service, this.preset, arguments),
102 * @return null or a string used to recover all annotation generated by this
105 public String getCalcId()
107 return service.getAlignAnalysisUI() == null ? null
108 : service.getAlignAnalysisUI().getCalcId();
111 public WsParamSetI getPreset()
116 public List<ArgumentI> getArguments()
122 * reconfigure and restart the AAConClient. This method will spawn a new
123 * thread that will wait until any current jobs are finished, modify the
124 * parameters and restart the conservation calculation with the new values.
127 * @param newarguments
129 public void updateParameters(final WsParamSetI newpreset,
130 final List<ArgumentI> newarguments)
133 arguments = newarguments;
134 calcMan.startWorker(this);
135 initViewportParams();
137 protected boolean alignedSeqs = true;
139 protected boolean nucleotidesAllowed = false;
141 protected boolean proteinAllowed = false;
144 * record sequences for mapping result back to afterwards
146 protected boolean bySequence = false;
148 protected Map<String, SequenceI> seqNames;
150 // TODO: convert to bitset
151 protected boolean[] gapMap;
159 private AlignFrame alignFrame;
161 public boolean[] getGapMap()
166 public SeqAnnotationServiceCalcWorker(ServiceWithParameters service,
167 AlignFrame alignFrame,
168 WsParamSetI preset, List<ArgumentI> paramset)
170 super(alignFrame.getCurrentView(), alignFrame.alignPanel);
171 // TODO: both these fields needed ?
172 this.alignFrame = alignFrame;
173 this.guiProgress = alignFrame;
174 this.preset = preset;
175 this.arguments = paramset;
176 this.service = service;
179 annotService = (jalview.ws.api.SequenceAnnotationServiceI) ((JalviewServiceEndpointProviderI) service)
181 } catch (ClassCastException cce)
184 JvOptionPane.showMessageDialog(Desktop.getInstance(),
185 MessageManager.formatMessage(
186 "label.service_called_is_not_an_annotation_service",
188 { service.getName() }),
189 MessageManager.getString("label.internal_jalview_error"),
190 JvOptionPane.WARNING_MESSAGE);
193 cancellable = CancellableI.class.isInstance(annotService);
194 // configure submission flags
195 proteinAllowed = service.isProteinService();
196 nucleotidesAllowed = service.isNucleotideService();
197 alignedSeqs = service.isNeedsAlignedSequences();
198 bySequence = !service.isAlignmentAnalysis();
199 filterNonStandardResidues = service.isFilterSymbols();
200 min_valid_seqs = service.getMinimumInputSequences();
201 submitGaps = service.isAlignmentAnalysis();
203 if (service.isInteractiveUpdate())
205 initViewportParams();
211 * @return true if the submission thread should attempt to submit data
213 public boolean hasService()
215 return annotService != null;
218 protected SequenceAnnotationServiceI annotService;
219 protected final boolean cancellable;
221 volatile JobId rslt = null;
223 AnnotationWsJob running = null;
225 private int min_valid_seqs;
228 private long progressId = -1;
229 JobStateSummary job = null;
230 WebserviceInfo info = null;
231 List<SequenceI> seqs = null;
234 public String getCalcName()
236 return service.getName();
239 @Override public void startUp() throws Throwable
241 if (alignViewport.isClosed())
251 StringBuffer msg = new StringBuffer();
252 job = new JobStateSummary();
253 info = new WebserviceInfo("foo", "bar", false);
255 seqs = getInputSequences(
256 alignViewport.getAlignment(),
257 bySequence ? alignViewport.getSelectionGroup() : null);
259 if (seqs == null || !checkValidInputSeqs(seqs))
261 jalview.bin.Cache.log.debug(
262 "Sequences for analysis service were null or not valid");
266 if (guiProgress != null)
268 guiProgress.setProgressBar(service.getActionText(),
269 progressId = System.currentTimeMillis());
271 jalview.bin.Cache.log.debug("submitted " + seqs.size()
272 + " sequences to " + service.getActionText());
274 rslt = annotService.submitToService(seqs, getPreset(),
280 // TODO: handle job submission error reporting here.
281 Cache.log.debug("Service " + service.getUri() + "\nSubmitted job ID: "
284 // otherwise, construct WsJob and any UI handlers
285 running = new AnnotationWsJob();
286 running.setJobHandle(rslt);
287 running.setSeqNames(seqNames);
288 running.setStartPos(start);
289 running.setSeqs(seqs);
290 job.updateJobPanelState(info, "", running);
291 if (guiProgress != null)
293 guiProgress.registerHandler(progressId,
294 new IProgressIndicatorHandler()
298 public boolean cancelActivity(long id)
300 calcMan.cancelWorker(SeqAnnotationServiceCalcWorker.this);
305 public boolean canCancel()
313 @Override public boolean poll() throws Throwable
315 boolean finished = false;
317 Cache.log.debug("Updating status for annotation service.");
318 annotService.updateStatus(running);
319 job.updateJobPanelState(info, "", running);
320 if (running.isSubjobComplete())
323 "Finished polling analysis service job: status reported is "
324 + running.getState());
329 Cache.log.debug("Status now " + running.getState());
332 // pull any stats - some services need to flush log output before
333 // results are available
334 Cache.log.debug("Updating progress log for annotation service.");
338 annotService.updateJobProgress(running);
339 } catch (Throwable thr)
341 Cache.log.debug("Ignoring exception during progress update.",
344 Cache.log.debug("Result of poll: " + running.getStatus());
349 Cache.log.debug("Job poll loop exited. Job is " + running.getState());
350 if (running.isFinished())
352 // expect there to be results to collect
353 // configure job with the associated view's feature renderer, if one
355 // TODO: here one would also grab the 'master feature renderer' in order
357 // features automatically according to user preferences
358 running.setFeatureRenderer(
359 ((jalview.gui.AlignmentPanel) ap).cloneFeatureRenderer());
360 Cache.log.debug("retrieving job results.");
361 final Map<String, FeatureColourI> featureColours = new HashMap<>();
362 final Map<String, FeatureMatcherSetI> featureFilters = new HashMap<>();
363 List<AlignmentAnnotation> returnedAnnot = annotService
364 .getAnnotationResult(running.getJobHandle(), seqs,
365 featureColours, featureFilters);
367 Cache.log.debug("Obtained " + (returnedAnnot == null ? "no rows"
368 : ("" + returnedAnnot.size())));
369 Cache.log.debug("There were " + featureColours.size()
370 + " feature colours and " + featureFilters.size()
371 + " filters defined.");
374 // copy over each annotation row reurned and also defined on each
375 // sequence, excluding regions not annotated due to gapMap/column
378 // update calcId if it is not already set on returned annotation
379 if (returnedAnnot != null)
381 for (AlignmentAnnotation aa : returnedAnnot)
383 // assume that any CalcIds already set
384 if (getCalcId() != null && aa.getCalcId() == null
385 || "".equals(aa.getCalcId()))
387 aa.setCalcId(getCalcId());
389 // autocalculated annotation are created by interactive alignment
391 aa.autoCalculated = service.isAlignmentAnalysis()
392 && service.isInteractiveUpdate();
396 running.setAnnotation(returnedAnnot);
398 if (running.hasResults())
400 jalview.bin.Cache.log.debug("Updating result annotation from Job "
401 + rslt + " at " + service.getUri());
402 updateResultAnnotation(true);
403 if (running.isTransferSequenceFeatures())
406 // look at each sequence and lift over any features, excluding
408 // not annotated due to gapMap/column visibility
410 jalview.bin.Cache.log.debug(
411 "Updating feature display settings and transferring features from Job "
412 + rslt + " at " + service.getUri());
413 // TODO: consider merge rather than apply here
414 alignViewport.applyFeaturesStyle(new FeatureSettingsAdapter()
417 public FeatureColourI getFeatureColour(String type)
419 return featureColours.get(type);
423 public FeatureMatcherSetI getFeatureFilters(String type)
425 return featureFilters.get(type);
429 public boolean isFeatureDisplayed(String type)
431 return featureColours.containsKey(type);
435 // TODO: JAL-1150 - create sequence feature settings API for
437 // styles and enabling/disabling feature overlay on alignment panel
439 if (alignFrame.alignPanel == ap)
441 alignViewport.setShowSequenceFeatures(true);
442 alignFrame.setMenusForViewport();
445 ap.adjustAnnotationHeight();
448 Cache.log.debug("Annotation Service Worker thread finished.");
455 @Override public void cancel()
460 @Override public void done()
464 if (guiProgress != null && progressId != -1)
466 guiProgress.removeProgressBar(progressId);
468 // TODO: may not need to paintAlignment again !
469 ap.paintAlignment(false, false);
474 * validate input for dynamic/non-dynamic update context TODO: move to
475 * analysis interface ?
478 * @return true if input is valid
480 boolean checkValidInputSeqs(List<SequenceI> seqs)
483 for (SequenceI sq : seqs)
485 if (sq.getStart() <= sq.getEnd()
486 && (sq.isProtein() ? proteinAllowed : nucleotidesAllowed))
489 || sq.getLength() == (sq.getEnd() - sq.getStart() + 1))
495 return nvalid >= min_valid_seqs;
498 public void cancelCurrentJob()
502 String id = running.getJobId();
503 if (cancellable && ((CancellableI) annotService).cancel(running))
505 System.err.println("Cancelled job " + id);
509 System.err.println("Job " + id + " couldn't be cancelled.");
511 } catch (Exception q)
518 * Interactive updating. Analysis calculations that work on the currently
519 * displayed alignment data should cancel existing jobs when the input data
522 * @return true if a running job should be cancelled because new input data is
523 * available for analysis
525 boolean isInteractiveUpdate()
527 return service.isInteractiveUpdate();
531 * decide what sequences will be analysed TODO: refactor to generate
532 * List<SequenceI> for submission to service interface
538 public List<SequenceI> getInputSequences(AlignmentI alignment,
539 AnnotatedCollectionI inputSeqs)
541 if (alignment == null || alignment.getWidth() <= 0
542 || alignment.getSequences() == null || alignment.isNucleotide()
543 ? !nucleotidesAllowed
548 if (inputSeqs == null || inputSeqs.getWidth() <= 0
549 || inputSeqs.getSequences() == null
550 || inputSeqs.getSequences().size() < 1)
552 inputSeqs = alignment;
555 List<SequenceI> seqs = new ArrayList<>();
561 seqNames = new HashMap<>();
563 gapMap = new boolean[0];
564 start = inputSeqs.getStartRes();
565 end = inputSeqs.getEndRes();
566 // TODO: URGENT! unify with JPred / MSA code to handle hidden regions
568 // TODO: push attributes into WsJob instance (so they can be safely
569 // persisted/restored
570 for (SequenceI sq : (inputSeqs.getSequences()))
573 ? sq.findPosition(end + 1)
574 - sq.findPosition(start + 1) > minlen - 1
575 : sq.getEnd() - sq.getStart() > minlen - 1)
577 String newname = SeqsetUtils.unique_name(seqs.size() + 1);
578 // make new input sequence with or without gaps
579 if (seqNames != null)
581 seqNames.put(newname, sq);
586 seqs.add(seq = new jalview.datamodel.Sequence(newname,
587 sq.getSequenceAsString()));
588 if (gapMap == null || gapMap.length < seq.getLength())
590 boolean[] tg = gapMap;
591 gapMap = new boolean[seq.getLength()];
592 System.arraycopy(tg, 0, gapMap, 0, tg.length);
593 for (int p = tg.length; p < gapMap.length; p++)
595 gapMap[p] = false; // init as a gap
598 for (int apos : sq.gapMap())
600 char sqc = sq.getCharAt(apos);
601 if (!filterNonStandardResidues
602 || (sq.isProtein() ? ResidueProperties.aaIndex[sqc] < 20
603 : ResidueProperties.nucleotideIndex[sqc] < 5))
605 gapMap[apos] = true; // aligned and real amino acid residue
612 // TODO: add ability to exclude hidden regions
613 seqs.add(seq = new jalview.datamodel.Sequence(newname,
614 AlignSeq.extractGaps(jalview.util.Comparison.GapChars,
615 sq.getSequenceAsString(start, end + 1))));
616 // for annotation need to also record map to sequence start/end
618 // then transfer back to original sequence on return.
620 if (seq.getLength() > ln)
622 ln = seq.getLength();
626 if (alignedSeqs && submitGaps)
629 for (int i = 0; i < gapMap.length; i++)
636 // try real hard to return something submittable
637 // TODO: some of AAcon measures need a minimum of two or three amino
638 // acids at each position, and AAcon doesn't gracefully degrade.
639 for (int p = 0; p < seqs.size(); p++)
641 SequenceI sq = seqs.get(p);
642 // strip gapped columns
643 char[] padded = new char[realw],
644 orig = sq.getSequence();
645 for (int i = 0, pp = 0; i < realw; pp++)
649 if (orig.length > pp)
651 padded[i++] = orig[pp];
659 seqs.set(p, new jalview.datamodel.Sequence(sq.getName(),
660 new String(padded)));
667 public void updateAnnotation()
669 updateResultAnnotation(false);
672 public void updateResultAnnotation(boolean immediate)
674 if ((immediate || !calcMan.isWorking(this)) && running != null
675 && running.hasResults())
677 List<AlignmentAnnotation> ourAnnot = running.getAnnotation(),
678 newAnnots = new ArrayList<>();
680 // update graphGroup for all annotation
683 * find a graphGroup greater than any existing ones this could be a method
684 * provided by alignment Alignment.getNewGraphGroup() - returns next
688 if (alignViewport.getAlignment().getAlignmentAnnotation() != null)
690 for (AlignmentAnnotation ala : alignViewport.getAlignment()
691 .getAlignmentAnnotation())
693 if (ala.graphGroup > graphGroup)
695 graphGroup = ala.graphGroup;
700 * update graphGroup in the annotation rows returned from service
702 // TODO: look at sequence annotation rows and update graph groups in the
703 // case of reference annotation.
704 for (AlignmentAnnotation ala : ourAnnot)
706 if (ala.graphGroup > 0)
708 ala.graphGroup += graphGroup;
710 SequenceI aseq = null;
713 * transfer sequence refs and adjust gapmap
715 if (ala.sequenceRef != null)
717 SequenceI seq = running.getSeqNames()
718 .get(ala.sequenceRef.getName());
720 while (seq.getDatasetSequence() != null)
722 seq = seq.getDatasetSequence();
725 Annotation[] resAnnot = ala.annotations,
726 gappedAnnot = new Annotation[Math.max(
727 alignViewport.getAlignment().getWidth(),
729 for (int p = 0, ap = start; ap < gappedAnnot.length; ap++)
731 if (gapMap != null && gapMap.length > ap && !gapMap[ap])
733 gappedAnnot[ap] = new Annotation("", "", ' ', Float.NaN);
735 else if (p < resAnnot.length)
737 gappedAnnot[ap] = resAnnot[p++];
740 ala.sequenceRef = aseq;
741 ala.annotations = gappedAnnot;
742 AlignmentAnnotation newAnnot = getAlignViewport().getAlignment()
743 .updateFromOrCopyAnnotation(ala);
747 aseq.addAlignmentAnnotation(newAnnot);
748 newAnnot.adjustForAlignment();
750 AlignmentAnnotationUtils.replaceAnnotationOnAlignmentWith(
751 newAnnot, newAnnot.label, newAnnot.getCalcId());
753 newAnnots.add(newAnnot);
756 for (SequenceI sq : running.getSeqs())
758 if (!sq.getFeatures().hasFeatures()
759 && (sq.getDBRefs() == null || sq.getDBRefs().size() == 0))
763 running.setTransferSequenceFeatures(true);
764 SequenceI seq = running.getSeqNames().get(sq.getName());
766 ContiguousI seqRange = seq.findPositions(start, end);
768 while ((dseq = seq).getDatasetSequence() != null)
770 seq = seq.getDatasetSequence();
772 List<ContiguousI> sourceRange = new ArrayList();
773 if (gapMap != null && gapMap.length >= end)
775 int lastcol = start, col = start;
778 if (col == end || !gapMap[col])
780 if (lastcol <= (col - 1))
782 seqRange = seq.findPositions(lastcol, col);
783 sourceRange.add(seqRange);
787 } while (++col <= end);
791 sourceRange.add(seq.findPositions(start, end));
794 int source_startend[] = new int[sourceRange.size() * 2];
796 for (ContiguousI range : sourceRange)
798 source_startend[i++] = range.getBegin();
799 source_startend[i++] = range.getEnd();
801 Mapping mp = new Mapping(
802 new MapList(source_startend, new int[]
803 { seq.getStart(), seq.getEnd() }, 1, 1));
804 dseq.transferAnnotation(sq, mp);
807 updateOurAnnots(newAnnots);
811 protected void updateOurAnnots(List<AlignmentAnnotation> ourAnnot)
813 List<AlignmentAnnotation> our = ourAnnots;
814 ourAnnots = ourAnnot;
815 AlignmentI alignment = alignViewport.getAlignment();
820 for (AlignmentAnnotation an : our)
822 if (!ourAnnots.contains(an))
824 // remove the old annotation
825 alignment.deleteAnnotation(an);
832 // validate rows and update Alignmment state
833 for (AlignmentAnnotation an : ourAnnots)
835 alignViewport.getAlignment().validateAnnotation(an);
837 // TODO: may need a menu refresh after this
838 // af.setMenusForViewport();
839 ap.adjustAnnotationHeight();
843 public SequenceAnnotationServiceI getService()