From 7420ce36f2b43280ef610e3743960207e4c2dbe3 Mon Sep 17 00:00:00 2001 From: James Procter Date: Mon, 27 Feb 2023 17:18:44 +0000 Subject: [PATCH] JAL-4134 allow tree groups to be stored/recovered on contact matrix for groupwise column selection --- src/jalview/api/AlignViewportI.java | 3 + src/jalview/datamodel/Alignment.java | 18 +++++- src/jalview/datamodel/ContactMatrixI.java | 11 ++++ src/jalview/gui/AnnotationPanel.java | 25 +++++++- src/jalview/viewmodel/AlignmentViewport.java | 9 +++ .../ws/datamodel/alphafold/PAEContactMatrix.java | 61 ++++++++++++++++++++ src/jalview/ws/dbsources/EBIAlfaFold.java | 3 +- .../analysis/AverageDistanceEngineTest.java | 19 +++++- 8 files changed, 143 insertions(+), 6 deletions(-) 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/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/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/gui/AnnotationPanel.java b/src/jalview/gui/AnnotationPanel.java index b68ff01..3aacb07 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; @@ -603,6 +605,26 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI, { GraphLine thr = aa[graphStretch].getThreshold(); int currentX = getColumnForXPos(evt.getX()); + ContactMatrixI matrix = av.getContactMatrix(aa[graphStretch]); + if (matrix!=null) + { + if (matrix.hasGroups()) + { + SequenceI rseq = aa[graphStretch].sequenceRef; + BitSet grp = matrix.getGroupsFor(currentX); + ColumnSelection cs = av.getColumnSelection(); + 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); + } + } + } else + { ContactListI forCurrentX = av.getContactList(aa[graphStretch], currentX); if (forCurrentX != null) @@ -662,9 +684,10 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI, } } } + } } } - } + }} } else { 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 48071bd..30c77d2 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; @@ -239,4 +244,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 72fd8d9..dd71ec3 100644 --- a/src/jalview/ws/dbsources/EBIAlfaFold.java +++ b/src/jalview/ws/dbsources/EBIAlfaFold.java @@ -410,6 +410,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); @@ -468,7 +469,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); sm.transfer(cmannot); diff --git a/test/jalview/analysis/AverageDistanceEngineTest.java b/test/jalview/analysis/AverageDistanceEngineTest.java index 7c068cb..6d9ab50 100644 --- a/test/jalview/analysis/AverageDistanceEngineTest.java +++ b/test/jalview/analysis/AverageDistanceEngineTest.java @@ -4,6 +4,7 @@ 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; @@ -58,14 +59,28 @@ public class AverageDistanceEngineTest 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()); - clusterer.findHeight(clusterer.getTopNode()); - List groups = clusterer.groupNodes(0.8f); + 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) { -- 1.7.10.2