From: James Procter Date: Wed, 8 Mar 2023 13:45:11 +0000 (+0000) Subject: Merge branch 'features/r2_11_2_alphafold/JAL-629' into features/JAL-4134_treeviewerfo... X-Git-Tag: Release_2_11_3_0~15^2^2~19 X-Git-Url: http://source.jalview.org/gitweb/?a=commitdiff_plain;h=fea1abc4a4d2c45a1ba6e6127bac5d2466de18fc;hp=1f41372a2bda3efcc27abaed092495548135b6b4;p=jalview.git Merge branch 'features/r2_11_2_alphafold/JAL-629' into features/JAL-4134_treeviewerforcolumns --- diff --git a/src/jalview/analysis/AlignmentSorter.java b/src/jalview/analysis/AlignmentSorter.java index 81bddc2..0f3edfd 100755 --- a/src/jalview/analysis/AlignmentSorter.java +++ b/src/jalview/analysis/AlignmentSorter.java @@ -25,6 +25,7 @@ import jalview.analysis.scoremodels.SimilarityParams; import jalview.datamodel.AlignmentAnnotation; import jalview.datamodel.AlignmentI; import jalview.datamodel.AlignmentOrder; +import jalview.datamodel.BinaryNode; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceGroup; import jalview.datamodel.SequenceI; @@ -534,7 +535,7 @@ public class AlignmentSorter * * @return DOCUMENT ME! */ - private static List _sortByTree(SequenceNode node, + private static List _sortByTree(BinaryNode node, List tmp, List seqset) { if (node == null) @@ -542,12 +543,12 @@ public class AlignmentSorter return tmp; } - SequenceNode left = (SequenceNode) node.left(); - SequenceNode right = (SequenceNode) node.right(); + BinaryNode left = (BinaryNode) node.left(); + BinaryNode right = (BinaryNode) node.right(); if ((left == null) && (right == null)) { - if (!node.isPlaceholder() && (node.element() != null)) + if (!(node instanceof SequenceNode && ((SequenceNode)node).isPlaceholder()) && (node.element() != null)) { if (node.element() instanceof SequenceI) { diff --git a/src/jalview/analysis/AverageDistanceEngine.java b/src/jalview/analysis/AverageDistanceEngine.java new file mode 100644 index 0000000..e6a763b --- /dev/null +++ b/src/jalview/analysis/AverageDistanceEngine.java @@ -0,0 +1,384 @@ +/* + * 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 . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ +package jalview.analysis; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.List; +import java.util.Vector; + +import jalview.datamodel.AlignmentAnnotation; +import jalview.datamodel.BinaryNode; +import jalview.datamodel.ContactListI; +import jalview.datamodel.ContactMatrixI; +import jalview.math.Matrix; +import jalview.viewmodel.AlignmentViewport; + +/** + * This class implements distance calculations used in constructing a Average + * Distance tree (also known as UPGMA) + */ +public class AverageDistanceEngine extends TreeEngine +{ + ContactMatrixI cm; + + AlignmentViewport av; + + AlignmentAnnotation aa; + + /** + * compute cosine distance matrix for a given contact matrix and create a + * UPGMA tree + * + * @param cm + */ + public AverageDistanceEngine(AlignmentViewport av, AlignmentAnnotation aa, + ContactMatrixI cm) + { + this.av = av; + this.aa = aa; + this.cm = cm; + calculate(cm); + + } + + // 0 - normalised dot product + // 1 - L1 - ie (abs(v_1-v_2)/dim(v)) + // L1 is more rational - since can reason about value of difference, + // normalised dot product might give cleaner clusters, but more difficult to + // understand. + + int mode = 1; + + public void calculate(ContactMatrixI cm) + { + this.cm = cm; + node = new Vector(); + clusters = new Vector(); + distances = new Matrix(new double[cm.getWidth()][cm.getWidth()]); + noseqs = cm.getWidth(); + done = new BitSet(); + double moduli[] = new double[cm.getWidth()]; + double max; + if (mode == 0) + { + max = 1; + } + else + { + max = cm.getMax() * cm.getMax(); + } + + for (int i = 0; i < cm.getWidth(); i++) + { + // init the tree engine node for this column + BinaryNode cnode = new BinaryNode(); + cnode.setElement(Integer.valueOf(i)); + cnode.setName("c" + i); + node.addElement(cnode); + BitSet bs = new BitSet(); + bs.set(i); + clusters.addElement(bs); + + // compute distance matrix element + ContactListI ith = cm.getContactList(i); + + for (int j = 0; j < i; j++) + { + distances.setValue(i, i, 0); + ContactListI jth = cm.getContactList(j); + double prd = 0; + for (int indx = 0; indx < cm.getHeight(); indx++) + { + if (mode == 0) + { + if (j == 0) + { + moduli[i] += ith.getContactAt(indx) * ith.getContactAt(indx); + } + prd += ith.getContactAt(indx) * jth.getContactAt(indx); + } + else + { + prd += Math + .abs(ith.getContactAt(indx) - jth.getContactAt(indx)); + } + } + if (mode == 0) + { + if (j == 0) + { + moduli[i] = Math.sqrt(moduli[i]); + } + prd = (moduli[i] != 0 && moduli[j] != 0) + ? prd / (moduli[i] * moduli[j]) + : 0; + prd = 1 - prd; + } + else + { + prd /= cm.getHeight(); + } + distances.setValue(i, j, prd); + distances.setValue(j, i, prd); + } + } + + noClus = clusters.size(); + cluster(); + } + + /** + * Calculates and saves the distance between the combination of cluster(i) and + * cluster(j) and all other clusters. An average of the distances from + * cluster(i) and cluster(j) is calculated, weighted by the sizes of each + * cluster. + * + * @param i + * @param j + */ + @Override + protected void findClusterDistance(int i, int j) + { + int noi = clusters.elementAt(i).cardinality(); + int noj = clusters.elementAt(j).cardinality(); + + // New distances from cluster i to others + double[] newdist = new double[noseqs]; + + for (int l = 0; l < noseqs; l++) + { + if ((l != i) && (l != j)) + { + newdist[l] = ((distances.getValue(i, l) * noi) + + (distances.getValue(j, l) * noj)) / (noi + noj); + } + else + { + newdist[l] = 0; + } + } + + for (int ii = 0; ii < noseqs; ii++) + { + distances.setValue(i, ii, newdist[ii]); + distances.setValue(ii, i, newdist[ii]); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected double findMinDistance() + { + double min = Double.MAX_VALUE; + + for (int i = 0; i < (noseqs - 1); i++) + { + for (int j = i + 1; j < noseqs; j++) + { + if (!done.get(i) && !done.get(j)) + { + if (distances.getValue(i, j) < min) + { + mini = i; + minj = j; + + min = distances.getValue(i, j); + } + } + } + } + return min; + } + + /** + * {@inheritDoc} + */ + @Override + protected void findNewDistances(BinaryNode nodei, BinaryNode nodej, + double dist) + { + double ih = 0; + double jh = 0; + + BinaryNode sni = nodei; + BinaryNode snj = nodej; + + while (sni != null) + { + ih = ih + sni.dist; + sni = (BinaryNode) sni.left(); + } + + while (snj != null) + { + jh = jh + snj.dist; + snj = (BinaryNode) snj.left(); + } + + nodei.dist = ((dist / 2) - ih); + nodej.dist = ((dist / 2) - jh); + } + + /*** + * not the right place - OH WELL! + */ + + /** + * Makes a list of groups, where each group is represented by a node whose + * height (distance from the root node), as a fraction of the height of the + * whole tree, is greater than the given threshold. This corresponds to + * selecting the nodes immediately to the right of a vertical line + * partitioning the tree (if the tree is drawn with root to the left). Each + * such node represents a group that contains all of the sequences linked to + * the child leaf nodes. + * + * @param threshold + * @see #getGroups() + */ + public List groupNodes(float threshold) + { + List groups = new ArrayList(); + _groupNodes(groups, getTopNode(), threshold); + return groups; + } + + protected void _groupNodes(List groups, BinaryNode nd, + float threshold) + { + if (nd == null) + { + return; + } + + if ((nd.height / maxheight) > threshold) + { + groups.add(nd); + } + else + { + _groupNodes(groups, nd.left(), threshold); + _groupNodes(groups, nd.right(), threshold); + } + } + + /** + * DOCUMENT ME! + * + * @param nd + * DOCUMENT ME! + * + * @return DOCUMENT ME! + */ + public double findHeight(BinaryNode nd) + { + if (nd == null) + { + return maxheight; + } + + if ((nd.left() == null) && (nd.right() == null)) + { + nd.height = ((BinaryNode) nd.parent()).height + nd.dist; + + if (nd.height > maxheight) + { + return nd.height; + } + else + { + return maxheight; + } + } + else + { + if (nd.parent() != null) + { + nd.height = ((BinaryNode) nd.parent()).height + nd.dist; + } + else + { + maxheight = 0; + nd.height = (float) 0.0; + } + + maxheight = findHeight((BinaryNode) (nd.left())); + maxheight = findHeight((BinaryNode) (nd.right())); + } + + return maxheight; + } + + /** + * Search for leaf nodes below (or at) the given node + * + * @param top2 + * root node to search from + * + * @return + */ + public Vector findLeaves(BinaryNode top2) + { + Vector leaves = new Vector(); + findLeaves(top2, leaves); + return leaves; + } + + /** + * Search for leaf nodes. + * + * @param nd + * root node to search from + * @param leaves + * Vector of leaves to add leaf node objects too. + * + * @return Vector of leaf nodes on binary tree + */ + Vector findLeaves(BinaryNode nd, Vector leaves) + { + if (nd == null) + { + return leaves; + } + + if ((nd.left() == null) && (nd.right() == null)) // Interior node + // detection + { + leaves.addElement(nd); + + return leaves; + } + else + { + /* + * TODO: Identify internal nodes... if (node.isSequenceLabel()) { + * leaves.addElement(node); } + */ + findLeaves(nd.left(), leaves); + findLeaves(nd.right(), leaves); + } + + return leaves; + } + +} diff --git a/src/jalview/analysis/AverageDistanceTree.java b/src/jalview/analysis/AverageDistanceTree.java index c726627..760962e 100644 --- a/src/jalview/analysis/AverageDistanceTree.java +++ b/src/jalview/analysis/AverageDistanceTree.java @@ -22,7 +22,7 @@ package jalview.analysis; import jalview.api.analysis.ScoreModelI; import jalview.api.analysis.SimilarityParamsI; -import jalview.datamodel.SequenceNode; +import jalview.datamodel.BinaryNode; import jalview.viewmodel.AlignmentViewport; /** @@ -113,25 +113,25 @@ public class AverageDistanceTree extends TreeBuilder * {@inheritDoc} */ @Override - protected void findNewDistances(SequenceNode nodei, SequenceNode nodej, + protected void findNewDistances(BinaryNode nodei, BinaryNode nodej, double dist) { double ih = 0; double jh = 0; - SequenceNode sni = nodei; - SequenceNode snj = nodej; + BinaryNode sni = nodei; + BinaryNode snj = nodej; while (sni != null) { ih = ih + sni.dist; - sni = (SequenceNode) sni.left(); + sni = (BinaryNode) sni.left(); } while (snj != null) { jh = jh + snj.dist; - snj = (SequenceNode) snj.left(); + snj = (BinaryNode) snj.left(); } nodei.dist = ((dist / 2) - ih); diff --git a/src/jalview/analysis/NJTree.java b/src/jalview/analysis/NJTree.java index 522c2b1..9a39ac0 100644 --- a/src/jalview/analysis/NJTree.java +++ b/src/jalview/analysis/NJTree.java @@ -22,7 +22,7 @@ package jalview.analysis; import jalview.api.analysis.ScoreModelI; import jalview.api.analysis.SimilarityParamsI; -import jalview.datamodel.SequenceNode; +import jalview.datamodel.BinaryNode; import jalview.viewmodel.AlignmentViewport; /** @@ -81,7 +81,7 @@ public class NJTree extends TreeBuilder * {@inheritDoc} */ @Override - protected void findNewDistances(SequenceNode nodei, SequenceNode nodej, + protected void findNewDistances(BinaryNode nodei, BinaryNode nodej, double dist) { nodei.dist = ((dist + ri) - rj) / 2; diff --git a/src/jalview/analysis/TreeBuilder.java b/src/jalview/analysis/TreeBuilder.java index 0601dd9..61f65ff 100644 --- a/src/jalview/analysis/TreeBuilder.java +++ b/src/jalview/analysis/TreeBuilder.java @@ -23,56 +23,26 @@ package jalview.analysis; import jalview.api.analysis.ScoreModelI; import jalview.api.analysis.SimilarityParamsI; import jalview.datamodel.AlignmentView; +import jalview.datamodel.BinaryNode; import jalview.datamodel.CigarArray; import jalview.datamodel.SeqCigar; import jalview.datamodel.SequenceI; import jalview.datamodel.SequenceNode; -import jalview.math.MatrixI; import jalview.viewmodel.AlignmentViewport; import java.util.BitSet; import java.util.Vector; -public abstract class TreeBuilder +public abstract class TreeBuilder extends TreeEngine { public static final String AVERAGE_DISTANCE = "AV"; public static final String NEIGHBOUR_JOINING = "NJ"; - protected Vector clusters; - protected SequenceI[] sequences; public AlignmentView seqData; - protected BitSet done; - - protected int noseqs; - - int noClus; - - protected MatrixI distances; - - protected int mini; - - protected int minj; - - protected double ri; - - protected double rj; - - SequenceNode maxdist; - - SequenceNode top; - - double maxDistValue; - - double maxheight; - - int ycount; - - Vector node; - private AlignmentView seqStrings; /** @@ -114,116 +84,6 @@ public abstract class TreeBuilder } /** - * DOCUMENT ME! - * - * @param nd - * DOCUMENT ME! - * - * @return DOCUMENT ME! - */ - double findHeight(SequenceNode nd) - { - if (nd == null) - { - return maxheight; - } - - if ((nd.left() == null) && (nd.right() == null)) - { - nd.height = ((SequenceNode) nd.parent()).height + nd.dist; - - if (nd.height > maxheight) - { - return nd.height; - } - else - { - return maxheight; - } - } - else - { - if (nd.parent() != null) - { - nd.height = ((SequenceNode) nd.parent()).height + nd.dist; - } - else - { - maxheight = 0; - nd.height = (float) 0.0; - } - - maxheight = findHeight((SequenceNode) (nd.left())); - maxheight = findHeight((SequenceNode) (nd.right())); - } - - return maxheight; - } - - /** - * DOCUMENT ME! - * - * @param nd - * DOCUMENT ME! - */ - void reCount(SequenceNode nd) - { - ycount = 0; - // _lycount = 0; - // _lylimit = this.node.size(); - _reCount(nd); - } - - /** - * DOCUMENT ME! - * - * @param nd - * DOCUMENT ME! - */ - void _reCount(SequenceNode nd) - { - // if (_lycount<_lylimit) - // { - // System.err.println("Warning: depth of _recount greater than number of - // nodes."); - // } - if (nd == null) - { - return; - } - // _lycount++; - - if ((nd.left() != null) && (nd.right() != null)) - { - - _reCount((SequenceNode) nd.left()); - _reCount((SequenceNode) nd.right()); - - SequenceNode l = (SequenceNode) nd.left(); - SequenceNode r = (SequenceNode) nd.right(); - - nd.count = l.count + r.count; - nd.ycount = (l.ycount + r.ycount) / 2; - } - else - { - nd.count = 1; - nd.ycount = ycount++; - } - // _lycount--; - } - - /** - * DOCUMENT ME! - * - * @return DOCUMENT ME! - */ - public SequenceNode getTopNode() - { - return top; - } - - /** * * @return true if tree has real distances */ @@ -247,40 +107,6 @@ public abstract class TreeBuilder } /** - * Form clusters by grouping sub-clusters, starting from one sequence per - * cluster, and finishing when only two clusters remain - */ - void cluster() - { - while (noClus > 2) - { - findMinDistance(); - - joinClusters(mini, minj); - - noClus--; - } - - int rightChild = done.nextClearBit(0); - int leftChild = done.nextClearBit(rightChild + 1); - - joinClusters(leftChild, rightChild); - top = (node.elementAt(leftChild)); - - reCount(top); - findHeight(top); - findMaxDist(top); - } - - /** - * Returns the minimum distance between two clusters, and also sets the - * indices of the clusters in fields mini and minj - * - * @return - */ - protected abstract double findMinDistance(); - - /** * Calculates the tree using the given score model and parameters, and the * configured tree type *

@@ -304,67 +130,9 @@ public abstract class TreeBuilder cluster(); } - /** - * Finds the node, at or below the given node, with the maximum distance, and - * saves the node and the distance value - * - * @param nd - */ - void findMaxDist(SequenceNode nd) - { - if (nd == null) - { - return; - } - - if ((nd.left() == null) && (nd.right() == null)) - { - double dist = nd.dist; - - if (dist > maxDistValue) - { - maxdist = nd; - maxDistValue = dist; - } - } - else - { - findMaxDist((SequenceNode) nd.left()); - findMaxDist((SequenceNode) nd.right()); - } - } - - /** - * Calculates and returns r, whatever that is - * - * @param i - * @param j - * - * @return - */ - protected double findr(int i, int j) - { - double tmp = 1; - - for (int k = 0; k < noseqs; k++) - { - if ((k != i) && (k != j) && (!done.get(k))) - { - tmp = tmp + distances.getValue(i, k); - } - } - - if (noClus > 2) - { - tmp = tmp / (noClus - 2); - } - - return tmp; - } - protected void init(AlignmentView seqView, int start, int end) { - this.node = new Vector(); + this.node = new Vector(); if (seqView != null) { this.seqData = seqView; @@ -398,62 +166,6 @@ public abstract class TreeBuilder } /** - * Merges cluster(j) to cluster(i) and recalculates cluster and node distances - * - * @param i - * @param j - */ - void joinClusters(final int i, final int j) - { - double dist = distances.getValue(i, j); - - ri = findr(i, j); - rj = findr(j, i); - - findClusterDistance(i, j); - - SequenceNode sn = new SequenceNode(); - - sn.setLeft((node.elementAt(i))); - sn.setRight((node.elementAt(j))); - - SequenceNode tmpi = (node.elementAt(i)); - SequenceNode tmpj = (node.elementAt(j)); - - findNewDistances(tmpi, tmpj, dist); - - tmpi.setParent(sn); - tmpj.setParent(sn); - - node.setElementAt(sn, i); - - /* - * move the members of cluster(j) to cluster(i) - * and mark cluster j as out of the game - */ - clusters.get(i).or(clusters.get(j)); - clusters.get(j).clear(); - done.set(j); - } - - /* - * Computes and stores new distances for nodei and nodej, given the previous - * distance between them - */ - protected abstract void findNewDistances(SequenceNode nodei, - SequenceNode nodej, double previousDistance); - - /** - * Calculates and saves the distance between the combination of cluster(i) and - * cluster(j) and all other clusters. The form of the calculation depends on - * the tree clustering method being used. - * - * @param i - * @param j - */ - protected abstract void findClusterDistance(int i, int j); - - /** * Start by making a cluster for each individual sequence */ void makeLeaves() diff --git a/src/jalview/analysis/TreeEngine.java b/src/jalview/analysis/TreeEngine.java new file mode 100644 index 0000000..daf7836 --- /dev/null +++ b/src/jalview/analysis/TreeEngine.java @@ -0,0 +1,317 @@ +/* + * 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 . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ +package jalview.analysis; + +import java.util.BitSet; +import java.util.Vector; + +import jalview.datamodel.BinaryNode; +import jalview.datamodel.SequenceNode; +import jalview.math.MatrixI; + +public abstract class TreeEngine +{ + + protected Vector clusters; + + protected BitSet done; + + protected int noseqs; + + protected int noClus; + + protected MatrixI distances; + + protected double ri; + + protected double rj; + + protected Vector node; + + BinaryNode maxdist; + + double maxDistValue; + + protected int mini; + + protected int minj; + + protected BinaryNode top; + + protected int ycount; + + double maxheight; + + /** + * Calculates and returns r, whatever that is + * + * @param i + * @param j + * + * @return + */ + protected double findr(int i, int j) + { + double tmp = 1; + + for (int k = 0; k < noseqs; k++) + { + if ((k != i) && (k != j) && (!done.get(k))) + { + tmp = tmp + distances.getValue(i, k); + } + } + + if (noClus > 2) + { + tmp = tmp / (noClus - 2); + } + + return tmp; + } + + /** + * Merges cluster(j) to cluster(i) and recalculates cluster and node distances + * + * @param i + * @param j + */ + protected void joinClusters(final int i, final int j) + { + double dist = distances.getValue(i, j); + + ri = findr(i, j); + rj = findr(j, i); + + findClusterDistance(i, j); + + BinaryNode sn = new BinaryNode(); + + sn.setLeft((node.elementAt(i))); + sn.setRight((node.elementAt(j))); + + BinaryNode tmpi = (node.elementAt(i)); + BinaryNode tmpj = (node.elementAt(j)); + + findNewDistances(tmpi, tmpj, dist); + + tmpi.setParent(sn); + tmpj.setParent(sn); + + node.setElementAt(sn, i); + + /* + * move the members of cluster(j) to cluster(i) + * and mark cluster j as out of the game + */ + clusters.get(i).or(clusters.get(j)); + clusters.get(j).clear(); + done.set(j); + } + + protected abstract void findNewDistances(BinaryNode nodei, + BinaryNode nodej, double previousDistance); + + /** + * Calculates and saves the distance between the combination of cluster(i) and + * cluster(j) and all other clusters. The form of the calculation depends on + * the tree clustering method being used. + * + * @param i + * @param j + */ + protected abstract void findClusterDistance(int i, int j); + + /** + * Finds the node, at or below the given node, with the maximum distance, and + * saves the node and the distance value + * + * @param nd + */ + protected void findMaxDist(BinaryNode nd) + { + if (nd == null) + { + return; + } + + if ((nd.left() == null) && (nd.right() == null)) + { + double dist = nd.dist; + + if (dist > maxDistValue) + { + maxdist = nd; + maxDistValue = dist; + } + } + else + { + findMaxDist((BinaryNode) nd.left()); + findMaxDist((BinaryNode) nd.right()); + } + } + + /** + * Form clusters by grouping sub-clusters, starting from one sequence per + * cluster, and finishing when only two clusters remain + */ + protected void cluster() + { + while (noClus > 2) + { + findMinDistance(); + + joinClusters(mini, minj); + + noClus--; + } + + int rightChild = done.nextClearBit(0); + int leftChild = done.nextClearBit(rightChild + 1); + + joinClusters(leftChild, rightChild); + top = (node.elementAt(leftChild)); + + reCount(top); + findHeight(top); + findMaxDist(top); + } + + /** + * Returns the minimum distance between two clusters, and also sets the + * indices of the clusters in fields mini and minj + * + * @return + */ + protected abstract double findMinDistance(); + + /** + * DOCUMENT ME! + * + * @param nd + * DOCUMENT ME! + */ + protected void _reCount(BinaryNode nd) + { + // if (_lycount<_lylimit) + // { + // System.err.println("Warning: depth of _recount greater than number of + // nodes."); + // } + if (nd == null) + { + return; + } + // _lycount++; + + if ((nd.left() != null) && (nd.right() != null)) + { + + _reCount(nd.left()); + _reCount((BinaryNode) nd.right()); + + BinaryNode l = nd.left(); + BinaryNode r = nd.right(); + + nd.count = l.count + r.count; + nd.ycount = (l.ycount + r.ycount) / 2; + } + else + { + nd.count = 1; + nd.ycount = ycount++; + } + // _lycount--; + } + + /** + * DOCUMENT ME! + * + * @param nd + * DOCUMENT ME! + * + * @return DOCUMENT ME! + */ + double findHeight(BinaryNode nd) + { + if (nd == null) + { + return maxheight; + } + + if ((nd.left() == null) && (nd.right() == null)) + { + nd.height = ((BinaryNode) nd.parent()).height + nd.dist; + + if (nd.height > maxheight) + { + return nd.height; + } + else + { + return maxheight; + } + } + else + { + if (nd.parent() != null) + { + nd.height = ((BinaryNode) nd.parent()).height + nd.dist; + } + else + { + maxheight = 0; + nd.height = (float) 0.0; + } + + maxheight = findHeight((BinaryNode) (nd.left())); + maxheight = findHeight((BinaryNode) (nd.right())); + } + + return maxheight; + } + + /** + * DOCUMENT ME! + * + * @param nd + * DOCUMENT ME! + */ + void reCount(BinaryNode nd) + { + ycount = 0; + // _lycount = 0; + // _lylimit = this.node.size(); + _reCount(nd); + } + + /** + * DOCUMENT ME! + * + * @return DOCUMENT ME! + */ + public BinaryNode getTopNode() + { + return top; + } + +} diff --git a/src/jalview/analysis/TreeModel.java b/src/jalview/analysis/TreeModel.java index 4d5e4b2..dd56424 100644 --- a/src/jalview/analysis/TreeModel.java +++ b/src/jalview/analysis/TreeModel.java @@ -20,7 +20,6 @@ */ package jalview.analysis; -import jalview.bin.Cache; import jalview.datamodel.AlignmentView; import jalview.datamodel.BinaryNode; import jalview.datamodel.NodeTransformI; @@ -51,7 +50,7 @@ public class TreeModel int noseqs; - SequenceNode top; + BinaryNode top; double maxDistValue; @@ -59,7 +58,7 @@ public class TreeModel int ycount; - Vector node; + Vector node; boolean hasDistances = true; // normal case for jalview trees @@ -81,7 +80,7 @@ public class TreeModel public TreeModel(SequenceI[] seqs, AlignmentView odata, NewickFile treefile) { - this(seqs, treefile.getTree(), treefile.HasDistances(), + this(seqs, treefile.getTree(), treefile.HasDistances(), treefile.HasBootstrap(), treefile.HasRootDistance()); seqData = odata; @@ -95,7 +94,7 @@ public class TreeModel */ public TreeModel(TreeBuilder tree) { - this(tree.getSequences(), tree.getTopNode(), tree.hasDistances(), + this(tree.getSequences(), tree.getTopNode(), tree.hasDistances(), tree.hasBootstrap(), tree.hasRootDistance()); seqData = tree.getOriginalData(); } @@ -109,7 +108,7 @@ public class TreeModel * @param hasBoot * @param hasRootDist */ - public TreeModel(SequenceI[] seqs, SequenceNode root, boolean hasDist, + public TreeModel(SequenceI[] seqs, BinaryNode root, boolean hasDist, boolean hasBoot, boolean hasRootDist) { this.sequences = seqs; @@ -129,7 +128,7 @@ public class TreeModel { SequenceIdMatcher algnIds = new SequenceIdMatcher(seqs); - Vector leaves = findLeaves(top); + Vector leaves = findLeaves(top); int i = 0; int namesleft = seqs.length; @@ -141,7 +140,8 @@ public class TreeModel // int countOne2Many = 0; while (i < leaves.size()) { - j = leaves.elementAt(i++); + // TODO - decide if we get rid of the polymorphism here ? + j = (SequenceNode)leaves.elementAt(i++); realnam = j.getName(); nam = null; @@ -206,7 +206,7 @@ public class TreeModel */ public void updatePlaceHolders(List list) { - Vector leaves = findLeaves(top); + Vector leaves = findLeaves(top); int sz = leaves.size(); SequenceIdMatcher seqmatcher = null; @@ -214,7 +214,7 @@ public class TreeModel while (i < sz) { - SequenceNode leaf = leaves.elementAt(i++); + SequenceNode leaf = (SequenceNode) leaves.elementAt(i++); if (list.contains(leaf.element())) { @@ -289,15 +289,15 @@ public class TreeModel /** * Search for leaf nodes below (or at) the given node * - * @param nd + * @param top2 * root node to search from * * @return */ - public Vector findLeaves(SequenceNode nd) + public Vector findLeaves(BinaryNode top2) { - Vector leaves = new Vector(); - findLeaves(nd, leaves); + Vector leaves = new Vector(); + findLeaves(top2, leaves); return leaves; } @@ -311,8 +311,8 @@ public class TreeModel * * @return Vector of leaf nodes on binary tree */ - Vector findLeaves(SequenceNode nd, - Vector leaves) + Vector findLeaves(BinaryNode nd, + Vector leaves) { if (nd == null) { @@ -332,8 +332,8 @@ public class TreeModel * TODO: Identify internal nodes... if (node.isSequenceLabel()) { * leaves.addElement(node); } */ - findLeaves((SequenceNode) nd.left(), leaves); - findLeaves((SequenceNode) nd.right(), leaves); + findLeaves(nd.left(), leaves); + findLeaves(nd.right(), leaves); } return leaves; @@ -345,7 +345,7 @@ public class TreeModel * @param nd * SequenceNode */ - void printNode(SequenceNode nd) + void printNode(BinaryNode nd) { if (nd == null) { @@ -361,8 +361,8 @@ public class TreeModel else { System.out.println("Dist " + nd.dist); - printNode((SequenceNode) nd.left()); - printNode((SequenceNode) nd.right()); + printNode((BinaryNode) nd.left()); + printNode((BinaryNode) nd.right()); } } @@ -388,14 +388,14 @@ public class TreeModel * @param threshold * @see #getGroups() */ - public List groupNodes(float threshold) + public List groupNodes(float threshold) { - List groups = new ArrayList(); + List groups = new ArrayList(); _groupNodes(groups, getTopNode(), threshold); return groups; } - protected void _groupNodes(List groups, SequenceNode nd, + protected void _groupNodes(List groups, BinaryNode nd, float threshold) { if (nd == null) @@ -422,7 +422,7 @@ public class TreeModel * * @return DOCUMENT ME! */ - public double findHeight(SequenceNode nd) + public double findHeight(BinaryNode nd) { if (nd == null) { @@ -431,7 +431,7 @@ public class TreeModel if ((nd.left() == null) && (nd.right() == null)) { - nd.height = ((SequenceNode) nd.parent()).height + nd.dist; + nd.height = ((BinaryNode) nd.parent()).height + nd.dist; if (nd.height > maxheight) { @@ -446,7 +446,7 @@ public class TreeModel { if (nd.parent() != null) { - nd.height = ((SequenceNode) nd.parent()).height + nd.dist; + nd.height = ((BinaryNode) nd.parent()).height + nd.dist; } else { @@ -454,8 +454,8 @@ public class TreeModel nd.height = (float) 0.0; } - maxheight = findHeight((SequenceNode) (nd.left())); - maxheight = findHeight((SequenceNode) (nd.right())); + maxheight = findHeight((BinaryNode) (nd.left())); + maxheight = findHeight((BinaryNode) (nd.right())); } return maxheight; @@ -467,7 +467,7 @@ public class TreeModel * @param nd * DOCUMENT ME! */ - void printN(SequenceNode nd) + void printN(BinaryNode nd) { if (nd == null) { @@ -476,8 +476,8 @@ public class TreeModel if ((nd.left() != null) && (nd.right() != null)) { - printN((SequenceNode) nd.left()); - printN((SequenceNode) nd.right()); + printN((BinaryNode) nd.left()); + printN((BinaryNode) nd.right()); } else { @@ -494,7 +494,7 @@ public class TreeModel * @param nd * DOCUMENT ME! */ - public void reCount(SequenceNode nd) + public void reCount(BinaryNode nd) { ycount = 0; // _lycount = 0; @@ -510,7 +510,7 @@ public class TreeModel * @param nd * DOCUMENT ME! */ - void _reCount(SequenceNode nd) + void _reCount(BinaryNode nd) { // if (_lycount<_lylimit) // { @@ -526,11 +526,11 @@ public class TreeModel if ((nd.left() != null) && (nd.right() != null)) { - _reCount((SequenceNode) nd.left()); - _reCount((SequenceNode) nd.right()); + _reCount((BinaryNode) nd.left()); + _reCount((BinaryNode) nd.right()); - SequenceNode l = (SequenceNode) nd.left(); - SequenceNode r = (SequenceNode) nd.right(); + BinaryNode l = (BinaryNode) nd.left(); + BinaryNode r = (BinaryNode) nd.right(); nd.count = l.count + r.count; nd.ycount = (l.ycount + r.ycount) / 2; @@ -549,14 +549,14 @@ public class TreeModel * @param nd * DOCUMENT ME! */ - public void swapNodes(SequenceNode nd) + public void swapNodes(BinaryNode nd) { if (nd == null) { return; } - SequenceNode tmp = (SequenceNode) nd.left(); + BinaryNode tmp = (BinaryNode) nd.left(); nd.setLeft(nd.right()); nd.setRight(tmp); @@ -570,7 +570,7 @@ public class TreeModel * @param dir * DOCUMENT ME! */ - void changeDirection(SequenceNode nd, SequenceNode dir) + void changeDirection(BinaryNode nd, BinaryNode dir) { if (nd == null) { @@ -579,9 +579,9 @@ public class TreeModel if (nd.parent() != top) { - changeDirection((SequenceNode) nd.parent(), nd); + changeDirection((BinaryNode) nd.parent(), nd); - SequenceNode tmp = (SequenceNode) nd.parent(); + BinaryNode tmp = (BinaryNode) nd.parent(); if (dir == nd.left()) { @@ -630,7 +630,7 @@ public class TreeModel * * @return DOCUMENT ME! */ - public SequenceNode getTopNode() + public BinaryNode getTopNode() { return top; } @@ -665,7 +665,7 @@ public class TreeModel */ public void applyToNodes(NodeTransformI nodeTransformI) { - for (Enumeration nodes = node.elements(); nodes + for (Enumeration nodes = node.elements(); nodes .hasMoreElements(); nodeTransformI .transform(nodes.nextElement())) { diff --git a/src/jalview/api/AlignViewportI.java b/src/jalview/api/AlignViewportI.java index b09538e..03efec5 100644 --- a/src/jalview/api/AlignViewportI.java +++ b/src/jalview/api/AlignViewportI.java @@ -28,6 +28,7 @@ import jalview.datamodel.AlignmentI; import jalview.datamodel.AlignmentView; import jalview.datamodel.ColumnSelection; import jalview.datamodel.ContactListI; +import jalview.datamodel.ContactMatrixI; import jalview.datamodel.ProfilesI; import jalview.datamodel.SearchResultsI; import jalview.datamodel.SequenceCollectionI; @@ -555,4 +556,6 @@ public interface AlignViewportI extends ViewStyleI * @return */ Iterator getViewAsVisibleContigs(boolean selectedRegionOnly); + + ContactMatrixI getContactMatrix(AlignmentAnnotation alignmentAnnotation); } diff --git a/src/jalview/appletgui/TreeCanvas.java b/src/jalview/appletgui/TreeCanvas.java index 2e5f938..8c3e39a 100755 --- a/src/jalview/appletgui/TreeCanvas.java +++ b/src/jalview/appletgui/TreeCanvas.java @@ -23,6 +23,7 @@ package jalview.appletgui; import jalview.analysis.Conservation; import jalview.analysis.TreeModel; import jalview.api.AlignViewportI; +import jalview.datamodel.BinaryNode; import jalview.datamodel.Sequence; import jalview.datamodel.SequenceGroup; import jalview.datamodel.SequenceI; @@ -86,7 +87,7 @@ public class TreeCanvas extends Panel Hashtable nodeHash = new Hashtable(); - SequenceNode highlightNode; + BinaryNode highlightNode; AlignmentPanel ap; @@ -122,15 +123,15 @@ public class TreeCanvas extends Panel tree2.findHeight(tree2.getTopNode()); // Now have to calculate longest name based on the leaves - Vector leaves = tree2.findLeaves(tree2.getTopNode()); + Vector leaves = tree2.findLeaves(tree2.getTopNode()); boolean has_placeholders = false; longestName = ""; for (int i = 0; i < leaves.size(); i++) { - SequenceNode lf = leaves.elementAt(i); + BinaryNode lf = leaves.elementAt(i); - if (lf.isPlaceholder()) + if (lf instanceof SequenceNode && ((SequenceNode)lf).isPlaceholder()) { has_placeholders = true; } @@ -146,7 +147,7 @@ public class TreeCanvas extends Panel setMarkPlaceholders(has_placeholders); } - public void drawNode(Graphics g, SequenceNode node, float chunk, + public void drawNode(Graphics g, BinaryNode node, float chunk, double scale, int width, int offx, int offy) { if (node == null) @@ -210,7 +211,7 @@ public class TreeCanvas extends Panel g.drawString(nodeLabel, xstart + 2, ypos - 2); } - String name = (markPlaceholders && node.isPlaceholder()) + String name = (markPlaceholders && node instanceof SequenceNode && ((SequenceNode) node).isPlaceholder()) ? (PLACEHOLDER + node.getName()) : node.getName(); FontMetrics fm = g.getFontMetrics(font); @@ -237,9 +238,9 @@ public class TreeCanvas extends Panel } else { - drawNode(g, (SequenceNode) node.left(), chunk, scale, width, offx, + drawNode(g, (BinaryNode) node.left(), chunk, scale, width, offx, offy); - drawNode(g, (SequenceNode) node.right(), chunk, scale, width, offx, + drawNode(g, (BinaryNode) node.right(), chunk, scale, width, offx, offy); double height = node.height; @@ -263,9 +264,9 @@ public class TreeCanvas extends Panel } int ystart = (int) (node.left() == null ? 0 - : (((SequenceNode) node.left()).ycount * chunk)) + offy; + : (((BinaryNode) node.left()).ycount * chunk)) + offy; int yend = (int) (node.right() == null ? 0 - : (((SequenceNode) node.right()).ycount * chunk)) + offy; + : (((BinaryNode) node.right()).ycount * chunk)) + offy; Rectangle pos = new Rectangle(xend - 2, ypos - 2, 5, 5); nodeHash.put(node, pos); @@ -338,20 +339,20 @@ public class TreeCanvas extends Panel int width = getSize().width; int height = getSize().height; - SequenceNode top = tree.getTopNode(); + BinaryNode top = tree.getTopNode(); double wscale = (float) (width * .8 - offx * 2) / tree.getMaxHeight(); if (top.count == 0) { - top.count = ((SequenceNode) top.left()).count - + ((SequenceNode) top.right()).count; + top.count = ((BinaryNode) top.left()).count + + ((BinaryNode) top.right()).count; } float chunk = (float) (height - offy) / top.count; pickNode(pickBox, top, chunk, wscale, width, offx, offy); } - public void pickNode(Rectangle pickBox, SequenceNode node, float chunk, + public void pickNode(Rectangle pickBox, BinaryNode node, float chunk, double scale, int width, int offx, int offy) { if (node == null) @@ -384,14 +385,14 @@ public class TreeCanvas extends Panel } else { - pickNode(pickBox, (SequenceNode) node.left(), chunk, scale, width, + pickNode(pickBox, (BinaryNode) node.left(), chunk, scale, width, offx, offy); - pickNode(pickBox, (SequenceNode) node.right(), chunk, scale, width, + pickNode(pickBox, (BinaryNode) node.right(), chunk, scale, width, offx, offy); } } - public void setColor(SequenceNode node, Color c) + public void setColor(BinaryNode node, Color c) { if (node == null) { @@ -410,8 +411,8 @@ public class TreeCanvas extends Panel else { node.color = c; - setColor((SequenceNode) node.left(), c); - setColor((SequenceNode) node.right(), c); + setColor((BinaryNode) node.left(), c); + setColor((BinaryNode) node.right(), c); } } @@ -469,12 +470,12 @@ public class TreeCanvas extends Panel double wscale = (width - labelLength - offx * 2) / tree.getMaxHeight(); - SequenceNode top = tree.getTopNode(); + BinaryNode top = tree.getTopNode(); if (top.count == 0) { - top.count = ((SequenceNode) top.left()).count - + ((SequenceNode) top.right()).count; + top.count = ((BinaryNode) top.left()).count + + ((BinaryNode) top.right()).count; } float chunk = (float) (height - offy) / top.count; @@ -527,7 +528,7 @@ public class TreeCanvas extends Panel } else { - Vector leaves = tree.findLeaves(highlightNode); + Vector leaves = tree.findLeaves(highlightNode); for (int i = 0; i < leaves.size(); i++) { @@ -554,9 +555,9 @@ public class TreeCanvas extends Panel Object ob = findElement(evt.getX(), evt.getY()); - if (ob instanceof SequenceNode) + if (ob instanceof BinaryNode) { - highlightNode = (SequenceNode) ob; + highlightNode = (BinaryNode) ob; repaint(); } else @@ -596,7 +597,7 @@ public class TreeCanvas extends Panel threshold = (float) (x - offx) / (float) (getSize().width - labelLength - 2 * offx); - List groups = tree.groupNodes(threshold); + List groups = tree.groupNodes(threshold); setColor(tree.getTopNode(), Color.black); av.setSelectionGroup(null); @@ -620,7 +621,7 @@ public class TreeCanvas extends Panel } - void colourGroups(List groups) + void colourGroups(List groups) { for (int i = 0; i < groups.size(); i++) { @@ -629,7 +630,7 @@ public class TreeCanvas extends Panel (int) (Math.random() * 255), (int) (Math.random() * 255)); setColor(groups.get(i), col.brighter()); - Vector l = tree.findLeaves(groups.get(i)); + Vector l = tree.findLeaves(groups.get(i)); Vector sequences = new Vector<>(); for (int j = 0; j < l.size(); j++) diff --git a/src/jalview/datamodel/Alignment.java b/src/jalview/datamodel/Alignment.java index 7f97f33..321eee3 100755 --- a/src/jalview/datamodel/Alignment.java +++ b/src/jalview/datamodel/Alignment.java @@ -2047,9 +2047,23 @@ public class Alignment implements AlignmentI, AutoCloseable } @Override - public ContactMatrixI getContactMatrixFor(AlignmentAnnotation ann) + public ContactMatrixI getContactMatrixFor(AlignmentAnnotation _aa) { - return cmholder.getContactMatrixFor(ann); + ContactMatrixI cm = cmholder.getContactMatrixFor(_aa); + if (cm==null && _aa.groupRef!=null) + { + cm = _aa.groupRef.getContactMatrixFor(_aa); + } + if (cm==null && _aa.sequenceRef!=null) + { + cm = _aa.sequenceRef.getContactMatrixFor(_aa); + if (cm==null) + { + // TODO fix up this logic and unify with getContactListFor + cm = _aa.sequenceRef.getDatasetSequence().getContactMatrixFor(_aa); + } + } + return cm; } @Override diff --git a/src/jalview/datamodel/BinaryNode.java b/src/jalview/datamodel/BinaryNode.java index c0ff0e4..5b55594 100755 --- a/src/jalview/datamodel/BinaryNode.java +++ b/src/jalview/datamodel/BinaryNode.java @@ -20,6 +20,8 @@ */ package jalview.datamodel; +import java.awt.Color; + /** * DOCUMENT ME! * @@ -38,9 +40,27 @@ public class BinaryNode BinaryNode parent; - /** DOCUMENT ME!! */ + /** Bootstrap value */ public int bootstrap; + /** DOCUMENT ME!! */ + public double dist; + + /** DOCUMENT ME!! */ + public int count; + + /** DOCUMENT ME!! */ + public double height; + + /** DOCUMENT ME!! */ + public float ycount; + + /** DOCUMENT ME!! */ + public Color color = Color.black; + + /** DOCUMENT ME!! */ + public boolean dummy = false; + /** * Creates a new BinaryNode object. */ @@ -48,6 +68,7 @@ public class BinaryNode { left = right = parent = null; bootstrap = 0; + dist = 0; } /** @@ -60,13 +81,28 @@ public class BinaryNode * @param name * DOCUMENT ME! */ - public BinaryNode(Object element, BinaryNode parent, String name) + public BinaryNode(Object element, BinaryNode parent, String name, + double dist) { + this(); this.element = element; this.parent = parent; this.name = name; + this.dist = dist; + } + + public BinaryNode(Object element, BinaryNode parent, String name, + double dist, int bootstrap) + { + this(element, parent, name, dist); + this.bootstrap = bootstrap; + } - left = right = null; + public BinaryNode(Object val, BinaryNode parent, String name, double dist, + int bootstrap, boolean dummy) + { + this(val, parent, name, dist, bootstrap); + this.dummy = dummy; } /** @@ -300,4 +336,45 @@ public class BinaryNode { return bootstrap; } + + /** + * @param dummy + * true if node is created for the representation of polytomous trees + */ + public boolean isDummy() + { + return dummy; + } + + /** + * DOCUMENT ME! + * + * @param newstate + * DOCUMENT ME! + * + * @return DOCUMENT ME! + */ + public boolean setDummy(boolean newstate) + { + boolean oldstate = dummy; + dummy = newstate; + + return oldstate; + } + + /** + * ascends the tree but doesn't stop until a non-dummy node is discovered. + * + */ + public BinaryNode AscendTree() + { + BinaryNode c = this; + + do + { + c = c.parent(); + } while ((c != null) && c.dummy); + + return c; + } } diff --git a/src/jalview/datamodel/ContactListImpl.java b/src/jalview/datamodel/ContactListImpl.java index beb557f..8e806e4 100644 --- a/src/jalview/datamodel/ContactListImpl.java +++ b/src/jalview/datamodel/ContactListImpl.java @@ -41,6 +41,7 @@ public class ContactListImpl implements ContactListI @Override public ContactRange getRangeFor(int from_column, int to_column) { + // TODO: consider caching ContactRange for a particular call ? if (clist instanceof ContactListI) { // clist may implement getRangeFor in a more efficient way, so use theirs diff --git a/src/jalview/datamodel/ContactMatrixI.java b/src/jalview/datamodel/ContactMatrixI.java index 2367414..4e2076d 100644 --- a/src/jalview/datamodel/ContactMatrixI.java +++ b/src/jalview/datamodel/ContactMatrixI.java @@ -1,5 +1,7 @@ package jalview.datamodel; +import java.util.BitSet; + public interface ContactMatrixI { @@ -25,5 +27,14 @@ public interface ContactMatrixI int getWidth(); int getHeight(); + + default boolean hasGroups() { + return false; + } + default BitSet getGroupsFor(int column) { + BitSet colbitset = new BitSet(); + colbitset.set(column); + return colbitset; + } } diff --git a/src/jalview/datamodel/SequenceNode.java b/src/jalview/datamodel/SequenceNode.java index 0a694c2..010d4aa 100755 --- a/src/jalview/datamodel/SequenceNode.java +++ b/src/jalview/datamodel/SequenceNode.java @@ -20,8 +20,6 @@ */ package jalview.datamodel; -import java.awt.Color; - /** * DOCUMENT ME! * @@ -30,24 +28,6 @@ import java.awt.Color; */ public class SequenceNode extends BinaryNode { - /** DOCUMENT ME!! */ - public double dist; - - /** DOCUMENT ME!! */ - public int count; - - /** DOCUMENT ME!! */ - public double height; - - /** DOCUMENT ME!! */ - public float ycount; - - /** DOCUMENT ME!! */ - public Color color = Color.black; - - /** DOCUMENT ME!! */ - public boolean dummy = false; - private boolean placeholder = false; /** @@ -58,57 +38,22 @@ public class SequenceNode extends BinaryNode super(); } - /** - * Creates a new SequenceNode object. - * - * @param val - * DOCUMENT ME! - * @param parent - * DOCUMENT ME! - * @param dist - * DOCUMENT ME! - * @param name - * DOCUMENT ME! - */ - public SequenceNode(Object val, SequenceNode parent, double dist, - String name) + public SequenceNode(SequenceI val, BinaryNode parent, String name, + double dist, int bootstrap, boolean dummy) { - super(val, parent, name); - this.dist = dist; + super(val, parent, name, dist, bootstrap, dummy); } - /** - * Creates a new SequenceNode object. - * - * @param val - * DOCUMENT ME! - * @param parent - * DOCUMENT ME! - * @param name - * DOCUMENT ME! - * @param dist - * DOCUMENT ME! - * @param bootstrap - * DOCUMENT ME! - * @param dummy - * DOCUMENT ME! - */ - public SequenceNode(Object val, SequenceNode parent, String name, - double dist, int bootstrap, boolean dummy) + public SequenceNode(SequenceI element, BinaryNode parent, String name, + double dist, int bootstrap) { - super(val, parent, name); - this.dist = dist; - this.bootstrap = bootstrap; - this.dummy = dummy; + super(element, parent, name, dist, bootstrap); } - /** - * @param dummy - * true if node is created for the representation of polytomous trees - */ - public boolean isDummy() + public SequenceNode(SequenceI element, BinaryNode parent, String name, + double dist) { - return dummy; + super(element, parent, name, dist); } /* @@ -123,22 +68,6 @@ public class SequenceNode extends BinaryNode /** * DOCUMENT ME! * - * @param newstate - * DOCUMENT ME! - * - * @return DOCUMENT ME! - */ - public boolean setDummy(boolean newstate) - { - boolean oldstate = dummy; - dummy = newstate; - - return oldstate; - } - - /** - * DOCUMENT ME! - * * @param Placeholder * DOCUMENT ME! */ @@ -148,23 +77,6 @@ public class SequenceNode extends BinaryNode } /** - * ascends the tree but doesn't stop until a non-dummy node is discovered. - * This will probably break if the tree is a mixture of BinaryNodes and - * SequenceNodes. - */ - public SequenceNode AscendTree() - { - SequenceNode c = this; - - do - { - c = (SequenceNode) c.parent(); - } while ((c != null) && c.dummy); - - return c; - } - - /** * test if this node has a name that might be a label rather than a bootstrap * value * diff --git a/src/jalview/gui/AlignFrame.java b/src/jalview/gui/AlignFrame.java index 7b310b1..82ec1d2 100644 --- a/src/jalview/gui/AlignFrame.java +++ b/src/jalview/gui/AlignFrame.java @@ -155,6 +155,7 @@ import jalview.viewmodel.AlignmentViewport; import jalview.viewmodel.ViewportRanges; import jalview.ws.DBRefFetcher; import jalview.ws.DBRefFetcher.FetchFinishedListenerI; +import jalview.ws.datamodel.alphafold.PAEContactMatrix; import jalview.ws.jws1.Discoverer; import jalview.ws.jws2.Jws2Discoverer; import jalview.ws.jws2.jabaws2.Jws2Instance; @@ -4131,6 +4132,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, return showNewickTree(nf, treeTitle, null, w, h, x, y); } + /** * Add a treeviewer for the tree extracted from a Newick file object to the * current alignment view @@ -4181,6 +4183,40 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, return tp; } + + public void showContactMapTree(AlignmentAnnotation aa, + PAEContactMatrix cm) + { + int x = 4, y = 5; + int w = 400, h = 500; + try + { + NewickFile fin = new NewickFile( + new FileParse(cm.getNewickString(), DataSourceType.PASTE)); + fin.parse(); + if (fin.getTree() == null) + { + return; + } + String title = "PAE Matrix Tree for " + + cm.getReferenceSeq().getDisplayId(false); + TreePanel tp = new TreePanel(alignPanel, fin, aa, title); + + tp.setSize(w, h); + + if (x > 0 && y > 0) + { + tp.setLocation(x, y); + } + + Desktop.addInternalFrame(tp, title, w, h); + } catch (Throwable xx) + { + Console.error("Unexpected exception showing tree for contact matrix", + xx); + } + + } private boolean buildingMenu = false; /** @@ -5930,6 +5966,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, { return lastFeatureSettingsBounds; } + } class PrintThread extends Thread diff --git a/src/jalview/gui/AnnotationLabels.java b/src/jalview/gui/AnnotationLabels.java index 804ab7b..c01a6f3 100755 --- a/src/jalview/gui/AnnotationLabels.java +++ b/src/jalview/gui/AnnotationLabels.java @@ -36,6 +36,7 @@ import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.geom.AffineTransform; +import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; @@ -50,18 +51,23 @@ import javax.swing.ToolTipManager; import jalview.analysis.AlignSeq; import jalview.analysis.AlignmentUtils; +import jalview.bin.Console; import jalview.datamodel.Alignment; import jalview.datamodel.AlignmentAnnotation; import jalview.datamodel.Annotation; +import jalview.datamodel.ContactMatrixI; import jalview.datamodel.HiddenColumns; import jalview.datamodel.Sequence; import jalview.datamodel.SequenceGroup; import jalview.datamodel.SequenceI; +import jalview.io.DataSourceType; import jalview.io.FileFormat; import jalview.io.FormatAdapter; +import jalview.io.NewickFile; import jalview.util.Comparison; import jalview.util.MessageManager; import jalview.util.Platform; +import jalview.ws.datamodel.alphafold.PAEContactMatrix; /** * The panel that holds the labels for alignment annotations, providing @@ -416,6 +422,31 @@ public class AnnotationLabels extends JPanel consclipbrd.addActionListener(this); pop.add(consclipbrd); } + if (aa[selectedRow].graph == AlignmentAnnotation.CONTACT_MAP + && PAEContactMatrix.PAEMATRIX + .equals(aa[selectedRow].getCalcId())) + { + final PAEContactMatrix cm = (PAEContactMatrix) av + .getContactMatrix(aa[selectedRow]); + if (cm.getNewickString()!=null && cm.getNewickString().length()>0) + { + item = new JMenuItem("Show Matrix"); + item.addActionListener(new ActionListener() + { + + @Override + public void actionPerformed(ActionEvent e) + { + + ap.alignFrame.showContactMapTree(aa[selectedRow],cm); + + } + }); + pop.addSeparator(); + pop.add(item); + } + + } } pop.show(this, evt.getX(), evt.getY()); } diff --git a/src/jalview/gui/AnnotationPanel.java b/src/jalview/gui/AnnotationPanel.java index b68ff01..b112383 100755 --- a/src/jalview/gui/AnnotationPanel.java +++ b/src/jalview/gui/AnnotationPanel.java @@ -41,6 +41,7 @@ import java.awt.event.MouseWheelListener; import java.awt.image.BufferedImage; import java.beans.PropertyChangeEvent; import java.util.ArrayList; +import java.util.BitSet; import java.util.Collections; import java.util.List; @@ -56,6 +57,7 @@ import jalview.datamodel.AlignmentI; import jalview.datamodel.Annotation; import jalview.datamodel.ColumnSelection; import jalview.datamodel.ContactListI; +import jalview.datamodel.ContactMatrixI; import jalview.datamodel.ContactRange; import jalview.datamodel.GraphLine; import jalview.datamodel.HiddenColumns; @@ -602,6 +604,7 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI, else { GraphLine thr = aa[graphStretch].getThreshold(); + int currentX = getColumnForXPos(evt.getX()); ContactListI forCurrentX = av.getContactList(aa[graphStretch], currentX); @@ -611,54 +614,104 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI, aa[graphStretch].graphHeight); ContactGeometry.contactInterval cXci = cXcgeom.mapFor(yOffset, yOffset); + /** + * start and end range corresponding to the row range under the + * mouse at column currentX + */ int fr, to; fr = Math.min(cXci.cStart, cXci.cEnd); to = Math.max(cXci.cStart, cXci.cEnd); - // select corresponding range in segment under mouse + + if (evt.isControlDown()) { - for (int c = fr; c <= to; c++) + ContactMatrixI matrix = av.getContactMatrix(aa[graphStretch]); + + if (matrix != null) { - av.getColumnSelection().addElement(c); + // simplest approach is to select all group containing column + if (matrix.hasGroups()) + { + SequenceI rseq = aa[graphStretch].sequenceRef; + BitSet grp = matrix.getGroupsFor(currentX); + for (int c=fr;c<=to; c++) + { + BitSet additionalGrp = matrix.getGroupsFor(c); + grp.or(additionalGrp); + } + HiddenColumns hc = av.getAlignment().getHiddenColumns(); + for (int p = grp.nextSetBit(0); p >= 0; p = grp + .nextSetBit(p + 1)) + { + int offp = (rseq != null) + ? rseq.findIndex(rseq.getStart() - 1 + p) + : p; + + if (!av.hasHiddenColumns() || hc.isVisible(offp)) + { + av.getColumnSelection().addElement(offp); + } + } + } + // possible alternative for interactive selection - threshold + // gives 'ceiling' for forming a cluster + // when a row+column is selected, farthest common ancestor less + // than thr is used to compute cluster + } - av.getColumnSelection().addElement(currentX); } - // PAE SPECIFIC - // and also select everything lower than the max range adjacent - // (kind of works) - if (PAEContactMatrix.PAEMATRIX.equals(aa[graphStretch].getCalcId())) + else { - int c = fr - 1; - ContactRange cr = forCurrentX.getRangeFor(fr, to); - double cval; - // TODO: could use GraphLine instead of arbitrary picking - // TODO: could report mean/median/variance for partitions (contiguous selected vs unselected regions and inter-contig regions) - // controls feathering - what other elements in row/column should we select - double thresh=cr.getMean()+(cr.getMax()-cr.getMean())*.15; - while (c > 0) + // select corresponding range in segment under mouse { - cval = forCurrentX.getContactAt(c); - if (// cr.getMin() <= cval && - cval <= thresh) - { - av.getColumnSelection().addElement(c--); - } - else + for (int c = fr; c <= to; c++) { - break; + av.getColumnSelection().addElement(c); } + av.getColumnSelection().addElement(currentX); } - c = to; - while (c < forCurrentX.getContactHeight()) + // PAE SPECIFIC + // and also select everything lower than the max range adjacent + // (kind of works) + if (PAEContactMatrix.PAEMATRIX + .equals(aa[graphStretch].getCalcId())) { - cval = forCurrentX.getContactAt(c); - if (// cr.getMin() <= cval && - cval <= thresh) + int c = fr - 1; + ContactRange cr = forCurrentX.getRangeFor(fr, to); + double cval; + // TODO: could use GraphLine instead of arbitrary picking + // TODO: could report mean/median/variance for partitions + // (contiguous selected vs unselected regions and inter-contig + // regions) + // controls feathering - what other elements in row/column + // should we select + double thresh = cr.getMean() + + (cr.getMax() - cr.getMean()) * .15; + while (c > 0) { - av.getColumnSelection().addElement(c++); + cval = forCurrentX.getContactAt(c); + if (// cr.getMin() <= cval && + cval <= thresh) + { + av.getColumnSelection().addElement(c--); + } + else + { + break; + } } - else + c = to; + while (c < forCurrentX.getContactHeight()) { - break; + cval = forCurrentX.getContactAt(c); + if (// cr.getMin() <= cval && + cval <= thresh) + { + av.getColumnSelection().addElement(c++); + } + else + { + break; + } } } } diff --git a/src/jalview/gui/TreeCanvas.java b/src/jalview/gui/TreeCanvas.java index 6f143db..354feac 100755 --- a/src/jalview/gui/TreeCanvas.java +++ b/src/jalview/gui/TreeCanvas.java @@ -36,6 +36,7 @@ import java.awt.print.PageFormat; import java.awt.print.Printable; import java.awt.print.PrinterException; import java.awt.print.PrinterJob; +import java.util.BitSet; import java.util.Hashtable; import java.util.List; import java.util.Map; @@ -50,6 +51,9 @@ import javax.swing.ToolTipManager; import jalview.analysis.Conservation; import jalview.analysis.TreeModel; import jalview.api.AlignViewportI; +import jalview.datamodel.BinaryNode; +import jalview.datamodel.ColumnSelection; +import jalview.datamodel.HiddenColumns; import jalview.datamodel.Sequence; import jalview.datamodel.SequenceGroup; import jalview.datamodel.SequenceI; @@ -106,9 +110,9 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable, Map nameHash = new Hashtable<>(); - Map nodeHash = new Hashtable<>(); + Map nodeHash = new Hashtable<>(); - SequenceNode highlightNode; + BinaryNode highlightNode; boolean applyToAllViews = false; @@ -173,15 +177,15 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable, tree.findHeight(tree.getTopNode()); // Now have to calculate longest name based on the leaves - Vector leaves = tree.findLeaves(tree.getTopNode()); + Vector leaves = tree.findLeaves(tree.getTopNode()); boolean has_placeholders = false; longestName = ""; for (int i = 0; i < leaves.size(); i++) { - SequenceNode lf = leaves.elementAt(i); + BinaryNode lf = leaves.elementAt(i); - if (lf.isPlaceholder()) + if (lf instanceof SequenceNode && ((SequenceNode)lf).isPlaceholder()) { has_placeholders = true; } @@ -215,7 +219,7 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable, * @param offy * DOCUMENT ME! */ - public void drawNode(Graphics g, SequenceNode node, float chunk, + public void drawNode(Graphics g, BinaryNode node, float chunk, double wscale, int width, int offx, int offy) { if (node == null) @@ -277,7 +281,7 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable, g.drawString(nodeLabel, xstart + 2, ypos - 2); } - String name = (markPlaceholders && node.isPlaceholder()) + String name = (markPlaceholders && ((node instanceof SequenceNode && ((SequenceNode)node).isPlaceholder()))) ? (PLACEHOLDER + node.getName()) : node.getName(); @@ -290,10 +294,22 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable, nameHash.put(node.element(), rect); // Colour selected leaves differently - SequenceGroup selected = av.getSelectionGroup(); + boolean isSelected = false; + if (tp.getColumnWise()) + { + isSelected = isColumnForNodeSelected(node); + } + else + { + SequenceGroup selected = av.getSelectionGroup(); - if ((selected != null) - && selected.getSequences(null).contains(node.element())) + if ((selected != null) + && selected.getSequences(null).contains(node.element())) + { + isSelected = true; + } + } + if (isSelected) { g.setColor(Color.gray); @@ -306,9 +322,9 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable, } else { - drawNode(g, (SequenceNode) node.left(), chunk, wscale, width, offx, + drawNode(g, (BinaryNode) node.left(), chunk, wscale, width, offx, offy); - drawNode(g, (SequenceNode) node.right(), chunk, wscale, width, offx, + drawNode(g, (BinaryNode) node.right(), chunk, wscale, width, offx, offy); double height = node.height; @@ -332,9 +348,9 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable, } int ystart = (node.left() == null ? 0 - : (int) (((SequenceNode) node.left()).ycount * chunk)) + offy; + : (int) (((BinaryNode) node.left()).ycount * chunk)) + offy; int yend = (node.right() == null ? 0 - : (int) (((SequenceNode) node.right()).ycount * chunk)) + : (int) (((BinaryNode) node.right()).ycount * chunk)) + offy; Rectangle pos = new Rectangle(xend - 2, ypos - 2, 5, 5); @@ -390,7 +406,7 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable, } } - for (Entry entry : nodeHash.entrySet()) + for (Entry entry : nodeHash.entrySet()) { Rectangle rect = entry.getValue(); @@ -415,14 +431,14 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable, int width = getWidth(); int height = getHeight(); - SequenceNode top = tree.getTopNode(); + BinaryNode top = tree.getTopNode(); double wscale = ((width * .8) - (offx * 2)) / tree.getMaxHeight(); if (top.count == 0) { - top.count = ((SequenceNode) top.left()).count - + ((SequenceNode) top.right()).count; + top.count = ((BinaryNode) top.left()).count + + ((BinaryNode) top.right()).count; } float chunk = (float) (height - (offy)) / top.count; @@ -448,7 +464,7 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable, * @param offy * DOCUMENT ME! */ - public void pickNode(Rectangle pickBox, SequenceNode node, float chunk, + public void pickNode(Rectangle pickBox, BinaryNode node, float chunk, double wscale, int width, int offx, int offy) { if (node == null) @@ -481,9 +497,9 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable, } else { - pickNode(pickBox, (SequenceNode) node.left(), chunk, wscale, width, + pickNode(pickBox, (BinaryNode) node.left(), chunk, wscale, width, offx, offy); - pickNode(pickBox, (SequenceNode) node.right(), chunk, wscale, width, + pickNode(pickBox, (BinaryNode) node.right(), chunk, wscale, width, offx, offy); } } @@ -496,7 +512,7 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable, * @param c * DOCUMENT ME! */ - public void setColor(SequenceNode node, Color c) + public void setColor(BinaryNode node, Color c) { if (node == null) { @@ -516,8 +532,8 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable, } } } - setColor((SequenceNode) node.left(), c); - setColor((SequenceNode) node.right(), c); + setColor((BinaryNode) node.left(), c); + setColor((BinaryNode) node.right(), c); } /** @@ -703,6 +719,7 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable, if (longestName == null || tree == null) { g2.drawString("Calculating tree.", 20, 20); + return; } offy = font.getSize() + 10; @@ -713,12 +730,12 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable, double wscale = (width - labelLength - (offx * 2)) / tree.getMaxHeight(); - SequenceNode top = tree.getTopNode(); + BinaryNode top = tree.getTopNode(); if (top.count == 0) { - top.count = ((SequenceNode) top.left()).count - + ((SequenceNode) top.right()).count; + top.count = ((BinaryNode) top.left()).count + + ((BinaryNode) top.right()).count; } float chunk = (float) (height - (offy)) / top.count; @@ -804,13 +821,16 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable, } else { - Vector leaves = tree.findLeaves(highlightNode); - + Vector leaves = tree.findLeaves(highlightNode); + if (tp.getColumnWise()) { + markColumnsFor(getAssociatedPanels(), leaves, Color.red); + } else { for (int i = 0; i < leaves.size(); i++) { SequenceI seq = (SequenceI) leaves.elementAt(i).element(); treeSelectionChanged(seq); } + } av.sendSelection(); } @@ -847,9 +867,9 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable, Object ob = findElement(evt.getX(), evt.getY()); - if (ob instanceof SequenceNode) + if (ob instanceof BinaryNode) { - highlightNode = (SequenceNode) ob; + highlightNode = (BinaryNode) ob; this.setToolTipText( "" + MessageManager.getString("label.highlightnode")); repaint(); @@ -922,7 +942,7 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable, av.sendSelection(); return; } - else if (!(ob instanceof SequenceNode)) + else if (!(ob instanceof BinaryNode)) { // Find threshold if (tree.getMaxHeight() != 0) @@ -930,7 +950,7 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable, threshold = (float) (x - offx) / (float) (getWidth() - labelLength - (2 * offx)); - List groups = tree.groupNodes(threshold); + List groups = tree.groupNodes(threshold); setColor(tree.getTopNode(), Color.black); AlignmentPanel[] aps = getAssociatedPanels(); @@ -970,7 +990,7 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable, } - void colourGroups(List groups) + void colourGroups(List groups) { AlignmentPanel[] aps = getAssociatedPanels(); for (int i = 0; i < groups.size(); i++) @@ -979,70 +999,134 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable, (int) (Math.random() * 255), (int) (Math.random() * 255)); setColor(groups.get(i), col.brighter()); - Vector l = tree.findLeaves(groups.get(i)); - - Vector sequences = new Vector<>(); + Vector l = tree.findLeaves(groups.get(i)); + if (!tp.getColumnWise()) { + createSeqGroupFor(aps, l, col); + } else { + markColumnsFor(aps,l,col); + } + } - for (int j = 0; j < l.size(); j++) + // notify the panel(s) to redo any group specific stuff + // also updates structure views if necessary + for (int a = 0; a < aps.length; a++) + { + aps[a].updateAnnotation(); + final AlignViewportI codingComplement = aps[a].av + .getCodingComplement(); + if (codingComplement != null) { - SequenceI s1 = (SequenceI) l.elementAt(j).element(); - - if (!sequences.contains(s1)) - { - sequences.addElement(s1); - } + ((AlignViewport) codingComplement).getAlignPanel() + .updateAnnotation(); } + } + } - ColourSchemeI cs = null; - SequenceGroup _sg = new SequenceGroup(sequences, null, cs, true, true, - false, 0, av.getAlignment().getWidth() - 1); + private boolean isColumnForNodeSelected(BinaryNode bn) + { + SequenceI rseq = tp.assocAnnotation.sequenceRef; + int colm = -1; + try + { + colm = Integer.parseInt( + bn.getName().substring(bn.getName().indexOf("c") + 1)); + } catch (Exception e) + { + return false; + } + ColumnSelection cs = av.getColumnSelection(); + HiddenColumns hc = av.getAlignment().getHiddenColumns(); + int offp = (rseq != null) ? rseq.findIndex(rseq.getStart() - 1 + colm) + : colm; - _sg.setName("JTreeGroup:" + _sg.hashCode()); - _sg.setIdColour(col); + if (!av.hasHiddenColumns() || hc.isVisible(offp)) + { + return cs.contains(offp); + } + return false; + } - for (int a = 0; a < aps.length; a++) + private void markColumnsFor(AlignmentPanel[] aps, Vector l, + Color col) + { + SequenceI rseq = tp.assocAnnotation.sequenceRef; + for (BinaryNode bn:l) + { + int colm=-1; + try { + colm = Integer.parseInt(bn.getName().substring(bn.getName().indexOf("c")+1)); + } catch (Exception e) { - SequenceGroup sg = new SequenceGroup(_sg); - AlignViewport viewport = aps[a].av; - - // Propagate group colours in each view - if (viewport.getGlobalColourScheme() != null) - { - cs = viewport.getGlobalColourScheme().getInstance(viewport, sg); - sg.setColourScheme(cs); - sg.getGroupColourScheme().setThreshold( - viewport.getResidueShading().getThreshold(), - viewport.isIgnoreGapsConsensus()); - - if (viewport.getResidueShading().conservationApplied()) + continue; + } + ColumnSelection cs = av.getColumnSelection(); + HiddenColumns hc = av.getAlignment().getHiddenColumns(); + { + int offp = (rseq!=null) ? rseq.findIndex(rseq.getStart()-1+colm) : colm; + + if (!av.hasHiddenColumns() || hc.isVisible(offp)) + { + if (cs.contains(offp)) { - Conservation c = new Conservation("Group", - sg.getSequences(null), sg.getStartRes(), - sg.getEndRes()); - c.calculate(); - c.verdict(false, viewport.getConsPercGaps()); - sg.cs.setConservation(c); + cs.removeElement(offp); + } else { + cs.addElement(offp); } } - // indicate that associated structure views will need an update - viewport.setUpdateStructures(true); - // propagate structure view update and sequence group to complement view - viewport.addSequenceGroup(sg); + } + } + } + + public void createSeqGroupFor(AlignmentPanel[] aps, Vector l, + Color col) + { + + Vector sequences = new Vector<>(); + + for (int j = 0; j < l.size(); j++) + { + SequenceI s1 = (SequenceI) l.elementAt(j).element(); + + if (!sequences.contains(s1)) + { + sequences.addElement(s1); } } - // notify the panel(s) to redo any group specific stuff - // also updates structure views if necessary + ColourSchemeI cs = null; + SequenceGroup _sg = new SequenceGroup(sequences, null, cs, true, true, + false, 0, av.getAlignment().getWidth() - 1); + + _sg.setName("JTreeGroup:" + _sg.hashCode()); + _sg.setIdColour(col); + for (int a = 0; a < aps.length; a++) { - aps[a].updateAnnotation(); - final AlignViewportI codingComplement = aps[a].av - .getCodingComplement(); - if (codingComplement != null) + SequenceGroup sg = new SequenceGroup(_sg); + AlignViewport viewport = aps[a].av; + + // Propagate group colours in each view + if (viewport.getGlobalColourScheme() != null) { - ((AlignViewport) codingComplement).getAlignPanel() - .updateAnnotation(); + cs = viewport.getGlobalColourScheme().getInstance(viewport, sg); + sg.setColourScheme(cs); + sg.getGroupColourScheme().setThreshold( + viewport.getResidueShading().getThreshold(), + viewport.isIgnoreGapsConsensus()); + + if (viewport.getResidueShading().conservationApplied()) + { + Conservation c = new Conservation("Group", sg.getSequences(null), + sg.getStartRes(), sg.getEndRes()); + c.calculate(); + c.verdict(false, viewport.getConsPercGaps()); + sg.cs.setConservation(c); + } } + // indicate that associated structure views will need an update + viewport.setUpdateStructures(true); + // propagate structure view update and sequence group to complement view + viewport.addSequenceGroup(sg); } } diff --git a/src/jalview/gui/TreePanel.java b/src/jalview/gui/TreePanel.java index 76e1884..6a07a4e 100755 --- a/src/jalview/gui/TreePanel.java +++ b/src/jalview/gui/TreePanel.java @@ -53,6 +53,7 @@ import jalview.bin.Console; import jalview.commands.CommandI; import jalview.commands.OrderCommand; import jalview.datamodel.Alignment; +import jalview.datamodel.AlignmentAnnotation; import jalview.datamodel.AlignmentI; import jalview.datamodel.AlignmentView; import jalview.datamodel.BinaryNode; @@ -123,6 +124,30 @@ public class TreePanel extends GTreePanel initTreePanel(alignPanel, null, null, newtree, inputData); } + /** + * columnwise tree associated with positions in aa + * + * @param alignPanel + * @param fin + * @param title + * @param aa + */ + public TreePanel(AlignmentPanel alignPanel, NewickFile fin, + AlignmentAnnotation aa, String title) + { + this(alignPanel, fin, title, null); + columnWise=true; + assocAnnotation = aa; + + + } + boolean columnWise=false; + AlignmentAnnotation assocAnnotation=null; + public boolean getColumnWise() + { + return columnWise; + } + public AlignmentI getAlignment() { return getTreeCanvas().getViewport().getAlignment(); @@ -721,7 +746,7 @@ public class TreePanel extends GTreePanel && !((SequenceNode) node).isDummy()) { String newname = null; - SequenceI sq = (SequenceI) ((SequenceNode) node).element(); + SequenceI sq = (SequenceI) ((BinaryNode) node).element(); if (sq != null) { // search dbrefs, features and annotation @@ -763,7 +788,7 @@ public class TreePanel extends GTreePanel { // String oldname = ((SequenceNode) node).getName(); // TODO : save oldname in the undo object for this modification. - ((SequenceNode) node).setName(newname); + ((BinaryNode) node).setName(newname); } } } diff --git a/src/jalview/io/NewickFile.java b/src/jalview/io/NewickFile.java index 027390a..269ffb3 100755 --- a/src/jalview/io/NewickFile.java +++ b/src/jalview/io/NewickFile.java @@ -28,6 +28,7 @@ package jalview.io; import java.util.Locale; +import jalview.datamodel.BinaryNode; import jalview.datamodel.SequenceNode; import jalview.util.MessageManager; @@ -78,7 +79,7 @@ import com.stevesoft.pat.Regex; */ public class NewickFile extends FileParse { - SequenceNode root; + BinaryNode root; private boolean HasBootstrap = false; @@ -145,7 +146,7 @@ public class NewickFile extends FileParse * @param newtree * DOCUMENT ME! */ - public NewickFile(SequenceNode newtree) + public NewickFile(BinaryNode newtree) { root = newtree; } @@ -174,7 +175,7 @@ public class NewickFile extends FileParse * @param distances * DOCUMENT ME! */ - public NewickFile(SequenceNode newtree, boolean bootstrap, + public NewickFile(BinaryNode newtree, boolean bootstrap, boolean distances) { root = newtree; @@ -194,7 +195,7 @@ public class NewickFile extends FileParse * @param rootdistance * DOCUMENT ME! */ - public NewickFile(SequenceNode newtree, boolean bootstrap, + public NewickFile(BinaryNode newtree, boolean bootstrap, boolean distances, boolean rootdistance) { root = newtree; @@ -275,8 +276,8 @@ public class NewickFile extends FileParse root = new SequenceNode(); - SequenceNode realroot = null; - SequenceNode c = root; + BinaryNode realroot = null; + BinaryNode c = root; int d = -1; int cp = 0; @@ -323,21 +324,21 @@ public class NewickFile extends FileParse { c.setRight(new SequenceNode(null, c, null, DefDistance, DefBootstrap, false)); - c = (SequenceNode) c.right(); + c = (BinaryNode) c.right(); } else { if (c.left() != null) { // Dummy node for polytomy - keeps c.left free for new node - SequenceNode tmpn = new SequenceNode(null, c, null, 0, 0, true); + BinaryNode tmpn = new SequenceNode(null, c, null, 0, 0, true); tmpn.SetChildren(c.left(), c.right()); c.setRight(tmpn); } c.setLeft(new SequenceNode(null, c, null, DefDistance, DefBootstrap, false)); - c = (SequenceNode) c.left(); + c = (BinaryNode) c.left(); } if (realroot == null) @@ -518,7 +519,7 @@ public class NewickFile extends FileParse else { // Find a place to put the leaf - SequenceNode newnode = new SequenceNode(null, c, nodename, + BinaryNode newnode = new SequenceNode(null, c, nodename, (HasDistances) ? distance : DefDistance, (HasBootstrap) ? bootstrap : DefBootstrap, false); parseNHXNodeProps(c, commentString2); @@ -538,7 +539,7 @@ public class NewickFile extends FileParse { // Insert a dummy node for polytomy // dummy nodes have distances - SequenceNode newdummy = new SequenceNode(null, c, null, + BinaryNode newdummy = new SequenceNode(null, c, null, (HasDistances ? 0 : DefDistance), 0, true); newdummy.SetChildren(c.left(), newnode); c.setLeft(newdummy); @@ -577,7 +578,7 @@ public class NewickFile extends FileParse // Just advance focus, if we need to if ((c.left() != null) && (!c.left().isLeaf())) { - c = (SequenceNode) c.left(); + c = (BinaryNode) c.left(); } } } @@ -631,7 +632,7 @@ public class NewickFile extends FileParse * @param commentString * @param commentString2 */ - private void parseNHXNodeProps(SequenceNode c, String commentString) + private void parseNHXNodeProps(BinaryNode c, String commentString) { // TODO: store raw comment on the sequenceNode so it can be recovered when // tree is output @@ -678,7 +679,7 @@ public class NewickFile extends FileParse * * @return DOCUMENT ME! */ - public SequenceNode getTree() + public BinaryNode getTree() { return root; } @@ -832,7 +833,7 @@ public class NewickFile extends FileParse * * @return DOCUMENT ME! */ - private String printNodeField(SequenceNode c) + private String printNodeField(BinaryNode c) { return ((c.getName() == null) ? "" : nodeName(c.getName())) + ((HasBootstrap) ? ((c.getBootstrap() > -1) @@ -849,7 +850,7 @@ public class NewickFile extends FileParse * * @return DOCUMENT ME! */ - private String printRootField(SequenceNode root) + private String printRootField(BinaryNode root) { return (printRootInfo) ? (((root.getName() == null) ? "" : nodeName(root.getName())) @@ -864,7 +865,7 @@ public class NewickFile extends FileParse } // Non recursive call deals with root node properties - public void print(StringBuffer tf, SequenceNode root) + public void print(StringBuffer tf, BinaryNode root) { if (root != null) { @@ -876,20 +877,20 @@ public class NewickFile extends FileParse { if (root.isDummy()) { - _print(tf, (SequenceNode) root.right()); - _print(tf, (SequenceNode) root.left()); + _print(tf, root.right()); + _print(tf, root.left()); } else { tf.append("("); - _print(tf, (SequenceNode) root.right()); + _print(tf, root.right()); if (root.left() != null) { tf.append(","); } - _print(tf, (SequenceNode) root.left()); + _print(tf, root.left()); tf.append(")" + printRootField(root)); } } @@ -897,7 +898,7 @@ public class NewickFile extends FileParse } // Recursive call for non-root nodes - public void _print(StringBuffer tf, SequenceNode c) + public void _print(StringBuffer tf, BinaryNode c) { if (c != null) { @@ -909,24 +910,24 @@ public class NewickFile extends FileParse { if (c.isDummy()) { - _print(tf, (SequenceNode) c.left()); + _print(tf, c.left()); if (c.left() != null) { tf.append(","); } - _print(tf, (SequenceNode) c.right()); + _print(tf, c.right()); } else { tf.append("("); - _print(tf, (SequenceNode) c.right()); + _print(tf, c.right()); if (c.left() != null) { tf.append(","); } - _print(tf, (SequenceNode) c.left()); + _print(tf, c.left()); tf.append(")" + printNodeField(c)); } } diff --git a/src/jalview/io/vamsas/Tree.java b/src/jalview/io/vamsas/Tree.java index e8f6f6b..c0d1774 100644 --- a/src/jalview/io/vamsas/Tree.java +++ b/src/jalview/io/vamsas/Tree.java @@ -322,7 +322,7 @@ public class Tree extends DatastoreItem Console.warn("Not updating SequenceTreeMap for " + tree.getVorbaId()); return; } - Vector leaves = tp.getTree() + Vector leaves = tp.getTree() .findLeaves(tp.getTree().getTopNode()); Treenode[] tn = tree.getTreenode(); // todo: select nodes for this // particular tree @@ -381,7 +381,7 @@ public class Tree extends DatastoreItem */ public Treenode[] makeTreeNodes(TreeModel treeModel, Newick newick) { - Vector leaves = treeModel + Vector leaves = treeModel .findLeaves(treeModel.getTopNode()); Vector tnv = new Vector(); Enumeration l = leaves.elements(); @@ -394,7 +394,7 @@ public class Tree extends DatastoreItem { if (!((jalview.datamodel.SequenceNode) tnode).isPlaceholder()) { - Object assocseq = ((jalview.datamodel.SequenceNode) tnode) + Object assocseq = ((BinaryNode) tnode) .element(); if (assocseq instanceof SequenceI) { diff --git a/src/jalview/viewmodel/AlignmentViewport.java b/src/jalview/viewmodel/AlignmentViewport.java index a42a2a4..36b0851 100644 --- a/src/jalview/viewmodel/AlignmentViewport.java +++ b/src/jalview/viewmodel/AlignmentViewport.java @@ -51,6 +51,7 @@ import jalview.datamodel.AlignmentView; import jalview.datamodel.Annotation; import jalview.datamodel.ColumnSelection; import jalview.datamodel.ContactListI; +import jalview.datamodel.ContactMatrixI; import jalview.datamodel.HiddenColumns; import jalview.datamodel.HiddenSequences; import jalview.datamodel.ProfilesI; @@ -2948,6 +2949,14 @@ public abstract class AlignmentViewport return alignment.getContactListFor(_aa, column); } + @Override + public ContactMatrixI getContactMatrix( + AlignmentAnnotation alignmentAnnotation) + { + return alignment.getContactMatrixFor(alignmentAnnotation); + } + + /** * get the consensus sequence as displayed under the PID consensus annotation * row. diff --git a/src/jalview/ws/datamodel/alphafold/PAEContactMatrix.java b/src/jalview/ws/datamodel/alphafold/PAEContactMatrix.java index d1a2e9d..db3addc 100644 --- a/src/jalview/ws/datamodel/alphafold/PAEContactMatrix.java +++ b/src/jalview/ws/datamodel/alphafold/PAEContactMatrix.java @@ -1,9 +1,14 @@ package jalview.ws.datamodel.alphafold; +import java.util.ArrayList; +import java.util.BitSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import jalview.analysis.AverageDistanceEngine; +import jalview.bin.Console; +import jalview.datamodel.BinaryNode; import jalview.datamodel.ContactListI; import jalview.datamodel.ContactListImpl; import jalview.datamodel.ContactListProviderI; @@ -248,4 +253,60 @@ public class PAEContactMatrix implements ContactMatrixI { return length; } + List groups=null; + @Override + public boolean hasGroups() + { + return groups!=null; + } + String newick=null; + public String getNewickString() + { + return newick; + } + public void makeGroups(float thresh,boolean abs) + { + AverageDistanceEngine clusterer = new AverageDistanceEngine(null, null, this); + double height = clusterer.findHeight(clusterer.getTopNode()); + newick = new jalview.io.NewickFile(clusterer.getTopNode(),false,true).print(); + + Console.trace("Newick string\n"+newick); + + List nodegroups; + if (abs ? height > thresh : 0 < thresh && thresh < 1) + { + float cut = abs ? (float) (thresh / height) : thresh; + Console.debug("Threshold "+cut+" for height="+height); + + nodegroups = clusterer.groupNodes(cut); + } + else + { + nodegroups = new ArrayList(); + nodegroups.add(clusterer.getTopNode()); + } + + groups = new ArrayList<>(); + for (BinaryNode root:nodegroups) + { + BitSet gpset=new BitSet(); + for (BinaryNode leaf:clusterer.findLeaves(root)) + { + gpset.set((Integer)leaf.element()); + } + groups.add(gpset); + } + } + + @Override + public BitSet getGroupsFor(int column) + { + for (BitSet gp:groups) { + if (gp.get(column)) + { + return gp; + } + } + return ContactMatrixI.super.getGroupsFor(column); + } } diff --git a/src/jalview/ws/dbsources/EBIAlfaFold.java b/src/jalview/ws/dbsources/EBIAlfaFold.java index 8592bd3..d9cbbd9 100644 --- a/src/jalview/ws/dbsources/EBIAlfaFold.java +++ b/src/jalview/ws/dbsources/EBIAlfaFold.java @@ -442,6 +442,7 @@ public class EBIAlfaFold extends EbiFileRetrievedProxy } ContactMatrixI matrix = new PAEContactMatrix(sequence, (Map) paeDict); + ((PAEContactMatrix) matrix).makeGroups(5f, true); AlignmentAnnotation cmannot = sequence.addContactList(matrix); pdbAlignment.addAnnotation(cmannot); @@ -495,7 +496,7 @@ public class EBIAlfaFold extends EbiFileRetrievedProxy ContactMatrixI matrix = new PAEContactMatrix(sm.getSequence(), (Map) pae_obj); - + ((PAEContactMatrix) matrix).makeGroups(5f, true); AlignmentAnnotation cmannot = sm.getSequence().addContactList(matrix); sm.getSequence().addAlignmentAnnotation(cmannot); diff --git a/test/jalview/analysis/AverageDistanceEngineTest.java b/test/jalview/analysis/AverageDistanceEngineTest.java new file mode 100644 index 0000000..6d9ab50 --- /dev/null +++ b/test/jalview/analysis/AverageDistanceEngineTest.java @@ -0,0 +1,97 @@ +package jalview.analysis; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.junit.Assert; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import jalview.bin.Cache; +import jalview.bin.Console; +import jalview.datamodel.AlignmentAnnotation; +import jalview.datamodel.AlignmentI; +import jalview.datamodel.BinaryNode; +import jalview.datamodel.ContactMatrixI; +import jalview.datamodel.SequenceI; +import jalview.gui.AlignFrame; +import jalview.gui.JvOptionPane; +import jalview.io.DataSourceType; +import jalview.io.FastaFile; +import jalview.io.FileLoader; +import jalview.io.FormatAdapter; +import jalview.util.Platform; +import jalview.ws.datamodel.alphafold.PAEContactMatrix; + +public class AverageDistanceEngineTest +{ + + @BeforeClass(alwaysRun = true) + public void setUpJvOptionPane() + { + JvOptionPane.setInteractiveMode(false); + JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION); + } + + @BeforeMethod(alwaysRun = true) + public void loadProperties() + { + Cache.loadProperties("test/jalview/bin/TestProps.jvprops"); + } + @Test + public void testUPGMAEngine() throws Exception + { + AlignFrame af = new FileLoader(false).LoadFileWaitTillLoaded("examples/test_fab41.result/sample.a3m",DataSourceType.FILE); + AlignmentI seqs = af.getViewport().getAlignment(); + SequenceI target = seqs.getSequenceAt(0); + File testPAE = new File("examples/test_fab41.result/test_fab41_predicted_aligned_error_v1.json"); + List pae_obj = (List) Platform.parseJSON(new FileInputStream(testPAE)); + if (pae_obj == null) + { + Assert.fail("JSON PAE file did not parse properly."); + } + ContactMatrixI matrix = new PAEContactMatrix(target, + (Map) pae_obj.get(0)); + AlignmentAnnotation aa = target.addContactList(matrix); + System.out.println("Matrix has max="+matrix.getMax()+" and min="+matrix.getMin()); + long start = System.currentTimeMillis(); + AverageDistanceEngine clusterer = new AverageDistanceEngine(af.getViewport(), null, matrix); + System.out.println("built a tree in "+(System.currentTimeMillis()-start)*0.001+" seconds."); + StringBuffer sb = new StringBuffer(); + System.out.println("Newick string\n"+ new jalview.io.NewickFile(clusterer.getTopNode(),true,true).print()); + + double height = clusterer.findHeight(clusterer.getTopNode()); + // compute height fraction to cut + // PAE matrixes are absolute measure in angstrom, so + // cluster all regions within threshold (e.g. 2A) - if height above threshold. Otherwise all nodes are in one cluster + double thr=.2; + List groups; + if (height>thr) + { + float cut = (float) (thr/height); + System.out.println("Threshold "+cut+" for height="+height); + groups = clusterer.groupNodes(cut); + } else{ + groups=new ArrayList(); + groups.add(clusterer.getTopNode()); + } + int n=1; + for (BinaryNode root:groups) + { + System.out.println("Cluster "+n++); + for (BinaryNode leaf:clusterer.findLeaves(root)) + { + System.out.print(" "+leaf.getName()); + } + System.out.println("\\"); + } + + } + +} diff --git a/test/jalview/io/NewickFileTests.java b/test/jalview/io/NewickFileTests.java index beedb11..466f218 100644 --- a/test/jalview/io/NewickFileTests.java +++ b/test/jalview/io/NewickFileTests.java @@ -24,6 +24,7 @@ import static org.testng.ConversionUtils.wrapDataProvider; import jalview.analysis.SequenceIdMatcher; import jalview.analysis.TreeModel; +import jalview.datamodel.BinaryNode; import jalview.datamodel.SequenceI; import jalview.datamodel.SequenceNode; import jalview.gui.JvOptionPane; @@ -105,7 +106,7 @@ public class NewickFileTests AssertJUnit.assertTrue( stage + "Invalid Tree '" + nf.getWarningMessage() + "'", nf.isValid()); - SequenceNode tree = nf.getTree(); + BinaryNode tree = nf.getTree(); AssertJUnit.assertTrue(stage + "Null Tree", tree != null); stage = "Creating newick file from testTree " + treename; String gentree = new NewickFile(tree).print(nf.HasBootstrap(), @@ -119,11 +120,11 @@ public class NewickFileTests stage + "Newick file is invalid ('" + nf_regen.getWarningMessage() + "')", nf_regen.isValid()); - SequenceNode tree_regen = nf.getTree(); + BinaryNode tree_regen = nf.getTree(); AssertJUnit.assertTrue(stage + "Null Tree", tree_regen != null); stage = "Compare original and generated tree" + treename; - Vector oseqs, nseqs; + Vector oseqs, nseqs; oseqs = new TreeModel(new SequenceI[0], null, nf) .findLeaves(nf.getTree()); AssertJUnit.assertTrue(stage + "No nodes in original tree.",