--- /dev/null
+ ##gff-version 2
+ # exonerate output in gff2 format; not gff3 because
+ # - 'similarity' is not a Sequence Ontology term
+ # - attributes' name/values are separated by space ' ' not equals '='
+ ##source-version exonerate:protein2genome:local 2.2.0
+ ##date 2015-01-16
+ ##type DNA
+ #
+ # exonerate run with --showtargetgff generates 'features on the target' i.e. mappings to the query
+ # tab-delimited
+ # seqname source feature start end score strand frame attributes
+ #
+ seq1 exonerate:protein2genome:local gene 8 11 3652 - . gene_id 0 ; sequence seq2 ; gene_orientation .
+ seq1 exonerate:protein2genome:local cds 9 11 . - .
+ seq1 exonerate:protein2genome:local exon 9 11 . - . insertions 3 ; deletions 6
+ #seq1 exonerate:protein2genome:local similarity 8 11 3652 - . alignment_id 0 ; Query seq2 ; Align 11 1 3
+ seq1 exonerate:protein2genome:local similarity 9 11 3652 - . alignment_id 0 ; Query seq2 ; Align 11 1 3
+ #
+ # appending FASTA sequences is strictly a GFF3 format feature
+ # but Jalview is able to handle this mixture of GFF2 / GFF3 :-)
+ #
+ ##FASTA
+ >seq1
+ ACTACGACACGACGACGACGACG
+ >seq2
+ CDEQEATGTQDAQEQAQC
-
*/
float getThreshold();
+ void setThreshold(float f);
+
++ /**
++ * Answers true if the colour varies between the actual minimum and maximum
++ * score values of the feature, or false if between absolute minimum and
++ * maximum values (or if not a graduated colour).
++ *
++ * @return
++ */
+ boolean isAutoScaled();
+
+ void setAutoScaled(boolean b);
+
+ /**
+ * Returns the maximum score of the graduated colour range
+ *
+ * @return
+ */
+ float getMax();
+
+ /**
+ * Returns the minimum score of the graduated colour range
+ *
+ * @return
+ */
+ float getMin();
+
+ /**
+ * Answers true if either isAboveThreshold or isBelowThreshold answers true
+ *
+ * @return
+ */
+ boolean hasThreshold();
+
+ /**
+ * Returns the computed colour for the given sequence feature
+ *
+ * @param feature
+ * @return
+ */
+ Color getColor(SequenceFeature feature);
+
+ /**
+ * Answers true if the feature has a simple colour, or is coloured by label,
+ * or has a graduated colour and the score of this feature instance is within
+ * the range to render (if any), i.e. does not lie below or above any
+ * threshold set.
+ *
+ * @param feature
+ * @return
+ */
+ boolean isColored(SequenceFeature feature);
+
+ /**
+ * Update the min-max range for a graduated colour scheme
+ *
+ * @param min
+ * @param max
+ */
+ void updateBounds(float min, float max);
+
/**
- * Answers true if ?
+ * Returns the colour in Jalview features file format
*
* @return
*/
boolean featuresFile = false;
try
{
- featuresFile = new jalview.io.FeaturesFile(file, type).parse(viewport
- .getAlignment(), alignPanel.seqPanel.seqCanvas
- .getFeatureRenderer().getFeatureColours(), featureLinks,
- true, viewport.applet.getDefaultParameter("relaxedidmatch",
- false));
- Map<String, Object> colours = alignPanel.seqPanel.seqCanvas
++ Map<String, FeatureColourI> colours = alignPanel.seqPanel.seqCanvas
+ .getFeatureRenderer().getFeatureColours();
+ boolean relaxedIdMatching = viewport.applet.getDefaultParameter(
+ "relaxedidmatch", false);
+ featuresFile = new FeaturesFile(file, type).parse(
+ viewport.getAlignment(), colours, true, relaxedIdMatching);
} catch (Exception ex)
{
ex.printStackTrace();
int x, int y)
{
final String type = check.type;
- final Object typeCol = fr.getFeatureStyle(type);
- java.awt.PopupMenu men = new PopupMenu(MessageManager.formatMessage(
+ final FeatureColourI typeCol = fr.getFeatureStyle(type);
+ PopupMenu men = new PopupMenu(MessageManager.formatMessage(
"label.settings_for_type", new String[] { type }));
- MenuItem scr = new MenuItem(
+ java.awt.MenuItem scr = new MenuItem(
MessageManager.getString("label.sort_by_score"));
men.add(scr);
final FeatureSettings me = this;
}
}
- @Override
- public void mousePressed(MouseEvent e)
- {
- }
-
+ /**
+ * Hide columns containing (or not containing) a given feature type
+ *
+ * @param type
+ * @param columnsContaining
+ */
+ void hideFeatureColumns(final String type,
+ boolean columnsContaining)
+ {
+ if (ap.alignFrame.avc.markColumnsContainingFeatures(
+ columnsContaining, false, false, type))
+ {
+ if (ap.alignFrame.avc.markColumnsContainingFeatures(
+ !columnsContaining, false, false, type))
+ {
+ ap.alignFrame.viewport.hideSelectedColumns();
+ }
+ }
+ }
+
++ @Override
++ public void mousePressed(MouseEvent e)
++ {
++ // TODO Auto-generated method stub
++
++ }
++
}
--- /dev/null
+ package jalview.ext.ensembl;
+
+ import jalview.api.FeatureColourI;
+ import jalview.api.FeatureSettingsModelI;
+ import jalview.datamodel.AlignmentI;
+ import jalview.datamodel.Sequence;
+ import jalview.datamodel.SequenceFeature;
+ import jalview.datamodel.SequenceI;
+ import jalview.io.gff.SequenceOntologyFactory;
+ import jalview.io.gff.SequenceOntologyI;
-import jalview.schemes.FeatureColourAdapter;
++import jalview.schemes.FeatureColour;
+ import jalview.schemes.FeatureSettingsAdapter;
+ import jalview.util.MapList;
+
+ import java.awt.Color;
+ import java.io.UnsupportedEncodingException;
+ import java.net.URLDecoder;
+ import java.util.ArrayList;
+ import java.util.Arrays;
+ import java.util.List;
+
+ import com.stevesoft.pat.Regex;
+
+ /**
+ * A class that fetches genomic sequence and all transcripts for an Ensembl gene
+ *
+ * @author gmcarstairs
+ */
+ public class EnsemblGene extends EnsemblSeqProxy
+ {
+ private static final String GENE_PREFIX = "gene:";
+
+ /*
+ * accepts anything as we will attempt lookup of gene or
+ * transcript id or gene name
+ */
+ private static final Regex ACCESSION_REGEX = new Regex(".*");
+
+ private static final EnsemblFeatureType[] FEATURES_TO_FETCH = {
+ EnsemblFeatureType.gene, EnsemblFeatureType.transcript,
+ EnsemblFeatureType.exon, EnsemblFeatureType.cds,
+ EnsemblFeatureType.variation };
+
+ /**
+ * Default constructor (to use rest.ensembl.org)
+ */
+ public EnsemblGene()
+ {
+ super();
+ }
+
+ /**
+ * Constructor given the target domain to fetch data from
+ *
+ * @param d
+ */
+ public EnsemblGene(String d)
+ {
+ super(d);
+ }
+
+ @Override
+ public String getDbName()
+ {
+ return "ENSEMBL";
+ }
+
+ @Override
+ protected EnsemblFeatureType[] getFeaturesToFetch()
+ {
+ return FEATURES_TO_FETCH;
+ }
+
+ @Override
+ protected EnsemblSeqType getSourceEnsemblType()
+ {
+ return EnsemblSeqType.GENOMIC;
+ }
+
+ /**
+ * Returns an alignment containing the gene(s) for the given gene or
+ * transcript identifier, or external identifier (e.g. Uniprot id). If given a
+ * gene name or external identifier, returns any related gene sequences found
+ * for model organisms. If only a single gene is queried for, then its
+ * transcripts are also retrieved and added to the alignment. <br>
+ * Method:
+ * <ul>
+ * <li>resolves a transcript identifier by looking up its parent gene id</li>
+ * <li>resolves an external identifier by looking up xref-ed gene ids</li>
+ * <li>fetches the gene sequence</li>
+ * <li>fetches features on the sequence</li>
+ * <li>identifies "transcript" features whose Parent is the requested gene</li>
+ * <li>fetches the transcript sequence for each transcript</li>
+ * <li>makes a mapping from the gene to each transcript</li>
+ * <li>copies features from gene to transcript sequences</li>
+ * <li>fetches the protein sequence for each transcript, maps and saves it as
+ * a cross-reference</li>
+ * <li>aligns each transcript against the gene sequence based on the position
+ * mappings</li>
+ * </ul>
+ *
+ * @param query
+ * a single gene or transcript identifier or gene name
+ * @return an alignment containing a gene, and possibly transcripts, or null
+ */
+ @Override
+ public AlignmentI getSequenceRecords(String query) throws Exception
+ {
+ /*
+ * convert to a non-duplicated list of gene identifiers
+ */
+ List<String> geneIds = getGeneIds(query);
+
+ AlignmentI al = null;
+ for (String geneId : geneIds)
+ {
+ /*
+ * fetch the gene sequence(s) with features and xrefs
+ */
+ AlignmentI geneAlignment = super.getSequenceRecords(geneId);
+
+ if (geneAlignment.getHeight() == 1)
+ {
+ getTranscripts(geneAlignment, geneId);
+ }
+ if (al == null)
+ {
+ al = geneAlignment;
+ }
+ else
+ {
+ al.append(geneAlignment);
+ }
+ }
+ return al;
+ }
+
+ /**
+ * Converts a query, which may contain one or more gene or transcript
+ * identifiers, into a non-redundant list of gene identifiers.
+ *
+ * @param accessions
+ * @return
+ */
+ List<String> getGeneIds(String accessions)
+ {
+ List<String> geneIds = new ArrayList<String>();
+
+ for (String acc : accessions.split(getAccessionSeparator()))
+ {
+ if (isGeneIdentifier(acc))
+ {
+ if (!geneIds.contains(acc))
+ {
+ geneIds.add(acc);
+ }
+ }
+
+ /*
+ * if given a transcript id, look up its gene parent
+ */
+ else if (isTranscriptIdentifier(acc))
+ {
+ String geneId = new EnsemblLookup(getDomain()).getParent(acc);
+ if (geneId != null && !geneIds.contains(geneId))
+ {
+ geneIds.add(geneId);
+ }
+ }
+
+ /*
+ * if given a gene or other external name, lookup and fetch
+ * the corresponding gene for all model organisms
+ */
+ else
+ {
+ List<String> ids = new EnsemblSymbol(getDomain()).getIds(acc);
+ for (String geneId : ids)
+ {
+ if (!geneIds.contains(geneId))
+ {
+ geneIds.add(geneId);
+ }
+ }
+ }
+ }
+ return geneIds;
+ }
+
+ /**
+ * Attempts to get Ensembl stable identifiers for model organisms for a gene
+ * name by calling the xrefs symbol REST service to resolve the gene name.
+ *
+ * @param query
+ * @return
+ */
+ protected String getGeneIdentifiersForName(String query)
+ {
+ List<String> ids = new EnsemblSymbol(getDomain()).getIds(query);
+ if (ids != null)
+ {
+ for (String id : ids)
+ {
+ if (isGeneIdentifier(id))
+ {
+ return id;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Constructs all transcripts for the gene, as identified by "transcript"
+ * features whose Parent is the requested gene. The coding transcript
+ * sequences (i.e. with introns omitted) are added to the alignment.
+ *
+ * @param al
+ * @param accId
+ * @throws Exception
+ */
+ protected void getTranscripts(AlignmentI al, String accId)
+ throws Exception
+ {
+ SequenceI gene = al.getSequenceAt(0);
+ List<SequenceFeature> transcriptFeatures = getTranscriptFeatures(accId,
+ gene);
+
+ for (SequenceFeature transcriptFeature : transcriptFeatures)
+ {
+ makeTranscript(transcriptFeature, al, gene);
+ }
+
+ clearGeneFeatures(gene);
+ }
+
+ /**
+ * Remove unwanted features (transcript, exon, CDS) from the gene sequence
+ * after we have used them to derive transcripts and transfer features
+ *
+ * @param gene
+ */
+ protected void clearGeneFeatures(SequenceI gene)
+ {
+ SequenceFeature[] sfs = gene.getSequenceFeatures();
+ if (sfs != null)
+ {
+ SequenceOntologyI so = SequenceOntologyFactory.getInstance();
+ List<SequenceFeature> filtered = new ArrayList<SequenceFeature>();
+ for (SequenceFeature sf : sfs)
+ {
+ String type = sf.getType();
+ if (!isTranscript(type) && !so.isA(type, SequenceOntologyI.EXON)
+ && !so.isA(type, SequenceOntologyI.CDS))
+ {
+ filtered.add(sf);
+ }
+ }
+ gene.setSequenceFeatures(filtered
+ .toArray(new SequenceFeature[filtered
+ .size()]));
+ }
+ }
+
+ /**
+ * Constructs a spliced transcript sequence by finding 'exon' features for the
+ * given id (or failing that 'CDS'). Copies features on to the new sequence.
+ * 'Aligns' the new sequence against the gene sequence by padding with gaps,
+ * and adds it to the alignment.
+ *
+ * @param transcriptFeature
+ * @param al
+ * the alignment to which to add the new sequence
+ * @param gene
+ * the parent gene sequence, with features
+ * @return
+ */
+ SequenceI makeTranscript(SequenceFeature transcriptFeature,
+ AlignmentI al, SequenceI gene)
+ {
+ String accId = getTranscriptId(transcriptFeature);
+ if (accId == null)
+ {
+ return null;
+ }
+
+ /*
+ * NB we are mapping from gene sequence (not genome), so do not
+ * need to check for reverse strand (gene and transcript sequences
+ * are in forward sense)
+ */
+
+ /*
+ * make a gene-length sequence filled with gaps
+ * we will fill in the bases for transcript regions
+ */
+ char[] seqChars = new char[gene.getLength()];
+ Arrays.fill(seqChars, al.getGapCharacter());
+
+ /*
+ * look for exon features of the transcript, failing that for CDS
+ * (for example ENSG00000124610 has 1 CDS but no exon features)
+ */
+ String parentId = "transcript:" + accId;
+ List<SequenceFeature> splices = findFeatures(gene,
+ SequenceOntologyI.EXON, parentId);
+ if (splices.isEmpty())
+ {
+ splices = findFeatures(gene, SequenceOntologyI.CDS, parentId);
+ }
+
+ int transcriptLength = 0;
+ final char[] geneChars = gene.getSequence();
+ int offset = gene.getStart(); // to convert to 0-based positions
+ List<int[]> mappedFrom = new ArrayList<int[]>();
+
+ for (SequenceFeature sf : splices)
+ {
+ int start = sf.getBegin() - offset;
+ int end = sf.getEnd() - offset;
+ int spliceLength = end - start + 1;
+ System.arraycopy(geneChars, start, seqChars, start, spliceLength);
+ transcriptLength += spliceLength;
+ mappedFrom.add(new int[] { sf.getBegin(), sf.getEnd() });
+ }
+
+ Sequence transcript = new Sequence(accId, seqChars, 1, transcriptLength);
+
+ /*
+ * Ensembl has gene name as transcript Name
+ * EnsemblGenomes doesn't, but has a url-encoded description field
+ */
+ String description = (String) transcriptFeature.getValue(NAME);
+ if (description == null)
+ {
+ description = (String) transcriptFeature.getValue(DESCRIPTION);
+ }
+ if (description != null)
+ {
+ try
+ {
+ transcript.setDescription(URLDecoder.decode(description, "UTF-8"));
+ } catch (UnsupportedEncodingException e)
+ {
+ e.printStackTrace(); // as if
+ }
+ }
+ transcript.createDatasetSequence();
+
+ al.addSequence(transcript);
+
+ /*
+ * transfer features to the new sequence; we use EnsemblCdna to do this,
+ * to filter out unwanted features types (see method retainFeature)
+ */
+ List<int[]> mapTo = new ArrayList<int[]>();
+ mapTo.add(new int[] { 1, transcriptLength });
+ MapList mapping = new MapList(mappedFrom, mapTo, 1, 1);
+ EnsemblCdna cdna = new EnsemblCdna(getDomain());
+ cdna.transferFeatures(gene.getSequenceFeatures(),
+ transcript.getDatasetSequence(), mapping, parentId);
+
+ /*
+ * fetch and save cross-references
+ */
+ cdna.getCrossReferences(transcript);
+
+ /*
+ * and finally fetch the protein product and save as a cross-reference
+ */
+ cdna.addProteinProduct(transcript);
+
+ return transcript;
+ }
+
+ /**
+ * Returns the 'transcript_id' property of the sequence feature (or null)
+ *
+ * @param feature
+ * @return
+ */
+ protected String getTranscriptId(SequenceFeature feature)
+ {
+ return (String) feature.getValue("transcript_id");
+ }
+
+ /**
+ * Returns a list of the transcript features on the sequence whose Parent is
+ * the gene for the accession id.
+ *
+ * @param accId
+ * @param geneSequence
+ * @return
+ */
+ protected List<SequenceFeature> getTranscriptFeatures(String accId,
+ SequenceI geneSequence)
+ {
+ List<SequenceFeature> transcriptFeatures = new ArrayList<SequenceFeature>();
+
+ String parentIdentifier = GENE_PREFIX + accId;
+ SequenceFeature[] sfs = geneSequence.getSequenceFeatures();
+
+ if (sfs != null)
+ {
+ for (SequenceFeature sf : sfs)
+ {
+ if (isTranscript(sf.getType()))
+ {
+ String parent = (String) sf.getValue(PARENT);
+ if (parentIdentifier.equals(parent))
+ {
+ transcriptFeatures.add(sf);
+ }
+ }
+ }
+ }
+
+ return transcriptFeatures;
+ }
+
+ @Override
+ public String getDescription()
+ {
+ return "Fetches all transcripts and variant features for a gene or transcript";
+ }
+
+ /**
+ * Default test query is a gene id (can also enter a transcript id)
+ */
+ @Override
+ public String getTestQuery()
+ {
+ return "ENSG00000157764"; // BRAF, 5 transcripts, reverse strand
+ // ENSG00000090266 // NDUFB2, 15 transcripts, forward strand
+ // ENSG00000101812 // H2BFM histone, 3 transcripts, forward strand
+ // ENSG00000123569 // H2BFWT histone, 2 transcripts, reverse strand
+ }
+
+ /**
+ * Answers true for a feature of type 'gene' (or a sub-type of gene in the
+ * Sequence Ontology), whose ID is the accession we are retrieving
+ */
+ @Override
+ protected boolean identifiesSequence(SequenceFeature sf, String accId)
+ {
+ if (SequenceOntologyFactory.getInstance().isA(sf.getType(),
+ SequenceOntologyI.GENE))
+ {
+ String id = (String) sf.getValue(ID);
+ if ((GENE_PREFIX + accId).equals(id))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Answers true unless feature type is 'gene', or 'transcript' with a parent
+ * which is a different gene. We need the gene features to identify the range,
+ * but it is redundant information on the gene sequence. Checking the parent
+ * allows us to drop transcript features which belong to different
+ * (overlapping) genes.
+ */
+ @Override
+ protected boolean retainFeature(SequenceFeature sf, String accessionId)
+ {
+ SequenceOntologyI so = SequenceOntologyFactory.getInstance();
+ String type = sf.getType();
+ if (so.isA(type, SequenceOntologyI.GENE))
+ {
+ return false;
+ }
+ if (isTranscript(type))
+ {
+ String parent = (String) sf.getValue(PARENT);
+ if (!(GENE_PREFIX + accessionId).equals(parent))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Answers false. This allows an optimisation - a single 'gene' feature is all
+ * that is needed to identify the positions of the gene on the genomic
+ * sequence.
+ */
+ @Override
+ protected boolean isSpliceable()
+ {
+ return false;
+ }
+
+ /**
+ * Override to do nothing as Ensembl doesn't return a protein sequence for a
+ * gene identifier
+ */
+ @Override
+ protected void addProteinProduct(SequenceI querySeq)
+ {
+ }
+
+ @Override
+ public Regex getAccessionValidator()
+ {
+ return ACCESSION_REGEX;
+ }
+
+ /**
+ * Returns a descriptor for suitable feature display settings with
+ * <ul>
+ * <li>only exon or sequence_variant features (or their subtypes in the
+ * Sequence Ontology) visible</li>
+ * <li>variant features coloured red</li>
+ * <li>exon features coloured by label (exon name)</li>
+ * <li>variants displayed above (on top of) exons</li>
+ * </ul>
+ */
+ @Override
+ public FeatureSettingsModelI getFeatureColourScheme()
+ {
+ return new FeatureSettingsAdapter()
+ {
+ SequenceOntologyI so = SequenceOntologyFactory.getInstance();
+ @Override
+ public boolean isFeatureDisplayed(String type)
+ {
+ return (so.isA(type, SequenceOntologyI.EXON) || so.isA(type,
+ SequenceOntologyI.SEQUENCE_VARIANT));
+ }
+
+ @Override
+ public FeatureColourI getFeatureColour(String type)
+ {
+ if (so.isA(type, SequenceOntologyI.EXON))
+ {
- return new FeatureColourAdapter()
++ return new FeatureColour()
+ {
+ @Override
+ public boolean isColourByLabel()
+ {
+ return true;
+ }
+ };
+ }
+ if (so.isA(type, SequenceOntologyI.SEQUENCE_VARIANT))
+ {
- return new FeatureColourAdapter()
++ return new FeatureColour()
+ {
+
+ @Override
+ public Color getColour()
+ {
+ return Color.RED;
+ }
+ };
+ }
+ return null;
+ }
+
+ /**
+ * order to render sequence_variant after exon after the rest
+ */
+ @Override
+ public int compare(String feature1, String feature2)
+ {
+ if (so.isA(feature1, SequenceOntologyI.SEQUENCE_VARIANT))
+ {
+ return +1;
+ }
+ if (so.isA(feature2, SequenceOntologyI.SEQUENCE_VARIANT))
+ {
+ return -1;
+ }
+ if (so.isA(feature1, SequenceOntologyI.EXON))
+ {
+ return +1;
+ }
+ if (so.isA(feature2, SequenceOntologyI.EXON))
+ {
+ return -1;
+ }
+ return 0;
+ }
+ };
+ }
+
+ }
*/
package jalview.gui;
+import jalview.api.FeatureColourI;
import jalview.datamodel.AlignmentAnnotation;
+ import jalview.datamodel.SequenceI;
import jalview.io.AnnotationFile;
import jalview.io.FeaturesFile;
import jalview.io.JalviewFileChooser;
.getString("label.no_features_on_alignment");
if (features)
{
+ Map<String, FeatureColourI> displayedFeatureColours = ap
+ .getFeatureRenderer().getDisplayedFeatureCols();
+ FeaturesFile formatter = new FeaturesFile();
+ SequenceI[] sequences = ap.av.getAlignment().getSequencesArray();
- Map<String, Object> featureColours = ap.getFeatureRenderer()
++ Map<String, FeatureColourI> featureColours = ap.getFeatureRenderer()
+ .getDisplayedFeatureCols();
+ boolean includeNonPositional = ap.av.isShowNPFeats();
if (GFFFormat.isSelected())
{
- text = new FeaturesFile().printGFFFormat(ap.av.getAlignment()
- .getDataset().getSequencesArray(), displayedFeatureColours, true, ap.av.isShowNPFeats());// ap.av.featuresDisplayed//);
++ text = new FeaturesFile().printGffFormat(ap.av.getAlignment()
++ .getDataset().getSequencesArray(), displayedFeatureColours,
++ true, ap.av.isShowNPFeats());
+ text = formatter.printGffFormat(sequences, featureColours, true,
+ includeNonPositional);
}
else
{
+ text = new FeaturesFile().printJalviewFormat(ap.av.getAlignment()
+ .getDataset().getSequencesArray(), displayedFeatureColours, true, ap.av.isShowNPFeats()); // ap.av.featuresDisplayed);
+ text = formatter.printJalviewFormat(sequences, featureColours,
+ true, includeNonPositional);
}
}
else
{
colour.setBackground(bigPanel.getBackground());
colour.setForeground(Color.black);
- FeatureSettings.renderGraduatedColor(colour, (GraduatedColor) col2);
- // colour.setForeground(colour.getBackground());
+ FeatureSettings.renderGraduatedColor(colour, col);
}
}
+
+ /**
+ * Orders features in render precedence (last in order is last to render, so
+ * displayed on top of other features)
+ *
+ * @param order
+ */
+ public void orderFeatures(Comparator<String> order)
+ {
+ Arrays.sort(renderOrder, order);
+ }
}
.toArray(new String[0]);
Vector<String> settingsAdded = new Vector<String>();
- Object gstyle = null;
- GraduatedColor gcol = null;
if (renderOrder != null)
{
- for (int ro = 0; ro < renderOrder.length; ro++)
+ for (String featureType : renderOrder)
{
- FeatureColourI gstyle = ap.getSeqPanel().seqCanvas
- gstyle = ap.getSeqPanel().seqCanvas.getFeatureRenderer()
++ FeatureColourI fcol = ap.getSeqPanel().seqCanvas
+ .getFeatureRenderer()
- .getFeatureStyle(renderOrder[ro]);
+ .getFeatureStyle(featureType);
Setting setting = new Setting();
- setting.setType(renderOrder[ro]);
- if (!gstyle.isSimpleColour())
+ setting.setType(featureType);
- if (gstyle instanceof GraduatedColor)
++ if (!fcol.isSimpleColour())
{
- setting.setColour(gstyle.getMaxColour().getRGB());
- setting.setMincolour(gstyle.getMinColour().getRGB());
- setting.setMin(gstyle.getMin());
- setting.setMax(gstyle.getMax());
- setting.setColourByLabel(gstyle.isColourByLabel());
- setting.setAutoScale(gstyle.isAutoScaled());
- setting.setThreshold(gstyle.getThreshold());
- gcol = (GraduatedColor) gstyle;
- setting.setColour(gcol.getMaxColor().getRGB());
- setting.setMincolour(gcol.getMinColor().getRGB());
- setting.setMin(gcol.getMin());
- setting.setMax(gcol.getMax());
- setting.setColourByLabel(gcol.isColourByLabel());
- setting.setAutoScale(gcol.isAutoScale());
- setting.setThreshold(gcol.getThresh());
- setting.setThreshstate(gcol.getThreshType());
++ setting.setColour(fcol.getMaxColour().getRGB());
++ setting.setMincolour(fcol.getMinColour().getRGB());
++ setting.setMin(fcol.getMin());
++ setting.setMax(fcol.getMax());
++ setting.setColourByLabel(fcol.isColourByLabel());
++ setting.setAutoScale(fcol.isAutoScaled());
++ setting.setThreshold(fcol.getThreshold());
+ // -1 = No threshold, 0 = Below, 1 = Above
- setting.setThreshstate(gstyle.isAboveThreshold() ? 1
- : (gstyle.isBelowThreshold() ? 0 : -1));
++ setting.setThreshstate(fcol.isAboveThreshold() ? 1
++ : (fcol.isBelowThreshold() ? 0 : -1));
}
else
{
- setting.setColour(gstyle.getColour().getRGB());
- setting.setColour(((Color) gstyle).getRGB());
++ setting.setColour(fcol.getColour().getRGB());
}
setting.setDisplay(av.getFeaturesDisplayed().isVisible(
*/
package jalview.io;
+ import jalview.analysis.AlignmentUtils;
import jalview.analysis.SequenceIdMatcher;
+ import jalview.api.AlignViewportI;
+import jalview.api.FeatureColourI;
+ import jalview.api.FeaturesSourceI;
import jalview.datamodel.AlignedCodonFrame;
+ import jalview.datamodel.Alignment;
import jalview.datamodel.AlignmentI;
import jalview.datamodel.SequenceDummy;
import jalview.datamodel.SequenceFeature;
import jalview.datamodel.SequenceI;
+ import jalview.io.gff.GffHelperBase;
+ import jalview.io.gff.GffHelperFactory;
+ import jalview.io.gff.GffHelperI;
-import jalview.schemes.AnnotationColourGradient;
-import jalview.schemes.GraduatedColor;
+import jalview.schemes.FeatureColour;
import jalview.schemes.UserColourScheme;
-import jalview.util.Format;
import jalview.util.MapList;
+ import jalview.util.ParseHtmlBodyAndLinks;
+ import jalview.util.StringUtils;
-import java.awt.Color;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
- import java.util.StringTokenizer;
- import java.util.Vector;
+ import java.util.Map.Entry;
-import java.util.StringTokenizer;
/**
- * Parse and create Jalview Features files Detects GFF format features files and
- * parses. Does not implement standard print() - call specific printFeatures or
- * printGFF. Uses AlignmentI.findSequence(String id) to find the sequence object
- * for the features annotation - this normally works on an exact match.
+ * Parses and writes features files, which may be in Jalview, GFF2 or GFF3
+ * format. These are tab-delimited formats but with differences in the use of
+ * columns.
+ *
+ * A Jalview feature file may define feature colours and then declare that the
+ * remainder of the file is in GFF format with the line 'GFF'.
+ *
+ * GFF3 files may include alignment mappings for features, which Jalview will
+ * attempt to model, and may include sequence data following a ##FASTA line.
+ *
*
* @author AMW
- * @version $Revision$
+ * @author jbprocter
+ * @author gmcarstairs
*/
- public class FeaturesFile extends AlignFile
+ public class FeaturesFile extends AlignFile implements FeaturesSourceI
{
- /**
- * work around for GFF interpretation bug where source string becomes
- * description rather than a group
- */
- private boolean doGffSource = true;
+ private static final String ID_NOT_SPECIFIED = "ID_NOT_SPECIFIED";
+
+ private static final String NOTE = "Note";
+
+ protected static final String TAB = "\t";
+
+ protected static final String GFF_VERSION = "##gff-version";
- private int gffversion;
+ private AlignmentI lastmatchedAl = null;
+
+ private SequenceIdMatcher matcher = null;
+
+ protected AlignmentI dataset;
+
+ protected int gffVersion;
/**
* Creates a new FeaturesFile object.
* - process html strings into plain text
* @return true if features were added
*/
- public boolean parse(AlignmentI align, Map colours, boolean removeHTML)
- {
- return parse(align, colours, null, removeHTML, false);
- }
-
- /**
- * Parse GFF or sequence features file optionally using case-independent
- * matching, discarding URLs
- *
- * @param align
- * - alignment/dataset containing sequences that are to be annotated
- * @param colours
- * - hashtable to store feature colour definitions
- * @param removeHTML
- * - process html strings into plain text
- * @param relaxedIdmatching
- * - when true, ID matches to compound sequence IDs are allowed
- * @return true if features were added
- */
- public boolean parse(AlignmentI align, Map colours, boolean removeHTML,
- boolean relaxedIdMatching)
- public boolean parse(AlignmentI align, Map<String, Object> colours,
++ public boolean parse(AlignmentI align,
++ Map<String, FeatureColourI> colours,
+ boolean removeHTML)
{
- return parse(align, colours, null, removeHTML, relaxedIdMatching);
+ return parse(align, colours, removeHTML, false);
}
/**
* - when true, ID matches to compound sequence IDs are allowed
* @return true if features were added
*/
- public boolean parse(AlignmentI align, Map colours, Map featureLink,
- public boolean parse(AlignmentI align, Map<String, Object> colours,
++ public boolean parse(AlignmentI align,
++ Map<String, FeatureColourI> colours,
boolean removeHTML, boolean relaxedIdmatching)
{
+ Map<String, String> gffProps = new HashMap<String, String>();
+ /*
+ * keep track of any sequences we try to create from the data
+ */
+ List<SequenceI> newseqs = new ArrayList<SequenceI>();
String line = null;
try
continue;
}
}
- if (st.countTokens() > 1 && st.countTokens() < 4)
+
+ if (gffColumns.length > 1 && gffColumns.length < 4)
{
- GFFFile = false;
- theType = st.nextToken();
- if (theType.equalsIgnoreCase("startgroup"))
+ /*
+ * if 2 or 3 tokens, we anticipate either 'startgroup', 'endgroup' or
+ * a feature type colour specification
+ */
+ String ft = gffColumns[0];
+ if (ft.equalsIgnoreCase("startgroup"))
{
- featureGroup = st.nextToken();
- if (st.hasMoreElements())
- {
- groupLink = st.nextToken();
- featureLink.put(featureGroup, groupLink);
- }
+ featureGroup = gffColumns[1];
}
- else if (theType.equalsIgnoreCase("endgroup"))
+ else if (ft.equalsIgnoreCase("endgroup"))
{
// We should check whether this is the current group,
-- // but at present theres no way of showing more than 1 group
- st.nextToken();
++ // but at present there's no way of showing more than 1 group
featureGroup = null;
}
else
{
- String colscheme = st.nextToken();
- try
- {
- FeatureColourI colour = FeatureColour
- .parseJalviewFeatureColour(colscheme);
- if (colour != null)
- {
- colours.put(theType, colour);
- }
- if (st.hasMoreElements())
- {
- String link = st.nextToken();
- typeLink.put(theType, link);
- if (featureLink == null)
- {
- featureLink = new Hashtable();
- }
- featureLink.put(theType, link);
- }
- } catch (IllegalArgumentException e)
- parseFeatureColour(line, ft, gffColumns, colours);
++ String colscheme = gffColumns[1];
++ FeatureColourI colour = FeatureColour
++ .parseJalviewFeatureColour(colscheme);
++ if (colour != null)
+ {
- System.err.println("Error parsing feature colour scheme "
- + colscheme + " : " + e.getMessage());
++ colours.put(ft, colour);
+ }
}
continue;
}
}
/**
- * take a sequence feature and examine its attributes to decide how it should
- * be added to a sequence
+ * Try to parse a Jalview format feature specification and add it as a
+ * sequence feature to any matching sequences in the alignment. Returns true
+ * if successful (a feature was added), or false if not.
*
- * @param seq
- * - the destination sequence constructed or discovered in the
- * current context
- * @param sf
- * - the base feature with ATTRIBUTES property containing any
- * additional attributes
- * @param gFFFile
- * - true if we are processing a GFF annotation file
- * @return true if sf was actually added to the sequence, false if it was
- * processed in another way
+ * @param line
+ * @param gffColumns
+ * @param alignment
+ * @param featureColours
+ * @param removeHTML
+ * @param relaxedIdmatching
+ * @param featureGroup
*/
- public boolean processOrAddSeqFeature(AlignmentI align,
- List<SequenceI> newseqs, SequenceI seq, SequenceFeature sf,
- boolean gFFFile, boolean relaxedIdMatching)
+ protected boolean parseJalviewFeature(String line, String[] gffColumns,
- AlignmentI alignment, Map<String, Object> featureColours,
++ AlignmentI alignment, Map<String, FeatureColourI> featureColours,
+ boolean removeHTML, boolean relaxedIdMatching, String featureGroup)
{
- String attr = (String) sf.getValue("ATTRIBUTES");
- boolean add = true;
- if (gFFFile && attr != null)
+ /*
+ * tokens: description seqid seqIndex start end type [score]
+ */
+ if (gffColumns.length < 6)
{
- int nattr = 8;
-
- for (String attset : attr.split("\t"))
- {
- if (attset == null || attset.trim().length() == 0)
- {
- continue;
- }
- nattr++;
- Map<String, List<String>> set = new HashMap<String, List<String>>();
- // normally, only expect one column - 9 - in this field
- // the attributes (Gff3) or groups (gff2) field
- for (String pair : attset.trim().split(";"))
- {
- pair = pair.trim();
- if (pair.length() == 0)
- {
- continue;
- }
-
- // expect either space seperated (gff2) or '=' separated (gff3)
- // key/value pairs here
-
- int eqpos = pair.indexOf('='), sppos = pair.indexOf(' ');
- String key = null, value = null;
-
- if (sppos > -1 && (eqpos == -1 || sppos < eqpos))
- {
- key = pair.substring(0, sppos);
- value = pair.substring(sppos + 1);
- }
- else
- {
- if (eqpos > -1 && (sppos == -1 || eqpos < sppos))
- {
- key = pair.substring(0, eqpos);
- value = pair.substring(eqpos + 1);
- }
- else
- {
- key = pair;
- }
- }
- if (key != null)
- {
- List<String> vals = set.get(key);
- if (vals == null)
- {
- vals = new ArrayList<String>();
- set.put(key, vals);
- }
- if (value != null)
- {
- vals.add(value.trim());
- }
- }
- }
- try
- {
- add &= processGffKey(set, nattr, seq, sf, align, newseqs,
- relaxedIdMatching); // process decides if
- // feature is actually
- // added
- } catch (InvalidGFF3FieldException ivfe)
- {
- System.err.println(ivfe);
- }
- }
- }
- if (add)
- {
- seq.addSequenceFeature(sf);
+ System.err.println("Ignoring feature line '" + line
+ + "' with too few columns (" + gffColumns.length + ")");
+ return false;
}
- return add;
- }
-
- public class InvalidGFF3FieldException extends Exception
- {
- String field, value;
+ String desc = gffColumns[0];
+ String seqId = gffColumns[1];
+ SequenceI seq = findSequence(seqId, alignment, null, relaxedIdMatching);
- public InvalidGFF3FieldException(String field,
- Map<String, List<String>> set, String message)
+ if (!ID_NOT_SPECIFIED.equals(seqId))
{
- super(message + " (Field was " + field + " and value was "
- + set.get(field).toString());
- this.field = field;
- this.value = set.get(field).toString();
+ seq = findSequence(seqId, alignment, null, relaxedIdMatching);
}
-
- }
-
- /**
- * take a set of keys for a feature and interpret them
- *
- * @param set
- * @param nattr
- * @param seq
- * @param sf
- * @return
- */
- public boolean processGffKey(Map<String, List<String>> set, int nattr,
- SequenceI seq, SequenceFeature sf, AlignmentI align,
- List<SequenceI> newseqs, boolean relaxedIdMatching)
- throws InvalidGFF3FieldException
- {
- String attr;
- // decide how to interpret according to type
- if (sf.getType().equals("similarity"))
+ else
{
- int strand = sf.getStrand();
- // exonerate cdna/protein map
- // look for fields
- List<SequenceI> querySeq = findNames(align, newseqs,
- relaxedIdMatching, set.get(attr = "Query"));
- if (querySeq == null || querySeq.size() != 1)
+ seqId = null;
+ seq = null;
+ String seqIndex = gffColumns[2];
+ try
{
- throw new InvalidGFF3FieldException(attr, set,
- "Expecting exactly one sequence in Query field (got "
- + set.get(attr) + ")");
- }
- if (set.containsKey(attr = "Align"))
+ int idx = Integer.parseInt(seqIndex);
+ seq = alignment.getSequenceAt(idx);
+ } catch (NumberFormatException ex)
{
- // process the align maps and create cdna/protein maps
- // ideally, the query sequences are in the alignment, but maybe not...
-
- AlignedCodonFrame alco = new AlignedCodonFrame();
- MapList codonmapping = constructCodonMappingFromAlign(set, attr,
- strand);
-
- // add codon mapping, and hope!
- alco.addMap(seq, querySeq.get(0), codonmapping);
- align.addCodonFrame(alco);
- // everything that's needed to be done is done
- // no features to create here !
- return false;
+ System.err.println("Invalid sequence index: " + seqIndex);
}
+ }
+ if (seq == null)
+ {
+ System.out.println("Sequence not found: " + line);
+ return false;
}
- return true;
- }
- private MapList constructCodonMappingFromAlign(
- Map<String, List<String>> set, String attr, int strand)
- throws InvalidGFF3FieldException
- {
- if (strand == 0)
+ int startPos = Integer.parseInt(gffColumns[3]);
+ int endPos = Integer.parseInt(gffColumns[4]);
+
+ String ft = gffColumns[5];
+
+ if (!featureColours.containsKey(ft))
{
- throw new InvalidGFF3FieldException(attr, set,
- "Invalid strand for a codon mapping (cannot be 0)");
+ /*
+ * Perhaps an old style groups file with no colours -
+ * synthesize a colour from the feature type
+ */
+ UserColourScheme ucs = new UserColourScheme(ft);
- featureColours.put(ft, ucs.findColour('A'));
++ featureColours.put(ft, new FeatureColour(ucs.findColour('A')));
}
- List<Integer> fromrange = new ArrayList<Integer>(), torange = new ArrayList<Integer>();
- int lastppos = 0, lastpframe = 0;
- for (String range : set.get(attr))
+ SequenceFeature sf = new SequenceFeature(ft, desc, "", startPos,
+ endPos, featureGroup);
+ if (gffColumns.length > 6)
{
- List<Integer> ints = new ArrayList<Integer>();
- StringTokenizer st = new StringTokenizer(range, " ");
- while (st.hasMoreTokens())
- {
- String num = st.nextToken();
- try
- {
- ints.add(new Integer(num));
- } catch (NumberFormatException nfe)
- {
- throw new InvalidGFF3FieldException(attr, set,
- "Invalid number in field " + num);
- }
- }
- // Align positionInRef positionInQuery LengthInRef
- // contig_1146 exonerate:protein2genome:local similarity 8534 11269
- // 3652 - . alignment_id 0 ;
- // Query DDB_G0269124
- // Align 11270 143 120
- // corresponds to : 120 bases align at pos 143 in protein to 11270 on
- // dna in strand direction
- // Align 11150 187 282
- // corresponds to : 282 bases align at pos 187 in protein to 11150 on
- // dna in strand direction
- //
- // Align 10865 281 888
- // Align 9977 578 1068
- // Align 8909 935 375
- //
- if (ints.size() != 3)
- {
- throw new InvalidGFF3FieldException(attr, set,
- "Invalid number of fields for this attribute ("
- + ints.size() + ")");
- }
- fromrange.add(new Integer(ints.get(0).intValue()));
- fromrange.add(new Integer(ints.get(0).intValue() + strand
- * ints.get(2).intValue()));
- // how are intron/exon boundaries that do not align in codons
- // represented
- if (ints.get(1).equals(lastppos) && lastpframe > 0)
+ float score = Float.NaN;
+ try
{
- // extend existing to map
- lastppos += ints.get(2) / 3;
- lastpframe = ints.get(2) % 3;
- torange.set(torange.size() - 1, new Integer(lastppos));
- }
- else
+ score = new Float(gffColumns[6]).floatValue();
+ // update colourgradient bounds if allowed to
+ } catch (NumberFormatException ex)
{
- // new to map range
- torange.add(ints.get(1));
- lastppos = ints.get(1) + ints.get(2) / 3;
- lastpframe = ints.get(2) % 3;
- torange.add(new Integer(lastppos));
+ // leave as NaN
}
- }
- // from and to ranges must end up being a series of start/end intervals
- if (fromrange.size() % 2 == 1)
- {
- throw new InvalidGFF3FieldException(attr, set,
- "Couldn't parse the DNA alignment range correctly");
- }
- if (torange.size() % 2 == 1)
- {
- throw new InvalidGFF3FieldException(attr, set,
- "Couldn't parse the protein alignment range correctly");
- }
- // finally, build the map
- int[] frommap = new int[fromrange.size()], tomap = new int[torange
- .size()];
- int p = 0;
- for (Integer ip : fromrange)
- {
- frommap[p++] = ip.intValue();
- }
- p = 0;
- for (Integer ip : torange)
- {
- tomap[p++] = ip.intValue();
+ sf.setScore(score);
}
- return new MapList(frommap, tomap, 3, 1);
- }
+ parseDescriptionHTML(sf, removeHTML);
- private List<SequenceI> findNames(AlignmentI align,
- List<SequenceI> newseqs, boolean relaxedIdMatching,
- List<String> list)
- {
- List<SequenceI> found = new ArrayList<SequenceI>();
- for (String seqId : list)
+ seq.addSequenceFeature(sf);
+
+ while (seqId != null
+ && (seq = alignment.findName(seq, seqId, false)) != null)
{
- SequenceI seq = findName(align, seqId, relaxedIdMatching, newseqs);
- if (seq != null)
- {
- found.add(seq);
- }
+ seq.addSequenceFeature(new SequenceFeature(sf));
}
- return found;
+ return true;
}
- private AlignmentI lastmatchedAl = null;
-
- private SequenceIdMatcher matcher = null;
-
/**
- * Process a feature type colour specification
- *
- * @param line
- * the current input line (for error messages only)
- * @param featureType
- * the first token on the line
- * @param gffColumns
- * holds tokens on the line
- * @param colours
- * map to which to add derived colour specification
- */
- protected void parseFeatureColour(String line, String featureType,
- String[] gffColumns, Map<String, Object> colours)
- {
- Object colour = null;
- String colscheme = gffColumns[1];
- if (colscheme.indexOf("|") > -1
- || colscheme.trim().equalsIgnoreCase("label"))
- {
- colour = parseGraduatedColourScheme(line, colscheme);
- }
- else
- {
- UserColourScheme ucs = new UserColourScheme(colscheme);
- colour = ucs.findColour('A');
- }
- if (colour != null)
- {
- colours.put(featureType, colour);
- }
- }
-
- /**
- * Parse a Jalview graduated colour descriptor
- *
- * @param line
- * @param colourDescriptor
- * @return
- */
- protected GraduatedColor parseGraduatedColourScheme(String line,
- String colourDescriptor)
- {
- // Parse '|' separated graduated colourscheme fields:
- // [label|][mincolour|maxcolour|[absolute|]minvalue|maxvalue|thresholdtype|thresholdvalue]
- // can either provide 'label' only, first is optional, next two
- // colors are required (but may be
- // left blank), next is optional, nxt two min/max are required.
- // first is either 'label'
- // first/second and third are both hexadecimal or word equivalent
- // colour.
- // next two are values parsed as floats.
- // fifth is either 'above','below', or 'none'.
- // sixth is a float value and only required when fifth is either
- // 'above' or 'below'.
- StringTokenizer gcol = new StringTokenizer(colourDescriptor, "|", true);
- // set defaults
- float min = Float.MIN_VALUE, max = Float.MAX_VALUE;
- boolean labelCol = false;
- // Parse spec line
- String mincol = gcol.nextToken();
- if (mincol == "|")
- {
- System.err
- .println("Expected either 'label' or a colour specification in the line: "
- + line);
- return null;
- }
- String maxcol = null;
- if (mincol.toLowerCase().indexOf("label") == 0)
- {
- labelCol = true;
- mincol = (gcol.hasMoreTokens() ? gcol.nextToken() : null); // skip '|'
- mincol = (gcol.hasMoreTokens() ? gcol.nextToken() : null);
- }
- String abso = null, minval, maxval;
- if (mincol != null)
- {
- // at least four more tokens
- if (mincol.equals("|"))
- {
- mincol = "";
- }
- else
- {
- gcol.nextToken(); // skip next '|'
- }
- // continue parsing rest of line
- maxcol = gcol.nextToken();
- if (maxcol.equals("|"))
- {
- maxcol = "";
- }
- else
- {
- gcol.nextToken(); // skip next '|'
- }
- abso = gcol.nextToken();
- gcol.nextToken(); // skip next '|'
- if (abso.toLowerCase().indexOf("abso") != 0)
- {
- minval = abso;
- abso = null;
- }
- else
- {
- minval = gcol.nextToken();
- gcol.nextToken(); // skip next '|'
- }
- maxval = gcol.nextToken();
- if (gcol.hasMoreTokens())
- {
- gcol.nextToken(); // skip next '|'
- }
- try
- {
- if (minval.length() > 0)
- {
- min = Float.valueOf(minval);
- }
- } catch (Exception e)
- {
- System.err
- .println("Couldn't parse the minimum value for graduated colour for type ("
- + colourDescriptor
- + ") - did you misspell 'auto' for the optional automatic colour switch ?");
- e.printStackTrace();
- }
- try
- {
- if (maxval.length() > 0)
- {
- max = Float.valueOf(maxval);
- }
- } catch (Exception e)
- {
- System.err
- .println("Couldn't parse the maximum value for graduated colour for type ("
- + colourDescriptor + ")");
- e.printStackTrace();
- }
- }
- else
- {
- // add in some dummy min/max colours for the label-only
- // colourscheme.
- mincol = "FFFFFF";
- maxcol = "000000";
- }
-
- GraduatedColor colour = null;
- try
- {
- colour = new GraduatedColor(
- new UserColourScheme(mincol).findColour('A'),
- new UserColourScheme(maxcol).findColour('A'), min, max);
- } catch (Exception e)
- {
- System.err.println("Couldn't parse the graduated colour scheme ("
- + colourDescriptor + ")");
- e.printStackTrace();
- }
- if (colour != null)
- {
- colour.setColourByLabel(labelCol);
- colour.setAutoScaled(abso == null);
- // add in any additional parameters
- String ttype = null, tval = null;
- if (gcol.hasMoreTokens())
- {
- // threshold type and possibly a threshold value
- ttype = gcol.nextToken();
- if (ttype.toLowerCase().startsWith("below"))
- {
- colour.setThreshType(AnnotationColourGradient.BELOW_THRESHOLD);
- }
- else if (ttype.toLowerCase().startsWith("above"))
- {
- colour.setThreshType(AnnotationColourGradient.ABOVE_THRESHOLD);
- }
- else
- {
- colour.setThreshType(AnnotationColourGradient.NO_THRESHOLD);
- if (!ttype.toLowerCase().startsWith("no"))
- {
- System.err.println("Ignoring unrecognised threshold type : "
- + ttype);
- }
- }
- }
- if (colour.getThreshType() != AnnotationColourGradient.NO_THRESHOLD)
- {
- try
- {
- gcol.nextToken();
- tval = gcol.nextToken();
- colour.setThresh(new Float(tval).floatValue());
- } catch (Exception e)
- {
- System.err.println("Couldn't parse threshold value as a float: ("
- + tval + ")");
- e.printStackTrace();
- }
- }
- // parse the thresh-is-min token ?
- if (gcol.hasMoreTokens())
- {
- System.err
- .println("Ignoring additional tokens in parameters in graduated colour specification\n");
- while (gcol.hasMoreTokens())
- {
- System.err.println("|" + gcol.nextToken());
- }
- System.err.println("\n");
- }
- }
- return colour;
- }
-
- /**
* clear any temporary handles used to speed up ID matching
*/
- private void resetMatcher()
+ protected void resetMatcher()
{
lastmatchedAl = null;
matcher = null;
* hash of feature types and colours
* @return features file contents
*/
- public String printJalviewFormat(SequenceI[] seqs,
- Map<String, FeatureColourI> map)
+ public String printJalviewFormat(SequenceI[] sequences,
- Map<String, Object> visible)
++ Map<String, FeatureColourI> visible)
{
- return printJalviewFormat(seqs, map, true, true);
+ return printJalviewFormat(sequences, visible, true, true);
}
/**
* @return features file contents
*/
public String printJalviewFormat(SequenceI[] sequences,
- Map<String, FeatureColourI> visible,
- boolean visOnly, boolean nonpos)
- Map<String, Object> visible, boolean visOnly, boolean nonpos)
++ Map<String, FeatureColourI> visible, boolean visOnly,
++ boolean nonpos)
{
+ StringBuilder out = new StringBuilder(256);
+ boolean featuresGen = false;
if (visOnly && !nonpos && (visible == null || visible.size() < 1))
{
// no point continuing.
// viewed features
// TODO: decide if feature links should also be written here ?
Iterator<String> en = visible.keySet().iterator();
- String featureType, color;
while (en.hasNext())
{
- String featureType = en.next();
- featureType = en.next().toString();
-
- if (visible.get(featureType) instanceof GraduatedColor)
- {
- GraduatedColor gc = (GraduatedColor) visible.get(featureType);
- color = (gc.isColourByLabel() ? "label|" : "")
- + Format.getHexString(gc.getMinColor()) + "|"
- + Format.getHexString(gc.getMaxColor())
- + (gc.isAutoScale() ? "|" : "|abso|") + gc.getMin() + "|"
- + gc.getMax() + "|";
- if (gc.getThreshType() != AnnotationColourGradient.NO_THRESHOLD)
- {
- if (gc.getThreshType() == AnnotationColourGradient.BELOW_THRESHOLD)
- {
- color += "below";
- }
- else
- {
- if (gc.getThreshType() != AnnotationColourGradient.ABOVE_THRESHOLD)
- {
- System.err.println("WARNING: Unsupported threshold type ("
- + gc.getThreshType() + ") : Assuming 'above'");
- }
- color += "above";
- }
- // add the value
- color += "|" + gc.getThresh();
- }
- else
- {
- color += "none";
- }
- }
- else if (visible.get(featureType) instanceof Color)
- {
- color = Format.getHexString((Color) visible.get(featureType));
- }
- else
- {
- // legacy support for integer objects containing colour triplet values
- color = Format.getHexString(new Color(Integer.parseInt(visible
- .get(featureType).toString())));
- }
- out.append(featureType);
- out.append(TAB);
- out.append(color);
- out.append(newline);
++ String featureType = en.next().toString();
+ FeatureColourI colour = visible.get(featureType);
+ out.append(colour.toJalviewFormat(featureType)).append(newline);
}
}
++
// Work out which groups are both present and visible
- Vector groups = new Vector();
+ List<String> groups = new ArrayList<String>();
int groupIndex = 0;
boolean isnonpos = false;
}
/**
- * generate a gff file for sequence features includes non-pos features by
- * default.
+ * Parse method that is called when a GFF file is dragged to the desktop
+ */
+ @Override
+ public void parse()
+ {
+ AlignViewportI av = getViewport();
+ if (av != null)
+ {
+ if (av.getAlignment() != null)
+ {
+ dataset = av.getAlignment().getDataset();
+ }
+ if (dataset == null)
+ {
+ // working in the applet context ?
+ dataset = av.getAlignment();
+ }
+ }
+ else
+ {
+ dataset = new Alignment(new SequenceI[] {});
+ }
+
+ boolean parseResult = parse(dataset, null, false, true);
+ if (!parseResult)
+ {
+ // pass error up somehow
+ }
+ if (av != null)
+ {
+ // update viewport with the dataset data ?
+ }
+ else
+ {
+ setSeqs(dataset.getSequencesArray());
+ }
+ }
+
+ /**
+ * Implementation of unused abstract method
*
- * @param seqs
- * @param map
+ * @return error message
+ */
+ @Override
+ public String print()
+ {
+ return "Use printGffFormat() or printJalviewFormat()";
+ }
+
+ /**
+ * Returns features output in GFF2 format, including hidden and non-positional
+ * features
+ *
+ * @param sequences
+ * the sequences whose features are to be output
+ * @param visible
+ * a map whose keys are the type names of visible features
* @return
*/
- public String printGFFFormat(SequenceI[] seqs,
- Map<String, FeatureColourI> map)
+ public String printGffFormat(SequenceI[] sequences,
- Map<String, Object> visible)
++ Map<String, FeatureColourI> visible)
{
- return printGFFFormat(seqs, map, true, true);
+ return printGffFormat(sequences, visible, true, true);
}
- public String printGFFFormat(SequenceI[] seqs,
- Map<String, FeatureColourI> map, boolean visOnly, boolean nonpos)
+ /**
+ * Returns features output in GFF2 format
+ *
+ * @param sequences
+ * the sequences whose features are to be output
+ * @param visible
+ * a map whose keys are the type names of visible features
+ * @param outputVisibleOnly
+ * @param includeNonPositionalFeatures
+ * @return
+ */
+ public String printGffFormat(SequenceI[] sequences,
- Map<String, Object> visible, boolean outputVisibleOnly,
++ Map<String, FeatureColourI> visible, boolean outputVisibleOnly,
+ boolean includeNonPositionalFeatures)
{
- StringBuffer out = new StringBuffer();
- SequenceFeature[] next;
+ StringBuilder out = new StringBuilder(256);
+ int version = gffVersion == 0 ? 2 : gffVersion;
+ out.append(String.format("%s %d\n", GFF_VERSION, version));
String source;
boolean isnonpos;
- for (int i = 0; i < seqs.length; i++)
+ for (SequenceI seq : sequences)
{
- if (seqs[i].getSequenceFeatures() != null)
+ SequenceFeature[] features = seq.getSequenceFeatures();
+ if (features != null)
{
- next = seqs[i].getSequenceFeatures();
- for (int j = 0; j < next.length; j++)
+ for (SequenceFeature sf : features)
{
- isnonpos = next[j].begin == 0 && next[j].end == 0;
- if ((!nonpos && isnonpos)
- || (!isnonpos && visOnly && !map
- .containsKey(next[j].type)))
+ isnonpos = sf.begin == 0 && sf.end == 0;
+ if (!includeNonPositionalFeatures && isnonpos)
+ {
+ /*
+ * ignore non-positional features if not wanted
+ */
+ continue;
+ }
+ // TODO why the test !isnonpos here?
+ // what about not visible non-positional features?
+ if (!isnonpos && outputVisibleOnly
+ && !visible.containsKey(sf.type))
{
+ /*
+ * ignore not visible features if not wanted
+ */
continue;
}
--- /dev/null
+ package jalview.io;
+
+ import jalview.api.FeatureColourI;
-import jalview.schemes.FeatureColourAdapter;
++import jalview.schemes.FeatureColour;
+ import jalview.schemes.FeatureSettingsAdapter;
+
+ import java.awt.Color;
+
+ public class PDBFeatureSettings extends FeatureSettingsAdapter
+ {
+
+ public static final String FEATURE_INSERTION = "INSERTION";
+
+ public static final String FEATURE_RES_NUM = "RESNUM";
+
+ @Override
+ public boolean isFeatureDisplayed(String type)
+ {
+ return type.equalsIgnoreCase(FEATURE_INSERTION)
+ || type.equalsIgnoreCase(FEATURE_RES_NUM);
+ }
+
+ @Override
+ public FeatureColourI getFeatureColour(String type)
+ {
+ if (type.equalsIgnoreCase(FEATURE_INSERTION))
+ {
- return new FeatureColourAdapter()
++ return new FeatureColour()
+ {
+
+ @Override
+ public Color getColour()
+ {
+ return Color.RED;
+ }
+ };
+ }
+ return null;
+ }
+
+ /**
+ * Order to render insertion after ResNum
+ */
+ @Override
+ public int compare(String feature1, String feature2)
+ {
+ if (feature1.equalsIgnoreCase(FEATURE_INSERTION))
+ {
+ return +1;
+ }
+ if (feature2.equalsIgnoreCase(FEATURE_INSERTION))
+ {
+ return -1;
+ }
+ if (feature1.equalsIgnoreCase(FEATURE_RES_NUM))
+ {
+ return +1;
+ }
+ if (feature2.equalsIgnoreCase(FEATURE_RES_NUM))
+ {
+ return -1;
+ }
+ return 0;
+ }
+ }
+
*/
package jalview.io.packed;
++import jalview.api.FeatureColourI;
import jalview.datamodel.AlignmentI;
import jalview.datamodel.SequenceI;
import jalview.io.NewickFile;
/**
* @return the featureColours
*/
- public Hashtable getFeatureColours()
- public Map<String, Object> getFeatureColours()
++ public Map<String, FeatureColourI> getFeatureColours()
{
return featureColours;
}
* @param featureColours
* the featureColours to set
*/
- public void setFeatureColours(Hashtable featureColours)
- public void setFeatureColours(Map<String, Object> featureColours)
++ public void setFeatureColours(Map<String, FeatureColourI> featureColours)
{
this.featureColours = featureColours;
}
/**
* current set of feature colours
*/
- Hashtable featureColours;
- Map<String, Object> featureColours;
++ Map<String, FeatureColourI> featureColours;
/**
* original identity of each sequence in results
seqDetails = new Hashtable();
al = new ArrayList<AlignmentSet>();
parentDataset = null;
- featureColours = new Hashtable();
- featureColours = new HashMap<String, Object>();
++ featureColours = new HashMap<String, FeatureColourI>();
}
/**
*
* @param parentAlignment
*/
- public JalviewDataset(AlignmentI aldataset, Hashtable fc,
- public JalviewDataset(AlignmentI aldataset, Map<String, Object> fc,
++ public JalviewDataset(AlignmentI aldataset,
++ Map<String, FeatureColourI> fc,
Hashtable seqDets)
{
+ // TODO not used - remove?
this(aldataset, fc, seqDets, null);
}
* (may be null) alignment to associate new annotation and trees
* with.
*/
- public JalviewDataset(AlignmentI aldataset, Hashtable fc,
- public JalviewDataset(AlignmentI aldataset, Map<String, Object> fc,
++ public JalviewDataset(AlignmentI aldataset,
++ Map<String, FeatureColourI> fc,
Hashtable seqDets, AlignmentI parentAlignment)
{
this();
*/
package jalview.io.packed;
++import jalview.api.FeatureColourI;
import jalview.datamodel.AlignmentI;
import jalview.io.AppletFormatAdapter;
import jalview.io.FileParse;
// if not, create one.
if (context.featureColours == null)
{
- context.featureColours = new Hashtable();
- context.featureColours = new HashMap<String, Object>();
++ context.featureColours = new HashMap<String, FeatureColourI>();
}
try
{
--- /dev/null
+package jalview.schemes;
+
+import jalview.api.FeatureColourI;
+import jalview.datamodel.SequenceFeature;
+import jalview.util.Format;
+
+import java.awt.Color;
+import java.util.StringTokenizer;
+
+/**
+ * A class that wraps either a simple colour or a graduated colour
+ */
+public class FeatureColour implements FeatureColourI
+{
+ private static final String BAR = "|";
+
+ final private Color colour;
+
+ final private Color minColour;
+
+ final private Color maxColour;
+
+ private boolean graduatedColour;
+
+ private boolean colourByLabel;
+
+ private float threshold;
+
+ private float base;
+
+ private float range;
+
+ private boolean belowThreshold;
+
+ private boolean aboveThreshold;
+
+ private boolean thresholdIsMinOrMax;
+
+ private boolean isHighToLow;
+
+ private boolean autoScaled;
+
+ final private float minRed;
+
+ final private float minGreen;
+
+ final private float minBlue;
+
+ final private float deltaRed;
+
+ final private float deltaGreen;
+
+ final private float deltaBlue;
+
+ /**
+ * Parses a Jalview features file format colour descriptor
+ * [label|][mincolour|maxcolour
+ * |[absolute|]minvalue|maxvalue|thresholdtype|thresholdvalue] Examples:
+ * <ul>
+ * <li>red</li>
+ * <li>a28bbb</li>
+ * <li>25,125,213</li>
+ * <li>label</li>
+ * <li>label|||0.0|0.0|above|12.5</li>
+ * <li>label|||0.0|0.0|below|12.5</li>
+ * <li>red|green|12.0|26.0|none</li>
+ * <li>a28bbb|3eb555|12.0|26.0|above|12.5</li>
+ * <li>a28bbb|3eb555|abso|12.0|26.0|below|12.5</li>
+ * </ul>
+ *
+ * @param descriptor
+ * @return
+ * @throws IllegalArgumentException
+ * if not parseable
+ */
+ public static FeatureColour parseJalviewFeatureColour(String descriptor)
+ {
+ StringTokenizer gcol = new StringTokenizer(descriptor, "|", true);
+ float min = Float.MIN_VALUE;
+ float max = Float.MAX_VALUE;
+ boolean labelColour = false;
+
+ String mincol = gcol.nextToken();
+ if (mincol == "|")
+ {
+ throw new IllegalArgumentException(
+ "Expected either 'label' or a colour specification in the line: "
+ + descriptor);
+ }
+ String maxcol = null;
+ if (mincol.toLowerCase().indexOf("label") == 0)
+ {
+ labelColour = true;
+ mincol = (gcol.hasMoreTokens() ? gcol.nextToken() : null);
+ // skip '|'
+ mincol = (gcol.hasMoreTokens() ? gcol.nextToken() : null);
+ }
+
+ if (!labelColour && !gcol.hasMoreTokens())
+ {
+ /*
+ * only a simple colour specification - parse it
+ */
+ Color colour = UserColourScheme.getColourFromString(descriptor);
+ if (colour == null)
+ {
+ throw new IllegalArgumentException("Invalid colour descriptor: "
+ + descriptor);
+ }
+ return new FeatureColour(colour);
+ }
+
+ /*
- * autoScaled == true: colours range over actual score range; autoScaled ==
- * false ('abso'): colours range over min/max range
++ * autoScaled == true: colours range over actual score range
++ * autoScaled == false ('abso'): colours range over min/max range
+ */
- boolean autoScaled = false;
++ boolean autoScaled = true;
+ String tok = null, minval, maxval;
+ if (mincol != null)
+ {
+ // at least four more tokens
+ if (mincol.equals("|"))
+ {
+ mincol = "";
+ }
+ else
+ {
+ gcol.nextToken(); // skip next '|'
+ }
+ maxcol = gcol.nextToken();
+ if (maxcol.equals("|"))
+ {
+ maxcol = "";
+ }
+ else
+ {
+ gcol.nextToken(); // skip next '|'
+ }
+ tok = gcol.nextToken();
+ gcol.nextToken(); // skip next '|'
- if (tok.toLowerCase().indexOf("abso") != 0)
++ if (tok.toLowerCase().startsWith("abso"))
+ {
- minval = tok;
- autoScaled = true;
++ minval = gcol.nextToken();
++ gcol.nextToken(); // skip next '|'
++ autoScaled = false;
+ }
+ else
+ {
- minval = gcol.nextToken();
- gcol.nextToken(); // skip next '|'
++ minval = tok;
+ }
+ maxval = gcol.nextToken();
+ if (gcol.hasMoreTokens())
+ {
+ gcol.nextToken(); // skip next '|'
+ }
+ try
+ {
+ if (minval.length() > 0)
+ {
+ min = new Float(minval).floatValue();
+ }
+ } catch (Exception e)
+ {
+ throw new IllegalArgumentException(
+ "Couldn't parse the minimum value for graduated colour ("
+ + descriptor + ")");
+ }
+ try
+ {
+ if (maxval.length() > 0)
+ {
+ max = new Float(maxval).floatValue();
+ }
+ } catch (Exception e)
+ {
+ throw new IllegalArgumentException(
+ "Couldn't parse the maximum value for graduated colour ("
+ + descriptor + ")");
+ }
+ }
+ else
+ {
+ // add in some dummy min/max colours for the label-only
+ // colourscheme.
+ mincol = "FFFFFF";
+ maxcol = "000000";
+ }
+
+ /*
+ * construct the FeatureColour
+ */
+ FeatureColour featureColour;
+ try
+ {
+ featureColour = new FeatureColour(
+ new UserColourScheme(mincol).findColour('A'),
+ new UserColourScheme(maxcol).findColour('A'), min, max);
+ featureColour.setColourByLabel(labelColour);
+ featureColour.setAutoScaled(autoScaled);
+ // add in any additional parameters
+ String ttype = null, tval = null;
+ if (gcol.hasMoreTokens())
+ {
+ // threshold type and possibly a threshold value
+ ttype = gcol.nextToken();
+ if (ttype.toLowerCase().startsWith("below"))
+ {
+ featureColour.setBelowThreshold(true);
+ }
+ else if (ttype.toLowerCase().startsWith("above"))
+ {
+ featureColour.setAboveThreshold(true);
+ }
+ else
+ {
+ if (!ttype.toLowerCase().startsWith("no"))
+ {
+ System.err.println("Ignoring unrecognised threshold type : "
+ + ttype);
+ }
+ }
+ }
+ if (featureColour.hasThreshold())
+ {
+ try
+ {
+ gcol.nextToken();
+ tval = gcol.nextToken();
+ featureColour.setThreshold(new Float(tval).floatValue());
+ } catch (Exception e)
+ {
+ System.err.println("Couldn't parse threshold value as a float: ("
+ + tval + ")");
+ }
+ }
+ if (gcol.hasMoreTokens())
+ {
+ System.err
+ .println("Ignoring additional tokens in parameters in graduated colour specification\n");
+ while (gcol.hasMoreTokens())
+ {
+ System.err.println("|" + gcol.nextToken());
+ }
+ System.err.println("\n");
+ }
+ return featureColour;
+ } catch (Exception e)
+ {
+ throw new IllegalArgumentException(e.getMessage());
+ }
+ }
+
+ /**
+ * Default constructor
+ */
+ public FeatureColour()
+ {
+ this((Color) null);
+ }
+
+ /**
+ * Constructor given a simple colour
+ *
+ * @param c
+ */
+ public FeatureColour(Color c)
+ {
+ minColour = Color.WHITE;
+ maxColour = Color.BLACK;
+ minRed = 0f;
+ minGreen = 0f;
+ minBlue = 0f;
+ deltaRed = 0f;
+ deltaGreen = 0f;
+ deltaBlue = 0f;
+ colour = c;
+ }
+
+ /**
+ * Constructor given a colour range and a score range
+ *
+ * @param low
+ * @param high
+ * @param min
+ * @param max
+ */
+ public FeatureColour(Color low, Color high, float min, float max)
+ {
+ graduatedColour = true;
+ colour = null;
+ minColour = low;
+ maxColour = high;
+ threshold = Float.NaN;
+ isHighToLow = min >= max;
+ minRed = low.getRed() / 255f;
+ minGreen = low.getGreen() / 255f;
+ minBlue = low.getBlue() / 255f;
+ deltaRed = (high.getRed() / 255f) - minRed;
+ deltaGreen = (high.getGreen() / 255f) - minGreen;
+ deltaBlue = (high.getBlue() / 255f) - minBlue;
+ if (isHighToLow)
+ {
+ base = max;
+ range = min - max;
+ }
+ else
+ {
+ base = min;
+ range = max - min;
+ }
+ }
+
+ /**
+ * Copy constructor
+ *
+ * @param fc
+ */
+ public FeatureColour(FeatureColour fc)
+ {
+ colour = fc.colour;
+ minColour = fc.minColour;
+ maxColour = fc.maxColour;
+ minRed = fc.minRed;
+ minGreen = fc.minGreen;
+ minBlue = fc.minBlue;
+ deltaRed = fc.deltaRed;
+ deltaGreen = fc.deltaGreen;
+ deltaBlue = fc.deltaBlue;
+ base = fc.base;
+ range = fc.range;
+ isHighToLow = fc.isHighToLow;
+ setAboveThreshold(fc.isAboveThreshold());
+ setBelowThreshold(fc.isBelowThreshold());
+ setThreshold(fc.getThreshold());
+ setAutoScaled(fc.isAutoScaled());
+ setColourByLabel(fc.isColourByLabel());
+ }
+
+ /**
+ * Copy constructor with new min/max ranges
+ * @param fc
+ * @param min
+ * @param max
+ */
+ public FeatureColour(FeatureColour fc, float min, float max)
+ {
+ this(fc);
+ graduatedColour = true;
+ updateBounds(min, max);
+ }
+
+ @Override
+ public boolean isGraduatedColour()
+ {
+ return graduatedColour;
+ }
+
+ /**
+ * Sets the 'graduated colour' flag. If true, also sets 'colour by label' to
+ * false.
+ */
- @Override
- public void setGraduatedColour(boolean b)
++ void setGraduatedColour(boolean b)
+ {
+ graduatedColour = b;
+ if (b)
+ {
+ setColourByLabel(false);
+ }
+ }
+
+ @Override
+ public Color getColour()
+ {
+ return colour;
+ }
+
+ @Override
+ public Color getMinColour()
+ {
+ return minColour;
+ }
+
+ @Override
+ public Color getMaxColour()
+ {
+ return maxColour;
+ }
+
+ @Override
+ public boolean isColourByLabel()
+ {
+ return colourByLabel;
+ }
+
+ /**
+ * Sets the 'colour by label' flag. If true, also sets 'graduated colour' to
+ * false.
+ */
+ @Override
+ public void setColourByLabel(boolean b)
+ {
+ colourByLabel = b;
+ if (b)
+ {
+ setGraduatedColour(false);
+ }
+ }
+ @Override
+ public boolean isBelowThreshold()
+ {
+ return belowThreshold;
+ }
+
+ @Override
+ public void setBelowThreshold(boolean b)
+ {
+ belowThreshold = b;
+ if (b)
+ {
+ setAboveThreshold(false);
+ }
+ }
+
+ @Override
+ public boolean isAboveThreshold()
+ {
+ return aboveThreshold;
+ }
+
+ @Override
+ public void setAboveThreshold(boolean b)
+ {
+ aboveThreshold = b;
+ if (b)
+ {
+ setBelowThreshold(false);
+ }
+ }
+
+ @Override
+ public boolean isThresholdMinMax()
+ {
+ return thresholdIsMinOrMax;
+ }
+
+ @Override
+ public void setThresholdMinMax(boolean b)
+ {
+ thresholdIsMinOrMax = b;
+ }
+
+ @Override
+ public float getThreshold()
+ {
+ return threshold;
+ }
+
+ @Override
+ public void setThreshold(float f)
+ {
+ threshold = f;
+ }
+
+ @Override
+ public boolean isAutoScaled()
+ {
+ return autoScaled;
+ }
+
+ @Override
+ public void setAutoScaled(boolean b)
+ {
+ this.autoScaled = b;
+ }
+
+ /**
+ * Updates the base and range appropriately for the given minmax range
+ *
+ * @param min
+ * @param max
+ */
+ @Override
+ public void updateBounds(float min, float max)
+ {
+ if (max < min)
+ {
+ base = max;
+ range = min - max;
+ isHighToLow = true;
+ }
+ else
+ {
+ base = min;
+ range = max - min;
+ isHighToLow = false;
+ }
+ }
+
+ /**
+ * Returns the colour for the given instance of the feature. This may be a
+ * simple colour, a colour generated from the feature description (if
+ * isColourByLabel()), or a colour derived from the feature score (if
+ * isGraduatedColour()).
+ *
+ * @param feature
+ * @return
+ */
+ @Override
+ public Color getColor(SequenceFeature feature)
+ {
+ if (isColourByLabel())
+ {
+ return UserColourScheme
+ .createColourFromName(feature.getDescription());
+ }
+
+ if (!isGraduatedColour())
+ {
+ return getColour();
+ }
+
+ // todo should we check for above/below threshold here?
+ if (range == 0.0)
+ {
+ return getMaxColour();
+ }
+ float scr = feature.getScore();
+ if (Float.isNaN(scr))
+ {
+ return getMinColour();
+ }
+ float scl = (scr - base) / range;
+ if (isHighToLow)
+ {
+ scl = -scl;
+ }
+ if (scl < 0f)
+ {
+ scl = 0f;
+ }
+ if (scl > 1f)
+ {
+ scl = 1f;
+ }
+ return new Color(minRed + scl * deltaRed, minGreen + scl * deltaGreen, minBlue + scl * deltaBlue);
+ }
+
+ /**
+ * Returns the maximum score of the graduated colour range
+ *
+ * @return
+ */
+ @Override
+ public float getMax()
+ {
+ // regenerate the original values passed in to the constructor
+ return (isHighToLow) ? base : (base + range);
+ }
+
+ /**
+ * Returns the minimum score of the graduated colour range
+ *
+ * @return
+ */
+ @Override
+ public float getMin()
+ {
+ // regenerate the original value passed in to the constructor
+ return (isHighToLow) ? (base + range) : base;
+ }
+
+ /**
+ * Answers true if the feature has a simple colour, or is coloured by label,
+ * or has a graduated colour and the score of this feature instance is within
+ * the range to render (if any), i.e. does not lie below or above any
+ * threshold set.
+ *
+ * @param feature
+ * @return
+ */
+ @Override
+ public boolean isColored(SequenceFeature feature)
+ {
+ if (isColourByLabel() || !isGraduatedColour())
+ {
+ return true;
+ }
+
+ float val = feature.getScore();
+ if (Float.isNaN(val))
+ {
+ return true;
+ }
+ if (Float.isNaN(this.threshold))
+ {
+ return true;
+ }
+
+ if (isAboveThreshold() && val <= threshold)
+ {
+ return false;
+ }
+ if (isBelowThreshold() && val >= threshold)
+ {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean isSimpleColour()
+ {
+ return (!isColourByLabel() && !isGraduatedColour());
+ }
+
+ @Override
+ public boolean hasThreshold()
+ {
+ return isAboveThreshold() || isBelowThreshold();
+ }
+
+ @Override
+ public String toJalviewFormat(String featureType)
+ {
+ String colourString = null;
+ if (isSimpleColour())
+ {
+ colourString = Format.getHexString(getColour());
+ }
+ else
+ {
+ StringBuilder sb = new StringBuilder(32);
+ if (isColourByLabel())
+ {
+ sb.append("label");
+ if (hasThreshold())
+ {
+ sb.append(BAR).append(BAR).append(BAR);
+ }
+ }
+ if (isGraduatedColour())
+ {
+ sb.append(Format.getHexString(getMinColour())).append(BAR);
+ sb.append(Format.getHexString(getMaxColour())).append(BAR);
- if (isAutoScaled())
++ if (!isAutoScaled())
+ {
+ sb.append("abso").append(BAR);
+ }
+ }
+ if (hasThreshold() || isGraduatedColour())
+ {
+ sb.append(getMin()).append(BAR);
+ sb.append(getMax()).append(BAR);
+ if (isBelowThreshold())
+ {
+ sb.append("below").append(BAR).append(getThreshold());
+ }
+ else if (isAboveThreshold())
+ {
+ sb.append("above").append(BAR).append(getThreshold());
+ }
+ else
+ {
+ sb.append("none");
+ }
+ }
+ colourString = sb.toString();
+ }
+ return String.format("%s\t%s", featureType, colourString);
+ }
+
+}
+++ /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.schemes;
--
- import jalview.datamodel.SequenceFeature;
-
- import java.awt.Color;
-
- /**
- * Value and/or thresholded colour scale used for colouring by annotation and
- * feature score
- *
- * @author JimP
- *
- */
- public class GraduatedColor
- {
- int thresholdState = AnnotationColourGradient.NO_THRESHOLD; // or
- // ABOVE_THRESHOLD
- // or
- // BELOW_THRESHOLD
-
- float lr, lg, lb, dr, dg, db;
-
- /**
- * linear scaling parameters, base, minimum colour threshold, range of linear
- * scale from lower to upper
- */
- float base, range, thrsh;
-
- /**
- * when true, colour from u to u-d rather than u to u+d
- */
- boolean tolow = false;
-
- /**
- * when false, min/max range has been manually set so should not be
- * dynamically adjusted.
- */
- boolean autoScale = true;
-
- /**
- * construct a graduatedColor object from simple parameters
- *
- * @param low
- * @param high
- * @param min
- * @param max
- * color low->high from min->max
- */
- public GraduatedColor(Color low, Color high, float min, float max)
- {
- thrsh = Float.NaN;
- tolow = min >= max;
- lr = low.getRed() / 255f;
- lg = low.getGreen() / 255f;
- lb = low.getBlue() / 255f;
- dr = (high.getRed() / 255f) - lr;
- dg = (high.getGreen() / 255f) - lg;
- db = (high.getBlue() / 255f) - lb;
- if (tolow)
- {
- base = max;
- range = min - max;
- }
- else
- {
- base = min;
- range = max - min;
- }
- }
-
- public GraduatedColor(GraduatedColor oldcs)
- {
- lr = oldcs.lr;
- lg = oldcs.lg;
- lb = oldcs.lb;
- dr = oldcs.dr;
- dg = oldcs.dg;
- db = oldcs.db;
- base = oldcs.base;
- range = oldcs.range;
- tolow = oldcs.tolow;
- thresholdState = oldcs.thresholdState;
- thrsh = oldcs.thrsh;
- autoScale = oldcs.autoScale;
- colourByLabel = oldcs.colourByLabel;
- }
-
- /**
- * make a new gradient from an old one with a different scale range
- *
- * @param oldcs
- * @param min
- * @param max
- */
- public GraduatedColor(GraduatedColor oldcs, float min, float max)
- {
- this(oldcs);
- updateBounds(min, max);
- }
-
- public Color getMinColor()
- {
- return new Color(lr, lg, lb);
- }
-
- public Color getMaxColor()
- {
- return new Color(lr + dr, lg + dg, lb + db);
- }
-
- /**
- *
- * @return true if original min/max scale was from high to low
- */
- public boolean getTolow()
- {
- return tolow;
- }
-
- public void setTolow(boolean tolower)
- {
- tolow = tolower;
- }
-
- public boolean isColored(SequenceFeature feature)
- {
- float val = feature.getScore();
- if (Float.isNaN(val))
- {
- return true;
- }
- if (this.thresholdState == AnnotationColourGradient.NO_THRESHOLD)
- {
- return true;
- }
- if (Float.isNaN(this.thrsh))
- {
- return true;
- }
- boolean rtn = thresholdState == AnnotationColourGradient.ABOVE_THRESHOLD;
- if (val <= thrsh)
- {
- return !rtn; // ? !tolow : tolow;
- }
- else
- {
- return rtn; // ? tolow : !tolow;
- }
- }
-
- /**
- * default implementor of a getColourFromString method. TODO: abstract an
- * interface enabling pluggable colour from string
- */
- private UserColourScheme ucs = null;
-
- private boolean colourByLabel = false;
-
- /**
- *
- * @return true if colourByLabel style is set
- */
- public boolean isColourByLabel()
- {
- return colourByLabel;
- }
-
- /**
- * @param colourByLabel
- * the colourByLabel to set
- */
- public void setColourByLabel(boolean colourByLabel)
- {
- this.colourByLabel = colourByLabel;
- }
-
- public Color findColor(SequenceFeature feature)
- {
- if (colourByLabel)
- {
- // TODO: allow user defined feature label colourschemes. Colour space is
- // {type,regex,%anytype%}x{description string, regex, keyword}
- if (ucs == null)
- {
- ucs = new UserColourScheme();
- }
- return ucs.createColourFromName(feature.getDescription());
- }
- if (range == 0.0)
- {
- return getMaxColor();
- }
- float scr = feature.getScore();
- if (Float.isNaN(scr))
- {
- return getMinColor();
- }
- float scl = (scr - base) / range;
- if (tolow)
- {
- scl = -scl;
- }
- if (scl < 0f)
- {
- scl = 0f;
- }
- if (scl > 1f)
- {
- scl = 1f;
- }
- return new Color(lr + scl * dr, lg + scl * dg, lb + scl * db);
- }
-
- public void setThresh(float value)
- {
- thrsh = value;
- }
-
- public float getThresh()
- {
- return thrsh;
- }
-
- public void setThreshType(int aboveThreshold)
- {
- thresholdState = aboveThreshold;
- }
-
- public int getThreshType()
- {
- return thresholdState;
- }
-
- public float getMax()
- {
- // regenerate the original values passed in to the constructor
- return (tolow) ? base : (base + range);
- }
-
- public float getMin()
- {
- // regenerate the original value passed in to the constructor
- return (tolow) ? (base + range) : base;
- }
-
- public boolean isAutoScale()
- {
- return autoScale;
- }
-
- public void setAutoScaled(boolean autoscale)
- {
- autoScale = autoscale;
- }
-
- /**
- * update the base and range appropriatly for the given minmax range
- *
- * @param a
- * float[] {min,max} array containing minmax range for the associated
- * score values
- */
- public void updateBounds(float min, float max)
- {
- if (max < min)
- {
- base = max;
- range = min - max;
- tolow = true;
- }
- else
- {
- base = min;
- range = max - min;
- tolow = false;
- }
- }
- }
-import jalview.api.FeatureColourI;
-import jalview.datamodel.SequenceFeature;
-
-import java.awt.Color;
-
-/**
- * Value and/or thresholded colour scale used for colouring by annotation and
- * feature score
- *
- * @author JimP
- *
- */
-public class GraduatedColor
-{
- int thresholdState = AnnotationColourGradient.NO_THRESHOLD; // or
- // ABOVE_THRESHOLD
- // or
- // BELOW_THRESHOLD
-
- float lr, lg, lb, dr, dg, db;
-
- /**
- * linear scaling parameters, base, minimum colour threshold, range of linear
- * scale from lower to upper
- */
- float base, range, thrsh;
-
- /**
- * when true, colour from u to u-d rather than u to u+d
- */
- boolean tolow = false;
-
- /**
- * when false, min/max range has been manually set so should not be
- * dynamically adjusted.
- */
- boolean autoScale = true;
-
- /**
- * construct a graduatedColor object from simple parameters
- *
- * @param low
- * @param high
- * @param min
- * @param max
- * color low->high from min->max
- */
- public GraduatedColor(Color low, Color high, float min, float max)
- {
- thrsh = Float.NaN;
- tolow = min >= max;
- lr = low.getRed() / 255f;
- lg = low.getGreen() / 255f;
- lb = low.getBlue() / 255f;
- dr = (high.getRed() / 255f) - lr;
- dg = (high.getGreen() / 255f) - lg;
- db = (high.getBlue() / 255f) - lb;
- if (tolow)
- {
- base = max;
- range = min - max;
- }
- else
- {
- base = min;
- range = max - min;
- }
- }
-
- public GraduatedColor(GraduatedColor oldcs)
- {
- lr = oldcs.lr;
- lg = oldcs.lg;
- lb = oldcs.lb;
- dr = oldcs.dr;
- dg = oldcs.dg;
- db = oldcs.db;
- base = oldcs.base;
- range = oldcs.range;
- tolow = oldcs.tolow;
- thresholdState = oldcs.thresholdState;
- thrsh = oldcs.thrsh;
- autoScale = oldcs.autoScale;
- colourByLabel = oldcs.colourByLabel;
- }
-
- /**
- * make a new gradient from an old one with a different scale range
- *
- * @param oldcs
- * @param min
- * @param max
- */
- public GraduatedColor(GraduatedColor oldcs, float min, float max)
- {
- this(oldcs);
- updateBounds(min, max);
- }
-
- public GraduatedColor(FeatureColourI col)
- {
- setColourByLabel(col.isColourByLabel());
- }
-
- public Color getMinColor()
- {
- return new Color(lr, lg, lb);
- }
-
- public Color getMaxColor()
- {
- return new Color(lr + dr, lg + dg, lb + db);
- }
-
- /**
- *
- * @return true if original min/max scale was from high to low
- */
- public boolean getTolow()
- {
- return tolow;
- }
-
- public void setTolow(boolean tolower)
- {
- tolow = tolower;
- }
-
- public boolean isColored(SequenceFeature feature)
- {
- float val = feature.getScore();
- if (Float.isNaN(val))
- {
- return true;
- }
- if (this.thresholdState == AnnotationColourGradient.NO_THRESHOLD)
- {
- return true;
- }
- if (Float.isNaN(this.thrsh))
- {
- return true;
- }
- boolean rtn = thresholdState == AnnotationColourGradient.ABOVE_THRESHOLD;
- if (val <= thrsh)
- {
- return !rtn; // ? !tolow : tolow;
- }
- else
- {
- return rtn; // ? tolow : !tolow;
- }
- }
-
- /**
- * default implementor of a getColourFromString method. TODO: abstract an
- * interface enabling pluggable colour from string
- */
- private UserColourScheme ucs = null;
-
- private boolean colourByLabel = false;
-
- /**
- *
- * @return true if colourByLabel style is set
- */
- public boolean isColourByLabel()
- {
- return colourByLabel;
- }
-
- /**
- * @param colourByLabel
- * the colourByLabel to set
- */
- public void setColourByLabel(boolean colourByLabel)
- {
- this.colourByLabel = colourByLabel;
- }
-
- public Color findColor(SequenceFeature feature)
- {
- if (colourByLabel)
- {
- // TODO: allow user defined feature label colourschemes. Colour space is
- // {type,regex,%anytype%}x{description string, regex, keyword}
- if (ucs == null)
- {
- ucs = new UserColourScheme();
- }
- return ucs.createColourFromName(feature.getDescription());
- }
- if (range == 0.0)
- {
- return getMaxColor();
- }
- float scr = feature.getScore();
- if (Float.isNaN(scr))
- {
- return getMinColor();
- }
- float scl = (scr - base) / range;
- if (tolow)
- {
- scl = -scl;
- }
- if (scl < 0f)
- {
- scl = 0f;
- }
- if (scl > 1f)
- {
- scl = 1f;
- }
- return new Color(lr + scl * dr, lg + scl * dg, lb + scl * db);
- }
-
- public void setThresh(float value)
- {
- thrsh = value;
- }
-
- public float getThresh()
- {
- return thrsh;
- }
-
- public void setThreshType(int aboveThreshold)
- {
- thresholdState = aboveThreshold;
- }
-
- public int getThreshType()
- {
- return thresholdState;
- }
-
- public float getMax()
- {
- // regenerate the original values passed in to the constructor
- return (tolow) ? base : (base + range);
- }
-
- public float getMin()
- {
- // regenerate the original value passed in to the constructor
- return (tolow) ? (base + range) : base;
- }
-
- public boolean isAutoScale()
- {
- return autoScale;
- }
-
- public void setAutoScaled(boolean autoscale)
- {
- autoScale = autoscale;
- }
-
- /**
- * update the base and range appropriatly for the given minmax range
- *
- * @param a
- * float[] {min,max} array containing minmax range for the associated
- * score values
- */
- public void updateBounds(float min, float max)
- {
- if (max < min)
- {
- base = max;
- range = min - max;
- tolow = true;
- }
- else
- {
- base = min;
- range = max - min;
- tolow = false;
- }
- }
-}
}
@Override
- public void setColour(String featureType, Object col)
+ public void setColour(String featureType, FeatureColourI col)
{
- {
- featureColours.put(featureType, col);
- }
- // overwrite
- // Color _col = (col instanceof Color) ? ((Color) col) : (col instanceof
- // GraduatedColor) ? ((GraduatedColor) col).getMaxColor() : null;
- // Object c = featureColours.get(featureType);
- // if (c == null || c instanceof Color || (c instanceof GraduatedColor &&
- // !((GraduatedColor)c).getMaxColor().equals(_col)))
- if (col instanceof FeatureColourI)
- {
- if (((FeatureColourI) col).isGraduatedColour())
- {
- col = new GraduatedColor((FeatureColourI) col);
- }
- else
- {
- col = ((FeatureColourI) col).getColour();
- }
- }
- featureColours.put(featureType, col);
++ featureColours.put(featureType, col);
}
public void setTransparency(float value)
* { String(Type), Colour(Type), Boolean(Displayed) }
* @param visibleNew
* when true current featureDisplay list will be cleared
+ * @return true if any visible features have been reordered or recoloured,
+ * else false (i.e. no need to repaint)
*/
- public void setFeaturePriority(Object[][] data, boolean visibleNew)
+ public boolean setFeaturePriority(Object[][] data, boolean visibleNew)
{
+ /*
+ * note visible feature ordering and colours before update
+ */
+ List<String> visibleFeatures = getDisplayedFeatureTypes();
- Map<String, Object> visibleColours = new HashMap<String, Object>(
++ Map<String, FeatureColourI> visibleColours = new HashMap<String, FeatureColourI>(
+ getFeatureColours());
+
FeaturesDisplayedI av_featuresdisplayed = null;
if (visibleNew)
{
package jalview.ws.jws1;
import jalview.analysis.AlignSeq;
++import jalview.api.FeatureColourI;
import jalview.bin.Cache;
import jalview.datamodel.Alignment;
import jalview.datamodel.AlignmentView;
*
* @return null or { Alignment(+features and annotation), NewickFile)}
*/
- public Object[] getAlignment(Alignment dataset, Map featureColours)
+ public Object[] getAlignment(Alignment dataset,
- Map<String, Object> featureColours)
++ Map<String, FeatureColourI> featureColours)
{
if (result != null && result.isFinished())
// NewickFile nf[] = new NewickFile[jobs.length];
for (int j = 0; j < jobs.length; j++)
{
- Map featureColours = new HashMap();
- Map<String, Object> featureColours = new HashMap<String, Object>();
++ Map<String, FeatureColourI> featureColours = new HashMap<String, FeatureColourI>();
Alignment al = null;
NewickFile nf = null;
if (jobs[j].hasResults())
package jalview.io;
import static org.testng.AssertJUnit.assertEquals;
+ import static org.testng.AssertJUnit.assertFalse;
import static org.testng.AssertJUnit.assertNotNull;
+ import static org.testng.AssertJUnit.assertNull;
import static org.testng.AssertJUnit.assertTrue;
+import jalview.api.FeatureColourI;
+ import jalview.api.FeatureRenderer;
+ import jalview.datamodel.Alignment;
import jalview.datamodel.AlignmentI;
+ import jalview.datamodel.SequenceDummy;
import jalview.datamodel.SequenceFeature;
+ import jalview.datamodel.SequenceI;
import jalview.gui.AlignFrame;
-import jalview.schemes.AnnotationColourGradient;
-import jalview.schemes.GraduatedColor;
import java.awt.Color;
import java.io.File;
assertEquals("netphos", sf.featureGroup);
assertEquals("PHOSPHORYLATION (T)", sf.type);
}
+
+ /**
+ * Test parsing a features file with a mix of Jalview and GFF formatted
+ * content
+ *
+ * @throws Exception
+ */
+ @Test(groups = { "Functional" })
+ public void testParse_mixedJalviewGff() throws Exception
+ {
+ File f = new File("examples/uniref50.fa");
+ AlignmentI al = readAlignmentFile(f);
+ AlignFrame af = new AlignFrame(al, 500, 500);
- Map<String, Object> colours = af.getFeatureRenderer()
++ Map<String, FeatureColourI> colours = af.getFeatureRenderer()
+ .getFeatureColours();
+ // GFF2 uses space as name/value separator in column 9
+ String gffData = "METAL\tcc9900\n" + "GFF\n"
+ + "FER_CAPAA\tuniprot\tMETAL\t44\t45\t4.0\t.\t.\tNote Iron-sulfur; Note 2Fe-2S\n"
+ + "FER1_SOLLC\tuniprot\tPfam\t55\t130\t2.0\t.\t.";
+ FeaturesFile featuresFile = new FeaturesFile(gffData,
+ FormatAdapter.PASTE);
+ assertTrue("Failed to parse features file",
+ featuresFile.parse(al.getDataset(), colours, true));
+
+ // verify colours read or synthesized
+ colours = af.getFeatureRenderer().getFeatureColours();
+ assertEquals("1 feature group colours not found", 1, colours.size());
- assertEquals(colours.get("METAL"), new Color(0xcc9900));
++ assertEquals(colours.get("METAL").getColour(), new Color(0xcc9900));
+
+ // verify feature on FER_CAPAA
+ SequenceFeature[] sfs = al.getSequenceAt(0).getDatasetSequence()
+ .getSequenceFeatures();
+ assertEquals(1, sfs.length);
+ SequenceFeature sf = sfs[0];
+ assertEquals("Iron-sulfur,2Fe-2S", sf.description);
+ assertEquals(44, sf.begin);
+ assertEquals(45, sf.end);
+ assertEquals("uniprot", sf.featureGroup);
+ assertEquals("METAL", sf.type);
+ assertEquals(4f, sf.getScore(), 0.001f);
+
+ // verify feature on FER1_SOLLC
+ sfs = al.getSequenceAt(2).getDatasetSequence().getSequenceFeatures();
+ assertEquals(1, sfs.length);
+ sf = sfs[0];
+ assertEquals("uniprot", sf.description);
+ assertEquals(55, sf.begin);
+ assertEquals(130, sf.end);
+ assertEquals("uniprot", sf.featureGroup);
+ assertEquals("Pfam", sf.type);
+ assertEquals(2f, sf.getScore(), 0.001f);
+ }
+
+ public static AlignmentI readAlignmentFile(File f) throws IOException
+ {
+ System.out.println("Reading file: " + f);
+ String ff = f.getPath();
+ FormatAdapter rf = new FormatAdapter();
+
+ AlignmentI al = rf.readFile(ff, FormatAdapter.FILE,
+ new IdentifyFile().identify(ff, FormatAdapter.FILE));
+
+ al.setDataset(null); // creates dataset sequences
+ assertNotNull("Couldn't read supplied alignment data.", al);
+ return al;
+ }
+
+ /**
- * Test various ways of describing a feature colour scheme
- *
- * @throws Exception
- */
- @Test(groups = { "Functional" })
- public void testParseGraduatedColourScheme() throws Exception
- {
- FeaturesFile ff = new FeaturesFile();
-
- // colour by label:
- GraduatedColor gc = ff.parseGraduatedColourScheme(
- "BETA-TURN-IR\t9a6a94", "label");
- assertTrue(gc.isColourByLabel());
- assertEquals(Color.white, gc.getMinColor());
- assertEquals(Color.black, gc.getMaxColor());
- assertTrue(gc.isAutoScale());
-
- // using colour name, rgb, etc:
- String spec = "blue|255,0,255|absolute|20.0|95.0|below|66.0";
- gc = ff.parseGraduatedColourScheme("BETA-TURN-IR\t" + spec, spec);
- assertFalse(gc.isColourByLabel());
- assertEquals(Color.blue, gc.getMinColor());
- assertEquals(new Color(255, 0, 255), gc.getMaxColor());
- assertFalse(gc.isAutoScale());
- assertFalse(gc.getTolow());
- assertEquals(20.0f, gc.getMin(), 0.001f);
- assertEquals(95.0f, gc.getMax(), 0.001f);
- assertEquals(AnnotationColourGradient.BELOW_THRESHOLD,
- gc.getThreshType());
- assertEquals(66.0f, gc.getThresh(), 0.001f);
-
- // inverse gradient high to low:
- spec = "blue|255,0,255|95.0|20.0|below|66.0";
- gc = ff.parseGraduatedColourScheme("BETA-TURN-IR\t" + spec, spec);
- assertTrue(gc.isAutoScale());
- assertTrue(gc.getTolow());
- }
-
- /**
+ * Test parsing a features file with GFF formatted content only
+ *
+ * @throws Exception
+ */
+ @Test(groups = { "Functional" })
+ public void testParse_pureGff3() throws Exception
+ {
+ File f = new File("examples/uniref50.fa");
+ AlignmentI al = readAlignmentFile(f);
+ AlignFrame af = new AlignFrame(al, 500, 500);
- Map<String, Object> colours = af.getFeatureRenderer()
++ Map<String, FeatureColourI> colours = af.getFeatureRenderer()
+ .getFeatureColours();
+ // GFF3 uses '=' separator for name/value pairs in colum 9
+ String gffData = "##gff-version 3\n"
+ + "FER_CAPAA\tuniprot\tMETAL\t39\t39\t0.0\t.\t.\t"
+ + "Note=Iron-sulfur (2Fe-2S);Note=another note;evidence=ECO:0000255|PROSITE-ProRule:PRU00465\n"
+ + "FER1_SOLLC\tuniprot\tPfam\t55\t130\t3.0\t.\t.\tID=$23";
+ FeaturesFile featuresFile = new FeaturesFile(gffData,
+ FormatAdapter.PASTE);
+ assertTrue("Failed to parse features file",
+ featuresFile.parse(al.getDataset(), colours, true));
+
+ // verify feature on FER_CAPAA
+ SequenceFeature[] sfs = al.getSequenceAt(0).getDatasetSequence()
+ .getSequenceFeatures();
+ assertEquals(1, sfs.length);
+ SequenceFeature sf = sfs[0];
+ // description parsed from Note attribute
+ assertEquals("Iron-sulfur (2Fe-2S),another note", sf.description);
+ assertEquals(39, sf.begin);
+ assertEquals(39, sf.end);
+ assertEquals("uniprot", sf.featureGroup);
+ assertEquals("METAL", sf.type);
+ assertEquals(
+ "Note=Iron-sulfur (2Fe-2S);Note=another note;evidence=ECO:0000255|PROSITE-ProRule:PRU00465",
+ sf.getValue("ATTRIBUTES"));
+
+ // verify feature on FER1_SOLLC1
+ sfs = al.getSequenceAt(2).getDatasetSequence().getSequenceFeatures();
+ assertEquals(1, sfs.length);
+ sf = sfs[0];
+ // ID used for description if available
+ assertEquals("$23", sf.description);
+ assertEquals(55, sf.begin);
+ assertEquals(130, sf.end);
+ assertEquals("uniprot", sf.featureGroup);
+ assertEquals("Pfam", sf.type);
+ assertEquals(3f, sf.getScore(), 0.001f);
+ }
+
+ /**
+ * Test parsing a features file with Jalview format features (but no colour
+ * descriptors or startgroup to give the hint not to parse as GFF)
+ *
+ * @throws Exception
+ */
+ @Test(groups = { "Functional" })
+ public void testParse_jalviewFeaturesOnly() throws Exception
+ {
+ File f = new File("examples/uniref50.fa");
+ AlignmentI al = readAlignmentFile(f);
+ AlignFrame af = new AlignFrame(al, 500, 500);
- Map<String, Object> colours = af.getFeatureRenderer()
++ Map<String, FeatureColourI> colours = af.getFeatureRenderer()
+ .getFeatureColours();
+
+ /*
+ * one feature on FER_CAPAA and one on sequence 3 (index 2) FER1_SOLLC
+ */
+ String featureData = "Iron-sulfur (2Fe-2S)\tFER_CAPAA\t-1\t39\t39\tMETAL\n"
+ + "Iron-phosphorus (2Fe-P)\tID_NOT_SPECIFIED\t2\t86\t87\tMETALLIC\n";
+ FeaturesFile featuresFile = new FeaturesFile(featureData,
+ FormatAdapter.PASTE);
+ assertTrue("Failed to parse features file",
+ featuresFile.parse(al.getDataset(), colours, true));
+
+ // verify FER_CAPAA feature
+ SequenceFeature[] sfs = al.getSequenceAt(0).getDatasetSequence()
+ .getSequenceFeatures();
+ assertEquals(1, sfs.length);
+ SequenceFeature sf = sfs[0];
+ assertEquals("Iron-sulfur (2Fe-2S)", sf.description);
+ assertEquals(39, sf.begin);
+ assertEquals(39, sf.end);
+ assertEquals("METAL", sf.type);
+
+ // verify FER1_SOLLC feature
+ sfs = al.getSequenceAt(2).getDatasetSequence().getSequenceFeatures();
+ assertEquals(1, sfs.length);
+ sf = sfs[0];
+ assertEquals("Iron-phosphorus (2Fe-P)", sf.description);
+ assertEquals(86, sf.begin);
+ assertEquals(87, sf.end);
+ assertEquals("METALLIC", sf.type);
+ }
+
+ private void checkDatasetfromSimpleGff3(AlignmentI dataset)
+ {
+ assertEquals("no sequences extracted from GFF3 file", 2,
+ dataset.getHeight());
+
+ SequenceI seq1 = dataset.findName("seq1");
+ SequenceI seq2 = dataset.findName("seq2");
+ assertNotNull(seq1);
+ assertNotNull(seq2);
+ assertFalse(
+ "Failed to replace dummy seq1 with real sequence",
+ seq1 instanceof SequenceDummy
+ && ((SequenceDummy) seq1).isDummy());
+ assertFalse(
+ "Failed to replace dummy seq2 with real sequence",
+ seq2 instanceof SequenceDummy
+ && ((SequenceDummy) seq2).isDummy());
+ String placeholderseq = new SequenceDummy("foo").getSequenceAsString();
+ assertFalse("dummy replacement buggy for seq1",
+ placeholderseq.equals(seq1.getSequenceAsString()));
+ assertFalse("dummy replacement buggy for seq2",
+ placeholderseq.equals(seq2.getSequenceAsString()));
+ assertNotNull("No features added to seq1", seq1.getSequenceFeatures());
+ assertEquals("Wrong number of features", 3,
+ seq1.getSequenceFeatures().length);
+ assertNull(seq2.getSequenceFeatures());
+ assertEquals(
+ "Wrong number of features",
+ 0,
+ seq2.getSequenceFeatures() == null ? 0 : seq2
+ .getSequenceFeatures().length);
+ assertTrue(
+ "Expected at least one CDNA/Protein mapping for seq1",
+ dataset.getCodonFrame(seq1) != null
+ && dataset.getCodonFrame(seq1).size() > 0);
+
+ }
+
+ @Test(groups = { "Functional" })
+ public void readGff3File() throws IOException
+ {
+ FeaturesFile gffreader = new FeaturesFile(true, simpleGffFile,
+ FormatAdapter.FILE);
+ Alignment dataset = new Alignment(gffreader.getSeqsAsArray());
+ gffreader.addProperties(dataset);
+ checkDatasetfromSimpleGff3(dataset);
+ }
+
+ @Test(groups = { "Functional" })
+ public void simpleGff3FileClass() throws IOException
+ {
+ AlignmentI dataset = new Alignment(new SequenceI[] {});
+ FeaturesFile ffile = new FeaturesFile(simpleGffFile,
+ FormatAdapter.FILE);
+
+ boolean parseResult = ffile.parse(dataset, null, false, false);
+ assertTrue("return result should be true", parseResult);
+ checkDatasetfromSimpleGff3(dataset);
+ }
+
+ @Test(groups = { "Functional" })
+ public void simpleGff3FileLoader() throws IOException
+ {
+ AlignFrame af = new FileLoader(false).LoadFileWaitTillLoaded(
+ simpleGffFile, FormatAdapter.FILE);
+ assertTrue(
+ "Didn't read the alignment into an alignframe from Gff3 File",
+ af != null);
+ checkDatasetfromSimpleGff3(af.getViewport().getAlignment());
+ }
+
+ @Test(groups = { "Functional" })
+ public void simpleGff3RelaxedIdMatching() throws IOException
+ {
+ AlignmentI dataset = new Alignment(new SequenceI[] {});
+ FeaturesFile ffile = new FeaturesFile(simpleGffFile,
+ FormatAdapter.FILE);
+
+ boolean parseResult = ffile.parse(dataset, null, false, true);
+ assertTrue("return result (relaxedID matching) should be true",
+ parseResult);
+ checkDatasetfromSimpleGff3(dataset);
+ }
+
+ @Test(groups = { "Functional" })
+ public void testPrintJalviewFormat() throws Exception
+ {
+ File f = new File("examples/uniref50.fa");
+ AlignmentI al = readAlignmentFile(f);
+ AlignFrame af = new AlignFrame(al, 500, 500);
- Map<String, Object> colours = af.getFeatureRenderer()
++ Map<String, FeatureColourI> colours = af.getFeatureRenderer()
+ .getFeatureColours();
+ String features = "METAL\tcc9900\n"
+ + "GAMMA-TURN\tred|0,255,255|20.0|95.0|below|66.0\n"
+ + "Pfam\tred\n"
+ + "STARTGROUP\tuniprot\n"
+ + "Iron\tFER_CAPAA\t-1\t39\t39\tMETAL\n"
+ + "Turn\tFER_CAPAA\t-1\t36\t38\tGAMMA-TURN\n"
+ + "<html>Pfam domain<a href=\"http://pfam.sanger.ac.uk/family/PF00111\">Pfam_3_4</a></html>\tFER_CAPAA\t-1\t20\t20\tPfam\n"
+ + "ENDGROUP\tuniprot\n";
+ FeaturesFile featuresFile = new FeaturesFile(features,
+ FormatAdapter.PASTE);
+ featuresFile.parse(al.getDataset(), colours, false);
+
+ /*
+ * first with no features displayed
+ */
+ FeatureRenderer fr = af.alignPanel.getFeatureRenderer();
- Map<String, Object> visible = fr
++ Map<String, FeatureColourI> visible = fr
+ .getDisplayedFeatureCols();
+ String exported = featuresFile.printJalviewFormat(
+ al.getSequencesArray(), visible);
+ String expected = "No Features Visible";
+ assertEquals(expected, exported);
+
+ /*
+ * set METAL (in uniprot group) and GAMMA-TURN visible, but not Pfam
+ */
+ fr.setVisible("METAL");
+ fr.setVisible("GAMMA-TURN");
+ visible = fr.getDisplayedFeatureCols();
+ exported = featuresFile.printJalviewFormat(al.getSequencesArray(),
+ visible);
+ expected = "METAL\tcc9900\n"
+ + "GAMMA-TURN\tff0000|00ffff|20.0|95.0|below|66.0\n"
+ + "\nSTARTGROUP\tuniprot\n"
+ + "Iron\tFER_CAPAA\t-1\t39\t39\tMETAL\t0.0\n"
+ + "Turn\tFER_CAPAA\t-1\t36\t38\tGAMMA-TURN\t0.0\n"
+ + "ENDGROUP\tuniprot\n";
+ assertEquals(expected, exported);
+
+ /*
+ * now set Pfam visible
+ */
+ fr.setVisible("Pfam");
+ visible = fr.getDisplayedFeatureCols();
+ exported = featuresFile.printJalviewFormat(al.getSequencesArray(),
+ visible);
+ /*
+ * note the order of feature types is uncontrolled - derives from
+ * FeaturesDisplayed.featuresDisplayed which is a HashSet
+ */
+ expected = "METAL\tcc9900\n"
+ + "Pfam\tff0000\n"
+ + "GAMMA-TURN\tff0000|00ffff|20.0|95.0|below|66.0\n"
+ + "\nSTARTGROUP\tuniprot\n"
+ + "Iron\tFER_CAPAA\t-1\t39\t39\tMETAL\t0.0\n"
+ + "Turn\tFER_CAPAA\t-1\t36\t38\tGAMMA-TURN\t0.0\n"
+ + "<html>Pfam domain<a href=\"http://pfam.sanger.ac.uk/family/PF00111\">Pfam_3_4</a></html>\tFER_CAPAA\t-1\t20\t20\tPfam\t0.0\n"
+ + "ENDGROUP\tuniprot\n";
+ assertEquals(expected, exported);
+ }
}
--- /dev/null
+package jalview.schemes;
+
+import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.assertFalse;
+import static org.testng.AssertJUnit.assertTrue;
+import static org.testng.AssertJUnit.fail;
+
+import jalview.datamodel.SequenceFeature;
+import jalview.util.Format;
+
+import java.awt.Color;
+
+import org.testng.annotations.Test;
+
+public class FeatureColourTest
+{
+ @Test(groups = { "Functional" })
+ public void testIsColored_simpleColour()
+ {
+ FeatureColour fc = new FeatureColour(Color.RED);
+ assertTrue(fc.isColored(new SequenceFeature()));
+ }
+
+ @Test(groups = { "Functional" })
+ public void testIsColored_colourByLabel()
+ {
+ FeatureColour fc = new FeatureColour();
+ fc.setColourByLabel(true);
+ assertTrue(fc.isColored(new SequenceFeature()));
+ }
+
+ @Test(groups = { "Functional" })
+ public void testIsColored_aboveThreshold()
+ {
+ // graduated colour range from score 20 to 100
+ FeatureColour fc = new FeatureColour(Color.WHITE, Color.BLACK, 20f,
+ 100f);
+
+ // score 0 is adjusted to bottom of range
+ SequenceFeature sf = new SequenceFeature("type", "desc", 0, 20, 0f,
+ null);
+ assertTrue(fc.isColored(sf));
+ assertEquals(Color.WHITE, fc.getColor(sf));
+
+ // score 120 is adjusted to top of range
+ sf.setScore(120f);
+ assertEquals(Color.BLACK, fc.getColor(sf));
+
+ // value below threshold is still rendered
+ // setting threshold has no effect yet...
+ fc.setThreshold(60f);
+ sf.setScore(36f);
+ assertTrue(fc.isColored(sf));
+ assertEquals(new Color(204, 204, 204), fc.getColor(sf));
+
+ // now apply threshold:
+ fc.setAboveThreshold(true);
+ assertFalse(fc.isColored(sf));
+ // colour is still returned though ?!?
+ assertEquals(new Color(204, 204, 204), fc.getColor(sf));
+
+ sf.setScore(84); // above threshold now
+ assertTrue(fc.isColored(sf));
+ assertEquals(new Color(51, 51, 51), fc.getColor(sf));
+ }
+
+ @Test(groups = { "Functional" })
+ public void testGetColor_simpleColour()
+ {
+ FeatureColour fc = new FeatureColour(Color.RED);
+ assertEquals(Color.RED, fc.getColor(new SequenceFeature()));
+ }
+
+ @Test(groups = { "Functional" })
+ public void testGetColor_colourByLabel()
+ {
+ FeatureColour fc = new FeatureColour();
+ fc.setColourByLabel(true);
+ SequenceFeature sf = new SequenceFeature("type", "desc", 0, 20, 1f,
+ null);
+ Color expected = UserColourScheme.createColourFromName("desc");
+ assertEquals(expected, fc.getColor(sf));
+ }
+
+ @Test(groups = { "Functional" })
+ public void testGetColor_Graduated()
+ {
+ // graduated colour from score 0 to 100, gray(128, 128, 128) to red(255, 0, 0)
+ FeatureColour fc = new FeatureColour(Color.GRAY, Color.RED, 0f, 100f);
+ // feature score is 75 which is 3/4 of the way from GRAY to RED
+ SequenceFeature sf = new SequenceFeature("type", "desc", 0, 20, 75f,
+ null);
+ // the colour gradient is computed in float values from 0-1 (where 1 == 255)
+ float red = 128 / 255f + 3 / 4f * (255 - 128) / 255f;
+ float green = 128 / 255f + 3 / 4f * (0 - 128) / 255f;
+ float blue = 128 / 255f + 3 / 4f * (0 - 128) / 255f;
+ Color expected = new Color(red, green, blue);
+ assertEquals(expected, fc.getColor(sf));
+ }
+
+ @Test(groups = { "Functional" })
+ public void testGetColor_belowThreshold()
+ {
+ // gradient from [50, 150] from WHITE(255, 255, 255) to BLACK(0, 0, 0)
+ FeatureColour fc = new FeatureColour(Color.WHITE, Color.BLACK, 50f,
+ 150f);
+ SequenceFeature sf = new SequenceFeature("type", "desc", 0, 20, 70f,
+ null);
+ fc.setThreshold(100f); // ignore for now
+ assertTrue(fc.isColored(sf));
+ assertEquals(new Color(204, 204, 204), fc.getColor(sf));
+
+ fc.setAboveThreshold(true); // feature lies below threshold
+ assertFalse(fc.isColored(sf));
+ assertEquals(new Color(204, 204, 204), fc.getColor(sf));
+ }
+
+ /**
+ * Test output of feature colours to Jalview features file format
+ */
+ @Test(groups = { "Functional" })
+ public void testToJalviewFormat()
+ {
+ /*
+ * plain colour - to RGB hex code
+ */
+ FeatureColour fc = new FeatureColour(Color.RED);
+ String redHex = Format.getHexString(Color.RED);
+ String hexColour = redHex;
+ assertEquals("domain\t" + hexColour, fc.toJalviewFormat("domain"));
+
+ /*
+ * colour by label (no threshold)
+ */
+ fc = new FeatureColour();
+ fc.setColourByLabel(true);
+ assertEquals("domain\tlabel", fc.toJalviewFormat("domain"));
+
+ /*
+ * colour by label (autoscaled) (an odd state you can reach by selecting
+ * 'above threshold', then deselecting 'threshold is min/max' then 'colour
+ * by label')
+ */
+ fc.setAutoScaled(true);
+ assertEquals("domain\tlabel", fc.toJalviewFormat("domain"));
+
+ /*
+ * colour by label (above threshold) (min/max values are output though not
+ * used by this scheme)
+ */
+ fc.setAutoScaled(false);
+ fc.setThreshold(12.5f);
+ fc.setAboveThreshold(true);
+ assertEquals("domain\tlabel|||0.0|0.0|above|12.5",
+ fc.toJalviewFormat("domain"));
+
+ /*
+ * colour by label (below threshold)
+ */
+ fc.setBelowThreshold(true);
+ assertEquals("domain\tlabel|||0.0|0.0|below|12.5",
+ fc.toJalviewFormat("domain"));
+
+ /*
+ * graduated colour, no threshold
+ */
+ fc = new FeatureColour(Color.GREEN, Color.RED, 12f, 25f);
+ String greenHex = Format.getHexString(Color.GREEN);
- String expected = String.format("domain\t%s|%s|12.0|25.0|none",
++ String expected = String.format("domain\t%s|%s|abso|12.0|25.0|none",
+ greenHex, redHex);
+ assertEquals(expected, fc.toJalviewFormat("domain"));
+
+ /*
+ * colour ranges over the actual score ranges (not min/max)
+ */
+ fc.setAutoScaled(true);
- expected = String.format("domain\t%s|%s|abso|12.0|25.0|none", greenHex,
++ expected = String.format("domain\t%s|%s|12.0|25.0|none", greenHex,
+ redHex);
+ assertEquals(expected, fc.toJalviewFormat("domain"));
+
+ /*
+ * graduated colour below threshold
+ */
+ fc.setThreshold(12.5f);
+ fc.setBelowThreshold(true);
- expected = String.format("domain\t%s|%s|abso|12.0|25.0|below|12.5",
++ expected = String.format("domain\t%s|%s|12.0|25.0|below|12.5",
+ greenHex, redHex);
+ assertEquals(expected, fc.toJalviewFormat("domain"));
+
+ /*
+ * graduated colour above threshold
+ */
+ fc.setThreshold(12.5f);
+ fc.setAboveThreshold(true);
++ fc.setAutoScaled(false);
+ expected = String.format("domain\t%s|%s|abso|12.0|25.0|above|12.5",
+ greenHex, redHex);
+ assertEquals(expected, fc.toJalviewFormat("domain"));
+ }
+
+ /**
+ * Test parsing of feature colours from Jalview features file format
+ */
+ @Test(groups = { "Functional" })
+ public void testParseJalviewFeatureColour()
+ {
+ /*
+ * simple colour by name
+ */
+ FeatureColour fc = FeatureColour.parseJalviewFeatureColour("red");
+ assertTrue(fc.isSimpleColour());
+ assertEquals(Color.RED, fc.getColour());
+
+ /*
+ * simple colour by hex code
+ */
+ fc = FeatureColour.parseJalviewFeatureColour(Format
+ .getHexString(Color.RED));
+ assertTrue(fc.isSimpleColour());
+ assertEquals(Color.RED, fc.getColour());
+
+ /*
+ * simple colour by rgb triplet
+ */
+ fc = FeatureColour.parseJalviewFeatureColour("255,0,0");
+ assertTrue(fc.isSimpleColour());
+ assertEquals(Color.RED, fc.getColour());
+
+ /*
+ * malformed colour
+ */
+ try
+ {
+ fc = FeatureColour.parseJalviewFeatureColour("oops");
+ fail("expected exception");
+ } catch (IllegalArgumentException e)
+ {
+ assertEquals("Invalid colour descriptor: oops", e.getMessage());
+ }
+
+ /*
+ * colour by label (no threshold)
+ */
+ fc = FeatureColour.parseJalviewFeatureColour("label");
+ assertTrue(fc.isColourByLabel());
+ assertFalse(fc.hasThreshold());
+
+ /*
+ * colour by label (with threshold)
+ */
+ fc = FeatureColour
+ .parseJalviewFeatureColour("label|||0.0|0.0|above|12.0");
+ assertTrue(fc.isColourByLabel());
+ assertTrue(fc.isAboveThreshold());
+ assertEquals(12.0f, fc.getThreshold());
+
+ /*
+ * graduated colour (by name) (no threshold)
+ */
+ fc = FeatureColour.parseJalviewFeatureColour("red|green|10.0|20.0");
+ assertTrue(fc.isGraduatedColour());
+ assertFalse(fc.hasThreshold());
+ assertEquals(Color.RED, fc.getMinColour());
+ assertEquals(Color.GREEN, fc.getMaxColour());
+ assertEquals(10f, fc.getMin());
+ assertEquals(20f, fc.getMax());
+ assertTrue(fc.isAutoScaled());
+
+ /*
+ * graduated colour (by hex code) (above threshold)
+ */
+ String descriptor = String.format("%s|%s|10.0|20.0|above|15",
+ Format.getHexString(Color.RED),
+ Format.getHexString(Color.GREEN));
+ fc = FeatureColour.parseJalviewFeatureColour(descriptor);
+ assertTrue(fc.isGraduatedColour());
+ assertTrue(fc.hasThreshold());
+ assertTrue(fc.isAboveThreshold());
+ assertEquals(15f, fc.getThreshold());
+ assertEquals(Color.RED, fc.getMinColour());
+ assertEquals(Color.GREEN, fc.getMaxColour());
+ assertEquals(10f, fc.getMin());
+ assertEquals(20f, fc.getMax());
+ assertTrue(fc.isAutoScaled());
+
+ /*
+ * graduated colour (by RGB triplet) (below threshold), absolute scale
+ */
+ descriptor = String.format("255,0,0|0,255,0|abso|10.0|20.0|below|15");
+ fc = FeatureColour.parseJalviewFeatureColour(descriptor);
+ assertTrue(fc.isGraduatedColour());
+ assertFalse(fc.isAutoScaled());
+ assertTrue(fc.hasThreshold());
+ assertTrue(fc.isBelowThreshold());
+ assertEquals(15f, fc.getThreshold());
+ assertEquals(Color.RED, fc.getMinColour());
+ assertEquals(Color.GREEN, fc.getMaxColour());
+ assertEquals(10f, fc.getMin());
+ assertEquals(20f, fc.getMax());
+ }
+}