*/
package jalview.analysis;
+import jalview.datamodel.AlignedCodonFrame;
import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentI;
import jalview.datamodel.Annotation;
import jalview.datamodel.SequenceI;
import jalview.util.Format;
+import jalview.util.MappingUtils;
+import jalview.util.QuickSort;
+import java.util.Arrays;
import java.util.Hashtable;
import java.util.List;
+import java.util.Set;
/**
* Takes in a vector or array of sequences and column start and column end and
public static final String PROFILE = "P";
+ public static final String ENCODED_CHARS = "E";
+
/*
* Quick look-up of String value of char 'A' to 'Z'
*/
{
for (v = 'A'; v <= 'Z'; v++)
{
- if (values[v] < 2 || values[v] < maxCount)
+ // TODO why ignore values[v] == 1?
+ if (values[v] < 1 /* 2 */|| values[v] < maxCount)
{
continue;
}
}
if (profile)
{
+ // TODO use a 1-dimensional array with jSize, nongap in [0] and [1]
residueHash.put(PROFILE, new int[][]
{ values, new int[]
{ jSize, nongap } });
nseq);
}
+ /**
+ * Derive the consensus annotations to be added to the alignment for display.
+ * This does not recompute the raw data, but may be called on a change in
+ * display options, such as 'show logo', which may in turn result in a change
+ * in the derived values.
+ *
+ * @param consensus
+ * the annotation row to add annotations to
+ * @param hconsensus
+ * the source consensus data
+ * @param iStart
+ * start column
+ * @param width
+ * end column
+ * @param ignoreGapsInConsensusCalculation
+ * if true, use the consensus calculated ignoring gaps
+ * @param includeAllConsSymbols
+ * if true include all consensus symbols, else just show modal
+ * residue
+ * @param alphabet
+ * @param nseq
+ * number of sequences
+ */
public static void completeConsensus(AlignmentAnnotation consensus,
Hashtable[] hconsensus, int iStart, int width,
boolean ignoreGapsInConsensusCalculation,
boolean includeAllConsSymbols, char[] alphabet, long nseq)
{
- float tval, value;
if (consensus == null || consensus.annotations == null
|| consensus.annotations.length < width)
{
// initialised properly
return;
}
- String fmtstr = "%3.1f";
- int precision = 0;
- while (nseq >= 10)
- {
- precision++;
- nseq /= 10;
- }
- final Format fmt;
- if (precision > 1)
- {
- // if (precision>2)
- {
- fmtstr = "%" + (2 + precision) + "." + (precision) + "f";
- }
- fmt = new Format(fmtstr);
- }
- else
- {
- fmt = null;
- }
+
+ final Format fmt = getPercentageFormat(nseq);
+
for (int i = iStart; i < width; i++)
{
Hashtable hci;
consensus.annotations[i] = null;
continue;
}
- value = 0;
- Float fv;
- if (ignoreGapsInConsensusCalculation)
- {
- fv = (Float) hci.get(AAFrequency.PID_NOGAPS);
- }
- else
- {
- fv = (Float) hci.get(AAFrequency.PID_GAPS);
- }
+ Float fv = (Float) hci
+ .get(ignoreGapsInConsensusCalculation ? PID_NOGAPS : PID_GAPS);
if (fv == null)
{
consensus.annotations[i] = null;
// data has changed below us .. give up and
continue;
}
- value = fv.floatValue();
+ float value = fv.floatValue();
String maxRes = hci.get(AAFrequency.MAXRESIDUE).toString();
- String mouseOver = hci.get(AAFrequency.MAXRESIDUE) + " ";
+ StringBuilder mouseOver = new StringBuilder(64);
if (maxRes.length() > 1)
{
- mouseOver = "[" + maxRes + "] ";
+ mouseOver.append("[").append(maxRes).append("] ");
maxRes = "+";
}
+ else
+ {
+ mouseOver.append(hci.get(AAFrequency.MAXRESIDUE) + " ");
+ }
int[][] profile = (int[][]) hci.get(AAFrequency.PROFILE);
+ int sequenceCount = profile[1][0];
+ int nonGappedCount = profile[1][1];
+ int normalisedBy = ignoreGapsInConsensusCalculation ? nonGappedCount
+ : sequenceCount;
if (profile != null && includeAllConsSymbols)
{
- mouseOver = "";
+ mouseOver.setLength(0);
if (alphabet != null)
{
for (int c = 0; c < alphabet.length; c++)
{
- tval = profile[0][alphabet[c]] * 100f
- / profile[1][ignoreGapsInConsensusCalculation ? 1 : 0];
- mouseOver += ((c == 0) ? "" : "; ") + alphabet[c] + " "
- + ((fmt != null) ? fmt.form(tval) : ((int) tval)) + "%";
+ float tval = profile[0][alphabet[c]] * 100f / normalisedBy;
+ mouseOver
+ .append(((c == 0) ? "" : "; "))
+ .append(alphabet[c])
+ .append(" ")
+ .append(((fmt != null) ? fmt.form(tval) : ((int) tval)))
+ .append("%");
}
}
else
{
- Object[] ca = new Object[profile[0].length];
+ // TODO do this sort once only in calculate()?
+ // char[][] ca = new char[profile[0].length][];
+ char[] ca = new char[profile[0].length];
float[] vl = new float[profile[0].length];
for (int c = 0; c < ca.length; c++)
{
- ca[c] = new char[]
- { (char) c };
+ ca[c] = (char) c;
+ // ca[c] = new char[]
+ // { (char) c };
vl[c] = profile[0][c];
}
- ;
- jalview.util.QuickSort.sort(vl, ca);
- for (int p = 0, c = ca.length - 1; profile[0][((char[]) ca[c])[0]] > 0; c--)
+ QuickSort.sort(vl, ca);
+ for (int p = 0, c = ca.length - 1; profile[0][ca[c]] > 0; c--)
{
- if (((char[]) ca[c])[0] != '-')
+ final char residue = ca[c];
+ if (residue != '-')
{
- tval = profile[0][((char[]) ca[c])[0]]
- * 100f
- / profile[1][ignoreGapsInConsensusCalculation ? 1 : 0];
- mouseOver += ((p == 0) ? "" : "; ") + ((char[]) ca[c])[0]
- + " "
- + ((fmt != null) ? fmt.form(tval) : ((int) tval))
- + "%";
+ float tval = profile[0][residue] * 100f / normalisedBy;
+ mouseOver
+ .append((((p == 0) ? "" : "; ")))
+ .append(residue)
+ .append(" ")
+ .append(((fmt != null) ? fmt.form(tval)
+ : ((int) tval))).append("%");
p++;
-
}
}
-
}
}
else
{
- mouseOver += ((fmt != null) ? fmt.form(value) : ((int) value))
- + "%";
+ mouseOver.append(
+ (((fmt != null) ? fmt.form(value) : ((int) value))))
+ .append("%");
}
- consensus.annotations[i] = new Annotation(maxRes, mouseOver, ' ',
+ consensus.annotations[i] = new Annotation(maxRes,
+ mouseOver.toString(), ' ',
value);
}
}
/**
- * get the sorted profile for the given position of the consensus
+ * Returns a Format designed to show all significant figures for profile
+ * percentages. For less than 100 sequences, returns null (the integer
+ * percentage value will be displayed). For 100-999 sequences, returns "%3.1f"
+ *
+ * @param nseq
+ * @return
+ */
+ protected static Format getPercentageFormat(long nseq)
+ {
+ int scale = 0;
+ while (nseq >= 10)
+ {
+ scale++;
+ nseq /= 10;
+ }
+ return scale <= 1 ? null : new Format("%3." + (scale - 1) + "f");
+ }
+
+ /**
+ * Returns the sorted profile for the given consensus data. The returned array
+ * contains
+ *
+ * <pre>
+ * [profileType, numberOfValues, nonGapCount, charValue1, percentage1, charValue2, percentage2, ...]
+ * in descending order of percentage value
+ * </pre>
*
* @param hconsensus
+ * the data table from which to extract and sort values
+ * @param ignoreGaps
+ * if true, only non-gapped values are included in percentage
+ * calculations
* @return
*/
public static int[] extractProfile(Hashtable hconsensus,
- boolean ignoreGapsInConsensusCalculation)
+ boolean ignoreGaps)
{
int[] rtnval = new int[64];
int[][] profile = (int[][]) hconsensus.get(AAFrequency.PROFILE);
{
return null;
}
- char[][] ca = new char[profile[0].length][];
+ char[] ca = new char[profile[0].length];
float[] vl = new float[profile[0].length];
for (int c = 0; c < ca.length; c++)
{
- ca[c] = new char[]
- { (char) c };
+ ca[c] = (char) c;
vl[c] = profile[0][c];
}
- jalview.util.QuickSort.sort(vl, ca);
- rtnval[0] = 2;
- rtnval[1] = 0;
- for (int c = ca.length - 1; profile[0][ca[c][0]] > 0; c--)
+ QuickSort.sort(vl, ca);
+ int nextArrayPos = 2;
+ int totalPercentage = 0;
+ int distinctValuesCount = 0;
+ final int divisor = profile[1][ignoreGaps ? 1 : 0];
+ for (int c = ca.length - 1; profile[0][ca[c]] > 0; c--)
{
- if (ca[c][0] != '-')
+ if (ca[c] != '-')
{
- rtnval[rtnval[0]++] = ca[c][0];
- rtnval[rtnval[0]] = (int) (profile[0][ca[c][0]] * 100f / profile[1][ignoreGapsInConsensusCalculation ? 1
- : 0]);
- rtnval[1] += rtnval[rtnval[0]++];
+ rtnval[nextArrayPos++] = ca[c];
+ final int percentage = (int) (profile[0][ca[c]] * 100f / divisor);
+ rtnval[nextArrayPos++] = percentage;
+ totalPercentage += percentage;
+ distinctValuesCount++;
}
}
- return rtnval;
+ rtnval[0] = distinctValuesCount;
+ rtnval[1] = totalPercentage;
+ int[] result = new int[rtnval.length + 1];
+ result[0] = AlignmentAnnotation.SEQUENCE_PROFILE;
+ System.arraycopy(rtnval, 0, result, 1, rtnval.length);
+
+ return result;
+ }
+
+ /**
+ * Extract a sorted extract of cDNA codon profile data. The returned array
+ * contains
+ *
+ * <pre>
+ * [profileType, numberOfValues, totalCount, charValue1, percentage1, charValue2, percentage2, ...]
+ * in descending order of percentage value, where the character values encode codon triplets
+ * </pre>
+ *
+ * @param hashtable
+ * @return
+ */
+ public static int[] extractCdnaProfile(Hashtable hashtable, boolean ignoreGaps)
+ {
+ // this holds #seqs, #ungapped, and then codon count, indexed by encoded
+ // codon triplet
+ int[] codonCounts = (int[]) hashtable.get(PROFILE);
+ int[] sortedCounts = new int[codonCounts.length - 2];
+ System.arraycopy(codonCounts, 2, sortedCounts, 0,
+ codonCounts.length - 2);
+
+ int[] result = new int[3 + 2 * sortedCounts.length];
+ // first value is just the type of profile data
+ result[0] = AlignmentAnnotation.CDNA_PROFILE;
+
+ char[] codons = new char[sortedCounts.length];
+ for (int i = 0; i < codons.length; i++)
+ {
+ codons[i] = (char) i;
+ }
+ QuickSort.sort(sortedCounts, codons);
+ int totalPercentage = 0;
+ int distinctValuesCount = 0;
+ int j = 3;
+ int divisor = ignoreGaps ? codonCounts[1] : codonCounts[0];
+ for (int i = codons.length - 1; i >= 0; i--)
+ {
+ final int codonCount = sortedCounts[i];
+ if (codonCount == 0)
+ {
+ break; // nothing else of interest here
+ }
+ distinctValuesCount++;
+ result[j++] = codons[i];
+ final int percentage = codonCount * 100 / divisor;
+ result[j++] = percentage;
+ totalPercentage += percentage;
+ }
+ result[2] = totalPercentage;
+
+ /*
+ * Just return the non-zero values
+ */
+ // todo next value is redundant if we limit the array to non-zero counts
+ result[1] = distinctValuesCount;
+ return Arrays.copyOfRange(result, 0, j);
+ }
+
+ /**
+ * Compute a consensus for the cDNA coding for a protein alignment.
+ *
+ * @param alignment
+ * the protein alignment (which should hold mappings to cDNA
+ * sequences)
+ * @param hconsensus
+ * the consensus data stores to be populated (one per column)
+ */
+ public static void calculateCdna(AlignmentI alignment,
+ Hashtable[] hconsensus)
+ {
+ final char gapCharacter = alignment.getGapCharacter();
+ Set<AlignedCodonFrame> mappings = alignment.getCodonFrames();
+ if (mappings == null || mappings.isEmpty())
+ {
+ return;
+ }
+
+ int cols = alignment.getWidth();
+ for (int col = 0; col < cols; col++)
+ {
+ // todo would prefer a Java bean for consensus data
+ Hashtable<String, int[]> columnHash = new Hashtable<String, int[]>();
+ // #seqs, #ungapped seqs, counts indexed by (codon encoded + 1)
+ int[] codonCounts = new int[66];
+ codonCounts[0] = alignment.getSequences().size();
+ int ungappedCount = 0;
+ for (SequenceI seq : alignment.getSequences())
+ {
+ if (seq.getCharAt(col) == gapCharacter)
+ {
+ continue;
+ }
+ char[] codon = MappingUtils.findCodonFor(seq, col, mappings);
+ int codonEncoded = CodingUtils.encodeCodon(codon);
+ if (codonEncoded >= 0)
+ {
+ codonCounts[codonEncoded + 2]++;
+ ungappedCount++;
+ }
+ }
+ codonCounts[1] = ungappedCount;
+ // todo: sort values here, save counts and codons?
+ columnHash.put(PROFILE, codonCounts);
+ hconsensus[col] = columnHash;
+ }
+ }
+
+ /**
+ * Derive displayable cDNA consensus annotation from computed consensus data.
+ *
+ * @param consensusAnnotation
+ * the annotation row to be populated for display
+ * @param consensusData
+ * the computed consensus data
+ * @param showProfileLogo
+ * if true show all symbols present at each position, else only the
+ * modal value
+ * @param nseqs
+ * the number of sequences in the alignment
+ */
+ public static void completeCdnaConsensus(
+ AlignmentAnnotation consensusAnnotation,
+ Hashtable[] consensusData, boolean showProfileLogo, int nseqs)
+ {
+ if (consensusAnnotation == null
+ || consensusAnnotation.annotations == null
+ || consensusAnnotation.annotations.length < consensusData.length)
+ {
+ // called with a bad alignment annotation row - wait for it to be
+ // initialised properly
+ return;
+ }
+
+ for (int col = 0; col < consensusData.length; col++)
+ {
+ Hashtable hci = consensusData[col];
+ if (hci == null)
+ {
+ // gapped protein column?
+ continue;
+ }
+ // array holds #seqs, #ungapped, then codon counts indexed by codon
+ final int[] codonCounts = (int[]) hci.get(PROFILE);
+ int totalCount = 0;
+ StringBuilder mouseOver = new StringBuilder(32);
+
+ /*
+ * First pass - get total count and find the highest
+ */
+ final char[] codons = new char[codonCounts.length - 2];
+ for (int j = 2; j < codonCounts.length; j++)
+ {
+ final int codonCount = codonCounts[j];
+ codons[j - 2] = (char) (j - 2);
+ totalCount += codonCount;
+ }
+
+ /*
+ * Sort array of encoded codons by count ascending - so the modal value
+ * goes to the end; start by copying the count (dropping the first value)
+ */
+ int[] sortedCodonCounts = new int[codonCounts.length - 2];
+ System.arraycopy(codonCounts, 2, sortedCodonCounts, 0,
+ codonCounts.length - 2);
+ QuickSort.sort(sortedCodonCounts, codons);
+
+ int modalCodonEncoded = codons[codons.length - 1];
+ int modalCodonCount = sortedCodonCounts[codons.length - 1];
+ String modalCodon = String.valueOf(CodingUtils
+ .decodeCodon(modalCodonEncoded));
+ if (sortedCodonCounts.length > 1
+ && sortedCodonCounts[codons.length - 2] == modalCodonEncoded)
+ {
+ modalCodon = "+";
+ }
+ float pid = sortedCodonCounts[sortedCodonCounts.length - 1] * 100
+ / (float) totalCount;
+
+ /*
+ * todo ? Replace consensus hashtable with sorted arrays of codons and
+ * counts (non-zero only). Include total count in count array [0].
+ */
+
+ /*
+ * Scan sorted array backwards for most frequent values first.
+ */
+ for (int j = codons.length - 1; j >= 0; j--)
+ {
+ int codonCount = sortedCodonCounts[j];
+ if (codonCount == 0)
+ {
+ break;
+ }
+ int codonEncoded = codons[j];
+ final int pct = codonCount * 100 / totalCount;
+ String codon = String
+ .valueOf(CodingUtils.decodeCodon(codonEncoded));
+ Format fmt = getPercentageFormat(nseqs);
+ String formatted = fmt == null ? Integer.toString(pct) : fmt
+ .form(pct);
+ if (showProfileLogo || codonCount == modalCodonCount)
+ {
+ mouseOver.append(codon).append(": ").append(formatted)
+ .append("% ");
+ }
+ }
+
+ consensusAnnotation.annotations[col] = new Annotation(modalCodon,
+ mouseOver.toString(), ' ', pid);
+ }
}
}
--- /dev/null
+package jalview.analysis;
+
+/**
+ * A utility class to provide encoding/decoding schemes for data.
+ *
+ * @author gmcarstairs
+ *
+ */
+public class CodingUtils
+{
+
+ /*
+ * Number of bits used when encoding codon characters. 2 is enough for ACGT.
+ * To accommodate more (e.g. ambiguity codes), simply increase this number
+ * (and adjust unit tests to match).
+ */
+ private static final int CODON_ENCODING_BITSHIFT = 2;
+
+ /**
+ * Encode a codon from e.g. ['A', 'G', 'C'] to a number in the range 0 - 63.
+ * Converts lower to upper case, U to T, then assembles a binary value by
+ * encoding A/C/G/T as 00/01/10/11 respectively and shifting.
+ *
+ * @param codon
+ * @return the encoded codon, or a negative number if unexpected characters
+ * found
+ */
+ public static int encodeCodon(char[] codon)
+ {
+ if (codon == null)
+ {
+ return -1;
+ }
+ return encodeCodon(codon[2])
+ + (encodeCodon(codon[1]) << CODON_ENCODING_BITSHIFT)
+ + (encodeCodon(codon[0]) << (2 * CODON_ENCODING_BITSHIFT));
+ }
+
+ /**
+ * Encodes aA/cC/gG/tTuU as 0/1/2/3 respectively. Returns Integer.MIN_VALUE (a
+ * large negative value) for any other character.
+ *
+ * @param c
+ * @return
+ */
+ public static int encodeCodon(char c)
+ {
+ int result = Integer.MIN_VALUE;
+ switch (c)
+ {
+ case 'A':
+ case 'a':
+ result = 0;
+ break;
+ case 'C':
+ case 'c':
+ result = 1;
+ break;
+ case 'G':
+ case 'g':
+ result = 2;
+ break;
+ case 'T':
+ case 't':
+ case 'U':
+ case 'u':
+ result = 3;
+ break;
+ }
+ return result;
+ }
+
+ /**
+ * Converts a binary encoded codon into an ['A', 'C', 'G'] (or 'T') triplet.
+ *
+ * The two low-order bits encode for A/C/G/T as 0/1/2/3, etc.
+ *
+ * @param encoded
+ * @return
+ */
+ public static char[] decodeCodon(int encoded)
+ {
+ char[] result = new char[3];
+ result[2] = decodeNucleotide(encoded & 3);
+ encoded = encoded >>> CODON_ENCODING_BITSHIFT;
+ result[1] = decodeNucleotide(encoded & 3);
+ encoded = encoded >>> CODON_ENCODING_BITSHIFT;
+ result[0] = decodeNucleotide(encoded & 3);
+ return result;
+ }
+
+ /**
+ * Convert value 0/1/2/3 to 'A'/'C'/'G'/'T'
+ *
+ * @param i
+ * @return
+ */
+ public static char decodeNucleotide(int i)
+ {
+ char result = '0';
+ switch (i)
+ {
+ case 0:
+ result = 'A';
+ break;
+ case 1:
+ result = 'C';
+ break;
+ case 2:
+ result = 'G';
+ break;
+ case 3:
+ result = 'T';
+ break;
+ }
+ return result;
+ }
+
+}
*/
package jalview.analysis;
-import java.util.*;
-
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.Annotation;
+import jalview.datamodel.SequenceFeature;
+import jalview.datamodel.SequenceI;
import jalview.util.Format;
-import jalview.datamodel.*;
+
+import java.util.ArrayList;
+import java.util.Hashtable;
/**
* Takes in a vector or array of sequences and column start and column end and
*/
public class StructureFrequency
{
+ public static final int STRUCTURE_PROFILE_LENGTH = 74;
+
// No need to store 1000s of strings which are not
// visible to the user.
public static final String MAXCOUNT = "C";
// UPDATE this for new values
if (profile)
{
+ // TODO 1-dim array with jsize in [0], nongapped in [1]; or Pojo
residueHash.put(PROFILE, new int[][]
{ values, new int[]
{ jSize, (jSize - values['-']) } });
public static int[] extractProfile(Hashtable hconsensus,
boolean ignoreGapsInConsensusCalculation)
{
- int[] rtnval = new int[74]; // 2*(5*5)+2
+ int[] rtnval = new int[STRUCTURE_PROFILE_LENGTH]; // 2*(5*5)+2
int[][] profile = (int[][]) hconsensus.get(StructureFrequency.PROFILE);
int[][] pairs = (int[][]) hconsensus
.get(StructureFrequency.PAIRPROFILE);
if (profile == null)
+ {
return null;
+ }
// TODO fix the object length, also do it in completeConsensus
- Object[] ca = new Object[625];
+ // Object[] ca = new Object[625];
+ int[][] ca = new int[625][];
float[] vl = new float[625];
int x = 0;
for (int c = 65; c < 90; c++)
}
jalview.util.QuickSort.sort(vl, ca);
- rtnval[0] = 2;
+ int valuesCount = 0;
rtnval[1] = 0;
+ int offset = 2;
for (int c = 624; c > 0; c--)
{
if (vl[c] > 0)
{
- rtnval[rtnval[0]++] = ((int[]) ca[c])[0];
- rtnval[rtnval[0]++] = ((int[]) ca[c])[1];
- rtnval[rtnval[0]] = (int) (vl[c] * 100f / profile[1][ignoreGapsInConsensusCalculation ? 1
+ rtnval[offset++] = ca[c][0];
+ rtnval[offset++] = ca[c][1];
+ rtnval[offset] = (int) (vl[c] * 100f / profile[1][ignoreGapsInConsensusCalculation ? 1
: 0]);
- rtnval[1] += rtnval[rtnval[0]++];
+ rtnval[1] += rtnval[offset++];
+ valuesCount++;
}
}
+ rtnval[0] = valuesCount;
- return rtnval;
+ // insert profile type code in position 0
+ int[] result = new int[rtnval.length + 1];
+ result[0] = AlignmentAnnotation.STRUCTURE_PROFILE;
+ System.arraycopy(rtnval, 0, result, 1, rtnval.length);
+ return result;
}
public static void main(String args[])
Hashtable[] getSequenceConsensusHash();
+ /**
+ * Get consensus data table for the cDNA complement of this alignment (if any)
+ *
+ * @return
+ */
+ Hashtable[] getComplementConsensusHash();
+
Hashtable[] getRnaStructureConsensusHash();
boolean isIgnoreGapsConsensus();
AlignmentAnnotation getAlignmentConsensusAnnotation();
/**
+ * get the container for cDNA complement consensus annotation
+ *
+ * @return
+ */
+ AlignmentAnnotation getComplementConsensusAnnotation();
+
+ /**
* Test to see if viewport is still open and active
*
* @return true indicates that all references to viewport should be dropped
void setSequenceConsensusHash(Hashtable[] hconsensus);
/**
+ * Set the cDNA complement consensus for the viewport
+ *
+ * @param hconsensus
+ */
+ void setComplementConsensusHash(Hashtable[] hconsensus);
+
+ /**
*
* @return the alignment annotatino row for the structure consensus
* calculation
package jalview.datamodel;
import jalview.util.MapList;
+import jalview.util.MappingUtils;
/**
* Stores mapping between the columns of a protein alignment and a DNA alignment
}
return null;
}
+
+ /**
+ * Returns the DNA codon for the given position (base 1) in a mapped protein
+ * sequence, or null if no mapping is found.
+ *
+ * @param protein
+ * the peptide dataset sequence
+ * @param aaPos
+ * residue position (base 1) in the peptide sequence
+ * @return
+ */
+ public char[] getMappedCodon(SequenceI protein, int aaPos)
+ {
+ if (dnaToProt == null)
+ {
+ return null;
+ }
+ MapList ml = null;
+ char[] dnaSeq = null;
+ for (int i = 0; i < dnaToProt.length; i++)
+ {
+ if (dnaToProt[i].to == protein)
+ {
+ ml = getdnaToProt()[i];
+ dnaSeq = dnaSeqs[i].getSequence();
+ break;
+ }
+ }
+ if (ml == null)
+ {
+ return null;
+ }
+ int[] codonPos = ml.locateInFrom(aaPos, aaPos);
+ if (codonPos == null)
+ {
+ return null;
+ }
+
+ /*
+ * Read off the mapped nucleotides (converting to position base 0)
+ */
+ codonPos = MappingUtils.flattenRanges(codonPos);
+ return new char[]
+ { dnaSeq[codonPos[0] - 1], dnaSeq[codonPos[1] - 1],
+ dnaSeq[codonPos[2] - 1] };
+ }
}
*/
public class AlignmentAnnotation
{
+ /*
+ * Identifers for different types of profile data
+ */
+ public static final int SEQUENCE_PROFILE = 0;
+
+ public static final int STRUCTURE_PROFILE = 1;
+
+ public static final int CDNA_PROFILE = 2;
+
/**
* If true, this annotations is calculated every edit, eg consensus, quality
* or conservation graphs
boolean asSplitFrame = true;
if (asSplitFrame)
{
- AlignFrame copyThis = new AlignFrame(
- AlignFrame.this.viewport.getAlignment(),
+ final Alignment copyAlignment = new Alignment(
+ AlignFrame.this.viewport.getAlignment());
+ AlignFrame copyThis = new AlignFrame(copyAlignment,
AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
copyThis.setTitle(AlignFrame.this.getTitle());
// SplitFrame with dna above, protein below
}
return null;
}
+
+ /**
+ * Set the 'other half' to hidden / revealed.
+ */
+ @Override
+ public void setComplementVisible(Object alignFrame, boolean show)
+ {
+ /*
+ * Hiding the AlignPanel suppresses unnecessary repaints
+ */
+ if (alignFrame == getTopFrame())
+ {
+ ((AlignFrame) getBottomFrame()).alignPanel.setVisible(show);
+ }
+ else if (alignFrame == getBottomFrame())
+ {
+ ((AlignFrame) getTopFrame()).alignPanel.setVisible(show);
+ }
+ super.setComplementVisible(alignFrame, show);
+ }
}
package jalview.renderer;
import jalview.analysis.AAFrequency;
+import jalview.analysis.CodingUtils;
import jalview.analysis.StructureFrequency;
import jalview.api.AlignViewportI;
import jalview.datamodel.AlignmentAnnotation;
private Hashtable[] hconsensus;
+ private Hashtable[] complementConsensus;
+
private Hashtable[] hStrucConsensus;
private boolean av_ignoreGapsConsensus;
profcolour = av.getAlignment().isNucleotide() ? new jalview.schemes.NucleotideColourScheme()
: new jalview.schemes.ZappoColourScheme();
}
- boolean rna = av.getAlignment().isNucleotide();
columnSelection = av.getColumnSelection();
- hconsensus = av.getSequenceConsensusHash();// hconsensus;
- hStrucConsensus = av.getRnaStructureConsensusHash(); // hStrucConsensus;
+ hconsensus = av.getSequenceConsensusHash();
+ complementConsensus = av.getComplementConsensusHash();
+ hStrucConsensus = av.getRnaStructureConsensusHash();
av_ignoreGapsConsensus = av.isIgnoreGapsConsensus();
}
+ /**
+ * Returns profile data; the first element is the profile type, the second is
+ * the number of distinct values, the third the total count, and the remainder
+ * depend on the profile type.
+ *
+ * @param aa
+ * @param column
+ * @return
+ */
public int[] getProfileFor(AlignmentAnnotation aa, int column)
{
// TODO : consider refactoring the global alignment calculation
// properties/rendering attributes as a global 'alignment group' which holds
// all vis settings for the alignment as a whole rather than a subset
//
- if (aa.autoCalculated && aa.label.startsWith("Consensus"))
+ if (aa.autoCalculated
+ && (aa.label.startsWith("Consensus") || aa.label
+ .startsWith("cDNA Consensus")))
{
+ boolean forComplement = aa.label.startsWith("cDNA Consensus");
if (aa.groupRef != null && aa.groupRef.consensusData != null
&& aa.groupRef.isShowSequenceLogo())
{
+ // TODO? group consensus for cDNA complement
return AAFrequency.extractProfile(
aa.groupRef.consensusData[column],
aa.groupRef.getIgnoreGapsConsensus());
// be stored
if (aa.groupRef == null && aa.sequenceRef == null)
{
- return AAFrequency.extractProfile(hconsensus[column],
- av_ignoreGapsConsensus);
+ if (forComplement)
+ {
+ return AAFrequency.extractCdnaProfile(
+ complementConsensus[column], av_ignoreGapsConsensus);
+ }
+ else
+ {
+ return AAFrequency.extractProfile(hconsensus[column],
+ av_ignoreGapsConsensus);
+ }
}
}
else
boolean validRes = false;
boolean validEnd = false;
boolean labelAllCols = false;
- boolean centreColLabels, centreColLabelsDef = av.isCentreColumnLabels();
+ boolean centreColLabels;
+ boolean centreColLabelsDef = av.isCentreColumnLabels();
boolean scaleColLabel = false;
- AlignmentAnnotation consensusAnnot = av
- .getAlignmentConsensusAnnotation(), structConsensusAnnot = av
+ final AlignmentAnnotation consensusAnnot = av
+ .getAlignmentConsensusAnnotation();
+ final AlignmentAnnotation structConsensusAnnot = av
.getAlignmentStrucConsensusAnnotation();
+ final AlignmentAnnotation complementConsensusAnnot = av
+ .getComplementConsensusAnnotation();
boolean renderHistogram = true, renderProfile = true, normaliseProfile = false, isRNA = rna;
BitSet graphGroupDrawn = new BitSet();
renderProfile = row.groupRef.isShowSequenceLogo();
normaliseProfile = row.groupRef.isNormaliseSequenceLogo();
}
- else if (row == consensusAnnot || row == structConsensusAnnot)
+ else if (row == consensusAnnot || row == structConsensusAnnot
+ || row == complementConsensusAnnot)
{
renderHistogram = av_renderHistogram;
renderProfile = av_renderProfile;
{
validRes = true;
}
+ final String displayChar = validRes ? row_annotations[column].displayCharacter
+ : null;
if (x > -1)
{
if (activeRow == i)
g.setColor(Color.orange.darker());
g.fillRect(x * charWidth, y, charWidth, charHeight);
}
- if (validCharWidth
- && validRes
- && row_annotations[column].displayCharacter != null
- && (row_annotations[column].displayCharacter.length() > 0))
+ if (validCharWidth && validRes && displayChar != null
+ && (displayChar.length() > 0))
{
- if (centreColLabels || scaleColLabel)
+ fmWidth = fm.charsWidth(displayChar.toCharArray(), 0,
+ displayChar.length());
+ if (/* centreColLabels || */scaleColLabel)
{
- fmWidth = fm.charsWidth(
- row_annotations[column].displayCharacter
- .toCharArray(), 0,
- row_annotations[column].displayCharacter.length());
-
- if (scaleColLabel)
- {
+ // fmWidth = fm.charsWidth(displayChar.toCharArray(), 0,
+ // displayChar.length());
+ //
+ // if (scaleColLabel)
+ // {
// justify the label and scale to fit in column
if (fmWidth > charWidth)
{
// and update the label's width to reflect the scaling.
fmWidth = charWidth;
}
- }
- }
- else
- {
- fmWidth = fm
- .charWidth(row_annotations[column].displayCharacter
- .charAt(0));
+ // }
}
+ // TODO is it ok to use width of / show all characters here?
+ // else
+ // {
+ // fmWidth = fm.charWidth(displayChar.charAt(0));
+ // }
charOffset = (int) ((charWidth - fmWidth) / 2f);
if (row_annotations[column].colour == null)
if (column == 0 || row.graph > 0)
{
- g.drawString(row_annotations[column].displayCharacter,
- (x * charWidth) + charOffset, y + iconOffset);
+ g.drawString(displayChar, (x * charWidth) + charOffset, y
+ + iconOffset);
}
else if (row_annotations[column - 1] == null
|| (labelAllCols
- || !row_annotations[column].displayCharacter
- .equals(row_annotations[column - 1].displayCharacter) || (row_annotations[column].displayCharacter
+ || !displayChar
+ .equals(row_annotations[column - 1].displayCharacter) || (displayChar
.length() < 2 && row_annotations[column].secondaryStructure == ' ')))
{
- g.drawString(row_annotations[column].displayCharacter, x
- * charWidth + charOffset, y + iconOffset);
+ g.drawString(displayChar, x * charWidth + charOffset, y
+ + iconOffset);
}
g.setFont(ofont);
}
if (ss == '(')
{
// distinguish between forward/backward base-pairing
- if (row_annotations[column].displayCharacter.indexOf(')') > -1)
+ if (displayChar.indexOf(')') > -1)
{
ss = ')';
}
if (ss == '[')
{
- if ((row_annotations[column].displayCharacter.indexOf(']') > -1))
+ if ((displayChar.indexOf(']') > -1))
{
ss = ']';
if (ss == '{')
{
// distinguish between forward/backward base-pairing
- if (row_annotations[column].displayCharacter.indexOf('}') > -1)
+ if (displayChar.indexOf('}') > -1)
{
ss = '}';
if (ss == '<')
{
// distinguish between forward/backward base-pairing
- if (row_annotations[column].displayCharacter.indexOf('<') > -1)
+ if (displayChar.indexOf('<') > -1)
{
ss = '>';
if (ss >= 65)
{
// distinguish between forward/backward base-pairing
- if (row_annotations[column].displayCharacter.indexOf(ss + 32) > -1)
+ if (displayChar.indexOf(ss + 32) > -1)
{
ss = (char) (ss + 32);
if (renderProfile)
{
+ /*
+ * {profile type, #values, total count, char1, pct1, char2, pct2...}
+ */
int profl[] = getProfileFor(_aa, column);
+
+ boolean isStructureProfile = profl[0] == AlignmentAnnotation.STRUCTURE_PROFILE;
+ boolean isCdnaProfile = profl[0] == AlignmentAnnotation.CDNA_PROFILE;
+
// just try to draw the logo if profl is not null
- if (profl != null && profl[1] != 0)
+ if (profl != null && profl[2] != 0)
{
float ht = normaliseProfile ? y - _aa.graphHeight : y1;
double htn = normaliseProfile ? _aa.graphHeight : (y2 - y1);// aa.graphHeight;
char[] dc;
/**
- * profl.length == 74 indicates that the profile of a secondary
- * structure conservation row was accesed. Therefore dc gets length 2,
- * to have space for a basepair instead of just a single nucleotide
+ * Render a single base for a sequence profile, a base pair for
+ * structure profile, and a triplet for a cdna profile
*/
- if (profl.length == 74)
- {
- dc = new char[2];
- }
- else
- {
- dc = new char[1];
- }
+ dc = new char[isStructureProfile ? 2 : (isCdnaProfile ? 3 : 1)];
+
LineMetrics lm = g.getFontMetrics(ofont).getLineMetrics("Q", g);
- double scale = 1f / (normaliseProfile ? profl[1] : 100f);
+ double scale = 1f / (normaliseProfile ? profl[2] : 100f);
float ofontHeight = 1f / lm.getAscent();// magnify to fill box
double scl = 0.0;
- for (int c = 2; c < profl[0];)
- {
- dc[0] = (char) profl[c++];
- if (_aa.label.startsWith("StrucConsensus"))
+ /*
+ * Traverse the character(s)/percentage data in the array
+ */
+ int c = 3;
+ int valuesProcessed = 0;
+ // profl[1] is the number of values in the profile
+ while (valuesProcessed < profl[1])
+ {
+ if (isStructureProfile)
{
+ // todo can we encode a structure pair as an int, like codons?
+ dc[0] = (char) profl[c++];
dc[1] = (char) profl[c++];
}
+ else if (isCdnaProfile)
+ {
+ dc = CodingUtils.decodeCodon(profl[c++]);
+ }
+ else
+ {
+ dc[0] = (char) profl[c++];
+ }
wdth = charWidth;
wdth /= fm.charsWidth(dc, 0, dc.length);
ht += scl;
+ // next profl[] position is profile % for the character(s)
+ scl = htn * scale * profl[c++];
+ lm = ofont.getLineMetrics(dc, 0, 1, g.getFontMetrics()
+ .getFontRenderContext());
+ g.setFont(ofont.deriveFont(AffineTransform.getScaleInstance(
+ wdth, scl / lm.getAscent())));
+ lm = g.getFontMetrics().getLineMetrics(dc, 0, 1, g);
+
+ // Debug - render boxes around characters
+ // g.setColor(Color.red);
+ // g.drawRect(x*av.charWidth, (int)ht, av.charWidth,
+ // (int)(scl));
+ // g.setColor(profcolour.findColour(dc[0]).darker());
+
+ /*
+ * Set character colour as per alignment colour scheme; use the
+ * codon translation if a cDNA profile
+ */
+ Color colour = null;
+ if (isCdnaProfile)
{
- scl = htn * scale * profl[c++];
- lm = ofont.getLineMetrics(dc, 0, 1, g.getFontMetrics()
- .getFontRenderContext());
- g.setFont(ofont.deriveFont(AffineTransform.getScaleInstance(
- wdth, scl / lm.getAscent())));
- lm = g.getFontMetrics().getLineMetrics(dc, 0, 1, g);
-
- // Debug - render boxes around characters
- // g.setColor(Color.red);
- // g.drawRect(x*av.charWidth, (int)ht, av.charWidth,
- // (int)(scl));
- // g.setColor(profcolour.findColour(dc[0]).darker());
- g.setColor(profcolour.findColour(dc[0], column, null));
-
- hght = (ht + (scl - lm.getDescent() - lm.getBaselineOffsets()[lm
- .getBaselineIndex()]));
-
- g.drawChars(dc, 0, dc.length, x * charWidth, (int) hght);
+ final String codonTranslation = ResidueProperties
+ .codonTranslate(new String(dc));
+ colour = profcolour.findColour(codonTranslation.charAt(0),
+ column, null);
}
+ else
+ {
+ colour = profcolour.findColour(dc[0], column, null);
+ }
+ g.setColor(colour == Color.white ? Color.lightGray : colour);
+
+ hght = (ht + (scl - lm.getDescent() - lm.getBaselineOffsets()[lm
+ .getBaselineIndex()]));
+
+ g.drawChars(dc, 0, dc.length, x * charWidth, (int) hght);
+ valuesProcessed++;
}
g.setFont(ofont);
}
private char fmt; // one of cdeEfgGiosxXos
+ private final String formatString;
+
/**
* Creates a new Format object.
*
*/
public Format(String s)
{
+ formatString = s;
width = 0;
precision = -1;
pre = "";
return f + p.substring(p.length() - 3, p.length());
}
+
+ @Override
+ public String toString()
+ {
+ return formatString;
+ }
}
return mappedColumns;
}
+ /**
+ * Returns the mapped codon for a given aligned sequence column position (base
+ * 0).
+ *
+ * @param seq
+ * an aligned peptide sequence
+ * @param col
+ * an aligned column position (base 0)
+ * @param mappings
+ * a set of codon mappings
+ * @return the bases of the mapped codon in the cDNA dataset sequence, or null
+ * if not found
+ */
+ public static char[] findCodonFor(SequenceI seq, int col,
+ Set<AlignedCodonFrame> mappings)
+ {
+ int dsPos = seq.findPosition(col);
+ for (AlignedCodonFrame mapping : mappings)
+ {
+ if (mapping.involvesSequence(seq))
+ {
+ return mapping.getMappedCodon(seq.getDatasetSequence(), dsPos);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Converts a series of [start, end] ranges into an array of individual
+ * positions.
+ *
+ * @param ranges
+ * @return
+ */
+ public static int[] flattenRanges(int[] ranges)
+ {
+ /*
+ * Count how many positions altogether
+ */
+ int count = 0;
+ for (int i = 0; i < ranges.length - 1; i += 2)
+ {
+ count += ranges[i + 1] - ranges[i] + 1;
+ }
+
+ int[] result = new int[count];
+ int k = 0;
+ for (int i = 0; i < ranges.length - 1; i += 2)
+ {
+ for (int j = ranges[i]; j <= ranges[i + 1]; j++)
+ {
+ result[k++] = j;
+ }
+ }
+ return result;
+ }
}
import jalview.api.FeaturesDisplayedI;
import jalview.api.ViewStyleI;
import jalview.commands.CommandI;
+import jalview.datamodel.AlignedCodonFrame;
import jalview.datamodel.AlignmentAnnotation;
import jalview.datamodel.AlignmentI;
import jalview.datamodel.AlignmentView;
import jalview.structure.VamsasSource;
import jalview.viewmodel.styles.ViewStyle;
import jalview.workers.AlignCalcManager;
+import jalview.workers.ComplementConsensusThread;
import jalview.workers.ConsensusThread;
import jalview.workers.StrucConsensusThread;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* base class holding visualization and analysis attributes and common logic for
protected AlignmentAnnotation consensus;
+ protected AlignmentAnnotation complementConsensus;
+
protected AlignmentAnnotation strucConsensus;
protected AlignmentAnnotation conservation;
protected Hashtable[] hconsensus = null;
/**
+ * results of cDNA complement consensus visible portion of view
+ */
+ protected Hashtable[] hcomplementConsensus = null;
+
+ /**
* results of secondary structure base pair consensus for visible portion of
* view
*/
public void setSequenceConsensusHash(Hashtable[] hconsensus)
{
this.hconsensus = hconsensus;
+ }
+ @Override
+ public void setComplementConsensusHash(Hashtable[] hconsensus)
+ {
+ this.hcomplementConsensus = hconsensus;
}
@Override
}
@Override
+ public Hashtable[] getComplementConsensusHash()
+ {
+ return hcomplementConsensus;
+ }
+
+ @Override
public Hashtable[] getRnaStructureConsensusHash()
{
return hStrucConsensus;
}
@Override
+ public AlignmentAnnotation getComplementConsensusAnnotation()
+ {
+ return complementConsensus;
+ }
+
+ @Override
public AlignmentAnnotation getAlignmentStrucConsensusAnnotation()
{
return strucConsensus;
{
calculator.registerWorker(new ConsensusThread(this, ap));
}
+
+ /*
+ * A separate thread to compute cDNA consensus for a protein alignment
+ */
+ final AlignmentI al = this.getAlignment();
+ if (!al.isNucleotide() && al.getCodonFrames() != null
+ && !al.getCodonFrames().isEmpty())
+ {
+ if (calculator
+ .getRegisteredWorkersOfClass(ComplementConsensusThread.class) == null)
+ {
+ calculator.registerWorker(new ComplementConsensusThread(this, ap));
+ }
+ }
}
// --------START Structure Conservation
// annotation update method from alignframe to viewport
this.showSequenceLogo = showSequenceLogo;
calculator.updateAnnotationFor(ConsensusThread.class);
+ calculator.updateAnnotationFor(ComplementConsensusThread.class);
calculator.updateAnnotationFor(StrucConsensusThread.class);
}
this.showSequenceLogo = showSequenceLogo;
{
initRNAStructure();
}
- initConsensus();
+ consensus = new AlignmentAnnotation("Consensus", "PID",
+ new Annotation[1], 0f, 100f, AlignmentAnnotation.BAR_GRAPH);
+ initConsensus(consensus);
+
+ if (!alignment.isNucleotide())
+ {
+ final Set<AlignedCodonFrame> codonMappings = alignment
+ .getCodonFrames();
+ if (codonMappings != null && !codonMappings.isEmpty())
+ {
+ complementConsensus = new AlignmentAnnotation("cDNA Consensus",
+ "PID for cDNA", new Annotation[1], 0f, 100f,
+ AlignmentAnnotation.BAR_GRAPH);
+ initConsensus(complementConsensus);
+ }
+ }
}
}
- private void initConsensus()
+ private void initConsensus(AlignmentAnnotation aa)
{
-
- consensus = new AlignmentAnnotation("Consensus", "PID",
- new Annotation[1], 0f, 100f, AlignmentAnnotation.BAR_GRAPH);
- consensus.hasText = true;
- consensus.autoCalculated = true;
+ aa.hasText = true;
+ aa.autoCalculated = true;
if (showConsensus)
{
- alignment.addAnnotation(consensus);
+ alignment.addAnnotation(aa);
}
}
public int calcPanelHeight()
{
// setHeight of panels
- AlignmentAnnotation[] aa = getAlignment().getAlignmentAnnotation();
+ AlignmentAnnotation[] anns = getAlignment().getAlignmentAnnotation();
int height = 0;
int charHeight = getCharHeight();
- if (aa != null)
+ if (anns != null)
{
BitSet graphgrp = new BitSet();
- for (int i = 0; i < aa.length; i++)
+ for (AlignmentAnnotation aa : anns)
{
- if (aa[i] == null)
+ if (aa == null)
{
System.err.println("Null annotation row: ignoring.");
continue;
}
- if (!aa[i].visible)
+ if (!aa.visible)
{
continue;
}
- if (aa[i].graphGroup > -1)
+ if (aa.graphGroup > -1)
{
- if (graphgrp.get(aa[i].graphGroup))
+ if (graphgrp.get(aa.graphGroup))
{
continue;
}
else
{
- graphgrp.set(aa[i].graphGroup);
+ graphgrp.set(aa.graphGroup);
}
}
- aa[i].height = 0;
+ aa.height = 0;
- if (aa[i].hasText)
+ if (aa.hasText)
{
- aa[i].height += charHeight;
+ aa.height += charHeight;
}
- if (aa[i].hasIcons)
+ if (aa.hasIcons)
{
- aa[i].height += 16;
+ aa.height += 16;
}
- if (aa[i].graph > 0)
+ if (aa.graph > 0)
{
- aa[i].height += aa[i].graphHeight;
+ aa.height += aa.graphHeight;
}
- if (aa[i].height == 0)
+ if (aa.height == 0)
{
- aa[i].height = 20;
+ aa.height = 20;
}
- height += aa[i].height;
+ height += aa.height;
}
}
if (height == 0)
*/
package jalview.workers;
+import jalview.api.AlignCalcManagerI;
+import jalview.api.AlignCalcWorkerI;
+import jalview.datamodel.AlignmentAnnotation;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
-import jalview.api.AlignCalcManagerI;
-import jalview.api.AlignCalcWorkerI;
-import jalview.datamodel.AlignmentAnnotation;
-
public class AlignCalcManager implements AlignCalcManagerI
{
private volatile List<AlignCalcWorkerI> restartable = Collections
for (List<AlignCalcWorkerI> workers : updating.values())
{
for (AlignCalcWorkerI worker : workers)
+ {
if (worker.involves(alignmentAnnotation))
{
return true;
}
+ }
}
}
return false;
AlignCalcWorkerI[] workers;
synchronized (canUpdate)
{
- workers = canUpdate.toArray(new AlignCalcWorkerI[0]);
+ workers = canUpdate.toArray(new AlignCalcWorkerI[canUpdate.size()]);
}
for (AlignCalcWorkerI worker : workers)
{
--- /dev/null
+package jalview.workers;
+
+import jalview.analysis.AAFrequency;
+import jalview.api.AlignViewportI;
+import jalview.api.AlignmentViewPanel;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.SequenceI;
+
+import java.util.Hashtable;
+
+/**
+ * A thread to recompute the consensus of the cDNA complement for a linked
+ * protein alignment.
+ *
+ * @author gmcarstairs
+ *
+ */
+public class ComplementConsensusThread extends ConsensusThread
+{
+
+ public ComplementConsensusThread(AlignViewportI alignViewport,
+ AlignmentViewPanel alignPanel)
+ {
+ super(alignViewport, alignPanel);
+ }
+
+ @Override
+ protected AlignmentAnnotation getConsensusAnnotation()
+ {
+ return alignViewport.getComplementConsensusAnnotation();
+ }
+
+ @Override
+ protected Hashtable[] getViewportConsensus()
+ {
+ return alignViewport.getComplementConsensusHash();
+ }
+
+ @Override
+ protected void computeConsensus(AlignmentI alignment)
+ {
+ Hashtable[] hconsensus = new Hashtable[alignment.getWidth()];
+
+ SequenceI[] aseqs = getSequences();
+ AAFrequency.calculateCdna(alignment, hconsensus);
+
+ alignViewport.setComplementConsensusHash(hconsensus);
+ }
+
+ /**
+ * Convert the computed consensus data into the desired annotation for
+ * display.
+ *
+ * @param consensusAnnotation
+ * the annotation to be populated
+ * @param consensusData
+ * the computed consensus data
+ */
+ @Override
+ protected void deriveConsensus(AlignmentAnnotation consensusAnnotation,
+ Hashtable[] consensusData)
+ {
+ AAFrequency.completeCdnaConsensus(consensusAnnotation, consensusData,
+ alignViewport.isShowSequenceLogo(), getSequences().length);
+ }
+
+}
public class ConsensusThread extends AlignCalcWorker implements
AlignCalcWorkerI
{
- private long nseq = -1;
-
public ConsensusThread(AlignViewportI alignViewport,
AlignmentViewPanel alignPanel)
{
long started = System.currentTimeMillis();
try
{
- AlignmentAnnotation consensus = alignViewport
- .getAlignmentConsensusAnnotation();
+ AlignmentAnnotation consensus = getConsensusAnnotation();
if (consensus == null || calcMan.isPending(this))
{
calcMan.workerComplete(this);
if (alignment == null || (aWidth = alignment.getWidth()) < 0)
{
calcMan.workerComplete(this);
- // .updatingConservation = false;
- // AlignViewport.UPDATING_CONSERVATION = false;
-
- return;
- }
- consensus = alignViewport.getAlignmentConsensusAnnotation();
-
- consensus.annotations = null;
- consensus.annotations = new Annotation[aWidth];
- Hashtable[] hconsensus = alignViewport.getSequenceConsensusHash();
- hconsensus = new Hashtable[aWidth];
- try
- {
- SequenceI aseqs[] = alignment.getSequencesArray();
- nseq = aseqs.length;
- AAFrequency.calculate(aseqs, 0, alignment.getWidth(), hconsensus,
- true);
- } catch (ArrayIndexOutOfBoundsException x)
- {
- // this happens due to a race condition -
- // alignment was edited at same time as calculation was running
- //
- // calcMan.workerCannotRun(this);
- calcMan.workerComplete(this);
return;
}
- alignViewport.setSequenceConsensusHash(hconsensus);
+ eraseConsensus(aWidth);
+ // long now = System.currentTimeMillis();
+ computeConsensus(alignment);
updateResultAnnotation(true);
- ColourSchemeI globalColourScheme = alignViewport
- .getGlobalColourScheme();
- if (globalColourScheme != null)
+ // System.out.println(System.currentTimeMillis() - now);
+
+ if (ap != null)
{
- globalColourScheme.setConsensus(hconsensus);
+ ap.paintAlignment(true);
}
-
} catch (OutOfMemoryError error)
{
calcMan.workerCannotRun(this);
-
- // consensus = null;
- // hconsensus = null;
ap.raiseOOMWarning("calculating consensus", error);
+ } finally
+ {
+ /*
+ * e.g. ArrayIndexOutOfBoundsException can happen due to a race condition
+ * - alignment was edited at same time as calculation was running
+ */
+ calcMan.workerComplete(this);
}
+ }
+
+ /**
+ * Clear out any existing consensus annotations
+ *
+ * @param aWidth
+ * the width (number of columns) of the annotated alignment
+ */
+ protected void eraseConsensus(int aWidth)
+ {
+ AlignmentAnnotation consensus = getConsensusAnnotation();
+ consensus.annotations = new Annotation[aWidth];
+ }
+
+ /**
+ * @param alignment
+ */
+ protected void computeConsensus(AlignmentI alignment)
+ {
+ Hashtable[] hconsensus = new Hashtable[alignment.getWidth()];
+
+ SequenceI[] aseqs = getSequences();
+ AAFrequency.calculate(aseqs, 0, alignment.getWidth(), hconsensus,
+ true);
+
+ alignViewport.setSequenceConsensusHash(hconsensus);
+ setColourSchemeConsensus(hconsensus);
+ }
+
+ /**
+ * @return
+ */
+ protected SequenceI[] getSequences()
+ {
+ return alignViewport.getAlignment().getSequencesArray();
+ }
- calcMan.workerComplete(this);
- if (ap != null)
+ /**
+ * @param hconsensus
+ */
+ protected void setColourSchemeConsensus(Hashtable[] hconsensus)
+ {
+ ColourSchemeI globalColourScheme = alignViewport
+ .getGlobalColourScheme();
+ if (globalColourScheme != null)
{
- ap.paintAlignment(true);
+ globalColourScheme.setConsensus(hconsensus);
}
}
/**
+ * Get the Consensus annotation for the alignment
+ *
+ * @return
+ */
+ protected AlignmentAnnotation getConsensusAnnotation()
+ {
+ return alignViewport.getAlignmentConsensusAnnotation();
+ }
+
+ /**
* update the consensus annotation from the sequence profile data using
* current visualization settings.
*/
public void updateResultAnnotation(boolean immediate)
{
- AlignmentAnnotation consensus = alignViewport
- .getAlignmentConsensusAnnotation();
- Hashtable[] hconsensus = alignViewport.getSequenceConsensusHash();
+ AlignmentAnnotation consensus = getConsensusAnnotation();
+ Hashtable[] hconsensus = getViewportConsensus();
if (immediate || !calcMan.isWorking(this) && consensus != null
&& hconsensus != null)
{
- AAFrequency.completeConsensus(consensus, hconsensus, 0,
- hconsensus.length, alignViewport.isIgnoreGapsConsensus(),
- alignViewport.isShowSequenceLogo(), nseq);
+ deriveConsensus(consensus, hconsensus);
}
}
+
+ /**
+ * Convert the computed consensus data into the desired annotation for
+ * display.
+ *
+ * @param consensusAnnotation
+ * the annotation to be populated
+ * @param consensusData
+ * the computed consensus data
+ */
+ protected void deriveConsensus(AlignmentAnnotation consensusAnnotation,
+ Hashtable[] consensusData)
+ {
+ long nseq = getSequences().length;
+ AAFrequency.completeConsensus(consensusAnnotation, consensusData, 0,
+ consensusData.length, alignViewport.isIgnoreGapsConsensus(),
+ alignViewport.isShowSequenceLogo(), nseq);
+ }
+
+ /**
+ * Get the consensus data stored on the viewport.
+ *
+ * @return
+ */
+ protected Hashtable[] getViewportConsensus()
+ {
+ return alignViewport.getSequenceConsensusHash();
+ }
}
-package jalview.analysis;
+package jalview.analysis;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import jalview.datamodel.Sequence;
Hashtable[] result = new Hashtable[seq1.getLength()];
AAFrequency.calculate(seqs, 0, seq1.getLength(), result, false);
+
+ // col 0 is 100% C
Hashtable col = result[0];
assertEquals(100f, (Float) col.get(G), 0.0001f);
assertEquals(100f, (Float) col.get(N), 0.0001f);
assertEquals(4, col.get(C));
assertEquals("C", col.get(R));
assertNull(col.get(P));
+
+ // col 1 is 75% A
col = result[1];
assertEquals(75f, (Float) col.get(G), 0.0001f);
assertEquals(100f, (Float) col.get(N), 0.0001f);
assertEquals(3, col.get(C));
assertEquals("A", col.get(R));
+
+ // col 2 is 50% G 50% C or 25/25 counting gaps
col = result[2];
- assertEquals(0f, (Float) col.get(G), 0.0001f);
- assertEquals(0f, (Float) col.get(N), 0.0001f);
- assertEquals(0, col.get(C));
- assertEquals("-", col.get(R));
+ assertEquals(25f, (Float) col.get(G), 0.0001f);
+ assertEquals(50f, (Float) col.get(N), 0.0001f);
+ assertEquals(1, col.get(C));
+ assertEquals("CG", col.get(R));
+
+ // col 3 is 75% T 25% G
col = result[3];
assertEquals(75f, (Float) col.get(G), 0.0001f);
assertEquals(75f, (Float) col.get(N), 0.0001f);
}
System.out.println(System.currentTimeMillis() - start);
}
+
+ @Test
+ public void testGetPercentageFormat()
+ {
+ assertNull(AAFrequency.getPercentageFormat(0));
+ assertNull(AAFrequency.getPercentageFormat(99));
+ assertEquals("%3.1f", AAFrequency.getPercentageFormat(100).toString());
+ assertEquals("%3.1f", AAFrequency.getPercentageFormat(999).toString());
+ assertEquals("%3.2f", AAFrequency.getPercentageFormat(1000).toString());
+ assertEquals("%3.2f", AAFrequency.getPercentageFormat(9999).toString());
+ }
}
--- /dev/null
+package jalview.analysis;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+
+public class CodingUtilsTest
+{
+
+ @Test
+ public void testDecodeCodon()
+ {
+ assertTrue(Arrays.equals(new char[]
+ { 'A', 'A', 'A' }, CodingUtils.decodeCodon(0)));
+ assertTrue(Arrays.equals(new char[]
+ { 'A', 'A', 'C' }, CodingUtils.decodeCodon(1)));
+ assertTrue(Arrays.equals(new char[]
+ { 'A', 'A', 'G' }, CodingUtils.decodeCodon(2)));
+ assertTrue(Arrays.equals(new char[]
+ { 'A', 'A', 'T' }, CodingUtils.decodeCodon(3)));
+ assertTrue(Arrays.equals(new char[]
+ { 'A', 'C', 'A' }, CodingUtils.decodeCodon(4)));
+ assertTrue(Arrays.equals(new char[]
+ { 'C', 'A', 'A' }, CodingUtils.decodeCodon(16)));
+ assertTrue(Arrays.equals(new char[]
+ { 'G', 'G', 'G' }, CodingUtils.decodeCodon(42)));
+ assertTrue(Arrays.equals(new char[]
+ { 'T', 'T', 'T' }, CodingUtils.decodeCodon(63)));
+ }
+
+ @Test
+ public void testDecodeNucleotide()
+ {
+ assertEquals('A', CodingUtils.decodeNucleotide(0));
+ assertEquals('C', CodingUtils.decodeNucleotide(1));
+ assertEquals('G', CodingUtils.decodeNucleotide(2));
+ assertEquals('T', CodingUtils.decodeNucleotide(3));
+ assertEquals('0', CodingUtils.decodeNucleotide(4));
+ }
+
+ @Test
+ public void testEncodeCodon()
+ {
+ assertTrue(CodingUtils.encodeCodon('Z') < 0);
+ assertEquals(0, CodingUtils.encodeCodon('a'));
+ assertEquals(0, CodingUtils.encodeCodon('A'));
+ assertEquals(1, CodingUtils.encodeCodon('c'));
+ assertEquals(1, CodingUtils.encodeCodon('C'));
+ assertEquals(2, CodingUtils.encodeCodon('g'));
+ assertEquals(2, CodingUtils.encodeCodon('G'));
+ assertEquals(3, CodingUtils.encodeCodon('t'));
+ assertEquals(3, CodingUtils.encodeCodon('T'));
+ assertEquals(3, CodingUtils.encodeCodon('u'));
+ assertEquals(3, CodingUtils.encodeCodon('U'));
+
+ assertEquals(-1, CodingUtils.encodeCodon(null));
+ assertEquals(0, CodingUtils.encodeCodon(new char[]
+ { 'A', 'A', 'A' }));
+ assertEquals(1, CodingUtils.encodeCodon(new char[]
+ { 'A', 'A', 'C' }));
+ assertEquals(2, CodingUtils.encodeCodon(new char[]
+ { 'A', 'A', 'G' }));
+ assertEquals(3, CodingUtils.encodeCodon(new char[]
+ { 'A', 'A', 'T' }));
+ assertEquals(4, CodingUtils.encodeCodon(new char[]
+ { 'A', 'C', 'A' }));
+ assertEquals(16, CodingUtils.encodeCodon(new char[]
+ { 'C', 'A', 'A' }));
+ assertEquals(42, CodingUtils.encodeCodon(new char[]
+ { 'G', 'G', 'G' }));
+ assertEquals(63, CodingUtils.encodeCodon(new char[]
+ { 'T', 'T', 'T' }));
+ }
+
+}
*/
assertNull(acf.getMappedRegion(seq1, aseq2, 1));
}
+
+ @Test
+ public void testGetMappedCodon()
+ {
+ final Sequence seq1 = new Sequence("Seq1", "c-G-TA-gC-gT-T");
+ seq1.createDatasetSequence();
+ final Sequence aseq1 = new Sequence("Seq1", "-P-R");
+ aseq1.createDatasetSequence();
+
+ /*
+ * First with no mappings
+ */
+ AlignedCodonFrame acf = new AlignedCodonFrame();
+
+ assertNull(acf.getMappedCodon(seq1.getDatasetSequence(), 0));
+
+ /*
+ * Set up the mappings for the exons (upper-case bases)
+ */
+ MapList map = new MapList(new int[]
+ { 2, 4, 6, 6, 8, 9 }, new int[]
+ { 1, 2 }, 3, 1);
+ acf.addMap(seq1.getDatasetSequence(), aseq1.getDatasetSequence(), map);
+
+ assertEquals("[G, T, A]", Arrays.toString(acf.getMappedCodon(
+ aseq1.getDatasetSequence(), 1)));
+ assertEquals("[C, T, T]", Arrays.toString(acf.getMappedCodon(
+ aseq1.getDatasetSequence(), 2)));
+ }
}
}
/**
- * Aligning protein from cDNA yet to be implemented, does nothing.
+ * Aligning protein from cDNA.
*
* @throws IOException
*/
@Test
public void testAlignAs_proteinAsCdna() throws IOException
{
+ // see also AlignmentUtilsTests
AlignmentI al1 = loadAlignment(CDNA_SEQS_1, "FASTA");
AlignmentI al2 = loadAlignment(AA_SEQS_1, "FASTA");
- String before0 = al2.getSequenceAt(0).getSequenceAsString();
- String before1 = al2.getSequenceAt(1).getSequenceAsString();
+ AlignedCodonFrame acf = new AlignedCodonFrame();
+ MapList ml = new MapList(new int[]
+ { 1, 12 }, new int[]
+ { 1, 4 }, 3, 1);
+ acf.addMap(al1.getSequenceAt(0), al2.getSequenceAt(0), ml);
+ acf.addMap(al1.getSequenceAt(1), al2.getSequenceAt(1), ml);
+ al2.addCodonFrame(acf);
((Alignment) al2).alignAs(al1, false, true);
- assertEquals(before0, al2.getSequenceAt(0).getSequenceAsString());
- assertEquals(before1, al2.getSequenceAt(1).getSequenceAsString());
+ assertEquals("K-Q-Y-L-", al2.getSequenceAt(0).getSequenceAsString());
+ assertEquals("-R-F-P-W", al2.getSequenceAt(1).getSequenceAsString());
}
/**
import java.awt.Color;
import java.io.IOException;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Set;
cs = MappingUtils.mapColumnSelection(colsel, dnaView, proteinView);
assertEquals("[0, 1, 3]", cs.getSelected().toString());
}
+
+ /**
+ * Tests for the method that converts a series of [start, end] ranges to
+ * single positions
+ */
+ @Test
+ public void testFlattenRanges()
+ {
+ assertEquals("[1, 2, 3, 4]",
+ Arrays.toString(MappingUtils.flattenRanges(new int[]
+ { 1, 4 })));
+ assertEquals("[1, 2, 3, 4]",
+ Arrays.toString(MappingUtils.flattenRanges(new int[]
+ { 1, 2, 3, 4 })));
+ assertEquals("[1, 2, 3, 4]",
+ Arrays.toString(MappingUtils.flattenRanges(new int[]
+ { 1, 1, 2, 2, 3, 3, 4, 4 })));
+ assertEquals("[1, 2, 3, 4, 7, 8, 9, 12]",
+ Arrays.toString(MappingUtils.flattenRanges(new int[]
+ { 1, 4, 7, 9, 12, 12 })));
+ // unpaired start position is ignored:
+ assertEquals("[1, 2, 3, 4, 7, 8, 9, 12]",
+ Arrays.toString(MappingUtils.flattenRanges(new int[]
+ { 1, 4, 7, 9, 12, 12, 15 })));
+ }
}