JAL-4489 JAL-4159 combine calculation, model and UI code for PaSiMap and PCA features/JAL-4489_clustering_and_pca_pasimap_views
authorJames Procter <j.procter@dundee.ac.uk>
Thu, 7 Nov 2024 16:58:39 +0000 (16:58 +0000)
committerJames Procter <j.procter@dundee.ac.uk>
Thu, 7 Nov 2024 16:58:39 +0000 (16:58 +0000)
src/jalview/analysis/PCA.java
src/jalview/analysis/PaSiMap.java
src/jalview/analysis/SpatialCalculationI.java [new file with mode: 0644]
src/jalview/gui/PCAPanel.java
src/jalview/gui/PaSiMapPanel.java
src/jalview/gui/SpatialPanel.java [new file with mode: 0644]
src/jalview/jbgui/GPCAPanel.java
src/jalview/viewmodel/PCAModel.java
src/jalview/viewmodel/PaSiMapModel.java
src/jalview/viewmodel/SpatialModel.java [new file with mode: 0644]

index 5cc81a8..90b7f87 100755 (executable)
@@ -32,7 +32,7 @@ import java.io.PrintStream;
 /**
  * Performs Principal Component Analysis on given sequences
  */
-public class PCA implements Runnable
+public class PCA implements Runnable, SpatialCalculationI
 {
   /*
    * inputs
@@ -201,31 +201,57 @@ public class PCA implements Runnable
   @Override
   public void run()
   {
+    isCancelled=false;
     try
     {
       /*
        * sequence pairwise similarity scores
        */
       pairwiseScores = scoreModel.findSimilarities(seqs, similarityParams);
-
+      if (isCancelled())
+      {
+        pairwiseScores=null;
+        return;
+      }
       /*
        * tridiagonal matrix
        */
+      
       tridiagonal = pairwiseScores.copy();
+      if (isCancelled()) {
+        clearResults();
+        return;
+      }
       tridiagonal.tred();
-
+      if (isCancelled()) {
+        clearResults();
+        return;
+      }
       /*
        * the diagonalization matrix
        */
       eigenMatrix = tridiagonal.copy();
+      if (isCancelled()) {
+        clearResults();
+        return;
+      }
       eigenMatrix.tqli();
+      if (isCancelled()) {
+        clearResults();
+        return;
+      }
     } catch (Exception q)
     {
       Console.error("Error computing PCA:  " + q.getMessage());
       q.printStackTrace();
     }
   }
-
+  private void clearResults()
+  {
+    pairwiseScores = null;
+    tridiagonal = null;
+    eigenMatrix = null;
+  }
   /**
    * Returns a PrintStream that wraps (appends its output to) the given
    * StringBuilder
@@ -263,7 +289,20 @@ public class PCA implements Runnable
     // TODO can any of seqs[] be null?
     return pairwiseScores.height();// seqs.getSequences().length;
   }
-
+  @Override
+  public int getWidth()
+  {
+    return getHeight();
+  }
+  @Override
+  public int getDim()
+  {
+    return component(1).length;
+  }
+  public int getTop()
+  {
+    return pairwiseScores.height()-1;
+  }
   /**
    * Answers the sequence pairwise similarity scores which were the first step
    * of the PCA calculation
@@ -299,4 +338,29 @@ public class PCA implements Runnable
   {
     this.tridiagonal = tridiagonal;
   }
+  
+  volatile boolean isCancelled = false;
+  @Override
+  public void cancel()
+  {
+    isCancelled = true;
+  }
+  @Override
+  public boolean isCancellable()
+  {
+    return true;
+  }
+  @Override
+  public boolean isCancelled()
+  {
+    return isCancelled;
+  }
+  @Override
+  public String getAlignmentOutput()
+  {
+    // TODO Auto-generated method stub
+    return null;
+  }
+  
+  
 }
index 5d7d435..6228379 100755 (executable)
@@ -41,7 +41,7 @@ import java.util.Hashtable;
  * 
  * @AUTHOR MorellThomas
  */
-public class PaSiMap implements Runnable
+public class PaSiMap implements Runnable,SpatialCalculationI
 {
   /*
    * inputs
@@ -329,6 +329,11 @@ public class PaSiMap implements Runnable
     // TODO can any of seqs[] be null?
     return eigenMatrix.width();// seqs.getSequences().length;
   }
+  
+  public int getTop()
+  {
+    return getWidth()-1;    
+  }
 
   /**
    * Answers the sequence pairwise similarity scores which were the first step
@@ -366,7 +371,7 @@ public class PaSiMap implements Runnable
     return alignment.getAlignmentOutput();
   }
 
-  public byte getDim()
+  public int getDim()
   {
     return dim;
   }
diff --git a/src/jalview/analysis/SpatialCalculationI.java b/src/jalview/analysis/SpatialCalculationI.java
new file mode 100644 (file)
index 0000000..1c2fa63
--- /dev/null
@@ -0,0 +1,32 @@
+package jalview.analysis;
+
+import jalview.datamodel.Point;
+
+public interface SpatialCalculationI
+{
+
+  void run();
+
+  boolean isCancelled();
+
+  int getHeight();
+
+  int getWidth();
+
+  Point[] getComponents(int i, int j, int k, float f);
+
+  int getDim();
+
+  String getAlignmentOutput();
+
+  String getDetails();
+
+  double[] component(int s);
+
+  void cancel();
+
+  boolean isCancellable();
+
+  int getTop();
+
+}
index 570fc5d..292237b 100644 (file)
@@ -61,7 +61,7 @@ import jalview.viewmodel.PCAModel;
 /**
  * The panel holding the Principal Component Analysis 3-D visualisation
  */
-public class PCAPanel extends GPCAPanel
+public class PCAPanel extends SpatialPanel
         implements Runnable, IProgressIndicator
 {
   private static final int MIN_WIDTH = 470;
index e3d5b0a..1b71e5e 100644 (file)
@@ -33,7 +33,7 @@ import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.gui.ImageExporter.ImageWriterI;
 import jalview.gui.JalviewColourChooser.ColourChooserListener;
-import jalview.jbgui.GPaSiMapPanel;
+import jalview.jbgui.GPCAPanel;
 import jalview.math.RotatableMatrix.Axis;
 import jalview.util.ImageMaker;
 import jalview.util.MessageManager;
@@ -64,7 +64,7 @@ import javax.swing.event.InternalFrameEvent;
 /**
  * The panel holding the Pairwise Similarity Map 3-D visualisation
  */
-public class PaSiMapPanel extends GPaSiMapPanel
+public class PaSiMapPanel extends SpatialPanel
         implements Runnable, IProgressIndicator
 {
   private static final int MIN_WIDTH = 470;
@@ -382,8 +382,8 @@ public class PaSiMapPanel extends GPaSiMapPanel
     {
     }
 
-    Object[] alAndColsel = getPasimapModel().getInputData()
-            .getAlignmentView(false).getAlignmentAndHiddenColumns(gc);
+    // TODO - this possibly now does compeltely the wrong thing - returns the alignment view for the model, which may be spurious
+    Object[] alAndColsel = getPasimapModel().getInputData().getAlignmentAndHiddenColumns(gc);
 
     if (alAndColsel != null && alAndColsel[0] != null)
     {
diff --git a/src/jalview/gui/SpatialPanel.java b/src/jalview/gui/SpatialPanel.java
new file mode 100644 (file)
index 0000000..388fd37
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.gui;
+
+import jalview.jbgui.GPCAPanel;
+
+/**
+ * Base panel holding spatial projections
+ */
+
+public class SpatialPanel extends GPCAPanel
+{
+  
+  public SpatialPanel()
+  {
+    this(8);
+  }
+  public SpatialPanel(int dim)
+  {
+    super(dim);
+  }
+}
index 65394ed..e094f6a 100755 (executable)
@@ -65,25 +65,14 @@ public class GPCAPanel extends JInternalFrame
 
   protected JMenuItem originalSeqData;
 
+  protected JMenuItem outputAlignment;
+
   /**
    * Constructor
    */
   public GPCAPanel()
   {
-    try
-    {
-      jbInit();
-    } catch (Exception e)
-    {
-      e.printStackTrace();
-    }
-
-    for (int i = 1; i < 8; i++)
-    {
-      xCombobox.addItem("dim " + i);
-      yCombobox.addItem("dim " + i);
-      zCombobox.addItem("dim " + i);
-    }
+    this(8);
   }
 
   public GPCAPanel(int dim)
@@ -107,7 +96,7 @@ public class GPCAPanel extends JInternalFrame
   private void jbInit() throws Exception
   {
     setFrameIcon(null);
-    setName("jalview-pca");
+    setName("jalview-"+this.getClass().getName());
     this.getContentPane().setLayout(new BorderLayout());
     JPanel jPanel2 = new JPanel();
     jPanel2.setLayout(new FlowLayout());
@@ -223,6 +212,17 @@ public class GPCAPanel extends JInternalFrame
         print_actionPerformed();
       }
     });
+    outputAlignment = new JMenuItem();
+    outputAlignment
+            .setText(MessageManager.getString("label.output_alignment"));
+    outputAlignment.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        outputAlignment_actionPerformed();
+      }
+    });
     viewMenu.setText(MessageManager.getString("action.view"));
     viewMenu.addMenuListener(new MenuListener()
     {
@@ -301,6 +301,7 @@ public class GPCAPanel extends JInternalFrame
     fileMenu.add(originalSeqData);
     fileMenu.add(outputPoints);
     fileMenu.add(outputProjPoints);
+    fileMenu.add(outputAlignment);
     saveMenu.add(eps);
     saveMenu.add(png);
     viewMenu.add(showLabels);
@@ -320,6 +321,10 @@ public class GPCAPanel extends JInternalFrame
   {
   }
 
+  protected void outputAlignment_actionPerformed()
+  {
+  }
+
   public void makePCAImage(TYPE imageType)
   {
   }
index 71bef00..05cecbf 100644 (file)
@@ -32,240 +32,28 @@ import jalview.datamodel.SequencePoint;
 import java.util.List;
 import java.util.Vector;
 
-public class PCAModel
+public class PCAModel extends SpatialModel<PCA>
 {
-  /*
-   * inputs
-   */
-  private AlignmentView inputData;
-
-  private final SequenceI[] seqs;
-
-  private final SimilarityParamsI similarityParams;
-
-  /*
-   * options - score model, nucleotide / protein
-   */
-  private ScoreModelI scoreModel;
-
-  private boolean nucleotide = false;
-
-  /*
-   * outputs
-   */
-  private PCA pca;
-
-  int top;
-
-  private List<SequencePoint> points;
-
-  /**
-   * Constructor given sequence data, score model and score calculation
-   * parameter options.
-   * 
-   * @param seqData
-   * @param sqs
-   * @param nuc
-   * @param modelName
-   * @param params
-   */
-  public PCAModel(AlignmentView seqData, SequenceI[] sqs, boolean nuc,
-          ScoreModelI modelName, SimilarityParamsI params)
-  {
-    inputData = seqData;
-    seqs = sqs;
-    nucleotide = nuc;
-    scoreModel = modelName;
-    similarityParams = params;
-  }
-
-  /**
-   * Performs the PCA calculation (in the same thread) and extracts result data
-   * needed for visualisation by PCAPanel
-   */
-  public void calculate()
-  {
-    pca = new PCA(inputData, scoreModel, similarityParams);
-    pca.run(); // executes in same thread, wait for completion
-
-    // Now find the component coordinates
-    int ii = 0;
-
-    while ((ii < seqs.length) && (seqs[ii] != null))
-    {
-      ii++;
-    }
-
-    int height = pca.getHeight();
-    // top = pca.getM().height() - 1;
-    top = height - 1;
-
-    points = new Vector<>();
-    Point[] scores = pca.getComponents(top - 1, top - 2, top - 3, 1);
-
-    for (int i = 0; i < height; i++)
-    {
-      SequencePoint sp = new SequencePoint(seqs[i], scores[i]);
-      points.add(sp);
-    }
-  }
-
-  public void updateRc(RotatableCanvasI rc)
-  {
-    rc.setPoints(points, pca.getHeight());
-  }
-
-  public boolean isNucleotide()
-  {
-    return nucleotide;
-  }
-
-  public void setNucleotide(boolean nucleotide)
-  {
-    this.nucleotide = nucleotide;
-  }
-
-  /**
-   * Answers the index of the principal dimension of the PCA
-   * 
-   * @return
-   */
-  public int getTop()
-  {
-    return top;
-  }
-
-  public void setTop(int t)
-  {
-    top = t;
-  }
-
-  /**
-   * Updates the 3D coordinates for the list of points to the given dimensions.
-   * Principal dimension is getTop(). Next greatest eigenvector is getTop()-1.
-   * Note - pca.getComponents starts counting the spectrum from rank-2 to zero,
-   * rather than rank-1, so getComponents(dimN ...) == updateRcView(dimN+1 ..)
-   * 
-   * @param dim1
-   * @param dim2
-   * @param dim3
-   */
-  public void updateRcView(int dim1, int dim2, int dim3)
-  {
-    // note: actual indices for components are dim1-1, etc (patch for JAL-1123)
-    Point[] scores = pca.getComponents(dim1 - 1, dim2 - 1, dim3 - 1, 1);
-
-    for (int i = 0; i < pca.getHeight(); i++)
-    {
-      points.get(i).coord = scores[i];
-    }
-  }
-
-  public String getDetails()
+  @Override
+  public PCA constructModel()
   {
-    return pca.getDetails();
+    return new PCA(inputData,scoreModel,similarityParams);
   }
-
-  public AlignmentView getInputData()
+  public PCAModel(AlignmentView seqData, SequenceI[] sqs, boolean nuc,
+          ScoreModelI modelName, SimilarityParamsI params)
   {
-    return inputData;
+    super(null, seqData,sqs, nuc,modelName, params);
   }
-
   public void setInputData(AlignmentView data)
   {
     inputData = data;
   }
-
-  public String getPointsasCsv(boolean transformed, int xdim, int ydim,
-          int zdim)
-  {
-    StringBuffer csv = new StringBuffer();
-    csv.append("\"Sequence\"");
-    if (transformed)
-    {
-      csv.append(",");
-      csv.append(xdim);
-      csv.append(",");
-      csv.append(ydim);
-      csv.append(",");
-      csv.append(zdim);
-    }
-    else
-    {
-      for (int d = 1, dmax = pca.component(1).length; d <= dmax; d++)
-      {
-        csv.append("," + d);
-      }
-    }
-    csv.append("\n");
-    for (int s = 0; s < seqs.length; s++)
-    {
-      csv.append("\"" + seqs[s].getName() + "\"");
-      double fl[];
-      if (!transformed)
-      {
-        // output pca in correct order
-        fl = pca.component(s);
-        for (int d = fl.length - 1; d >= 0; d--)
-        {
-          csv.append(",");
-          csv.append(fl[d]);
-        }
-      }
-      else
-      {
-        Point p = points.get(s).coord;
-        csv.append(",").append(p.x);
-        csv.append(",").append(p.y);
-        csv.append(",").append(p.z);
-      }
-      csv.append("\n");
-    }
-    return csv.toString();
-  }
-
-  public String getScoreModelName()
-  {
-    return scoreModel == null ? "" : scoreModel.getName();
-  }
-
-  public void setScoreModel(ScoreModelI sm)
-  {
-    this.scoreModel = sm;
-  }
-
-  /**
-   * Answers the parameters configured for pairwise similarity calculations
-   * 
-   * @return
-   */
-  public SimilarityParamsI getSimilarityParameters()
-  {
-    return similarityParams;
-  }
-
-  public List<SequencePoint> getSequencePoints()
-  {
-    return points;
-  }
-
-  public void setSequencePoints(List<SequencePoint> sp)
-  {
-    points = sp;
-  }
-
-  /**
-   * Answers the object holding the values of the computed PCA
-   * 
-   * @return
-   */
   public PCA getPcaData()
   {
-    return pca;
+    return outputModel;
   }
-
-  public void setPCA(PCA data)
+  public void setPCA(PCA pca)
   {
-    pca = data;
+    outputModel = pca;
   }
 }
index 6b6dc41..504980d 100644 (file)
@@ -20,6 +20,7 @@
  */
 package jalview.viewmodel;
 
+import jalview.analysis.PCA;
 import jalview.analysis.PaSiMap;
 import jalview.api.RotatableCanvasI;
 import jalview.api.analysis.ScoreModelI;
@@ -34,48 +35,18 @@ import jalview.viewmodel.AlignmentViewport;
 import java.util.List;
 import java.util.Vector;
 
-public class PaSiMapModel
+public class PaSiMapModel extends SpatialModel<PaSiMap>
 {
-  /*
-   * inputs
-   */
-  private AlignmentViewport inputData;
-
-  private final SequenceI[] seqs;
-
-  /*
-   * options - score model, nucleotide / protein
-   */
-  private ScoreModelI scoreModel;
-
-  private boolean nucleotide = false;
-
-  /*
-   * outputs
-   */
-  private PaSiMap pasimap;
-
-  int top;
-
-  private List<SequencePoint> points;
-
-  /**
-   * Constructor given sequence data, score model and score calculation
-   * parameter options.
-   * 
-   * @param seqData
-   * @param sqs
-   * @param nuc
-   * @param modelName
-   * @param params
-   */
+  PairwiseAlignPanel pap = null;
+  @Override
+  public PaSiMap constructModel()
+  {
+    return new PaSiMap(av, scoreModel, pap);
+  }
   public PaSiMapModel(AlignmentViewport seqData, SequenceI[] sqs,
           boolean nuc, ScoreModelI modelName)
   {
-    inputData = seqData;
-    seqs = sqs;
-    nucleotide = nuc;
-    scoreModel = modelName;
+    super(seqData,null,sqs,nuc, modelName,null);
   }
 
   /**
@@ -84,206 +55,20 @@ public class PaSiMapModel
    */
   public void calculate(PairwiseAlignPanel pap)
   {
-    pasimap = new PaSiMap(inputData, scoreModel, pap);
-    pasimap.run(); // executes in same thread, wait for completion
-    if (pasimap.isCancelled())
-    {
-      // no more work to do
-      return;
-    }
-
-    // Now find the component coordinates
-    int ii = 0;
-
-    while ((ii < seqs.length) && (seqs[ii] != null))
-    {
-      ii++;
-    }
-
-    int width = pasimap.getWidth();
-    int height = pasimap.getHeight();
-    top = width;
-
-    points = new Vector<>();
-    Point[] scores = pasimap.getComponents(width - 1, width - 2, width - 3,
-            1);
-
-    for (int i = 0; i < height; i++)
-    {
-      SequencePoint sp = new SequencePoint(seqs[i], scores[i]);
-      points.add(sp);
-    }
-  }
-
-  public void updateRc(RotatableCanvasI rc)
-  {
-    rc.setPoints(points, pasimap.getHeight());
-  }
-
-  public boolean isNucleotide()
-  {
-    return nucleotide;
-  }
-
-  public void setNucleotide(boolean nucleotide)
-  {
-    this.nucleotide = nucleotide;
-  }
-
-  /**
-   * Answers the index of the principal dimension of the PaSiMap
-   * 
-   * @return
-   */
-  public int getTop()
-  {
-    return top;
-  }
-
-  public void setTop(int t)
-  {
-    top = t;
+    this.pap = pap;
+    calculate();
   }
-
-  /**
-   * Updates the 3D coordinates for the list of points to the given dimensions.
-   * Principal dimension is getTop(). Next greatest eigenvector is getTop()-1.
-   * Note - pasimap.getComponents starts counting the spectrum from rank-2 to
-   * zero, rather than rank-1, so getComponents(dimN ...) == updateRcView(dimN+1
-   * ..)
-   * 
-   * @param dim1
-   * @param dim2
-   * @param dim3
-   */
-  public void updateRcView(int dim1, int dim2, int dim3)
-  {
-    // note: actual indices for components are dim1-1, etc (patch for JAL-1123)
-    Point[] scores = pasimap.getComponents(dim1 - 1, dim2 - 1, dim3 - 1, 1);
-
-    for (int i = 0; i < pasimap.getHeight(); i++)
-    {
-      points.get(i).coord = scores[i];
-    }
-  }
-
-  public String getDetails()
-  {
-    return pasimap.getDetails();
-  }
-
-  public String getAlignmentOutput()
-  {
-    return pasimap.getAlignmentOutput();
-  }
-
-  public AlignmentViewport getInputData()
-  {
-    return inputData;
-  }
-
   public void setInputData(AlignmentViewport data)
   {
-    inputData = data;
-  }
-
-  public String getPointsasCsv(boolean transformed, int xdim, int ydim,
-          int zdim)
-  {
-    StringBuffer csv = new StringBuffer();
-    csv.append("\"Sequence\"");
-    if (transformed)
-    {
-      csv.append(",");
-      csv.append(xdim);
-      csv.append(",");
-      csv.append(ydim);
-      csv.append(",");
-      csv.append(zdim);
-    }
-    else
-    {
-      for (int d = 1, dmax = (int) pasimap.getDim(); d <= dmax; d++)
-      {
-        csv.append("," + d);
-      }
-    }
-    csv.append("\n");
-    for (int s = 0; s < seqs.length; s++)
-    {
-      csv.append("\"" + seqs[s].getName() + "\"");
-      if (!transformed)
-      {
-        double[] fl = pasimap.component(s);
-        for (int d = fl.length - 1; d >= 0; d--)
-        {
-          csv.append(",");
-          csv.append(fl[d]);
-        }
-      }
-      else
-      {
-        Point p = points.get(s).coord;
-        csv.append(",").append(p.x);
-        csv.append(",").append(p.y);
-        csv.append(",").append(p.z);
-      }
-      csv.append("\n");
-    }
-    return csv.toString();
+    av = data;
   }
-
-  public String getScoreModelName()
-  {
-    return scoreModel == null ? "" : scoreModel.getName();
-  }
-
-  public void setScoreModel(ScoreModelI sm)
-  {
-    this.scoreModel = sm;
-  }
-
-  public List<SequencePoint> getSequencePoints()
-  {
-    return points;
-  }
-
-  public void setSequencePoints(List<SequencePoint> sp)
-  {
-    points = sp;
-  }
-
-  /**
-   * Answers the object holding the values of the computed PaSiMap
-   * 
-   * @return
-   */
   public PaSiMap getPasimapData()
   {
-    return pasimap;
+    return outputModel;
   }
-
-  public void setPaSiMap(PaSiMap data)
+  public void setPasimapData(PaSiMap pasimapData)
   {
-    pasimap = data;
+    outputModel = pasimapData;
   }
 
-  public boolean isCancelled()
-  {
-    if (pasimap==null || pasimap.isCancelled())
-    {
-      return true;
-    }
-    return false;
-  }
-
-  public void cancel()
-  {
-    pasimap.cancel();    
-  }
-
-  public boolean canCancel()
-  {
-    return (!isCancelled() && pasimap.isCancellable());
-  }
 }
diff --git a/src/jalview/viewmodel/SpatialModel.java b/src/jalview/viewmodel/SpatialModel.java
new file mode 100644 (file)
index 0000000..082225b
--- /dev/null
@@ -0,0 +1,276 @@
+package jalview.viewmodel;
+
+import java.util.List;
+import java.util.Vector;
+
+import jalview.analysis.SpatialCalculationI;
+import jalview.api.RotatableCanvasI;
+import jalview.api.analysis.ScoreModelI;
+import jalview.api.analysis.SimilarityParamsI;
+import jalview.datamodel.AlignmentView;
+import jalview.datamodel.Point;
+import jalview.datamodel.SequenceI;
+import jalview.datamodel.SequencePoint;
+
+public abstract class SpatialModel<T extends SpatialCalculationI>
+{
+  /*
+   * inputs
+   */
+  protected AlignmentViewport av;
+  
+  protected AlignmentView inputData;
+
+  protected final SequenceI[] seqs;
+
+  protected final SimilarityParamsI similarityParams;
+
+  /*
+   * options - score model, nucleotide / protein
+   */
+  protected ScoreModelI scoreModel;
+
+  protected boolean nucleotide = false;
+
+  protected T outputModel;  
+
+  private int top;
+
+  private List<SequencePoint> points;
+
+  /**
+   * Constructor given sequence data, score model and score calculation
+   * parameter options.
+   * 
+   * @param seqData
+   * @param sqs
+   * @param nuc
+   * @param modelName
+   * @param params
+   */
+  public SpatialModel(AlignmentViewport av, AlignmentView seqData, SequenceI[] sqs, boolean nuc,
+          ScoreModelI modelName, SimilarityParamsI params)
+  {
+    inputData = seqData;
+    seqs = sqs;
+    nucleotide = nuc;
+    scoreModel = modelName;
+    similarityParams = params;
+  }
+
+  public abstract T constructModel();
+  
+  public void calculate()
+  {
+    outputModel = constructModel();
+    outputModel.run(); // executes in same thread, wait for completion
+    if (outputModel.isCancelled())
+    {
+      // no more work to do
+      return;
+    }
+
+    // Now find the component coordinates
+//    // delete this ?
+//    int ii = 0;
+//
+//    while ((ii < seqs.length) && (seqs[ii] != null))
+//    {
+//      ii++;
+//    }
+    
+    // NB height for PaSiMap is Width.
+    int height = outputModel.getHeight();
+    int width = outputModel.getWidth();
+    
+    // top = pca.getM().height() - 1;
+    // top = pasimap.getWidth() - 1;
+    top = outputModel.getTop();
+
+    points = new Vector<>();
+    Point[] scores = outputModel.getComponents(top - 1, top - 2, top - 3, 1f);
+
+    for (int i = 0; i < height; i++)
+    {
+      SequencePoint sp = new SequencePoint(seqs[i], scores[i]);
+      points.add(sp);
+    }
+  }
+
+  public void updateRc(RotatableCanvasI rc)
+  {
+    rc.setPoints(points, outputModel.getHeight());
+  }
+
+  public boolean isNucleotide()
+  {
+    return nucleotide;
+  }
+
+  public void setNucleotide(boolean nucleotide)
+  {
+    this.nucleotide = nucleotide;
+  }
+
+  /**
+   * Answers the index of the principal dimension of the PCA
+   * 
+   * @return
+   */
+  public int getTop()
+  {
+    return top;
+  }
+  /** ? used ?
+   * 
+   * @param t
+   */
+  public void setTop(int t)
+  {
+    top = t;
+  }
+
+  /**
+   * Updates the 3D coordinates for the list of points to the given dimensions.
+   * Principal dimension is getTop(). Next greatest eigenvector is getTop()-1.
+   * Note - pca.getComponents starts counting the spectrum from rank-2 to zero,
+   * rather than rank-1, so getComponents(dimN ...) == updateRcView(dimN+1 ..)
+   * 
+   * @param dim1
+   * @param dim2
+   * @param dim3
+   */
+  public void updateRcView(int dim1, int dim2, int dim3)
+  {
+    // note: actual indices for components are dim1-1, etc (patch for JAL-1123)
+    Point[] scores = outputModel.getComponents(dim1 - 1, dim2 - 1, dim3 - 1, 1);
+
+    for (int i = 0; i < outputModel.getHeight(); i++)
+    {
+      points.get(i).coord = scores[i];
+    }
+  }
+
+  public String getDetails()
+  {
+    return outputModel.getDetails();
+  }
+
+  public String getAlignmentOutput()
+  {
+    return outputModel.getAlignmentOutput();
+  }
+
+  public AlignmentViewport getInputAlignmentView()
+  {
+    return av;
+  }
+  public AlignmentView getInputData()
+  {
+    return inputData;
+  }
+
+  public String getPointsasCsv(boolean transformed, int xdim, int ydim,
+          int zdim)
+  {
+    StringBuffer csv = new StringBuffer();
+    csv.append("\"Sequence\"");
+    if (transformed)
+    {
+      csv.append(",");
+      csv.append(xdim);
+      csv.append(",");
+      csv.append(ydim);
+      csv.append(",");
+      csv.append(zdim);
+    }
+    else
+    {
+      // pca.component(1).length == pasimap.getDim
+      for (int d = 1, dmax = (int) outputModel.getDim(); d <= dmax; d++)
+      {
+        csv.append("," + d);
+      }
+    }
+    csv.append("\n");
+    for (int s = 0; s < seqs.length; s++)
+    {
+      csv.append("\"" + seqs[s].getName() + "\"");
+      if (!transformed)
+      {
+        double[] fl = outputModel.component(s);
+        for (int d = fl.length - 1; d >= 0; d--)
+        {
+          csv.append(",");
+          csv.append(fl[d]);
+        }
+      }
+      else
+      {
+        Point p = points.get(s).coord;
+        csv.append(",").append(p.x);
+        csv.append(",").append(p.y);
+        csv.append(",").append(p.z);
+      }
+      csv.append("\n");
+    }
+    return csv.toString();
+  }
+
+  public String getScoreModelName()
+  {
+    return scoreModel == null ? "" : scoreModel.getName();
+  }
+
+  public void setScoreModel(ScoreModelI sm)
+  {
+    this.scoreModel = sm;
+  }
+
+  /**
+   * Answers the parameters configured for pairwise similarity calculations
+   * 
+   * @return
+   */
+  public SimilarityParamsI getSimilarityParameters()
+  {
+    return similarityParams;
+  }
+
+  public List<SequencePoint> getSequencePoints()
+  {
+    return points;
+  }
+
+  public void setSequencePoints(List<SequencePoint> sp)
+  {
+    points = sp;
+  }
+  public T getOutputModel() {
+    return outputModel;
+  }
+  public void setOutputModel(T newModel)
+  {
+    outputModel = newModel;
+  }
+
+  public boolean isCancelled()
+  {
+    if (outputModel==null || outputModel.isCancelled())
+    {
+      return true;
+    }
+    return false;
+  }
+
+  public void cancel()
+  {
+    outputModel.cancel();    
+  }
+
+  public boolean canCancel()
+  {
+    return (!isCancelled() && outputModel.isCancellable());
+  }
+
+}