--- /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)
<tocitem text="PDB Sequence Fetcher" target="pdbfetcher" />
<tocitem text="PDB Structure Chooser" target="pdbchooser" />
<tocitem text="Chimera Viewer" target="chimera" />
- <tocitem text="Select columns by annotation" target="selectcolbyannot" />
+ <tocitem text="Select Columns by Annotation" target="selectcolbyannot" />
<tocitem text="Latest Release Notes" target="release"/>
</tocitem>
<tocitem text="Viewing Trees" target="treeviewer" expand="false" />
<tocitem text="Fetching Sequences" target="seqfetch" />
- <tocitem text="Select columns by annotation" target="selectcolbyannot" />
+ <tocitem text="Select Columns by Annotation" target="selectcolbyannot" />
<tocitem text="Nucleic Acid Support" target="nucleicAcids" expand="false">
<tocitem text="Viewing RNA structure" target="varna" />
<tocitem text="Consensus" target="calcs.consensus" />
<tocitem text="RNA Structure Consensus" target="calcs.alstrconsensus" />
<tocitem text="Annotations File Format" target="annotations.fileformat" />
- <tocitem text="Select Column by Annotation" target="calcs.annotation" />
+ <tocitem text="Select Columns by Annotation" target="selectcolbyannot" />
</tocitem>
<tocitem text="3D Structure Data" target="viewingpdbs" expand="false">
<tocitem text="PDB Sequence Fetcher" target="pdbfetcher" />
info.error_creating_file = Error creating {0} file.
exception.outofmemory_loading_mmcif_file = Out of memory loading mmCIF File
info.error_creating_file = Error creating {0} file.
+label.run_groovy = Run Groovy console script
+label.run_groovy_tip = Run the script in the Groovy console over this alignment
+label.couldnt_run_groovy_script = Failed to run Groovy script
import jalview.datamodel.AlignmentAnnotation;
+/**
+ * Interface describing a worker that calculates alignment annotation(s). The
+ * main (re-)calculation should be performed by the inherited run() method.
+ */
public interface AlignCalcWorkerI extends Runnable
{
-
+ /**
+ * Answers true if this worker updates the given annotation (regardless of its
+ * current state)
+ *
+ * @param annot
+ * @return
+ */
public boolean involves(AlignmentAnnotation annot);
+ /**
+ * Updates the display of calculated annotation values (does not recalculate
+ * the values). This allows for quick redraw of annotations when display
+ * settings are changed.
+ */
public void updateAnnotation();
- void removeOurAnnotation();
+ /**
+ * Removes any annotation managed by this worker from the alignment
+ */
+ void removeAnnotation();
}
import java.awt.dnd.DropTargetListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
+import java.awt.event.FocusAdapter;
+import java.awt.event.FocusEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyAdapter;
import java.awt.print.PrinterJob;
import java.beans.PropertyChangeEvent;
import java.io.File;
+import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
{
formatMenu.add(vsel);
}
+ addFocusListener(new FocusAdapter()
+ {
+ @Override
+ public void focusGained(FocusEvent e)
+ {
+ Desktop.setCurrentAlignFrame(AlignFrame.this);
+ }
+ });
}
.setSelected(av.getGlobalColourScheme() instanceof jalview.schemes.RNAHelicesColour);
showProducts.setEnabled(canShowProducts());
+ setGroovyEnabled(Desktop.getGroovyConsole() != null);
updateEditMenuBar();
}
+ /**
+ * Set the enabled state of the 'Run Groovy' option in the Calculate menu
+ *
+ * @param b
+ */
+ public void setGroovyEnabled(boolean b)
+ {
+ runGroovy.setEnabled(b);
+ }
+
private IProgressIndicator progressBar;
/*
return;
}
}
+
+ /**
+ * Try to run a script in the Groovy console, having first ensured that this
+ * AlignFrame is set as currentAlignFrame in Desktop, to allow the script to
+ * be targeted at this alignment.
+ */
+ @Override
+ protected void runGroovy_actionPerformed()
+ {
+ Desktop.setCurrentAlignFrame(this);
+ Object console = Desktop.instance.getGroovyConsole();
+ if (console != null)
+ {
+ /*
+ * use reflection here to avoid compile-time dependency
+ * on Groovy libraries
+ */
+ try
+ {
+ Class<?> gcClass = getClass().getClassLoader().loadClass(
+ "groovy.ui.Console");
+ Method runScript = gcClass.getMethod("runScript");
+ runScript.invoke(console);
+ } catch (Exception ex)
+ {
+ System.err.println((ex.toString()));
+ JOptionPane
+ .showInternalMessageDialog(Desktop.desktop, MessageManager
+ .getString("label.couldnt_run_groovy_script"),
+ MessageManager
+ .getString("label.groovy_support_failed"),
+ JOptionPane.ERROR_MESSAGE);
+ }
+ }
+ else
+ {
+ System.err.println("Can't run Groovy script as console not found");
+ }
+ }
}
class PrintThread extends Thread
java.lang.reflect.Method setvar = gcClass.getMethod("setVariable",
new Class[] { String.class, Object.class });
java.lang.reflect.Method run = gcClass.getMethod("run");
- Object gc = gccons.newInstance();
- setvar.invoke(gc, new Object[] { "Jalview", this });
- run.invoke(gc);
+ groovyConsole = gccons.newInstance();
+ setvar.invoke(groovyConsole, new Object[] { "Jalview", this });
+ run.invoke(groovyConsole);
+ /*
+ * and rebuild alignframe menus to enable 'Run Groovy'
+ */
+
+ AlignFrame[] alignFrames = getAlignFrames();
+ if (alignFrames != null)
+ {
+ for (AlignFrame af : alignFrames)
+ {
+ af.setGroovyEnabled(true);
+ }
+ }
} catch (Exception ex)
{
jalview.bin.Cache.log.error("Groovy Shell Creation failed.", ex);
*/
private java.util.concurrent.Semaphore block = new Semaphore(0);
+ /*
+ * groovy.ui.Console object - if Groovy jars are present and the
+ * user has activated the Groovy console. Use via reflection to
+ * avoid compile-time dependency on Groovy libraries.
+ */
+ private static Object groovyConsole;
+
/**
* add another dialog thread to the queue
*
Desktop.currentAlignFrame = currentAlignFrame;
}
+ public static Object getGroovyConsole()
+ {
+ return groovyConsole;
+ }
+
}
protected JMenu showProducts = new JMenu();
+ protected JMenuItem runGroovy = new JMenuItem();
+
protected JMenuItem rnahelicesColour = new JMenuItem();
protected JCheckBoxMenuItem autoCalculate = new JCheckBoxMenuItem();
// for show products actions see AlignFrame.canShowProducts
showProducts.setText(MessageManager.getString("label.get_cross_refs"));
+ runGroovy.setText(MessageManager.getString("label.run_groovy"));
+ runGroovy.setToolTipText(MessageManager.getString("label.run_groovy_tip"));
+ runGroovy.addActionListener(new ActionListener()
+ {
+ @Override
+ public void actionPerformed(ActionEvent e)
+ {
+ runGroovy_actionPerformed();
+ }
+ });
+
JMenuItem openFeatureSettings = new JMenuItem(
MessageManager.getString("action.feature_settings"));
openFeatureSettings.addActionListener(new ActionListener()
alignFrameMenuBar.add(colourMenu);
alignFrameMenuBar.add(calculateMenu);
alignFrameMenuBar.add(webService);
+
fileMenu.add(fetchSequence);
fileMenu.add(addSequenceMenu);
fileMenu.add(reload);
fileMenu.add(associatedData);
fileMenu.addSeparator();
fileMenu.add(closeMenuItem);
+
+ pasteMenu.add(pasteNew);
+ pasteMenu.add(pasteThis);
editMenu.add(undoMenuItem);
editMenu.add(redoMenuItem);
editMenu.add(cut);
// editMenu.addSeparator();
editMenu.add(padGapsMenuitem);
+ showMenu.add(showAllColumns);
+ showMenu.add(showAllSeqs);
+ showMenu.add(showAllhidden);
+ hideMenu.add(hideSelColumns);
+ hideMenu.add(hideSelSequences);
+ hideMenu.add(hideAllSelection);
+ hideMenu.add(hideAllButSelection);
viewMenu.add(newView);
viewMenu.add(expandViews);
viewMenu.add(gatherViews);
colourMenu.add(modifyPID);
colourMenu.add(annotationColour);
colourMenu.add(rnahelicesColour);
+
+ sort.add(sortIDMenuItem);
+ sort.add(sortLengthMenuItem);
+ sort.add(sortGroupMenuItem);
+ sort.add(sortPairwiseMenuItem);
+ sort.add(sortByTreeMenu);
calculateMenu.add(sort);
calculateMenu.add(calculateTree);
calculateMenu.addSeparator();
calculateMenu.add(autoCalculate);
calculateMenu.add(sortByTree);
calculateMenu.addSeparator();
+ calculateMenu.add(expandAlignment);
calculateMenu.add(extractScores);
+ calculateMenu.addSeparator();
+ calculateMenu.add(runGroovy);
+
webServiceNoServices = new JMenuItem(
MessageManager.getString("label.no_services"));
webService.add(webServiceNoServices);
- pasteMenu.add(pasteNew);
- pasteMenu.add(pasteThis);
- sort.add(sortIDMenuItem);
- sort.add(sortLengthMenuItem);
- sort.add(sortGroupMenuItem);
- sort.add(sortPairwiseMenuItem);
- sort.add(sortByTreeMenu);
exportImageMenu.add(htmlMenuItem);
exportImageMenu.add(epsFile);
exportImageMenu.add(createPNG);
this.getContentPane().add(statusPanel, java.awt.BorderLayout.SOUTH);
statusPanel.add(statusBar, null);
this.getContentPane().add(tabbedPane, java.awt.BorderLayout.CENTER);
- showMenu.add(showAllColumns);
- showMenu.add(showAllSeqs);
- showMenu.add(showAllhidden);
- hideMenu.add(hideSelColumns);
- hideMenu.add(hideSelSequences);
- hideMenu.add(hideAllSelection);
- hideMenu.add(hideAllButSelection);
formatMenu.add(font);
formatMenu.addSeparator();
selectMenu.add(grpsFromSelection);
selectMenu.add(deleteGroups);
selectMenu.add(annotationColumn);
- calculateMenu.add(expandAlignment);
// TODO - determine if the listenToViewSelections button is needed : see bug
// JAL-574
// selectMenu.addSeparator();
}
/**
+ * Try to run script in a Groovy console, having first ensured that this
+ * alignframe is set as currentAlignFrame in Desktop
+ */
+ protected void runGroovy_actionPerformed()
+ {
+
+ }
+ /**
* Adds the given action listener and key accelerator to the given menu item.
* Also saves in a lookup table to support lookup of action by key stroke.
*
return col == null ? null : col.brighter().brighter().brighter();
}
+ /**
+ * Returns a color between minColour and maxColour; the RGB values are in
+ * proportion to where 'value' lies between minValue and maxValue
+ *
+ * @param value
+ * @param minValue
+ * @param minColour
+ * @param maxValue
+ * @param maxColour
+ * @return
+ */
+ public static Color getGraduatedColour(float value, float minValue,
+ Color minColour, float maxValue, Color maxColour)
+ {
+ if (minValue == maxValue)
+ {
+ return minColour;
+ }
+ if (value < minValue)
+ {
+ value = minValue;
+ }
+ if (value > maxValue)
+ {
+ value = maxValue;
+ }
+
+ /*
+ * prop = proportion of the way value is from minValue to maxValue
+ */
+ float prop = (value - minValue) / (maxValue - minValue);
+ float r = minColour.getRed() + prop
+ * (maxColour.getRed() - minColour.getRed());
+ float g = minColour.getGreen() + prop
+ * (maxColour.getGreen() - minColour.getGreen());
+ float b = minColour.getBlue() + prop
+ * (maxColour.getBlue() - minColour.getBlue());
+ return new Color(r / 255, g / 255, b / 255);
+ }
}
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
+ }
+}
package jalview.workers;
import jalview.analysis.AAFrequency;
-import jalview.api.AlignCalcWorkerI;
import jalview.api.AlignViewportI;
import jalview.api.AlignmentViewPanel;
import jalview.datamodel.AlignmentAnnotation;
import java.util.Hashtable;
-public class ConsensusThread extends AlignCalcWorker implements
- AlignCalcWorkerI
+public class ConsensusThread extends AlignCalcWorker
{
public ConsensusThread(AlignViewportI alignViewport,
AlignmentViewPanel alignPanel)
package jalview.workers;
import jalview.analysis.Conservation;
-import jalview.api.AlignCalcWorkerI;
import jalview.api.AlignViewportI;
import jalview.api.AlignmentViewPanel;
import jalview.datamodel.AlignmentAnnotation;
import java.util.ArrayList;
import java.util.List;
-public class ConservationThread extends AlignCalcWorker implements
- AlignCalcWorkerI
+public class ConservationThread extends AlignCalcWorker
{
private int ConsPercGaps = 25; // JBPNote : This should be a configurable
--- /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();
+}
package jalview.workers;
import jalview.analysis.StructureFrequency;
-import jalview.api.AlignCalcWorkerI;
import jalview.api.AlignViewportI;
import jalview.api.AlignmentViewPanel;
import jalview.datamodel.AlignmentAnnotation;
import java.util.Hashtable;
-public class StrucConsensusThread extends AlignCalcWorker implements
- AlignCalcWorkerI
+public class StrucConsensusThread extends AlignCalcWorker
{
public StrucConsensusThread(AlignViewportI alignViewport,
AlignmentViewPanel alignPanel)
*/
package jalview.ws.jws2;
-import jalview.api.AlignCalcWorkerI;
import jalview.bin.Cache;
import jalview.datamodel.AlignmentAnnotation;
import jalview.datamodel.GraphLine;
import compbio.data.sequence.ScoreManager.ScoreHolder;
import compbio.metadata.Argument;
-public class AADisorderClient extends JabawsCalcWorker implements
- AlignCalcWorkerI
+public class AADisorderClient extends JabawsCalcWorker
{
private static final String THRESHOLD = "THRESHOLD";
*/
package jalview.ws.jws2;
-import jalview.api.AlignCalcWorkerI;
import jalview.datamodel.AlignmentAnnotation;
import jalview.datamodel.Annotation;
import jalview.gui.AlignFrame;
import compbio.metadata.Argument;
public class JPred301Client extends JabawsMsaInterfaceAlignCalcWorker
- implements AlignCalcWorkerI
{
-
/**
*
* @return default args for this service when run as dynamic web service
return (seqs.size() > 1);
}
+ @Override
public String getServiceActionText()
{
return "calculating consensus secondary structure prediction using JPred service";
* update the consensus annotation from the sequence profile data using
* current visualization settings.
*/
+ @Override
public void updateResultAnnotation(boolean immediate)
{
if (immediate || !calcMan.isWorking(this) && msascoreset != null)
*/
package jalview.ws.jws2;
-import jalview.api.AlignCalcWorkerI;
import jalview.datamodel.AlignmentAnnotation;
import jalview.datamodel.Annotation;
import jalview.gui.AlignFrame;
*
*/
-public class RNAalifoldClient extends JabawsCalcWorker implements
- AlignCalcWorkerI
+public class RNAalifoldClient extends JabawsCalcWorker
{
String methodName;
initViewportParams();
}
+ @Override
public String getCalcId()
{
return CALC_ID;
import jalview.io.FileLoader;
import jalview.io.FormatAdapter;
-import org.testng.AssertJUnit;
+import org.testng.Assert;
import org.testng.annotations.Test;
public class FeatureScoreModelTest
int[] sf3 = new int[] { -1, -1, -1, -1, -1, -1, 76, 77 };
- @Test(groups = { "Functional" })
- public void testFeatureScoreModel() throws Exception
+ public AlignFrame getTestAlignmentFrame()
{
AlignFrame alf = new FileLoader(false).LoadFileWaitTillLoaded(
alntestFile, FormatAdapter.PASTE);
AlignmentI al = alf.getViewport().getAlignment();
- AssertJUnit.assertEquals(4, al.getHeight());
- AssertJUnit.assertEquals(5, al.getWidth());
+ Assert.assertEquals(al.getHeight(), 4);
+ Assert.assertEquals(al.getWidth(), 5);
for (int i = 0; i < 4; i++)
{
SequenceI ds = al.getSequenceAt(i).getDatasetSequence();
alf.getFeatureRenderer().setVisible("sf2");
alf.getFeatureRenderer().setVisible("sf3");
alf.getFeatureRenderer().findAllFeatures(true);
- AssertJUnit.assertEquals("Number of feature types", 3, alf
- .getFeatureRenderer().getDisplayedFeatureTypes().size());
- AssertJUnit.assertTrue(alf.getCurrentView().areFeaturesDisplayed());
+ Assert.assertEquals(alf.getFeatureRenderer().getDisplayedFeatureTypes()
+ .size(), 3, "Number of feature types");
+ Assert.assertTrue(alf.getCurrentView().areFeaturesDisplayed());
+ return alf;
+ }
+
+ @Test(groups = { "Functional" })
+ public void testFeatureScoreModel() throws Exception
+ {
+ AlignFrame alf = getTestAlignmentFrame();
FeatureScoreModel fsm = new FeatureScoreModel();
- AssertJUnit.assertTrue(fsm.configureFromAlignmentView(alf
+ Assert.assertTrue(fsm.configureFromAlignmentView(alf
.getCurrentView().getAlignPanel()));
alf.selectAllSequenceMenuItem_actionPerformed(null);
float[][] dm = fsm.findDistances(alf.getViewport().getAlignmentView(
true));
- AssertJUnit.assertTrue("FER1_MESCR should be identical with RAPSA (2)",
- dm[0][2] == 0f);
- AssertJUnit
- .assertTrue(
- "FER1_MESCR should be further from SPIOL (1) than it is from RAPSA (2)",
- dm[0][1] > dm[0][2]);
+ Assert.assertTrue(dm[0][2] == 0f,
+ "FER1_MESCR (0) should be identical with RAPSA (2)");
+ Assert.assertTrue(dm[0][1] > dm[0][2],
+ "FER1_MESCR (0) should be further from SPIOL (1) than it is from RAPSA (2)");
+ }
+
+ @Test(groups = { "Functional" })
+ public void testFeatureScoreModel_hiddenFirstColumn() throws Exception
+ {
+ AlignFrame alf = getTestAlignmentFrame();
+ // hiding first two columns shouldn't affect the tree
+ alf.getViewport().hideColumns(0, 1);
+ FeatureScoreModel fsm = new FeatureScoreModel();
+ Assert.assertTrue(fsm.configureFromAlignmentView(alf.getCurrentView()
+ .getAlignPanel()));
+ alf.selectAllSequenceMenuItem_actionPerformed(null);
+ float[][] dm = fsm.findDistances(alf.getViewport().getAlignmentView(
+ true));
+ Assert.assertTrue(dm[0][2] == 0f,
+ "FER1_MESCR (0) should be identical with RAPSA (2)");
+ Assert.assertTrue(dm[0][1] > dm[0][2],
+ "FER1_MESCR (0) should be further from SPIOL (1) than it is from RAPSA (2)");
+ }
+ @Test(groups = { "Functional" })
+ public void testFeatureScoreModel_HiddenColumns() throws Exception
+ {
+ AlignFrame alf = getTestAlignmentFrame();
+ // hide columns and check tree changes
+ alf.getViewport().hideColumns(3, 4);
+ alf.getViewport().hideColumns(0, 1);
+ FeatureScoreModel fsm = new FeatureScoreModel();
+ Assert.assertTrue(fsm.configureFromAlignmentView(alf.getCurrentView()
+ .getAlignPanel()));
+ alf.selectAllSequenceMenuItem_actionPerformed(null);
+ float[][] dm = fsm.findDistances(alf.getViewport().getAlignmentView(
+ true));
+ Assert.assertTrue(dm[0][2] == 0f,
+ "After hiding last two columns FER1_MESCR (0) should still be identical with RAPSA (2)");
+ Assert.assertTrue(dm[0][1] == 0f,
+ "After hiding last two columns FER1_MESCR (0) should now also be identical with SPIOL (1)");
+ for (int s=0;s<3;s++)
+ {
+ Assert.assertTrue(dm[s][3] > 0f, "After hiding last two columns "
+ + alf.getViewport().getAlignment().getSequenceAt(s).getName()
+ + "(" + s + ") should still be distinct from FER1_MAIZE (3)");
+ }
}
}
assertEquals("#800080", ColorUtils.toTkCode(new Color(128, 0, 128))); // purple
assertEquals("#00ff00", ColorUtils.toTkCode(new Color(0, 255, 0))); // lime
}
+
+ @Test(groups = { "Functional" })
+ public void testGetGraduatedColour()
+ {
+ Color minColour = new Color(100, 100, 100);
+ Color maxColour = new Color(180, 200, 220);
+
+ /*
+ * value half-way between min and max
+ */
+ Color col = ColorUtils.getGraduatedColour(20f, 10f, minColour, 30f,
+ maxColour);
+ assertEquals(140, col.getRed());
+ assertEquals(150, col.getGreen());
+ assertEquals(160, col.getBlue());
+
+ /*
+ * value two-thirds of the way between min and max
+ */
+ col = ColorUtils
+ .getGraduatedColour(30f, 10f, minColour, 40f, maxColour);
+ assertEquals(153, col.getRed());
+ // Color constructor rounds float value to nearest int
+ assertEquals(167, col.getGreen());
+ assertEquals(180, col.getBlue());
+
+ /*
+ * value = min
+ */
+ col = ColorUtils
+ .getGraduatedColour(10f, 10f, minColour, 30f, maxColour);
+ assertEquals(minColour, col);
+
+ /*
+ * value = max
+ */
+ col = ColorUtils
+ .getGraduatedColour(30f, 10f, minColour, 30f, maxColour);
+ assertEquals(maxColour, col);
+
+ /*
+ * value < min
+ */
+ col = ColorUtils.getGraduatedColour(0f, 10f, minColour, 30f, maxColour);
+ assertEquals(minColour, col);
+
+ /*
+ * value > max
+ */
+ col = ColorUtils
+ .getGraduatedColour(40f, 10f, minColour, 30f,
+ maxColour);
+ assertEquals(maxColour, col);
+
+ /*
+ * min = max
+ */
+ col = ColorUtils
+ .getGraduatedColour(40f, 10f, minColour, 10f, maxColour);
+ assertEquals(minColour, col);
+ }
}