--- /dev/null
+import jalview.workers.FeatureCounterI;
+import jalview.workers.AlignmentAnnotationFactory;
+
+/*
+ * Example script that registers two alignment annotation calculators
+ * - one that counts residues in a column with Pfam annotation
+ * - one that counts only charged residues with Pfam annotation
+ * Modify this example as required to count by column any desired value that can be
+ * derived from the residue and sequence features at each position of an alignment.
+ */
+
+/*
+ * A closure that returns true for any Charged residue
+ */
+def isCharged = { residue ->
+ switch(residue) {
+ case ['D', 'd', 'E', 'e', 'H', 'h', 'K', 'k', 'R', 'r']:
+ return true
+ }
+ false
+}
+
+/*
+ * A closure that returns 1 if sequence features include type 'Pfam', else 0
+ * Argument should be a list of SequenceFeature
+ */
+def hasPfam = { features ->
+ for (sf in features)
+ {
+ /*
+ * Here we inspect the type of the sequence feature.
+ * You can also test sf.description, sf.score, sf.featureGroup,
+ * sf.strand, sf.phase, sf.begin, sf.end
+ * or sf.getValue(attributeName) for GFF 'column 9' properties
+ */
+ if ("Pfam".equals(sf.type))
+ {
+ return true
+ }
+ }
+ false
+}
+
+/*
+ * Closure that counts residues with a Pfam feature annotation
+ * Parameters are
+ * - the name (label) for the alignment annotation
+ * - the description (tooltip) for the annotation
+ * - a closure (groovy function) that tests whether to include a residue
+ * - a closure that tests whether to increment count based on sequence features
+ */
+def getColumnCounter = { name, desc, residueTester, featureCounter ->
+ [
+ getName: { name },
+ getDescription: { desc },
+ getMinColour: { [0, 255, 255] }, // cyan
+ getMaxColour: { [0, 0, 255] }, // blue
+ count:
+ { res, feats ->
+ def c = 0
+ if (residueTester.call(res))
+ {
+ if (featureCounter.call(feats))
+ {
+ c++
+ }
+ }
+ c
+ }
+ ] as FeatureCounterI
+}
+
+/*
+ * Define annotation that counts any residue with Pfam domain annotation
+ */
+def pfamAnnotation = getColumnCounter("Pfam", "Count of residues with Pfam domain annotation", {true}, hasPfam)
+
+/*
+ * Define annotation that counts charged residues with Pfam domain annotation
+ */
+def chargedPfamAnnotation = getColumnCounter("Pfam charged", "Count of charged residues with Pfam domain annotation", isCharged, hasPfam)
+
+/*
+ * Register the annotations
+ */
+AlignmentAnnotationFactory.newCalculator(pfamAnnotation)
+AlignmentAnnotationFactory.newCalculator(chargedPfamAnnotation)
--- /dev/null
+import jalview.workers.AlignmentAnnotationFactory;
+import jalview.workers.AnnotationProviderI;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.Annotation;
+import jalview.util.ColorUtils;
+import jalview.util.Comparison;
+import java.awt.Color;
+
+/*
+ * Example script to compute two alignment annotations
+ * - count of Phosphorylation features
+ * - count of Turn features
+ * To try this, first load example file uniref50.fa and load on features file
+ * exampleFeatures.txt, before running this script
+ *
+ * The script only needs to be run once - it will be registered by Jalview
+ * and recalculated automatically when the alignment changes.
+ */
+
+/*
+ * A closure that returns true if value includes "PHOSPHORYLATION"
+ */
+def phosCounter = { type -> type.contains("PHOSPHORYLATION") }
+
+/*
+ * A closure that returns true if value includes "TURN"
+ */
+def turnCounter = { type -> type.contains("TURN") }
+
+/*
+ * A closure that computes and returns an array of Annotation values,
+ * one for each column of the alignment
+ */
+def getAnnotations(al, fr, counter)
+{
+ def width = al.width
+ def counts = new int[width]
+ def max = 0
+
+ /*
+ * count features in each column, record the maximum value
+ */
+ for (col = 0 ; col < width ; col++)
+ {
+ def count = 0
+ for (row = 0 ; row < al.height ; row++)
+ {
+ seq = al.getSequenceAt(row)
+ if (seq != null && col < seq.getLength())
+ {
+ def res = seq.getCharAt(col)
+ if (!Comparison.isGap(res))
+ {
+ pos = seq.findPosition(col)
+ features = fr.findFeaturesAtRes(seq, pos)
+ for (feature in features)
+ {
+ if (counter.call(feature.type))
+ {
+ count++
+ }
+ }
+ }
+ }
+ }
+ counts[col] = count
+ if (count > max)
+ {
+ max = count
+ }
+ }
+
+ /*
+ * make the Annotation objects, with a graduated colour scale
+ * (from min value to max value) for the histogram bars
+ */
+ def zero = '0' as char
+ def anns = new Annotation[width]
+ for (col = 0 ; col < width ; col++)
+ {
+ def c = counts[col]
+ if (c > 0)
+ {
+ Color color = ColorUtils.getGraduatedColour(c, 0, Color.cyan,
+ max, Color.blue)
+ anns[col] = AlignmentAnnotationFactory.newAnnotation(String.valueOf(c),
+ String.valueOf(c), zero, c, color)
+ }
+ }
+ anns
+}
+
+/*
+ * Define the method that performs the calculations, and builds two
+ * AlignmentAnnotation objects
+ */
+def annotator =
+ [ calculateAnnotation: { al, fr ->
+ def phosAnns = getAnnotations(al, fr, phosCounter)
+ def ann1 = AlignmentAnnotationFactory.newAlignmentAnnotation("Phosphorylation", "Count of Phosphorylation features", phosAnns)
+ def turnAnns = getAnnotations(al, fr, turnCounter)
+ def ann2 = AlignmentAnnotationFactory.newAlignmentAnnotation("Turn", "Count of Turn features", turnAnns)
+ return [ann1, ann2]
+ }
+ ] as AnnotationProviderI
+
+/*
+ * Register the annotation calculator with Jalview
+ */
+AlignmentAnnotationFactory.newCalculator(annotator)
protected AlignmentViewPanel ap;
- protected List<AlignmentAnnotation> ourAnnots = null;
+ protected List<AlignmentAnnotation> ourAnnots;
public AlignCalcWorker(AlignViewportI alignViewport,
AlignmentViewPanel alignPanel)
}
+ @Override
public boolean involves(AlignmentAnnotation i)
{
return ourAnnots != null && ourAnnots.contains(i);
}
/**
- * permanently remove from the alignment all annotation rows managed by this
+ * Permanently removes from the alignment all annotation rows managed by this
* worker
*/
@Override
- public void removeOurAnnotation()
+ public void removeAnnotation()
{
if (ourAnnots != null && alignViewport != null)
{
alignment.deleteAnnotation(aa, true);
}
}
+ ourAnnots.clear();
}
}
// TODO: allow GUI to query workers associated with annotation to add items to
--- /dev/null
+package jalview.workers;
+
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.Annotation;
+import jalview.gui.AlignFrame;
+import jalview.gui.Desktop;
+
+import java.awt.Color;
+
+/**
+ * Factory class with methods which allow clients (including external scripts
+ * such as Groovy) to 'register and forget' an alignment annotation calculator. <br>
+ * Currently supports two flavours of calculator:
+ * <ul>
+ * <li>a 'feature counter' which can count any desired property derivable from
+ * residue value and any sequence features at each position of the alignment</li>
+ * <li>a 'general purpose' calculator which computes one more complete
+ * AlignmentAnnotation objects</li>
+ * </ul>
+ */
+public class AlignmentAnnotationFactory
+{
+ /**
+ * Constructs and registers a new alignment annotation worker
+ *
+ * @param counter
+ * provider of feature counts per alignment position
+ */
+ public static void newCalculator(FeatureCounterI counter)
+ {
+ if (Desktop.getCurrentAlignFrame() != null)
+ {
+ newCalculator(Desktop.getCurrentAlignFrame(), counter);
+ }
+ else
+ {
+ System.err
+ .println("Can't register calculator as no alignment window has focus");
+ }
+ }
+
+ /**
+ * Constructs and registers a new alignment annotation worker
+ *
+ * @param af
+ * the AlignFrame for which the annotation is to be calculated
+ * @param counter
+ * provider of feature counts per alignment position
+ */
+ public static void newCalculator(AlignFrame af, FeatureCounterI counter)
+ {
+ new ColumnCounterWorker(af, counter);
+ }
+
+ /**
+ * Constructs and registers a new alignment annotation worker
+ *
+ * @param calculator
+ * provider of AlignmentAnnotation for the alignment
+ */
+ public static void newCalculator(AnnotationProviderI calculator)
+ {
+ if (Desktop.getCurrentAlignFrame() != null)
+ {
+ newCalculator(Desktop.getCurrentAlignFrame(), calculator);
+ }
+ else
+ {
+ System.err
+ .println("Can't register calculator as no alignment window has focus");
+ }
+ }
+
+ /**
+ * Constructs and registers a new alignment annotation worker
+ *
+ * @param af
+ * the AlignFrame for which the annotation is to be calculated
+ * @param calculator
+ * provider of AlignmentAnnotation for the alignment
+ */
+ public static void newCalculator(AlignFrame af,
+ AnnotationProviderI calculator)
+ {
+ new AnnotationWorker(af, calculator);
+ }
+
+ /**
+ * Factory method to construct an Annotation object
+ *
+ * @param displayChar
+ * @param desc
+ * @param secondaryStructure
+ * @param val
+ * @param color
+ * @return
+ */
+ public static Annotation newAnnotation(String displayChar, String desc,
+ char secondaryStructure, float val, Color color)
+ {
+ return new Annotation(displayChar, desc, secondaryStructure, val, color);
+ }
+
+ /**
+ * Factory method to construct an AlignmentAnnotation object
+ *
+ * @param name
+ * @param desc
+ * @param anns
+ * @return
+ */
+ public static AlignmentAnnotation newAlignmentAnnotation(String name,
+ String desc, Annotation[] anns)
+ {
+ return new AlignmentAnnotation(name, desc, anns);
+ }
+}
--- /dev/null
+package jalview.workers;
+
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentI;
+import jalview.gui.FeatureRenderer;
+
+import java.util.List;
+
+/**
+ * Interface to be satisfied by any class which computes one or more alignment
+ * annotations
+ */
+public interface AnnotationProviderI
+{
+ List<AlignmentAnnotation> calculateAnnotation(AlignmentI al,
+ FeatureRenderer fr);
+}
--- /dev/null
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ *
+ * This file is part of Jalview.
+ *
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * Jalview is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.workers;
+
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentI;
+import jalview.gui.AlignFrame;
+import jalview.gui.AlignmentPanel;
+import jalview.gui.FeatureRenderer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A class to create and update one or more alignment annotations, given a
+ * 'calculator'.
+ *
+ */
+class AnnotationWorker extends AlignCalcWorker
+{
+ /*
+ * the provider of the annotation calculations
+ */
+ AnnotationProviderI counter;
+
+ /**
+ * Constructor
+ *
+ * @param af
+ * @param counter
+ */
+ public AnnotationWorker(AlignFrame af, AnnotationProviderI counter)
+ {
+ super(af.getViewport(), af.alignPanel);
+ ourAnnots = new ArrayList<AlignmentAnnotation>();
+ this.counter = counter;
+ calcMan.registerWorker(this);
+ }
+
+ @Override
+ public void run()
+ {
+ try
+ {
+ calcMan.notifyStart(this);
+
+ while (!calcMan.notifyWorking(this))
+ {
+ try
+ {
+ Thread.sleep(200);
+ } catch (InterruptedException ex)
+ {
+ ex.printStackTrace();
+ }
+ }
+ if (alignViewport.isClosed())
+ {
+ abortAndDestroy();
+ return;
+ }
+
+ removeAnnotations();
+ AlignmentI alignment = alignViewport.getAlignment();
+ if (alignment != null)
+ {
+ try
+ {
+ List<AlignmentAnnotation> anns = counter.calculateAnnotation(
+ alignment, new FeatureRenderer((AlignmentPanel) ap));
+ for (AlignmentAnnotation ann : anns)
+ {
+ ann.showAllColLabels = true;
+ ann.graph = AlignmentAnnotation.BAR_GRAPH;
+ ourAnnots.add(ann);
+ alignment.addAnnotation(ann);
+ }
+ } catch (IndexOutOfBoundsException x)
+ {
+ // probable race condition. just finish and return without any fuss.
+ return;
+ }
+ }
+ } catch (OutOfMemoryError error)
+ {
+ ap.raiseOOMWarning("calculating annotations", error);
+ calcMan.workerCannotRun(this);
+ } finally
+ {
+ calcMan.workerComplete(this);
+ }
+
+ if (ap != null)
+ {
+ ap.adjustAnnotationHeight();
+ ap.paintAlignment(true);
+ }
+
+ }
+
+ /**
+ * Remove all our annotations before re-calculating them
+ */
+ void removeAnnotations()
+ {
+ for (AlignmentAnnotation ann : ourAnnots)
+ {
+ alignViewport.getAlignment().deleteAnnotation(ann);
+ }
+ ourAnnots.clear();
+ }
+
+ @Override
+ public void updateAnnotation()
+ {
+ // do nothing
+ }
+}
--- /dev/null
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ *
+ * This file is part of Jalview.
+ *
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * Jalview is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.workers;
+
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.Annotation;
+import jalview.datamodel.SequenceFeature;
+import jalview.datamodel.SequenceI;
+import jalview.gui.AlignFrame;
+import jalview.gui.AlignmentPanel;
+import jalview.gui.FeatureRenderer;
+import jalview.util.ColorUtils;
+import jalview.util.Comparison;
+
+import java.awt.Color;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A class to compute an alignment annotation with column counts of any
+ * properties of interest of positions in an alignment. <br>
+ * This is designed to be extensible, by supplying to the constructor an object
+ * that computes a count for each residue position, based on the residue value
+ * and any sequence features at that position.
+ *
+ */
+class ColumnCounterWorker extends AlignCalcWorker
+{
+ FeatureCounterI counter;
+
+ /**
+ * Constructor registers the annotation for the given alignment frame
+ *
+ * @param af
+ * @param counter
+ */
+ public ColumnCounterWorker(AlignFrame af, FeatureCounterI counter)
+ {
+ super(af.getViewport(), af.alignPanel);
+ ourAnnots = new ArrayList<AlignmentAnnotation>();
+ this.counter = counter;
+ calcMan.registerWorker(this);
+ }
+
+ /**
+ * method called under control of AlignCalcManager to recompute the annotation
+ * when the alignment changes
+ */
+ @Override
+ public void run()
+ {
+ try
+ {
+ calcMan.notifyStart(this);
+
+ while (!calcMan.notifyWorking(this))
+ {
+ try
+ {
+ Thread.sleep(200);
+ } catch (InterruptedException ex)
+ {
+ ex.printStackTrace();
+ }
+ }
+ if (alignViewport.isClosed())
+ {
+ abortAndDestroy();
+ return;
+ }
+
+ removeAnnotation();
+ if (alignViewport.getAlignment() != null)
+ {
+ try
+ {
+ computeAnnotations();
+ } catch (IndexOutOfBoundsException x)
+ {
+ // probable race condition. just finish and return without any fuss.
+ return;
+ }
+ }
+ } catch (OutOfMemoryError error)
+ {
+ ap.raiseOOMWarning("calculating feature counts", error);
+ calcMan.workerCannotRun(this);
+ } finally
+ {
+ calcMan.workerComplete(this);
+ }
+
+ if (ap != null)
+ {
+ ap.adjustAnnotationHeight();
+ ap.paintAlignment(true);
+ }
+
+ }
+
+ /**
+ * Scan each column of the alignment to calculate a count by feature type. Set
+ * the count as the value of the alignment annotation for that feature type.
+ */
+ void computeAnnotations()
+ {
+ FeatureRenderer fr = new FeatureRenderer((AlignmentPanel) ap);
+ // TODO use the commented out code once JAL-2075 is fixed
+ // to get adequate performance on genomic length sequence
+ AlignmentI alignment = alignViewport.getAlignment();
+ // AlignmentView alignmentView = alignViewport.getAlignmentView(false);
+ // AlignmentI alignment = alignmentView.getVisibleAlignment(' ');
+
+ // int width = alignmentView.getWidth();
+ int width = alignment.getWidth();
+ int height = alignment.getHeight();
+ int[] counts = new int[width];
+ int max = 0;
+
+ for (int col = 0; col < width; col++)
+ {
+ int count = 0;
+ for (int row = 0; row < height; row++)
+ {
+ count += countFeaturesAt(alignment, col, row, fr);
+ }
+ counts[col] = count;
+ max = Math.max(count, max);
+ }
+
+ Annotation[] anns = new Annotation[width];
+ /*
+ * add non-zero counts as annotations
+ */
+ for (int i = 0; i < counts.length; i++)
+ {
+ int count = counts[i];
+ if (count > 0)
+ {
+ Color color = ColorUtils.getGraduatedColour(count, 0, Color.cyan,
+ max, Color.blue);
+ anns[i] = new Annotation(String.valueOf(count),
+ String.valueOf(count), '0', count, color);
+ }
+ }
+
+ /*
+ * construct the annotation, save it and add it to the displayed alignment
+ */
+ AlignmentAnnotation ann = new AlignmentAnnotation(counter.getName(),
+ counter.getDescription(), anns);
+ ann.showAllColLabels = true;
+ ann.graph = AlignmentAnnotation.BAR_GRAPH;
+ ourAnnots.add(ann);
+ alignViewport.getAlignment().addAnnotation(ann);
+ }
+
+ /**
+ * Returns a count of any feature types present at the specified position of
+ * the alignment
+ *
+ * @param alignment
+ * @param col
+ * @param row
+ * @param fr
+ */
+ int countFeaturesAt(AlignmentI alignment, int col, int row,
+ FeatureRenderer fr)
+ {
+ SequenceI seq = alignment.getSequenceAt(row);
+ if (seq == null)
+ {
+ return 0;
+ }
+ if (col >= seq.getLength())
+ {
+ return 0;// sequence doesn't extend this far
+ }
+ char res = seq.getCharAt(col);
+ if (Comparison.isGap(res))
+ {
+ return 0;
+ }
+ int pos = seq.findPosition(col);
+
+ /*
+ * compute a count for any displayed features at residue
+ */
+ // NB have to adjust pos if using AlignmentView.getVisibleAlignment
+ // see JAL-2075
+ List<SequenceFeature> features = fr.findFeaturesAtRes(seq, pos);
+ int count = this.counter.count(String.valueOf(res), features);
+ return count;
+ }
+
+ /**
+ * Method called when the user changes display options that may affect how the
+ * annotation is rendered, but do not change its values. Currently no such
+ * options affect user-defined annotation, so this method does nothing.
+ */
+ @Override
+ public void updateAnnotation()
+ {
+ // do nothing
+ }
+}
--- /dev/null
+package jalview.workers;
+
+import jalview.datamodel.SequenceFeature;
+
+import java.util.List;
+
+/**
+ * An interface for a type that returns counts of any value of interest at a
+ * sequence position that can be determined from the sequence character and any
+ * features present at that position
+ *
+ */
+public interface FeatureCounterI
+{
+ /**
+ * Returns a count of some property of interest, for example
+ * <ul>
+ * <li>the number of variant features at the position</li>
+ * <li>the number of Cath features of status 'True Positive'</li>
+ * <li>1 if the residue is hydrophobic, else 0</li>
+ * <li>etc</li>
+ * </ul>
+ *
+ * @param residue
+ * the residue (or gap) at the position
+ * @param a
+ * list of any sequence features which include the position
+ */
+ int count(String residue, List<SequenceFeature> features);
+
+ /**
+ * Returns a name for the annotation that this is counting, for use as the
+ * displayed label
+ *
+ * @return
+ */
+ String getName();
+
+ /**
+ * Returns a description for the annotation, for display as a tooltip
+ *
+ * @return
+ */
+ String getDescription();
+
+ /**
+ * Returns the colour (as [red, green, blue] values in the range 0-255) to use
+ * for the minimum value on histogram bars. If this is different to
+ * getMaxColour(), then bars will have a graduated colour.
+ *
+ * @return
+ */
+ int[] getMinColour();
+
+ /**
+ * Returns the colour (as [red, green, blue] values in the range 0-255) to use
+ * for the maximum value on histogram bars. If this is the same as
+ * getMinColour(), then bars will have a single colour (not graduated).
+ *
+ * @return
+ */
+ int[] getMaxColour();
+}