From: Jim Procter Date: Wed, 9 Jan 2019 16:24:44 +0000 (+0000) Subject: Merge branch 'feature/JAL-3063JAXB' into features/pca_jaxb_datasetrefs_JAL-3171_JAL... X-Git-Tag: Release_2_11_1_0~78^2~4 X-Git-Url: http://source.jalview.org/gitweb/?p=jalview.git;a=commitdiff_plain;h=3bb8cfd24aeaea364b7aad2a238d6412e1c639c7;hp=2f0267f5df56bb5969b3946434d45110bf994ce7 Merge branch 'feature/JAL-3063JAXB' into features/pca_jaxb_datasetrefs_JAL-3171_JAL-3063_JAL-1767 --- diff --git a/examples/testdata/projects/manyViews.jvp b/examples/testdata/projects/manyViews.jvp new file mode 100644 index 0000000..065b29c Binary files /dev/null and b/examples/testdata/projects/manyViews.jvp differ diff --git a/examples/testdata/projects/twoViews.jvp b/examples/testdata/projects/twoViews.jvp new file mode 100644 index 0000000..80333cd Binary files /dev/null and b/examples/testdata/projects/twoViews.jvp differ diff --git a/help/html/calculations/pca.html b/help/html/calculations/pca.html index 0104078..5b76d10 100755 --- a/help/html/calculations/pca.html +++ b/help/html/calculations/pca.html @@ -60,15 +60,15 @@ Calculating PCAs for aligned sequences
Jalview can perform PCA analysis on both proteins and nucleotide sequence alignments. In both cases, components are generated by an - eigenvector decomposition of the matrix formed from the sum of - substitution matrix scores at each aligned position between each - pair of sequences - computed with one of the available score - matrices, such as BLOSUM62, + eigenvector decomposition of the matrix formed from pairwise similarity + scores between each pair of sequences. The similarity score model is + selected on the calculations dialog, and + may use one of the available score matrices, such as + BLOSUM62, PAM250, or the simple single - nucleotide substitution matrix. The options available for - calculation are given in the Change - Parameters menu. + nucleotide substitution matrix, or by sequence percentage identity, + or sequence feature similarity.

diff --git a/resources/lang/Messages.properties b/resources/lang/Messages.properties index ae5b0e7..449c9b6 100644 --- a/resources/lang/Messages.properties +++ b/resources/lang/Messages.properties @@ -172,10 +172,9 @@ label.principal_component_analysis = Principal Component Analysis label.average_distance_identity = Average Distance Using % Identity label.neighbour_joining_identity = Neighbour Joining Using % Identity label.choose_calculation = Choose Calculation -label.treecalc_title = {0} Using {1} +label.calc_title = {0} Using {1} label.tree_calc_av = Average Distance label.tree_calc_nj = Neighbour Joining -label.select_score_model = Select score model label.score_model_pid = % Identity label.score_model_blosum62 = BLOSUM62 label.score_model_pam250 = PAM 250 @@ -879,7 +878,6 @@ label.error_unsupported_owwner_user_colour_scheme = Unsupported owner for User C label.save_alignment_to_file = Save Alignment to file label.save_features_to_file = Save Features to File label.save_annotation_to_file = Save Annotation to File -label.no_features_on_alignment = No features found on alignment label.save_pdb_file = Save PDB File label.save_text_to_file = Save Text to File label.save_state = Save State diff --git a/resources/lang/Messages_es.properties b/resources/lang/Messages_es.properties index 555977d..3c82386 100644 --- a/resources/lang/Messages_es.properties +++ b/resources/lang/Messages_es.properties @@ -169,10 +169,9 @@ label.principal_component_analysis = An label.average_distance_identity = Distancia Media Usando % de Identidad label.neighbour_joining_identity = Unir vecinos utilizando % de Identidad label.choose_calculation = Elegir el cálculo -label.treecalc_title = {0} utilizando {1} +label.calc_title = {0} utilizando {1} label.tree_calc_av = Distancia media label.tree_calc_nj = Unir vecinos -label.select_score_model = Selecciones modelo de puntuación label.score_model_pid = % Identidad label.score_model_blosum62 = BLOSUM62 label.score_model_pam250 = PAM 250 @@ -804,7 +803,6 @@ label.error_unsupported_owwner_user_colour_scheme = Propietario no soportado par label.save_alignment_to_file = Guardar Alineamiento en fichero label.save_features_to_file = Guardar Características en un fichero label.save_annotation_to_file = Guardar Anotación en un fichero -label.no_features_on_alignment = No se han encontrado características en el alineamiento label.save_pdb_file = Guardar fichero PDB label.save_text_to_file = Guardar Texto en un fichero label.save_state = Guardar estado diff --git a/schemas/jalview.xsd b/schemas/jalview.xsd index 7511ad2..07dee98 100755 --- a/schemas/jalview.xsd +++ b/schemas/jalview.xsd @@ -499,6 +499,51 @@ + + + + + + + + + + + + + + + + endpoints of X, Y and Z axes in that order + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -637,5 +682,46 @@ - + + + + parameters that condition a similarity score calculation + + + + + + + + + + + + + + + + The results of a PCA calculation + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/jalview/analysis/PCA.java b/src/jalview/analysis/PCA.java index 42a168d..d51f00e 100755 --- a/src/jalview/analysis/PCA.java +++ b/src/jalview/analysis/PCA.java @@ -22,7 +22,9 @@ package jalview.analysis; import jalview.api.analysis.ScoreModelI; import jalview.api.analysis.SimilarityParamsI; +import jalview.bin.Cache; import jalview.datamodel.AlignmentView; +import jalview.datamodel.Point; import jalview.math.MatrixI; import java.io.PrintStream; @@ -32,28 +34,37 @@ import java.io.PrintStream; */ public class PCA implements Runnable { - MatrixI symm; - - double[] eigenvalue; + /* + * inputs + */ + final private AlignmentView seqs; - MatrixI eigenvector; + final private ScoreModelI scoreModel; - StringBuilder details = new StringBuilder(1024); + final private SimilarityParamsI similarityParams; - final private AlignmentView seqs; + /* + * outputs + */ + private MatrixI pairwiseScores; - private ScoreModelI scoreModel; + private MatrixI tridiagonal; - private SimilarityParamsI similarityParams; + private MatrixI eigenMatrix; - public PCA(AlignmentView s, ScoreModelI sm, SimilarityParamsI options) + /** + * Constructor given the sequences to compute for, the similarity model to + * use, and a set of parameters for sequence comparison + * + * @param sequences + * @param sm + * @param options + */ + public PCA(AlignmentView sequences, ScoreModelI sm, SimilarityParamsI options) { - this.seqs = s; - this.similarityParams = options; + this.seqs = sequences; this.scoreModel = sm; - - details.append("PCA calculation using " + sm.getName() - + " sequence similarity matrix\n========\n\n"); + this.similarityParams = options; } /** @@ -66,7 +77,7 @@ public class PCA implements Runnable */ public double getEigenvalue(int i) { - return eigenvector.getD()[i]; + return eigenMatrix.getD()[i]; } /** @@ -83,15 +94,16 @@ public class PCA implements Runnable * * @return DOCUMENT ME! */ - public float[][] getComponents(int l, int n, int mm, float factor) + public Point[] getComponents(int l, int n, int mm, float factor) { - float[][] out = new float[getHeight()][3]; + Point[] out = new Point[getHeight()]; for (int i = 0; i < getHeight(); i++) { - out[i][0] = (float) component(i, l) * factor; - out[i][1] = (float) component(i, n) * factor; - out[i][2] = (float) component(i, mm) * factor; + float x = (float) component(i, l) * factor; + float y = (float) component(i, n) * factor; + float z = (float) component(i, mm) * factor; + out[i] = new Point(x, y, z); } return out; @@ -132,84 +144,111 @@ public class PCA implements Runnable { double out = 0.0; - for (int i = 0; i < symm.width(); i++) + for (int i = 0; i < pairwiseScores.width(); i++) { - out += (symm.getValue(row, i) * eigenvector.getValue(i, n)); + out += (pairwiseScores.getValue(row, i) * eigenMatrix.getValue(i, n)); } - return out / eigenvector.getD()[n]; + return out / eigenMatrix.getD()[n]; } + /** + * Answers a formatted text report of the PCA calculation results (matrices + * and eigenvalues) suitable for display + * + * @return + */ public String getDetails() { - return details.toString(); + StringBuilder sb = new StringBuilder(1024); + sb.append("PCA calculation using ").append(scoreModel.getName()) + .append(" sequence similarity matrix\n========\n\n"); + PrintStream ps = wrapOutputBuffer(sb); + + /* + * pairwise similarity scores + */ + sb.append(" --- OrigT * Orig ---- \n"); + pairwiseScores.print(ps, "%8.2f"); + + /* + * tridiagonal matrix, with D and E vectors + */ + sb.append(" ---Tridiag transform matrix ---\n"); + sb.append(" --- D vector ---\n"); + tridiagonal.printD(ps, "%15.4e"); + ps.println(); + sb.append("--- E vector ---\n"); + tridiagonal.printE(ps, "%15.4e"); + ps.println(); + + /* + * eigenvalues matrix, with D vector + */ + sb.append(" --- New diagonalization matrix ---\n"); + eigenMatrix.print(ps, "%8.2f"); + sb.append(" --- Eigenvalues ---\n"); + eigenMatrix.printD(ps, "%15.4e"); + ps.println(); + + return sb.toString(); } /** - * DOCUMENT ME! + * Performs the PCA calculation */ @Override public void run() { + try + { + /* + * sequence pairwise similarity scores + */ + pairwiseScores = scoreModel.findSimilarities(seqs, similarityParams); + + /* + * tridiagonal matrix + */ + tridiagonal = pairwiseScores.copy(); + tridiagonal.tred(); + + /* + * the diagonalization matrix + */ + eigenMatrix = tridiagonal.copy(); + eigenMatrix.tqli(); + } catch (Exception q) + { + Cache.log.error("Error computing PCA: " + q.getMessage()); + q.printStackTrace(); + } + } + + /** + * Returns a PrintStream that wraps (appends its output to) the given + * StringBuilder + * + * @param sb + * @return + */ + protected PrintStream wrapOutputBuffer(StringBuilder sb) + { PrintStream ps = new PrintStream(System.out) { @Override public void print(String x) { - details.append(x); + sb.append(x); } @Override public void println() { - details.append("\n"); + sb.append("\n"); } }; - - // long now = System.currentTimeMillis(); - try - { - eigenvector = scoreModel.findSimilarities(seqs, similarityParams); - - details.append(" --- OrigT * Orig ---- \n"); - eigenvector.print(ps, "%8.2f"); - - symm = eigenvector.copy(); - - eigenvector.tred(); - - details.append(" ---Tridiag transform matrix ---\n"); - details.append(" --- D vector ---\n"); - eigenvector.printD(ps, "%15.4e"); - ps.println(); - details.append("--- E vector ---\n"); - eigenvector.printE(ps, "%15.4e"); - ps.println(); - - // Now produce the diagonalization matrix - eigenvector.tqli(); - } catch (Exception q) - { - q.printStackTrace(); - details.append("\n*** Unexpected exception when performing PCA ***\n" - + q.getLocalizedMessage()); - details.append( - "*** Matrices below may not be fully diagonalised. ***\n"); - } - - details.append(" --- New diagonalization matrix ---\n"); - eigenvector.print(ps, "%8.2f"); - details.append(" --- Eigenvalues ---\n"); - eigenvector.printD(ps, "%15.4e"); - ps.println(); - /* - * for (int seq=0;seq(); + models = new LinkedHashMap<>(); BLOSUM62 = loadScoreMatrix("scoreModel/blosum62.scm"); PAM250 = loadScoreMatrix("scoreModel/pam250.scm"); - registerScoreModel(new PIDModel()); DNA = loadScoreMatrix("scoreModel/dna.scm"); + registerScoreModel(new PIDModel()); registerScoreModel(new FeatureDistanceModel()); } diff --git a/src/jalview/analysis/scoremodels/SimilarityParams.java b/src/jalview/analysis/scoremodels/SimilarityParams.java index 58b08dd..5c47703 100644 --- a/src/jalview/analysis/scoremodels/SimilarityParams.java +++ b/src/jalview/analysis/scoremodels/SimilarityParams.java @@ -147,4 +147,57 @@ public class SimilarityParams implements SimilarityParamsI { return matchGaps; } + + /** + * IDE-generated hashCode method + */ + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + (denominateByShortestLength ? 1231 : 1237); + result = prime * result + (includeGappedColumns ? 1231 : 1237); + result = prime * result + (includeGaps ? 1231 : 1237); + result = prime * result + (matchGaps ? 1231 : 1237); + return result; + } + + /** + * IDE-generated equals method + */ + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (getClass() != obj.getClass()) + { + return false; + } + SimilarityParams other = (SimilarityParams) obj; + if (denominateByShortestLength != other.denominateByShortestLength) + { + return false; + } + if (includeGappedColumns != other.includeGappedColumns) + { + return false; + } + if (includeGaps != other.includeGaps) + { + return false; + } + if (matchGaps != other.matchGaps) + { + return false; + } + return true; + } } diff --git a/src/jalview/api/RotatableCanvasI.java b/src/jalview/api/RotatableCanvasI.java index a57bcdb..c6eb6de 100644 --- a/src/jalview/api/RotatableCanvasI.java +++ b/src/jalview/api/RotatableCanvasI.java @@ -1,6 +1,6 @@ /* - * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) - * Copyright (C) $$Year-Rel$$ The Jalview Authors + * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2b1) + * Copyright (C) 2014 The Jalview Authors * * This file is part of Jalview. * @@ -22,7 +22,7 @@ package jalview.api; import jalview.datamodel.SequencePoint; -import java.util.Vector; +import java.util.List; /** * interface implemented by RotatatableCanvas GUI elements (such as point clouds @@ -33,7 +33,23 @@ import java.util.Vector; */ public interface RotatableCanvasI { + void setPoints(List points, int rows); - void setPoints(Vector points, int rows); + /** + * Zoom the view in (or out) by the given factor, which should be >= 0. A + * factor greater than 1 zooms in (expands the display), a factor less than 1 + * zooms out (shrinks the display). + * + * @param factor + */ + void zoom(float factor); + /** + * Rotates the view by the specified number of degrees about the x and/or y + * axis + * + * @param x + * @param y + */ + void rotate(float x, float y); } diff --git a/src/jalview/appletgui/PCAPanel.java b/src/jalview/appletgui/PCAPanel.java index fc1d359..7c0dfa9 100644 --- a/src/jalview/appletgui/PCAPanel.java +++ b/src/jalview/appletgui/PCAPanel.java @@ -134,7 +134,7 @@ public class PCAPanel extends EmbmenuFrame { nuclSetting.setState(pcaModel.isNucleotide()); protSetting.setState(!pcaModel.isNucleotide()); - pcaModel.run(); + pcaModel.calculate(); // //////////////// xCombobox.select(0); yCombobox.select(1); @@ -167,9 +167,7 @@ public class PCAPanel extends EmbmenuFrame int dim2 = top - yCombobox.getSelectedIndex(); int dim3 = top - zCombobox.getSelectedIndex(); pcaModel.updateRcView(dim1, dim2, dim3); - rc.img = null; - rc.rotmat.setIdentity(); - rc.initAxes(); + rc.resetView(); rc.paint(rc.getGraphics()); } @@ -281,7 +279,7 @@ public class PCAPanel extends EmbmenuFrame { } ; - Object[] alAndColsel = pcaModel.getSeqtrings() + Object[] alAndColsel = pcaModel.getInputData() .getAlignmentAndHiddenColumns(gc); if (alAndColsel != null && alAndColsel[0] != null) diff --git a/src/jalview/appletgui/RotatableCanvas.java b/src/jalview/appletgui/RotatableCanvas.java index afb4e95..34f8ea5 100755 --- a/src/jalview/appletgui/RotatableCanvas.java +++ b/src/jalview/appletgui/RotatableCanvas.java @@ -21,11 +21,12 @@ package jalview.appletgui; import jalview.api.RotatableCanvasI; +import jalview.datamodel.Point; import jalview.datamodel.SequenceGroup; import jalview.datamodel.SequenceI; import jalview.datamodel.SequencePoint; import jalview.math.RotatableMatrix; -import jalview.util.Format; +import jalview.math.RotatableMatrix.Axis; import jalview.util.MessageManager; import jalview.viewmodel.AlignmentViewport; @@ -40,32 +41,26 @@ import java.awt.event.KeyListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; -import java.util.Vector; +import java.util.List; public class RotatableCanvas extends Panel implements MouseListener, MouseMotionListener, KeyListener, RotatableCanvasI { - RotatableMatrix idmat = new RotatableMatrix(3, 3); - - RotatableMatrix objmat = new RotatableMatrix(3, 3); - - RotatableMatrix rotmat = new RotatableMatrix(3, 3); + private static final int DIMS = 3; String tooltip; - int toolx, tooly; + int toolx; + + int tooly; // RubberbandRectangle rubberband; boolean drawAxes = true; - int omx = 0; - - int mx = 0; - - int omy = 0; + int mouseX = 0; - int my = 0; + int mouseY = 0; Image img; @@ -73,13 +68,13 @@ public class RotatableCanvas extends Panel implements MouseListener, Dimension prefsize; - float centre[] = new float[3]; + Point centre; - float width[] = new float[3]; + float[] width = new float[DIMS]; - float max[] = new float[3]; + float[] max = new float[DIMS]; - float min[] = new float[3]; + float[] min = new float[DIMS]; float maxwidth; @@ -87,11 +82,11 @@ public class RotatableCanvas extends Panel implements MouseListener, int npoint; - Vector points; + List points; - float[][] orig; + Point[] orig; - float[][] axes; + Point[] axisEndPoints; int startx; @@ -115,9 +110,10 @@ public class RotatableCanvas extends Panel implements MouseListener, boolean showLabels = false; - public RotatableCanvas(AlignmentViewport av) + public RotatableCanvas(AlignmentViewport viewport) { - this.av = av; + this.av = viewport; + axisEndPoints = new Point[DIMS]; } public void showLabels(boolean b) @@ -126,46 +122,23 @@ public class RotatableCanvas extends Panel implements MouseListener, repaint(); } - public void setPoints(Vector points, int npoint) + @Override + public void setPoints(List points, int npoint) { this.points = points; this.npoint = npoint; PaintRefresher.Register(this, av.getSequenceSetId()); prefsize = getPreferredSize(); - orig = new float[npoint][3]; + orig = new Point[npoint]; for (int i = 0; i < npoint; i++) { - SequencePoint sp = (SequencePoint) points.elementAt(i); - for (int j = 0; j < 3; j++) - { - orig[i][j] = sp.coord[j]; - } - } - // Initialize the matrices to identity - - for (int i = 0; i < 3; i++) - { - for (int j = 0; j < 3; j++) - { - if (i != j) - { - idmat.addElement(i, j, 0); - objmat.addElement(i, j, 0); - rotmat.addElement(i, j, 0); - } - else - { - idmat.addElement(i, j, 0); - objmat.addElement(i, j, 0); - rotmat.addElement(i, j, 0); - } - } + SequencePoint sp = points.get(i); + orig[i] = sp.coord; } - axes = new float[3][3]; - initAxes(); + resetAxes(); findCentre(); findWidth(); @@ -195,115 +168,93 @@ public class RotatableCanvas extends Panel implements MouseListener, * super.removeNotify(); } */ - public void initAxes() + /** + * Resets axes to the initial state: x-axis to the right, y-axis up, z-axis to + * back (so obscured in a 2-D display) + */ + public void resetAxes() { - for (int i = 0; i < 3; i++) - { - for (int j = 0; j < 3; j++) - { - if (i != j) - { - axes[i][j] = 0; - } - else - { - axes[i][j] = 1; - } - } - } + axisEndPoints[0] = new Point(1f, 0f, 0f); + axisEndPoints[1] = new Point(0f, 1f, 0f); + axisEndPoints[2] = new Point(0f, 0f, 1f); } + /** + * Computes and saves the maximum and minimum (x, y, z) positions of any + * sequence point, and also the min-max range (width) for each dimension, and + * the maximum width for all dimensions + */ public void findWidth() { max = new float[3]; min = new float[3]; - max[0] = (float) -1e30; - max[1] = (float) -1e30; - max[2] = (float) -1e30; + max[0] = Float.MIN_VALUE; + max[1] = Float.MIN_VALUE; + max[2] = Float.MIN_VALUE; - min[0] = (float) 1e30; - min[1] = (float) 1e30; - min[2] = (float) 1e30; + min[0] = Float.MAX_VALUE; + min[1] = Float.MAX_VALUE; + min[2] = Float.MAX_VALUE; - for (int i = 0; i < 3; i++) + for (SequencePoint sp : points) { - for (int j = 0; j < npoint; j++) - { - SequencePoint sp = (SequencePoint) points.elementAt(j); - if (sp.coord[i] >= max[i]) - { - max[i] = sp.coord[i]; - } - if (sp.coord[i] <= min[i]) - { - min[i] = sp.coord[i]; - } - } + max[0] = Math.max(max[0], sp.coord.x); + max[1] = Math.max(max[1], sp.coord.y); + max[2] = Math.max(max[2], sp.coord.z); + min[0] = Math.min(min[0], sp.coord.x); + min[1] = Math.min(min[1], sp.coord.y); + min[2] = Math.min(min[2], sp.coord.z); } - // System.out.println("xmax " + max[0] + " min " + min[0]); - // System.out.println("ymax " + max[1] + " min " + min[1]); - // System.out.println("zmax " + max[2] + " min " + min[2]); - width[0] = Math.abs(max[0] - min[0]); width[1] = Math.abs(max[1] - min[1]); width[2] = Math.abs(max[2] - min[2]); - maxwidth = width[0]; - - if (width[1] > width[0]) - { - maxwidth = width[1]; - } - if (width[2] > width[1]) - { - maxwidth = width[2]; - } - - // System.out.println("Maxwidth = " + maxwidth); + maxwidth = Math.max(width[0], Math.max(width[1], width[2])); } public float findScale() { - int dim, width, height; + int dim, w, height; if (getSize().width != 0) { - width = getSize().width; + w = getSize().width; height = getSize().height; } else { - width = prefsize.width; + w = prefsize.width; height = prefsize.height; } - if (width < height) + if (w < height) { - dim = width; + dim = w; } else { dim = height; } - return (float) (dim * scalefactor / (2 * maxwidth)); + return dim * scalefactor / (2 * maxwidth); } + /** + * Computes and saves the position of the centre of the view + */ public void findCentre() { - // Find centre coordinate findWidth(); - centre[0] = (max[0] + min[0]) / 2; - centre[1] = (max[1] + min[1]) / 2; - centre[2] = (max[2] + min[2]) / 2; + float x = (max[0] + min[0]) / 2; + float y = (max[1] + min[1]) / 2; + float z = (max[2] + min[2]) / 2; - // System.out.println("Centre x " + centre[0]); - // System.out.println("Centre y " + centre[1]); - // System.out.println("Centre z " + centre[2]); + centre = new Point(x, y, z); } + @Override public Dimension getPreferredSize() { if (prefsize != null) @@ -316,16 +267,19 @@ public class RotatableCanvas extends Panel implements MouseListener, } } + @Override public Dimension getMinimumSize() { return getPreferredSize(); } + @Override public void update(Graphics g) { paint(g); } + @Override public void paint(Graphics g) { if (points == null) @@ -355,7 +309,7 @@ public class RotatableCanvas extends Panel implements MouseListener, drawBackground(ig, Color.black); drawScene(ig); - if (drawAxes == true) + if (drawAxes) { drawAxes(ig); } @@ -377,8 +331,8 @@ public class RotatableCanvas extends Panel implements MouseListener, for (int i = 0; i < 3; i++) { g.drawLine(getSize().width / 2, getSize().height / 2, - (int) (axes[i][0] * scale * max[0] + getSize().width / 2), - (int) (axes[i][1] * scale * max[1] + getSize().height / 2)); + (int) (axisEndPoints[i].x * scale * max[0] + getSize().width / 2), + (int) (axisEndPoints[i].y * scale * max[1] + getSize().height / 2)); } } @@ -390,81 +344,85 @@ public class RotatableCanvas extends Panel implements MouseListener, public void drawScene(Graphics g) { - // boolean darker = false; - - int halfwidth = getSize().width / 2; - int halfheight = getSize().height / 2; - for (int i = 0; i < npoint; i++) { - SequencePoint sp = (SequencePoint) points.elementAt(i); - int x = (int) ((float) (sp.coord[0] - centre[0]) * scale) + halfwidth; - int y = (int) ((float) (sp.coord[1] - centre[1]) * scale) - + halfheight; - float z = sp.coord[1] - centre[2]; - - if (av.getSequenceColour(sp.sequence) == Color.black) - { - g.setColor(Color.white); - } - else - { - g.setColor(av.getSequenceColour(sp.sequence)); - } - + SequencePoint sp = points.get(i); + SequenceI sequence = sp.getSequence(); + Color sequenceColour = av.getSequenceColour(sequence); + g.setColor( + sequenceColour == Color.black ? Color.white : sequenceColour); if (av.getSelectionGroup() != null) { if (av.getSelectionGroup().getSequences(null) - .contains(((SequencePoint) points.elementAt(i)).sequence)) + .contains(sequence)) { g.setColor(Color.gray); } } - if (z < 0) + + if (sp.coord.z < centre.z) { g.setColor(g.getColor().darker()); } + int halfwidth = getSize().width / 2; + int halfheight = getSize().height / 2; + int x = (int) ((sp.coord.x - centre.x) * scale) + halfwidth; + int y = (int) ((sp.coord.y - centre.y) * scale) + halfheight; g.fillRect(x - 3, y - 3, 6, 6); + if (showLabels) { g.setColor(Color.red); - g.drawString( - ((SequencePoint) points.elementAt(i)).sequence.getName(), - x - 3, y - 4); + g.drawString(sequence.getName(), x - 3, y - 4); } } } - public Dimension minimumsize() - { - return prefsize; - } - - public Dimension preferredsize() - { - return prefsize; - } - + @Override public void keyTyped(KeyEvent evt) { } + @Override public void keyReleased(KeyEvent evt) { } + @Override public void keyPressed(KeyEvent evt) { - if (evt.getKeyCode() == KeyEvent.VK_UP) + boolean shiftDown = evt.isShiftDown(); + int keyCode = evt.getKeyCode(); + if (keyCode == KeyEvent.VK_UP) + { + if (shiftDown) + { + rotate(0f, -1f); + } + else + { + zoom(1.1f); + } + } + else if (keyCode == KeyEvent.VK_DOWN) { - scalefactor = (float) (scalefactor * 1.1); - scale = findScale(); + if (shiftDown) + { + rotate(0f, 1f); + } + else + { + zoom(0.9f); + } } - else if (evt.getKeyCode() == KeyEvent.VK_DOWN) + else if (shiftDown && keyCode == KeyEvent.VK_LEFT) { - scalefactor = (float) (scalefactor * 0.9); - scale = findScale(); + rotate(1f, 0f); + } + else if (shiftDown && keyCode == KeyEvent.VK_RIGHT) + { + rotate(-1f, 0f); } else if (evt.getKeyChar() == 's') { @@ -478,46 +436,34 @@ public class RotatableCanvas extends Panel implements MouseListener, repaint(); } - public void printPoints() - { - for (int i = 0; i < npoint; i++) - { - SequencePoint sp = (SequencePoint) points.elementAt(i); - Format.print(System.out, "%5d ", i); - for (int j = 0; j < 3; j++) - { - Format.print(System.out, "%13.3f ", sp.coord[j]); - } - System.out.println(); - } - } - + @Override public void mouseClicked(MouseEvent evt) { } + @Override public void mouseEntered(MouseEvent evt) { } + @Override public void mouseExited(MouseEvent evt) { } + @Override public void mouseReleased(MouseEvent evt) { } + @Override public void mousePressed(MouseEvent evt) { int x = evt.getX(); int y = evt.getY(); - mx = x; - my = y; - - omx = mx; - omy = my; + mouseX = x; + mouseY = y; startx = x; starty = y; @@ -528,7 +474,7 @@ public class RotatableCanvas extends Panel implements MouseListener, rectx2 = -1; recty2 = -1; - SequenceI found = findPoint(x, y); + SequenceI found = findSequenceAtPoint(x, y); if (found != null) { @@ -552,9 +498,10 @@ public class RotatableCanvas extends Panel implements MouseListener, repaint(); } + @Override public void mouseMoved(MouseEvent evt) { - SequenceI found = findPoint(evt.getX(), evt.getY()); + SequenceI found = findSequenceAtPoint(evt.getX(), evt.getY()); if (found == null) { tooltip = null; @@ -568,40 +515,22 @@ public class RotatableCanvas extends Panel implements MouseListener, repaint(); } + @Override public void mouseDragged(MouseEvent evt) { - mx = evt.getX(); - my = evt.getY(); - - rotmat.setIdentity(); + int xPos = evt.getX(); + int yPos = evt.getY(); - rotmat.rotate((float) (my - omy), 'x'); - rotmat.rotate((float) (mx - omx), 'y'); - - for (int i = 0; i < npoint; i++) + if (xPos == mouseX && yPos == mouseY) { - SequencePoint sp = (SequencePoint) points.elementAt(i); - sp.coord[0] -= centre[0]; - sp.coord[1] -= centre[1]; - sp.coord[2] -= centre[2]; - - // Now apply the rotation matrix - sp.coord = rotmat.vectorMultiply(sp.coord); - - // Now translate back again - sp.coord[0] += centre[0]; - sp.coord[1] += centre[1]; - sp.coord[2] += centre[2]; + return; } - for (int i = 0; i < 3; i++) - { - axes[i] = rotmat.vectorMultiply(axes[i]); - } - omx = mx; - omy = my; + int xDelta = xPos - mouseX; + int yDelta = yPos - mouseY; - paint(this.getGraphics()); + rotate(xDelta, yDelta); + repaint(); } public void rectSelect(int x1, int y1, int x2, int y2) @@ -609,29 +538,38 @@ public class RotatableCanvas extends Panel implements MouseListener, // boolean changedSel = false; for (int i = 0; i < npoint; i++) { - SequencePoint sp = (SequencePoint) points.elementAt(i); - int tmp1 = (int) ((sp.coord[0] - centre[0]) * scale - + (float) getSize().width / 2.0); - int tmp2 = (int) ((sp.coord[1] - centre[1]) * scale - + (float) getSize().height / 2.0); + SequencePoint sp = points.get(i); + int tmp1 = (int) ((sp.coord.x - centre.x) * scale + + getSize().width / 2.0); + int tmp2 = (int) ((sp.coord.y - centre.y) * scale + + getSize().height / 2.0); + SequenceI sequence = sp.getSequence(); if (tmp1 > x1 && tmp1 < x2 && tmp2 > y1 && tmp2 < y2) { if (av != null) { if (!av.getSelectionGroup().getSequences(null) - .contains(sp.sequence)) + .contains(sequence)) { - av.getSelectionGroup().addSequence(sp.sequence, true); + av.getSelectionGroup().addSequence(sequence, true); } } } } } - public SequenceI findPoint(int x, int y) + /** + * Answers the first sequence found whose point on the display is within 2 + * pixels of the given coordinates, or null if none is found + * + * @param x + * @param y + * + * @return + */ + public SequenceI findSequenceAtPoint(int x, int y) { - int halfwidth = getSize().width / 2; int halfheight = getSize().height / 2; @@ -640,20 +578,22 @@ public class RotatableCanvas extends Panel implements MouseListener, for (int i = 0; i < npoint; i++) { - SequencePoint sp = (SequencePoint) points.elementAt(i); - int px = (int) ((float) (sp.coord[0] - centre[0]) * scale) + SequencePoint sp = points.get(i); + int px = (int) ((sp.coord.x - centre.x) * scale) + halfwidth; - int py = (int) ((float) (sp.coord[1] - centre[1]) * scale) + int py = (int) ((sp.coord.y - centre.y) * scale) + halfheight; if (Math.abs(px - x) < 3 && Math.abs(py - y) < 3) { found = i; + break; } } + if (found != -1) { - return ((SequencePoint) points.elementAt(found)).sequence; + return points.get(found).getSequence(); } else { @@ -661,4 +601,79 @@ public class RotatableCanvas extends Panel implements MouseListener, } } + /** + * Resets the view to initial state (no rotation) + */ + public void resetView() + { + img = null; + resetAxes(); + } + + @Override + public void zoom(float factor) + { + if (factor > 0f) + { + scalefactor *= factor; + } + scale = findScale(); + } + + @Override + public void rotate(float x, float y) + { + if (x == 0f && y == 0f) + { + return; + } + + /* + * get the identity transformation... + */ + RotatableMatrix rotmat = new RotatableMatrix(); + + /* + * rotate around the X axis for change in Y + * (mouse movement up/down); note we are equating a + * number of pixels with degrees of rotation here! + */ + if (y != 0) + { + rotmat.rotate(y, Axis.X); + } + + /* + * rotate around the Y axis for change in X + * (mouse movement left/right) + */ + if (x != 0) + { + rotmat.rotate(x, Axis.Y); + } + + /* + * apply the composite transformation to sequence points + */ + for (int i = 0; i < npoint; i++) + { + SequencePoint sp = points.get(i); + sp.translate(-centre.x, -centre.y, -centre.z); + + // Now apply the rotation matrix + sp.coord = rotmat.vectorMultiply(sp.coord); + + // Now translate back again + sp.translate(centre.x, centre.y, centre.z); + } + + /* + * rotate the x/y/z axis positions + */ + for (int i = 0; i < DIMS; i++) + { + axisEndPoints[i] = rotmat.vectorMultiply(axisEndPoints[i]); + } + } + } diff --git a/src/jalview/datamodel/Point.java b/src/jalview/datamodel/Point.java new file mode 100644 index 0000000..e7c77c0 --- /dev/null +++ b/src/jalview/datamodel/Point.java @@ -0,0 +1,71 @@ +package jalview.datamodel; + +/** + * A bean that models an (x, y, z) position in 3-D space + */ +public final class Point +{ + public final float x; + + public final float y; + + public final float z; + + public Point(float xVal, float yVal, float zVal) + { + x = xVal; + y = yVal; + z = zVal; + } + + /** + * toString for convenience of inspection in debugging or logging + */ + @Override + public String toString() + { + return String.format("[%f, %f, %f]", x, y, z); + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + Float.floatToIntBits(x); + result = prime * result + Float.floatToIntBits(y); + result = prime * result + Float.floatToIntBits(z); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (getClass() != obj.getClass()) + { + return false; + } + Point other = (Point) obj; + if (Float.floatToIntBits(x) != Float.floatToIntBits(other.x)) + { + return false; + } + if (Float.floatToIntBits(y) != Float.floatToIntBits(other.y)) + { + return false; + } + if (Float.floatToIntBits(z) != Float.floatToIntBits(other.z)) + { + return false; + } + return true; + } +} diff --git a/src/jalview/datamodel/SequencePoint.java b/src/jalview/datamodel/SequencePoint.java index a6b967e..3db7cee 100755 --- a/src/jalview/datamodel/SequencePoint.java +++ b/src/jalview/datamodel/SequencePoint.java @@ -21,33 +21,69 @@ package jalview.datamodel; /** - * DOCUMENT ME! - * - * @author $author$ - * @version $Revision$ + * A bean that models a set of (x, y, z) values and a reference to a sequence. + * As used in Principal Component Analysis, the (x, y, z) values are the + * sequence's score for the currently selected first, second and third + * dimensions of the PCA. */ public class SequencePoint { - // SMJS PUBLIC + /* + * Associated alignment sequence, or dummy sequence object + */ + private final SequenceI sequence; + + /* + * x, y, z values + */ + public Point coord; + /** - * for points with no real physical association with an alignment sequence + * Constructor + * + * @param sequence + * @param coord */ - public boolean isPlaceholder = false; + public SequencePoint(SequenceI sequence, Point pt) + { + this.sequence = sequence; + this.coord = pt; + } /** - * Associated alignment sequence, or dummy sequence object. + * Constructor given a sequence and an array of x, y, z coordinate positions + * + * @param sequence + * @param coords + * @throws ArrayIndexOutOfBoundsException + * if array length is less than 3 */ - public SequenceI sequence; + public SequencePoint(SequenceI sequence, float[] coords) + { + this(sequence, new Point(coords[0], coords[1], coords[2])); + } + + public SequenceI getSequence() + { + return sequence; + } /** - * array of coordinates in embedded sequence space. + * Applies a translation to the (x, y, z) coordinates + * + * @param centre */ - public float[] coord; + public void translate(float x, float y, float z) + { + coord = new Point(coord.x + x, coord.y + y, coord.z + z); + } - // SMJS ENDPUBLIC - public SequencePoint(SequenceI sequence, float[] coord) + /** + * string representation for ease of inspection in debugging or logging only + */ + @Override + public String toString() { - this.sequence = sequence; - this.coord = coord; + return sequence.getName() + " " + coord.toString(); } } diff --git a/src/jalview/gui/CalculationChooser.java b/src/jalview/gui/CalculationChooser.java index 183419e..336a312 100644 --- a/src/jalview/gui/CalculationChooser.java +++ b/src/jalview/gui/CalculationChooser.java @@ -25,6 +25,7 @@ import jalview.analysis.scoremodels.ScoreModels; import jalview.analysis.scoremodels.SimilarityParams; import jalview.api.analysis.ScoreModelI; import jalview.api.analysis.SimilarityParamsI; +import jalview.bin.Cache; import jalview.datamodel.SequenceGroup; import jalview.util.MessageManager; @@ -103,7 +104,7 @@ public class CalculationChooser extends JPanel final ComboBoxTooltipRenderer renderer = new ComboBoxTooltipRenderer(); - List tips = new ArrayList(); + List tips = new ArrayList<>(); /* * the most recently opened PCA results panel @@ -375,7 +376,7 @@ public class CalculationChooser extends JPanel */ protected JComboBox buildModelOptionsList() { - final JComboBox scoreModelsCombo = new JComboBox(); + final JComboBox scoreModelsCombo = new JComboBox<>(); scoreModelsCombo.setRenderer(renderer); /* @@ -418,39 +419,42 @@ public class CalculationChooser extends JPanel { Object curSel = comboBox.getSelectedItem(); toolTips.clear(); - DefaultComboBoxModel model = new DefaultComboBoxModel(); + DefaultComboBoxModel model = new DefaultComboBoxModel<>(); + + /* + * select the score models applicable to the alignment type + */ + boolean nucleotide = af.getViewport().getAlignment().isNucleotide(); + List models = getApplicableScoreModels(nucleotide, + pca.isSelected()); /* * now we can actually add entries to the combobox, * remembering their descriptions for tooltips */ - ScoreModels scoreModels = ScoreModels.getInstance(); boolean selectedIsPresent = false; - for (ScoreModelI sm : scoreModels.getModels()) + for (ScoreModelI sm : models) { - boolean nucleotide = af.getViewport().getAlignment().isNucleotide(); - if (sm.isDNA() && nucleotide || sm.isProtein() && !nucleotide) + if (curSel != null && sm.getName().equals(curSel)) + { + selectedIsPresent = true; + curSel = sm.getName(); + } + model.addElement(sm.getName()); + + /* + * tooltip is description if provided, else text lookup with + * fallback on the model name + */ + String tooltip = sm.getDescription(); + if (tooltip == null) { - if (curSel != null && sm.getName().equals(curSel)) - { - selectedIsPresent = true; - curSel = sm.getName(); - } - model.addElement(sm.getName()); - - /* - * tooltip is description if provided, else text lookup with - * fallback on the model name - */ - String tooltip = sm.getDescription(); - if (tooltip == null) - { - tooltip = MessageManager.getStringOrReturn("label.score_model_", - sm.getName()); - } - toolTips.add(tooltip); + tooltip = MessageManager.getStringOrReturn("label.score_model_", + sm.getName()); } + toolTips.add(tooltip); } + if (selectedIsPresent) { model.setSelectedItem(curSel); @@ -460,6 +464,47 @@ public class CalculationChooser extends JPanel } /** + * Builds a list of score models which are applicable for the alignment and + * calculation type (peptide or generic models for protein, nucleotide or + * generic models for nucleotide). + *

+ * As a special case, includes BLOSUM62 as an extra option for nucleotide PCA. + * This is for backwards compatibility with Jalview prior to 2.8 when BLOSUM62 + * was the only score matrix supported. This is included if property + * BLOSUM62_PCA_FOR_NUCLEOTIDE is set to true in the Jalview properties file. + * + * @param nucleotide + * @param forPca + * @return + */ + protected static List getApplicableScoreModels( + boolean nucleotide, boolean forPca) + { + List filtered = new ArrayList<>(); + + ScoreModels scoreModels = ScoreModels.getInstance(); + for (ScoreModelI sm : scoreModels.getModels()) + { + if (!nucleotide && sm.isProtein() || nucleotide && sm.isDNA()) + { + filtered.add(sm); + } + } + + /* + * special case: add BLOSUM62 as last option for nucleotide PCA, + * for backwards compatibility with Jalview < 2.8 (JAL-2962) + */ + if (nucleotide && forPca + && Cache.getDefault("BLOSUM62_PCA_FOR_NUCLEOTIDE", false)) + { + filtered.add(scoreModels.getBlosum62()); + } + + return filtered; + } + + /** * Open and calculate the selected tree or PCA on 'OK' */ protected void calculate_actionPerformed() @@ -539,7 +584,13 @@ public class CalculationChooser extends JPanel JvOptionPane.WARNING_MESSAGE); return; } + + /* + * construct the panel and kick off its calculation thread + */ pcaPanel = new PCAPanel(af.alignPanel, modelName, params); + new Thread(pcaPanel).start(); + } /** diff --git a/src/jalview/gui/Jalview2XML.java b/src/jalview/gui/Jalview2XML.java index 87b8d87..7f07a20 100644 --- a/src/jalview/gui/Jalview2XML.java +++ b/src/jalview/gui/Jalview2XML.java @@ -20,9 +20,18 @@ */ 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; @@ -169,6 +194,12 @@ public class Jalview2XML private static final String UTF_8 = "UTF-8"; + /** + * prefix for recovering datasets for alignments with multiple views where + * non-existent dataset IDs were written for some views + */ + private static final String UNIQSEQSETID = "uniqueSeqSetId."; + // use this with nextCounter() to make unique names for entities private int counter = 0; @@ -1124,6 +1155,7 @@ public class Jalview2XML tree.setXpos(tp.getX()); tree.setYpos(tp.getY()); tree.setId(makeHashCode(tp, null)); + tree.setLinkToAllViews(tp.getTreeCanvas().applyToAllViews); jms.addTree(tree); } } @@ -1131,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 @@ -1508,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.getPcaModel(); + viewer.setScoreModelName(pcaModel.getScoreModelName()); + viewer.setXDim(panel.getSelectedDimensionIndex(X)); + viewer.setYDim(panel.getSelectedDimensionIndex(Y)); + viewer.setZDim(panel.getSelectedDimensionIndex(Z)); + viewer.setBgColour(panel.getRotatableCanvas().getBackgroundColour().getRGB()); + viewer.setScaleFactor(panel.getRotatableCanvas().getScaleFactor()); + float[] spMin = panel.getRotatableCanvas().getSeqMin(); + SeqPointMin spmin = new SeqPointMin(); + spmin.setXPos(spMin[0]); + spmin.setYPos(spMin[1]); + spmin.setZPos(spMin[2]); + viewer.setSeqPointMin(spmin); + float[] spMax = panel.getRotatableCanvas().getSeqMax(); + SeqPointMax spmax = new SeqPointMax(); + spmax.setXPos(spMax[0]); + spmax.setYPos(spMax[1]); + spmax.setZPos(spMax[2]); + viewer.setSeqPointMax(spmax); + viewer.setShowLabels(panel.getRotatableCanvas().isShowLabels()); + viewer.setLinkToAllViews(panel.getRotatableCanvas().isApplyToAllViews()); + 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.getRotatableCanvas().getAxisEndPoints()) + { + 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 *