Merge branch 'features/JAL-1767pcaInProject' into bug/JAL-3171_maintain_datasets_acro...
[jalview.git] / src / jalview / gui / Jalview2XML.java
index 5cd8f9a..42760f3 100644 (file)
  */
 package jalview.gui;
 
+import static jalview.math.RotatableMatrix.Axis.X;
+import static jalview.math.RotatableMatrix.Axis.Y;
+import static jalview.math.RotatableMatrix.Axis.Z;
+
 import jalview.analysis.Conservation;
+import jalview.analysis.PCA;
+import jalview.analysis.scoremodels.ScoreModels;
+import jalview.analysis.scoremodels.SimilarityParams;
 import jalview.api.FeatureColourI;
 import jalview.api.ViewStyleI;
+import jalview.api.analysis.ScoreModelI;
+import jalview.api.analysis.SimilarityParamsI;
 import jalview.api.structures.JalviewStructureDisplayI;
 import jalview.bin.Cache;
 import jalview.datamodel.AlignedCodonFrame;
@@ -31,6 +40,7 @@ import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.GraphLine;
 import jalview.datamodel.PDBEntry;
+import jalview.datamodel.Point;
 import jalview.datamodel.RnaViewerModel;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceGroup;
@@ -45,16 +55,22 @@ import jalview.ext.varna.RnaModel;
 import jalview.gui.StructureViewer.ViewerType;
 import jalview.io.DataSourceType;
 import jalview.io.FileFormat;
+import jalview.math.Matrix;
+import jalview.math.MatrixI;
 import jalview.renderer.ResidueShaderI;
 import jalview.schemabinding.version2.AlcodMap;
 import jalview.schemabinding.version2.AlcodonFrame;
 import jalview.schemabinding.version2.Annotation;
 import jalview.schemabinding.version2.AnnotationColours;
 import jalview.schemabinding.version2.AnnotationElement;
+import jalview.schemabinding.version2.Axis;
 import jalview.schemabinding.version2.CalcIdParam;
-import jalview.schemabinding.version2.Colour;
 import jalview.schemabinding.version2.CompoundMatcher;
+import jalview.schemabinding.version2.D;
 import jalview.schemabinding.version2.DBRef;
+import jalview.schemabinding.version2.DoubleMatrix;
+import jalview.schemabinding.version2.E;
+import jalview.schemabinding.version2.EigenMatrix;
 import jalview.schemabinding.version2.Features;
 import jalview.schemabinding.version2.Group;
 import jalview.schemabinding.version2.HiddenColumns;
@@ -69,18 +85,26 @@ import jalview.schemabinding.version2.MappingChoice;
 import jalview.schemabinding.version2.MatchCondition;
 import jalview.schemabinding.version2.MatcherSet;
 import jalview.schemabinding.version2.OtherData;
+import jalview.schemabinding.version2.PairwiseMatrix;
+import jalview.schemabinding.version2.PcaData;
+import jalview.schemabinding.version2.PcaViewer;
 import jalview.schemabinding.version2.PdbentryItem;
 import jalview.schemabinding.version2.Pdbids;
 import jalview.schemabinding.version2.Property;
 import jalview.schemabinding.version2.RnaViewer;
+import jalview.schemabinding.version2.Row;
 import jalview.schemabinding.version2.SecondaryStructure;
+import jalview.schemabinding.version2.SeqPointMax;
+import jalview.schemabinding.version2.SeqPointMin;
 import jalview.schemabinding.version2.Sequence;
+import jalview.schemabinding.version2.SequencePoint;
 import jalview.schemabinding.version2.SequenceSet;
 import jalview.schemabinding.version2.SequenceSetProperties;
 import jalview.schemabinding.version2.Setting;
 import jalview.schemabinding.version2.StructureState;
 import jalview.schemabinding.version2.ThresholdLine;
 import jalview.schemabinding.version2.Tree;
+import jalview.schemabinding.version2.TridiagonalMatrix;
 import jalview.schemabinding.version2.UserColours;
 import jalview.schemabinding.version2.Viewport;
 import jalview.schemabinding.version2.types.ColourThreshTypeType;
@@ -101,6 +125,7 @@ import jalview.util.StringUtils;
 import jalview.util.jarInputStreamProvider;
 import jalview.util.matcher.Condition;
 import jalview.viewmodel.AlignmentViewport;
+import jalview.viewmodel.PCAModel;
 import jalview.viewmodel.ViewportRanges;
 import jalview.viewmodel.seqfeatures.FeatureRendererSettings;
 import jalview.viewmodel.seqfeatures.FeaturesDisplayed;
@@ -1130,6 +1155,7 @@ public class Jalview2XML
               tree.setXpos(tp.getX());
               tree.setYpos(tp.getY());
               tree.setId(makeHashCode(tp, null));
+              tree.setLinkToAllViews(tp.treeCanvas.applyToAllViews);
               jms.addTree(tree);
             }
           }
@@ -1137,6 +1163,24 @@ public class Jalview2XML
       }
     }
 
+    /*
+     * save PCA viewers
+     */
+    if (!storeDS && Desktop.desktop != null)
+    {
+      for (JInternalFrame frame : Desktop.desktop.getAllFrames())
+      {
+        if (frame instanceof PCAPanel)
+        {
+          PCAPanel panel = (PCAPanel) frame;
+          if (panel.av.getAlignment() == jal)
+          {
+            savePCA(panel, jms);
+          }
+        }
+      }
+    }
+
     // SAVE ANNOTATIONS
     /**
      * store forward refs from an annotationRow to any groups
@@ -1514,6 +1558,165 @@ public class Jalview2XML
   }
 
   /**
+   * Writes PCA viewer attributes and computed values to an XML model object and adds it to the JalviewModel. Any exceptions are reported by logging.
+   */
+  protected void savePCA(PCAPanel panel, JalviewModelSequence jms)
+  {
+    try
+    {
+      PcaViewer viewer = new PcaViewer();
+      viewer.setHeight(panel.getHeight());
+      viewer.setWidth(panel.getWidth());
+      viewer.setXpos(panel.getX());
+      viewer.setYpos(panel.getY());
+      viewer.setTitle(panel.getTitle());
+      PCAModel pcaModel = panel.pcaModel;
+      viewer.setScoreModelName(pcaModel.getScoreModelName());
+      viewer.setXDim(panel.getSelectedDimensionIndex(X));
+      viewer.setYDim(panel.getSelectedDimensionIndex(Y));
+      viewer.setZDim(panel.getSelectedDimensionIndex(Z));
+      viewer.setBgColour(panel.rc.getBackgroundColour().getRGB());
+      viewer.setScaleFactor(panel.rc.scaleFactor);
+      float[] spMin = panel.rc.getSeqMin();
+      SeqPointMin spmin = new SeqPointMin();
+      spmin.setXPos(spMin[0]);
+      spmin.setYPos(spMin[1]);
+      spmin.setZPos(spMin[2]);
+      viewer.setSeqPointMin(spmin);
+      float[] spMax = panel.rc.getSeqMax();
+      SeqPointMax spmax = new SeqPointMax();
+      spmax.setXPos(spMax[0]);
+      spmax.setYPos(spMax[1]);
+      spmax.setZPos(spMax[2]);
+      viewer.setSeqPointMax(spmax);
+      viewer.setShowLabels(panel.rc.showLabels);
+      viewer.setLinkToAllViews(panel.rc.applyToAllViews);
+      SimilarityParamsI sp = pcaModel.getSimilarityParameters();
+      viewer.setIncludeGaps(sp.includeGaps());
+      viewer.setMatchGaps(sp.matchGaps());
+      viewer.setIncludeGappedColumns(sp.includeGappedColumns());
+      viewer.setDenominateByShortestLength(sp.denominateByShortestLength());
+
+      /*
+       * sequence points on display
+       */
+      for (jalview.datamodel.SequencePoint spt : pcaModel
+              .getSequencePoints())
+      {
+        SequencePoint point = new SequencePoint();
+        point.setSequenceRef(seqHash(spt.getSequence()));
+        point.setXPos(spt.coord.x);
+        point.setYPos(spt.coord.y);
+        point.setZPos(spt.coord.z);
+        viewer.addSequencePoint(point);
+      }
+
+      /*
+       * (end points of) axes on display
+       */
+      for (Point p : panel.rc.axisEndPoints)
+      {
+        Axis axis = new Axis();
+        axis.setXPos(p.x);
+        axis.setYPos(p.y);
+        axis.setZPos(p.z);
+        viewer.addAxis(axis);
+      }
+
+      /*
+       * raw PCA data (note we are not restoring PCA inputs here -
+       * alignment view, score model, similarity parameters)
+       */
+      PcaData data = new PcaData();
+      viewer.setPcaData(data);
+      PCA pca = pcaModel.getPcaData();
+
+      PairwiseMatrix pm = new PairwiseMatrix();
+      saveDoubleMatrix(pca.getPairwiseScores(), pm);
+      data.setPairwiseMatrix(pm);
+
+      TridiagonalMatrix tm = new TridiagonalMatrix();
+      saveDoubleMatrix(pca.getTridiagonal(), tm);
+      data.setTridiagonalMatrix(tm);
+
+      EigenMatrix eigenMatrix = new EigenMatrix();
+      data.setEigenMatrix(eigenMatrix);
+      saveDoubleMatrix(pca.getEigenmatrix(), eigenMatrix);
+
+      jms.addPcaViewer(viewer);
+    } catch (Throwable t)
+    {
+      Cache.log.error("Error saving PCA: " + t.getMessage());
+    }
+  }
+
+  /**
+   * Stores values from a matrix into an XML element, including (if present) the
+   * D or E vectors
+   * 
+   * @param m
+   * @param xmlMatrix
+   * @see #loadDoubleMatrix(DoubleMatrix)
+   */
+  protected void saveDoubleMatrix(MatrixI m, DoubleMatrix xmlMatrix)
+  {
+    xmlMatrix.setRows(m.height());
+    xmlMatrix.setColumns(m.width());
+    for (int i = 0; i < m.height(); i++)
+    {
+      Row row = new Row();
+      for (int j = 0; j < m.width(); j++)
+      {
+        row.addV(m.getValue(i, j));
+      }
+      xmlMatrix.addRow(row);
+    }
+    if (m.getD() != null)
+    {
+      D dVector = new D();
+      dVector.setV(m.getD());
+      xmlMatrix.setD(dVector);
+    }
+    if (m.getE() != null)
+    {
+      E eVector = new E();
+      eVector.setV(m.getE());
+      xmlMatrix.setE(eVector);
+    }
+  }
+
+  /**
+   * Loads XML matrix data into a new Matrix object, including the D and/or E
+   * vectors (if present)
+   * 
+   * @param mData
+   * @return
+   * @see Jalview2XML#saveDoubleMatrix(MatrixI, DoubleMatrix)
+   */
+  protected MatrixI loadDoubleMatrix(DoubleMatrix mData)
+  {
+    int rows = mData.getRows();
+    double[][] vals = new double[rows][];
+
+    for (int i = 0; i < rows; i++)
+    {
+      vals[i] = mData.getRow(i).getV();
+    }
+
+    MatrixI m = new Matrix(vals);
+    
+    if (mData.getD() != null) {
+      m.setD(mData.getD().getV());
+    }
+    if (mData.getE() != null)
+    {
+      m.setE(mData.getE().getV());
+    }
+
+    return m;
+  }
+
+  /**
    * Save any Varna viewers linked to this sequence. Writes an rnaViewer element
    * for each viewer, with
    * <ul>
@@ -3642,6 +3845,7 @@ public class Jalview2XML
     if (loadTreesAndStructures)
     {
       loadTrees(jms, view, af, av, ap);
+      loadPCAViewers(jms, ap);
       loadPDBStructures(jprovider, jseqs, af, ap);
       loadRnaViewers(jprovider, jseqs, ap);
     }
@@ -3789,7 +3993,6 @@ public class Jalview2XML
           // works still
           tp.getTreeCanvas().setViewport(av); // af.viewport;
           tp.getTreeCanvas().setAssociatedPanel(ap); // af.alignPanel;
-
         }
         if (tp == null)
         {
@@ -3817,6 +4020,7 @@ public class Jalview2XML
         tp.showDistances(tree.getShowDistances());
 
         tp.getTreeCanvas().setThreshold(tree.getThreshold());
+        tp.treeCanvas.applyToAllViews = tree.isLinkToAllViews();
 
         if (tree.getCurrentTree())
         {
@@ -5751,6 +5955,126 @@ public class Jalview2XML
   }
 
   /**
+   * Loads any saved PCA viewers
+   * 
+   * @param jms
+   * @param ap
+   */
+  protected void loadPCAViewers(JalviewModelSequence jms, AlignmentPanel ap)
+  {
+    try
+    {
+      for (int t = 0; t < jms.getPcaViewerCount(); t++)
+      {
+        PcaViewer viewer = jms.getPcaViewer(t);
+        String modelName = viewer.getScoreModelName();
+        SimilarityParamsI params = new SimilarityParams(
+                viewer.isIncludeGappedColumns(),
+                viewer.isMatchGaps(), viewer.isIncludeGaps(),
+                viewer.isDenominateByShortestLength());
+
+        /*
+         * create the panel (without computing the PCA)
+         */
+        PCAPanel panel = new PCAPanel(ap, modelName, params);
+
+        panel.setTitle(viewer.getTitle());
+        panel.setBounds(new Rectangle(viewer.getXpos(), viewer.getYpos(),
+                viewer.getWidth(), viewer.getHeight()));
+  
+        boolean showLabels = viewer.isShowLabels();
+        panel.setShowLabels(showLabels);
+        panel.rc.showLabels = showLabels;
+        panel.rc.bgColour = new Color(viewer.getBgColour());
+        panel.rc.applyToAllViews = viewer.isLinkToAllViews();
+
+        /*
+         * load PCA output data
+         */
+        ScoreModelI scoreModel = ScoreModels.getInstance()
+                .getScoreModel(modelName, ap);
+        PCA pca = new PCA(null, scoreModel, params);
+        PcaData pcaData = viewer.getPcaData();
+
+        MatrixI pairwise = loadDoubleMatrix(pcaData.getPairwiseMatrix());
+        pca.setPairwiseScores(pairwise);
+
+        MatrixI triDiag = loadDoubleMatrix(pcaData.getTridiagonalMatrix());
+        pca.setTridiagonal(triDiag);
+
+        MatrixI result = loadDoubleMatrix(pcaData.getEigenMatrix());
+        pca.setEigenmatrix(result);
+
+        panel.pcaModel.setPCA(pca);
+
+        /*
+         * we haven't saved the input data! (JAL-2647 to do)
+         */
+        panel.setInputData(null);
+
+        /*
+         * add the sequence points for the PCA display
+         */
+        List<jalview.datamodel.SequencePoint> seqPoints = new ArrayList<>();
+        for (SequencePoint sp : viewer.getSequencePoint())
+        {
+          String seqId = sp.getSequenceRef();
+          SequenceI seq = seqRefIds.get(seqId);
+          if (seq == null)
+          {
+            throw new IllegalStateException(
+                    "Unmatched seqref for PCA: " + seqId);
+          }
+          Point pt = new Point(sp.getXPos(), sp.getYPos(), sp.getZPos());
+          jalview.datamodel.SequencePoint seqPoint = new jalview.datamodel.SequencePoint(
+                  seq, pt);
+          seqPoints.add(seqPoint);
+        }
+        panel.rc.setPoints(seqPoints, seqPoints.size());
+
+        /*
+         * set min-max ranges and scale after setPoints (which recomputes them)
+         */
+        panel.rc.scaleFactor = viewer.getScaleFactor();
+        SeqPointMin spMin = viewer.getSeqPointMin();
+        float[] min = new float[] { spMin.getXPos(), spMin.getYPos(),
+            spMin.getZPos() };
+        SeqPointMax spMax = viewer.getSeqPointMax();
+        float[] max = new float[] { spMax.getXPos(), spMax.getYPos(),
+            spMax.getZPos() };
+        panel.rc.setSeqMinMax(min, max);
+
+        // todo: hold points list in PCAModel only
+        panel.pcaModel.setSequencePoints(seqPoints);
+
+        panel.setSelectedDimensionIndex(viewer.getXDim(), X);
+        panel.setSelectedDimensionIndex(viewer.getYDim(), Y);
+        panel.setSelectedDimensionIndex(viewer.getZDim(), Z);
+
+        // is this duplication needed?
+        panel.top = seqPoints.size() - 1;
+        panel.pcaModel.setTop(seqPoints.size() - 1);
+
+        /*
+         * add the axes' end points for the display
+         */
+        for (int i = 0; i < 3; i++)
+        {
+          Axis axis = viewer.getAxis(i);
+          panel.rc.axisEndPoints[i] = new Point(axis.getXPos(),
+                  axis.getYPos(), axis.getZPos());
+        }
+
+        Desktop.addInternalFrame(panel, MessageManager.formatMessage(
+                "label.calc_title", "PCA", modelName), 475, 450);
+      }
+    } catch (Exception ex)
+    {
+      Cache.log.error("Error loading PCA: " + ex.toString());
+    }
+  }
+
+  /**
    * Populates an XML model of the feature colour scheme for one feature type
    * 
    * @param featureType