+ String description = null;
+ if (counts != null && showSequenceLogo)
+ {
+ int normaliseBy = ignoreGaps ? profile.getNonGapped()
+ : profile.getHeight();
+ description = counts.getTooltip(normaliseBy, dp);
+ }
+ else
+ {
+ StringBuilder sb = new StringBuilder(64);
+ String maxRes = profile.getModalResidue();
+ if (maxRes.length() > 1)
+ {
+ sb.append("[").append(maxRes).append("]");
+ }
+ else
+ {
+ sb.append(maxRes);
+ }
+ if (maxRes.length() > 0)
+ {
+ sb.append(" ");
+ Format.appendPercentage(sb, pid, dp);
+ sb.append("%");
+ }
+ description = sb.toString();
+ }
+ return description;
+ }
+
+ /**
+ * 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 profile
+ * the data object 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(ProfileI profile, boolean ignoreGaps)
+ {
+ int[] rtnval = new int[64];
+ ResidueCount counts = profile.getCounts();
+ if (counts == null)
+ {
+ return null;
+ }
+
+ SymbolCounts symbolCounts = counts.getSymbolCounts();
+ char[] symbols = symbolCounts.symbols;
+ int[] values = symbolCounts.values;
+ QuickSort.sort(values, symbols);
+ int nextArrayPos = 2;
+ int totalPercentage = 0;
+ final int divisor = ignoreGaps ? profile.getNonGapped()
+ : profile.getHeight();
+
+ /*
+ * traverse the arrays in reverse order (highest counts first)
+ */
+ for (int i = symbols.length - 1; i >= 0; i--)
+ {
+ int theChar = symbols[i];
+ int charCount = values[i];
+
+ rtnval[nextArrayPos++] = theChar;
+ final int percentage = (charCount * 100) / divisor;
+ rtnval[nextArrayPos++] = percentage;
+ totalPercentage += percentage;
+ }
+ rtnval[0] = symbols.length;
+ 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();
+ List<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<>();
+ // #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)