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;
233 @Override public void startUp() throws Throwable
235 if (alignViewport.isClosed())
245 StringBuffer msg = new StringBuffer();
246 job = new JobStateSummary();
247 info = new WebserviceInfo("foo", "bar", false);
249 seqs = getInputSequences(
250 alignViewport.getAlignment(),
251 bySequence ? alignViewport.getSelectionGroup() : null);
253 if (seqs == null || !checkValidInputSeqs(seqs))
255 jalview.bin.Cache.log.debug(
256 "Sequences for analysis service were null or not valid");
260 if (guiProgress != null)
262 guiProgress.setProgressBar(service.getActionText(),
263 progressId = System.currentTimeMillis());
265 jalview.bin.Cache.log.debug("submitted " + seqs.size()
266 + " sequences to " + service.getActionText());
268 rslt = annotService.submitToService(seqs, getPreset(),
274 // TODO: handle job submission error reporting here.
275 Cache.log.debug("Service " + service.getUri() + "\nSubmitted job ID: "
278 // otherwise, construct WsJob and any UI handlers
279 running = new AnnotationWsJob();
280 running.setJobHandle(rslt);
281 running.setSeqNames(seqNames);
282 running.setStartPos(start);
283 running.setSeqs(seqs);
284 job.updateJobPanelState(info, "", running);
285 if (guiProgress != null)
287 guiProgress.registerHandler(progressId,
288 new IProgressIndicatorHandler()
292 public boolean cancelActivity(long id)
294 calcMan.cancelWorker(SeqAnnotationServiceCalcWorker.this);
299 public boolean canCancel()
307 @Override public boolean poll() throws Throwable
309 boolean finished = false;
311 Cache.log.debug("Updating status for annotation service.");
312 annotService.updateStatus(running);
313 job.updateJobPanelState(info, "", running);
314 if (running.isSubjobComplete())
317 "Finished polling analysis service job: status reported is "
318 + running.getState());
323 Cache.log.debug("Status now " + running.getState());
326 // pull any stats - some services need to flush log output before
327 // results are available
328 Cache.log.debug("Updating progress log for annotation service.");
332 annotService.updateJobProgress(running);
333 } catch (Throwable thr)
335 Cache.log.debug("Ignoring exception during progress update.",
338 Cache.log.debug("Result of poll: " + running.getStatus());
343 Cache.log.debug("Job poll loop exited. Job is " + running.getState());
344 if (running.isFinished())
346 // expect there to be results to collect
347 // configure job with the associated view's feature renderer, if one
349 // TODO: here one would also grab the 'master feature renderer' in order
351 // features automatically according to user preferences
352 running.setFeatureRenderer(
353 ((jalview.gui.AlignmentPanel) ap).cloneFeatureRenderer());
354 Cache.log.debug("retrieving job results.");
355 final Map<String, FeatureColourI> featureColours = new HashMap<>();
356 final Map<String, FeatureMatcherSetI> featureFilters = new HashMap<>();
357 List<AlignmentAnnotation> returnedAnnot = annotService
358 .getAnnotationResult(running.getJobHandle(), seqs,
359 featureColours, featureFilters);
361 Cache.log.debug("Obtained " + (returnedAnnot == null ? "no rows"
362 : ("" + returnedAnnot.size())));
363 Cache.log.debug("There were " + featureColours.size()
364 + " feature colours and " + featureFilters.size()
365 + " filters defined.");
368 // copy over each annotation row reurned and also defined on each
369 // sequence, excluding regions not annotated due to gapMap/column
372 // update calcId if it is not already set on returned annotation
373 if (returnedAnnot != null)
375 for (AlignmentAnnotation aa : returnedAnnot)
377 // assume that any CalcIds already set
378 if (getCalcId() != null && aa.getCalcId() == null
379 || "".equals(aa.getCalcId()))
381 aa.setCalcId(getCalcId());
383 // autocalculated annotation are created by interactive alignment
385 aa.autoCalculated = service.isAlignmentAnalysis()
386 && service.isInteractiveUpdate();
390 running.setAnnotation(returnedAnnot);
392 if (running.hasResults())
394 jalview.bin.Cache.log.debug("Updating result annotation from Job "
395 + rslt + " at " + service.getUri());
396 updateResultAnnotation(true);
397 if (running.isTransferSequenceFeatures())
400 // look at each sequence and lift over any features, excluding
402 // not annotated due to gapMap/column visibility
404 jalview.bin.Cache.log.debug(
405 "Updating feature display settings and transferring features from Job "
406 + rslt + " at " + service.getUri());
407 // TODO: consider merge rather than apply here
408 alignViewport.applyFeaturesStyle(new FeatureSettingsAdapter()
411 public FeatureColourI getFeatureColour(String type)
413 return featureColours.get(type);
417 public FeatureMatcherSetI getFeatureFilters(String type)
419 return featureFilters.get(type);
423 public boolean isFeatureDisplayed(String type)
425 return featureColours.containsKey(type);
429 // TODO: JAL-1150 - create sequence feature settings API for
431 // styles and enabling/disabling feature overlay on alignment panel
433 if (alignFrame.alignPanel == ap)
435 alignViewport.setShowSequenceFeatures(true);
436 alignFrame.setMenusForViewport();
439 ap.adjustAnnotationHeight();
442 Cache.log.debug("Annotation Service Worker thread finished.");
449 @Override public void cancel()
454 @Override public void done()
458 if (guiProgress != null && progressId != -1)
460 guiProgress.removeProgressBar(progressId);
462 // TODO: may not need to paintAlignment again !
463 ap.paintAlignment(false, false);
468 * validate input for dynamic/non-dynamic update context TODO: move to
469 * analysis interface ?
472 * @return true if input is valid
474 boolean checkValidInputSeqs(List<SequenceI> seqs)
477 for (SequenceI sq : seqs)
479 if (sq.getStart() <= sq.getEnd()
480 && (sq.isProtein() ? proteinAllowed : nucleotidesAllowed))
483 || sq.getLength() == (sq.getEnd() - sq.getStart() + 1))
489 return nvalid >= min_valid_seqs;
492 public void cancelCurrentJob()
496 String id = running.getJobId();
497 if (cancellable && ((CancellableI) annotService).cancel(running))
499 System.err.println("Cancelled job " + id);
503 System.err.println("Job " + id + " couldn't be cancelled.");
505 } catch (Exception q)
512 * Interactive updating. Analysis calculations that work on the currently
513 * displayed alignment data should cancel existing jobs when the input data
516 * @return true if a running job should be cancelled because new input data is
517 * available for analysis
519 boolean isInteractiveUpdate()
521 return service.isInteractiveUpdate();
525 * decide what sequences will be analysed TODO: refactor to generate
526 * List<SequenceI> for submission to service interface
532 public List<SequenceI> getInputSequences(AlignmentI alignment,
533 AnnotatedCollectionI inputSeqs)
535 if (alignment == null || alignment.getWidth() <= 0
536 || alignment.getSequences() == null || alignment.isNucleotide()
537 ? !nucleotidesAllowed
542 if (inputSeqs == null || inputSeqs.getWidth() <= 0
543 || inputSeqs.getSequences() == null
544 || inputSeqs.getSequences().size() < 1)
546 inputSeqs = alignment;
549 List<SequenceI> seqs = new ArrayList<>();
555 seqNames = new HashMap<>();
557 gapMap = new boolean[0];
558 start = inputSeqs.getStartRes();
559 end = inputSeqs.getEndRes();
560 // TODO: URGENT! unify with JPred / MSA code to handle hidden regions
562 // TODO: push attributes into WsJob instance (so they can be safely
563 // persisted/restored
564 for (SequenceI sq : (inputSeqs.getSequences()))
567 ? sq.findPosition(end + 1)
568 - sq.findPosition(start + 1) > minlen - 1
569 : sq.getEnd() - sq.getStart() > minlen - 1)
571 String newname = SeqsetUtils.unique_name(seqs.size() + 1);
572 // make new input sequence with or without gaps
573 if (seqNames != null)
575 seqNames.put(newname, sq);
580 seqs.add(seq = new jalview.datamodel.Sequence(newname,
581 sq.getSequenceAsString()));
582 if (gapMap == null || gapMap.length < seq.getLength())
584 boolean[] tg = gapMap;
585 gapMap = new boolean[seq.getLength()];
586 System.arraycopy(tg, 0, gapMap, 0, tg.length);
587 for (int p = tg.length; p < gapMap.length; p++)
589 gapMap[p] = false; // init as a gap
592 for (int apos : sq.gapMap())
594 char sqc = sq.getCharAt(apos);
595 if (!filterNonStandardResidues
596 || (sq.isProtein() ? ResidueProperties.aaIndex[sqc] < 20
597 : ResidueProperties.nucleotideIndex[sqc] < 5))
599 gapMap[apos] = true; // aligned and real amino acid residue
606 // TODO: add ability to exclude hidden regions
607 seqs.add(seq = new jalview.datamodel.Sequence(newname,
608 AlignSeq.extractGaps(jalview.util.Comparison.GapChars,
609 sq.getSequenceAsString(start, end + 1))));
610 // for annotation need to also record map to sequence start/end
612 // then transfer back to original sequence on return.
614 if (seq.getLength() > ln)
616 ln = seq.getLength();
620 if (alignedSeqs && submitGaps)
623 for (int i = 0; i < gapMap.length; i++)
630 // try real hard to return something submittable
631 // TODO: some of AAcon measures need a minimum of two or three amino
632 // acids at each position, and AAcon doesn't gracefully degrade.
633 for (int p = 0; p < seqs.size(); p++)
635 SequenceI sq = seqs.get(p);
636 // strip gapped columns
637 char[] padded = new char[realw],
638 orig = sq.getSequence();
639 for (int i = 0, pp = 0; i < realw; pp++)
643 if (orig.length > pp)
645 padded[i++] = orig[pp];
653 seqs.set(p, new jalview.datamodel.Sequence(sq.getName(),
654 new String(padded)));
661 public void updateAnnotation()
663 updateResultAnnotation(false);
666 public void updateResultAnnotation(boolean immediate)
668 if ((immediate || !calcMan.isWorking(this)) && running != null
669 && running.hasResults())
671 List<AlignmentAnnotation> ourAnnot = running.getAnnotation(),
672 newAnnots = new ArrayList<>();
674 // update graphGroup for all annotation
677 * find a graphGroup greater than any existing ones this could be a method
678 * provided by alignment Alignment.getNewGraphGroup() - returns next
682 if (alignViewport.getAlignment().getAlignmentAnnotation() != null)
684 for (AlignmentAnnotation ala : alignViewport.getAlignment()
685 .getAlignmentAnnotation())
687 if (ala.graphGroup > graphGroup)
689 graphGroup = ala.graphGroup;
694 * update graphGroup in the annotation rows returned from service
696 // TODO: look at sequence annotation rows and update graph groups in the
697 // case of reference annotation.
698 for (AlignmentAnnotation ala : ourAnnot)
700 if (ala.graphGroup > 0)
702 ala.graphGroup += graphGroup;
704 SequenceI aseq = null;
707 * transfer sequence refs and adjust gapmap
709 if (ala.sequenceRef != null)
711 SequenceI seq = running.getSeqNames()
712 .get(ala.sequenceRef.getName());
714 while (seq.getDatasetSequence() != null)
716 seq = seq.getDatasetSequence();
719 Annotation[] resAnnot = ala.annotations,
720 gappedAnnot = new Annotation[Math.max(
721 alignViewport.getAlignment().getWidth(),
723 for (int p = 0, ap = start; ap < gappedAnnot.length; ap++)
725 if (gapMap != null && gapMap.length > ap && !gapMap[ap])
727 gappedAnnot[ap] = new Annotation("", "", ' ', Float.NaN);
729 else if (p < resAnnot.length)
731 gappedAnnot[ap] = resAnnot[p++];
734 ala.sequenceRef = aseq;
735 ala.annotations = gappedAnnot;
736 AlignmentAnnotation newAnnot = getAlignViewport().getAlignment()
737 .updateFromOrCopyAnnotation(ala);
741 aseq.addAlignmentAnnotation(newAnnot);
742 newAnnot.adjustForAlignment();
744 AlignmentAnnotationUtils.replaceAnnotationOnAlignmentWith(
745 newAnnot, newAnnot.label, newAnnot.getCalcId());
747 newAnnots.add(newAnnot);
750 for (SequenceI sq : running.getSeqs())
752 if (!sq.getFeatures().hasFeatures()
753 && (sq.getDBRefs() == null || sq.getDBRefs().size() == 0))
757 running.setTransferSequenceFeatures(true);
758 SequenceI seq = running.getSeqNames().get(sq.getName());
760 ContiguousI seqRange = seq.findPositions(start, end);
762 while ((dseq = seq).getDatasetSequence() != null)
764 seq = seq.getDatasetSequence();
766 List<ContiguousI> sourceRange = new ArrayList();
767 if (gapMap != null && gapMap.length >= end)
769 int lastcol = start, col = start;
772 if (col == end || !gapMap[col])
774 if (lastcol <= (col - 1))
776 seqRange = seq.findPositions(lastcol, col);
777 sourceRange.add(seqRange);
781 } while (++col <= end);
785 sourceRange.add(seq.findPositions(start, end));
788 int source_startend[] = new int[sourceRange.size() * 2];
790 for (ContiguousI range : sourceRange)
792 source_startend[i++] = range.getBegin();
793 source_startend[i++] = range.getEnd();
795 Mapping mp = new Mapping(
796 new MapList(source_startend, new int[]
797 { seq.getStart(), seq.getEnd() }, 1, 1));
798 dseq.transferAnnotation(sq, mp);
801 updateOurAnnots(newAnnots);
805 protected void updateOurAnnots(List<AlignmentAnnotation> ourAnnot)
807 List<AlignmentAnnotation> our = ourAnnots;
808 ourAnnots = ourAnnot;
809 AlignmentI alignment = alignViewport.getAlignment();
814 for (AlignmentAnnotation an : our)
816 if (!ourAnnots.contains(an))
818 // remove the old annotation
819 alignment.deleteAnnotation(an);
826 // validate rows and update Alignmment state
827 for (AlignmentAnnotation an : ourAnnots)
829 alignViewport.getAlignment().validateAnnotation(an);
831 // TODO: may need a menu refresh after this
832 // af.setMenusForViewport();
833 ap.adjustAnnotationHeight();
837 public SequenceAnnotationServiceI getService()