X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fproject%2FJalview2XML.java;h=0e062aca838b748f0933f3e41ea8e017780cc495;hb=aada6c7d3345363bf8b889f960c6209e7bbe8c06;hp=4cc43d32677aed9e745ace96cf10ef07e27ed6e9;hpb=e837aa274d86db61848aeb6990eaa09c0c1e6a20;p=jalview.git diff --git a/src/jalview/project/Jalview2XML.java b/src/jalview/project/Jalview2XML.java index 4cc43d3..0e062ac 100644 --- a/src/jalview/project/Jalview2XML.java +++ b/src/jalview/project/Jalview2XML.java @@ -20,17 +20,77 @@ */ package jalview.project; +import static jalview.math.RotatableMatrix.Axis.X; +import static jalview.math.RotatableMatrix.Axis.Y; +import static jalview.math.RotatableMatrix.Axis.Z; + +import java.awt.Color; +import java.awt.Font; +import java.awt.Rectangle; +import java.io.BufferedReader; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.lang.reflect.InvocationTargetException; +import java.math.BigInteger; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.Vector; +import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; +import java.util.jar.JarOutputStream; + +import javax.swing.JInternalFrame; +import javax.swing.SwingUtilities; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBElement; +import javax.xml.bind.Marshaller; +import javax.xml.datatype.DatatypeConfigurationException; +import javax.xml.datatype.DatatypeFactory; +import javax.xml.datatype.XMLGregorianCalendar; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamReader; + 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; import jalview.datamodel.Alignment; import jalview.datamodel.AlignmentAnnotation; import jalview.datamodel.AlignmentI; +import jalview.datamodel.DBRefEntry; +import jalview.datamodel.GeneLocus; import jalview.datamodel.GraphLine; import jalview.datamodel.PDBEntry; +import jalview.datamodel.Point; import jalview.datamodel.RnaViewerModel; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceGroup; @@ -48,19 +108,21 @@ import jalview.gui.AlignmentPanel; import jalview.gui.AppVarna; import jalview.gui.ChimeraViewFrame; import jalview.gui.Desktop; -import jalview.gui.FeatureRenderer; -import jalview.gui.Jalview2XML_V1; import jalview.gui.JvOptionPane; import jalview.gui.OOMWarning; +import jalview.gui.PCAPanel; import jalview.gui.PaintRefresher; import jalview.gui.SplitFrame; import jalview.gui.StructureViewer; import jalview.gui.StructureViewer.ViewerType; import jalview.gui.StructureViewerBase; import jalview.gui.TreePanel; +import jalview.io.BackupFiles; import jalview.io.DataSourceType; import jalview.io.FileFormat; import jalview.io.NewickFile; +import jalview.math.Matrix; +import jalview.math.MatrixI; import jalview.renderer.ResidueShaderI; import jalview.schemes.AnnotationColourGradient; import jalview.schemes.ColourSchemeI; @@ -71,13 +133,16 @@ import jalview.schemes.UserColourScheme; import jalview.structure.StructureSelectionManager; import jalview.structures.models.AAStructureBindingModel; import jalview.util.Format; +import jalview.util.HttpUtils; import jalview.util.MessageManager; import jalview.util.Platform; 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.FeatureRendererModel; import jalview.viewmodel.seqfeatures.FeatureRendererSettings; import jalview.viewmodel.seqfeatures.FeaturesDisplayed; import jalview.ws.jws2.Jws2Discoverer; @@ -92,6 +157,8 @@ import jalview.xml.binding.jalview.Annotation; import jalview.xml.binding.jalview.Annotation.ThresholdLine; import jalview.xml.binding.jalview.AnnotationColourScheme; import jalview.xml.binding.jalview.AnnotationElement; +import jalview.xml.binding.jalview.DoubleMatrix; +import jalview.xml.binding.jalview.DoubleVector; import jalview.xml.binding.jalview.Feature; import jalview.xml.binding.jalview.Feature.OtherData; import jalview.xml.binding.jalview.FeatureMatcherSet.CompoundMatcher; @@ -106,6 +173,11 @@ import jalview.xml.binding.jalview.JalviewModel.JSeq.Pdbids; import jalview.xml.binding.jalview.JalviewModel.JSeq.Pdbids.StructureState; import jalview.xml.binding.jalview.JalviewModel.JSeq.RnaViewer; import jalview.xml.binding.jalview.JalviewModel.JSeq.RnaViewer.SecondaryStructure; +import jalview.xml.binding.jalview.JalviewModel.PcaViewer; +import jalview.xml.binding.jalview.JalviewModel.PcaViewer.Axis; +import jalview.xml.binding.jalview.JalviewModel.PcaViewer.SeqPointMax; +import jalview.xml.binding.jalview.JalviewModel.PcaViewer.SeqPointMin; +import jalview.xml.binding.jalview.JalviewModel.PcaViewer.SequencePoint; import jalview.xml.binding.jalview.JalviewModel.Tree; import jalview.xml.binding.jalview.JalviewModel.UserColours; import jalview.xml.binding.jalview.JalviewModel.Viewport; @@ -118,6 +190,7 @@ import jalview.xml.binding.jalview.MapListType.MapListTo; import jalview.xml.binding.jalview.Mapping; import jalview.xml.binding.jalview.NoValueColour; import jalview.xml.binding.jalview.ObjectFactory; +import jalview.xml.binding.jalview.PcaDataType; import jalview.xml.binding.jalview.Pdbentry.Property; import jalview.xml.binding.jalview.Sequence; import jalview.xml.binding.jalview.Sequence.DBRef; @@ -126,54 +199,6 @@ import jalview.xml.binding.jalview.SequenceSet.SequenceSetProperties; import jalview.xml.binding.jalview.ThresholdType; import jalview.xml.binding.jalview.VAMSAS; -import java.awt.Color; -import java.awt.Font; -import java.awt.Rectangle; -import java.io.BufferedReader; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.lang.reflect.InvocationTargetException; -import java.math.BigInteger; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Enumeration; -import java.util.GregorianCalendar; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Hashtable; -import java.util.IdentityHashMap; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.Vector; -import java.util.jar.JarEntry; -import java.util.jar.JarInputStream; -import java.util.jar.JarOutputStream; - -import javax.swing.JInternalFrame; -import javax.swing.SwingUtilities; -import javax.xml.bind.JAXBContext; -import javax.xml.bind.JAXBElement; -import javax.xml.bind.Marshaller; -import javax.xml.datatype.DatatypeConfigurationException; -import javax.xml.datatype.DatatypeFactory; -import javax.xml.datatype.XMLGregorianCalendar; -import javax.xml.stream.XMLInputFactory; -import javax.xml.stream.XMLStreamReader; - /** * Write out the current jalview desktop state as a Jalview XML stream. * @@ -529,24 +554,30 @@ public class Jalview2XML public void saveState(File statefile) { FileOutputStream fos = null; + try { + fos = new FileOutputStream(statefile); + JarOutputStream jout = new JarOutputStream(fos); saveState(jout); + fos.close(); } catch (Exception e) { + Cache.log.error("Couln't write Jalview state to " + statefile, e); // TODO: inform user of the problem - they need to know if their data was // not saved ! if (errorMessage == null) { - errorMessage = "Couldn't write Jalview Archive to output file '" + errorMessage = "Did't write Jalview Archive to output file '" + statefile + "' - See console error log for details"; } else { - errorMessage += "(output file was '" + statefile + "')"; + errorMessage += "(Didn't write Jalview Archive to output file '" + + statefile + ")"; } e.printStackTrace(); } finally @@ -716,7 +747,11 @@ public class Jalview2XML { try { - FileOutputStream fos = new FileOutputStream(jarFile); + // create backupfiles object and get new temp filename destination + BackupFiles backupfiles = new BackupFiles(jarFile); + FileOutputStream fos = new FileOutputStream( + backupfiles.getTempFilePath()); + JarOutputStream jout = new JarOutputStream(fos); List frames = new ArrayList<>(); @@ -738,7 +773,12 @@ public class Jalview2XML } ; jout.close(); - return true; + boolean success = true; + + backupfiles.setWriteSuccess(success); + success = backupfiles.rollBackupsAndRenameTempFile(); + + return success; } catch (Exception ex) { errorMessage = "Couldn't Write alignment view to Jalview Archive - see error output for details"; @@ -907,7 +947,7 @@ public class Jalview2XML else { vamsasSeq = createVamsasSequence(id, jds); -// vamsasSet.addSequence(vamsasSeq); + // vamsasSet.addSequence(vamsasSeq); vamsasSet.getSequence().add(vamsasSeq); vamsasSetIds.put(id, vamsasSeq); seqRefIds.put(id, jds); @@ -1215,6 +1255,9 @@ public class Jalview2XML tree.setXpos(tp.getX()); tree.setYpos(tp.getY()); tree.setId(makeHashCode(tp, null)); + tree.setLinkToAllViews( + tp.getTreeCanvas().isApplyToAllViews()); + // jms.addTree(tree); object.getTree().add(tree); } @@ -1223,6 +1266,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.getAlignViewport().getAlignment() == jal) + { + savePCA(panel, object); + } + } + } + } + // SAVE ANNOTATIONS /** * store forward refs from an annotationRow to any groups @@ -1279,9 +1340,8 @@ public class Jalview2XML if (colourScheme instanceof jalview.schemes.UserColourScheme) { - jGroup.setColour( - setUserColourScheme(colourScheme, userColours, - object)); + jGroup.setColour(setUserColourScheme(colourScheme, + userColours, object)); } else { @@ -1327,7 +1387,7 @@ public class Jalview2XML } } - //jms.setJGroup(groups); + // jms.setJGroup(groups); Object group; for (JGroup grp : groups) { @@ -1441,11 +1501,14 @@ public class Jalview2XML view.setFollowHighlight(av.isFollowHighlight()); view.setFollowSelection(av.followSelection); view.setIgnoreGapsinConsensus(av.isIgnoreGapsConsensus()); + view.setShowComplementFeatures(av.isShowComplementFeatures()); + view.setShowComplementFeaturesOnTop( + av.isShowComplementFeaturesOnTop()); if (av.getFeaturesDisplayed() != null) { FeatureSettings fs = new FeatureSettings(); - FeatureRenderer fr = ap.getSeqPanel().seqCanvas + FeatureRendererModel fr = ap.getSeqPanel().seqCanvas .getFeatureRenderer(); String[] renderOrder = fr.getRenderOrder().toArray(new String[0]); @@ -1461,11 +1524,13 @@ public class Jalview2XML * save any filter for the feature type */ FeatureMatcherSetI filter = fr.getFeatureFilter(featureType); - if (filter != null) { - Iterator filters = filter.getMatchers().iterator(); + if (filter != null) + { + Iterator filters = filter.getMatchers() + .iterator(); FeatureMatcherI firstFilter = filters.next(); - setting.setMatcherSet(Jalview2XML.marshalFilter( - firstFilter, filters, filter.isAnded())); + setting.setMatcherSet(Jalview2XML.marshalFilter(firstFilter, + filters, filter.isAnded())); } /* @@ -1514,8 +1579,7 @@ public class Jalview2XML setting.setDisplay( av.getFeaturesDisplayed().isVisible(featureType)); - float rorder = fr - .getOrder(featureType); + float rorder = fr.getOrder(featureType); if (rorder > -1) { setting.setOrder(rorder); @@ -1539,7 +1603,7 @@ public class Jalview2XML Group g = new Group(); g.setName(grp); g.setDisplay(((Boolean) fr.checkGroupVisibility(grp, false)) - .booleanValue()); + .booleanValue()); // fs.addGroup(g); fs.getGroup().add(g); groupsAdded.addElement(grp); @@ -1630,6 +1694,196 @@ 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, JalviewModel object) + { + 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.getSequencePoint().add(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.getAxis().add(axis); + } + + /* + * raw PCA data (note we are not restoring PCA inputs here - + * alignment view, score model, similarity parameters) + */ + PcaDataType data = new PcaDataType(); + viewer.setPcaData(data); + PCA pca = pcaModel.getPcaData(); + + DoubleMatrix pm = new DoubleMatrix(); + saveDoubleMatrix(pca.getPairwiseScores(), pm); + data.setPairwiseMatrix(pm); + + DoubleMatrix tm = new DoubleMatrix(); + saveDoubleMatrix(pca.getTridiagonal(), tm); + data.setTridiagonalMatrix(tm); + + DoubleMatrix eigenMatrix = new DoubleMatrix(); + data.setEigenMatrix(eigenMatrix); + saveDoubleMatrix(pca.getEigenmatrix(), eigenMatrix); + + object.getPcaViewer().add(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++) + { + DoubleVector row = new DoubleVector(); + for (int j = 0; j < m.width(); j++) + { + row.getV().add(m.getValue(i, j)); + } + xmlMatrix.getRow().add(row); + } + if (m.getD() != null) + { + DoubleVector dVector = new DoubleVector(); + for (double d : m.getD()) + { + dVector.getV().add(d); + } + xmlMatrix.setD(dVector); + } + if (m.getE() != null) + { + DoubleVector eVector = new DoubleVector(); + for (double e : m.getE()) + { + eVector.getV().add(e); + } + 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++) + { + List dVector = mData.getRow().get(i).getV(); + vals[i] = new double[dVector.size()]; + int dvi = 0; + for (Double d : dVector) + { + vals[i][dvi++] = d; + } + } + + MatrixI m = new Matrix(vals); + + if (mData.getD() != null) + { + List dVector = mData.getD().getV(); + double[] vec = new double[dVector.size()]; + int dvi = 0; + for (Double d : dVector) + { + vec[dvi++] = d; + } + m.setD(vec); + } + if (mData.getE() != null) + { + List dVector = mData.getE().getV(); + double[] vec = new double[dVector.size()]; + int dvi = 0; + for (Double d : dVector) + { + vec[dvi++] = d; + } + m.setE(vec); + } + + return m; + } + + /** * Save any Varna viewers linked to this sequence. Writes an rnaViewer element * for each viewer, with *
    @@ -2115,7 +2369,8 @@ public class Jalview2XML { if (calcIdParam.getVersion().equals("1.0")) { - final String[] calcIds = calcIdParam.getServiceURL().toArray(new String[0]); + final String[] calcIds = calcIdParam.getServiceURL() + .toArray(new String[0]); Jws2Instance service = Jws2Discoverer.getDiscoverer() .getPreferredServiceFor(calcIds); if (service != null) @@ -2257,21 +2512,29 @@ public class Jalview2XML parentseq = jds; } } + + /* + * save any dbrefs; special subclass GeneLocus is flagged as 'locus' + */ if (dbrefs != null) { for (int d = 0; d < dbrefs.length; d++) { DBRef dbref = new DBRef(); - dbref.setSource(dbrefs[d].getSource()); - dbref.setVersion(dbrefs[d].getVersion()); - dbref.setAccessionId(dbrefs[d].getAccessionId()); - if (dbrefs[d].hasMap()) + DBRefEntry dbRefEntry = dbrefs[d]; + dbref.setSource(dbRefEntry.getSource()); + dbref.setVersion(dbRefEntry.getVersion()); + dbref.setAccessionId(dbRefEntry.getAccessionId()); + if (dbRefEntry instanceof GeneLocus) { - Mapping mp = createVamsasMapping(dbrefs[d].getMap(), parentseq, + dbref.setLocus(true); + } + if (dbRefEntry.hasMap()) + { + Mapping mp = createVamsasMapping(dbRefEntry.getMap(), parentseq, jds, recurse); dbref.setMapping(mp); } - // vamsasSeq.addDBRef(dbref); vamsasSeq.getDBRef().add(dbref); } } @@ -2393,12 +2656,12 @@ public class Jalview2XML return id; } - jalview.schemes.UserColourScheme getUserColourScheme( - JalviewModel jm, String id) + jalview.schemes.UserColourScheme getUserColourScheme(JalviewModel jm, + String id) { List uc = jm.getUserColours(); UserColours colours = null; -/* + /* for (int i = 0; i < uc.length; i++) { if (uc[i].getId().equals(id)) @@ -2407,7 +2670,7 @@ public class Jalview2XML break; } } -*/ + */ for (UserColours c : uc) { if (c.getId().equals(id)) @@ -2435,10 +2698,9 @@ public class Jalview2XML newColours = new java.awt.Color[23]; for (int i = 0; i < 23; i++) { - newColours[i] = new java.awt.Color(Integer.parseInt( - colours.getUserColourScheme().getColour().get(i + 24) - .getRGB(), - 16)); + newColours[i] = new java.awt.Color( + Integer.parseInt(colours.getUserColourScheme().getColour() + .get(i + 24).getRGB(), 16)); } ucs.setLowerCaseColours(newColours); } @@ -2517,7 +2779,7 @@ public class Jalview2XML viewportsAdded.clear(); frefedSequence = null; - if (file.startsWith("http://")) + if (HttpUtils.startsWithHttpOrHttps(file)) { url = new URL(file); } @@ -2594,8 +2856,8 @@ public class Jalview2XML XMLStreamReader streamReader = XMLInputFactory.newInstance() .createXMLStreamReader(jin); javax.xml.bind.Unmarshaller um = jc.createUnmarshaller(); - JAXBElement jbe = um - .unmarshal(streamReader, JalviewModel.class); + JAXBElement jbe = um.unmarshal(streamReader, + JalviewModel.class); JalviewModel object = jbe.getValue(); /* @@ -2649,16 +2911,7 @@ public class Jalview2XML ex.printStackTrace(System.err); if (attemptversion1parse) { - // Is Version 1 Jar file? - try - { - af = new Jalview2XML_V1(raiseGUI).LoadJalviewAlign(jprovider); - } catch (Exception ex2) - { - System.err.println("Exception whilst loading as jalviewXMLV1:"); - ex2.printStackTrace(); - af = null; - } + // used to attempt to parse as V1 castor-generated xml } if (Desktop.instance != null) { @@ -2911,25 +3164,25 @@ public class Jalview2XML * @param prefix * a prefix for the temporary file name, must be at least three * characters long - * @param origFile + * @param suffixModel * null or original file - so new file can be given the same suffix * as the old one * @return */ protected String copyJarEntry(jarInputStreamProvider jprovider, - String jarEntryName, String prefix, String origFile) + String jarEntryName, String prefix, String suffixModel) { BufferedReader in = null; PrintWriter out = null; String suffix = ".tmp"; - if (origFile == null) + if (suffixModel == null) { - origFile = jarEntryName; + suffixModel = jarEntryName; } - int sfpos = origFile.lastIndexOf("."); - if (sfpos > -1 && sfpos < (origFile.length() - 3)) + int sfpos = suffixModel.lastIndexOf("."); + if (sfpos > -1 && sfpos < (suffixModel.length() - 1)) { - suffix = "." + origFile.substring(sfpos + 1); + suffix = "." + suffixModel.substring(sfpos + 1); } try { @@ -3024,7 +3277,8 @@ public class Jalview2XML AlignFrame loadFromObject(JalviewModel jalviewModel, String file, boolean loadTreesAndStructures, jarInputStreamProvider jprovider) { - SequenceSet vamsasSet = jalviewModel.getVamsasModel().getSequenceSet().get(0); + SequenceSet vamsasSet = jalviewModel.getVamsasModel().getSequenceSet() + .get(0); List vamsasSeqs = vamsasSet.getSequence(); // JalviewModelSequence jms = object.getJalviewModelSequence(); @@ -3083,9 +3337,10 @@ public class Jalview2XML if (tmpSeq.getStart() != jseq.getStart() || tmpSeq.getEnd() != jseq.getEnd()) { - System.err.println( - "Warning JAL-2154 regression: updating start/end for sequence " - + tmpSeq.toString() + " to " + jseq); + System.err.println(String.format( + "Warning JAL-2154 regression: updating start/end for sequence %s from %d/%d to %d/%d", + tmpSeq.getName(), tmpSeq.getStart(), tmpSeq.getEnd(), + jseq.getStart(), jseq.getEnd())); } } else @@ -3368,8 +3623,8 @@ public class Jalview2XML else { // defer to later - frefedSequence.add( - newAlcodMapRef(map.getDnasq(), cf, mapping)); + frefedSequence + .add(newAlcodMapRef(map.getDnasq(), cf, mapping)); } } } @@ -3577,8 +3832,7 @@ public class Jalview2XML jaa.setCalcId(annotation.getCalcId()); if (annotation.getProperty().size() > 0) { - for (Annotation.Property prop : annotation - .getProperty()) + for (Annotation.Property prop : annotation.getProperty()) { jaa.setProperty(prop.getName(), prop.getValue()); } @@ -3620,7 +3874,7 @@ public class Jalview2XML } else { - cs = ColourSchemeProperty.getColourScheme(al, + cs = ColourSchemeProperty.getColourScheme(null, al, jGroup.getColour()); } } @@ -3659,9 +3913,9 @@ public class Jalview2XML sg.setShowNonconserved(safeBoolean(jGroup.isShowUnconserved())); sg.thresholdTextColour = safeInt(jGroup.getTextColThreshold()); // attributes with a default in the schema are never null - sg.setShowConsensusHistogram(jGroup.isShowConsensusHistogram()); - sg.setshowSequenceLogo(jGroup.isShowSequenceLogo()); - sg.setNormaliseSequenceLogo(jGroup.isNormaliseSequenceLogo()); + sg.setShowConsensusHistogram(jGroup.isShowConsensusHistogram()); + sg.setshowSequenceLogo(jGroup.isShowSequenceLogo()); + sg.setNormaliseSequenceLogo(jGroup.isNormaliseSequenceLogo()); sg.setIgnoreGapsConsensus(jGroup.isIgnoreGapsinConsensus()); if (jGroup.getConsThreshold() != null && jGroup.getConsThreshold().intValue() != 0) @@ -3705,8 +3959,9 @@ public class Jalview2XML if (addAnnotSchemeGroup) { // reconstruct the annotation colourscheme - sg.setColourScheme(constructAnnotationColour( - jGroup.getAnnotationColours(), null, al, jalviewModel, false)); + sg.setColourScheme( + constructAnnotationColour(jGroup.getAnnotationColours(), + null, al, jalviewModel, false)); } } } @@ -3799,6 +4054,7 @@ public class Jalview2XML if (loadTreesAndStructures) { loadTrees(jalviewModel, view, af, av, ap); + loadPCAViewers(jalviewModel, ap); loadPDBStructures(jprovider, jseqs, af, ap); loadRnaViewers(jprovider, jseqs, ap); } @@ -3911,8 +4167,8 @@ public class Jalview2XML * @param av * @param ap */ - protected void loadTrees(JalviewModel jm, Viewport view, - AlignFrame af, AlignViewport av, AlignmentPanel ap) + protected void loadTrees(JalviewModel jm, Viewport view, AlignFrame af, + AlignViewport av, AlignmentPanel ap) { // TODO result of automated refactoring - are all these parameters needed? try @@ -3947,8 +4203,8 @@ public class Jalview2XML // TODO: verify 'associate with all views' works still tp.getTreeCanvas().setViewport(av); // af.viewport; tp.getTreeCanvas().setAssociatedPanel(ap); // af.alignPanel; - } + tp.getTreeCanvas().setApplyToAllViews(tree.isLinkToAllViews()); if (tp == null) { warn("There was a problem recovering stored Newick tree: \n" @@ -4020,8 +4276,8 @@ public class Jalview2XML for (int s = 0; s < structureStateCount; s++) { // check to see if we haven't already created this structure view - final StructureState structureState = pdbid - .getStructureState().get(s); + final StructureState structureState = pdbid.getStructureState() + .get(s); String sviewid = (structureState.getViewId() == null) ? null : structureState.getViewId() + uniqueSetSuffix; jalview.datamodel.PDBEntry jpdb = new jalview.datamodel.PDBEntry(); @@ -4084,8 +4340,8 @@ public class Jalview2XML colourByViewer &= structureState.isColourByJmol(); jmoldat.setColourByViewer(colourByViewer); - if (jmoldat.getStateData().length() < structureState - .getValue()/*Content()*/.length()) + if (jmoldat.getStateData().length() < structureState.getValue() + /*Content()*/.length()) { jmoldat.setStateData(structureState.getValue());// Content()); } @@ -4194,7 +4450,7 @@ public class Jalview2XML */ String viewerJarEntryName = getViewerJarEntryName(data.getViewId()); chimeraSessionFile = copyJarEntry(jprovider, viewerJarEntryName, - "chimera", null); + "chimera", ".py"); Set> fileData = data.getFileData() .entrySet(); @@ -4280,7 +4536,8 @@ public class Jalview2XML String reformatedOldFilename = oldfilenam.replaceAll("/", "\\\\"); filedat = oldFiles.get(new File(reformatedOldFilename)); } - newFileLoc.append(Platform.escapeString(filedat.getFilePath())); + newFileLoc + .append(Platform.escapeBackslashes(filedat.getFilePath())); pdbfilenames.add(filedat.getFilePath()); pdbids.add(filedat.getPdbId()); seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0])); @@ -4593,9 +4850,9 @@ public class Jalview2XML } AlignFrame loadViewport(String file, List JSEQ, - List hiddenSeqs, AlignmentI al, - JalviewModel jm, Viewport view, String uniqueSeqSetId, - String viewId, List autoAlan) + List hiddenSeqs, AlignmentI al, JalviewModel jm, + Viewport view, String uniqueSeqSetId, String viewId, + List autoAlan) { AlignFrame af = null; af = new AlignFrame(al, safeInt(view.getWidth()), @@ -4676,9 +4933,8 @@ public class Jalview2XML viewport.setColourText(safeBoolean(view.isShowColourText())); - viewport - .setConservationSelected( - safeBoolean(view.isConservationSelected())); + viewport.setConservationSelected( + safeBoolean(view.isConservationSelected())); viewport.setIncrement(safeInt(view.getConsThreshold())); viewport.setShowJVSuffix(safeBoolean(view.isShowFullId())); viewport.setRightAlignIds(safeBoolean(view.isRightAlignIds())); @@ -4731,30 +4987,31 @@ public class Jalview2XML } else { - cs = ColourSchemeProperty.getColourScheme(al, view.getBgColour()); + cs = ColourSchemeProperty.getColourScheme(af.getViewport(), al, + view.getBgColour()); } } + /* + * turn off 'alignment colour applies to all groups' + * while restoring global colour scheme + */ + viewport.setColourAppliesToAllGroups(false); viewport.setGlobalColourScheme(cs); viewport.getResidueShading().setThreshold(pidThreshold, view.isIgnoreGapsinConsensus()); viewport.getResidueShading() .setConsensus(viewport.getSequenceConsensusHash()); - viewport.setColourAppliesToAllGroups(false); - if (safeBoolean(view.isConservationSelected()) && cs != null) { viewport.getResidueShading() .setConservationInc(safeInt(view.getConsThreshold())); } - af.changeColour(cs); - viewport.setColourAppliesToAllGroups(true); - viewport - .setShowSequenceFeatures( - safeBoolean(view.isShowSequenceFeatures())); + viewport.setShowSequenceFeatures( + safeBoolean(view.isShowSequenceFeatures())); viewport.setCentreColumnLabels(view.isCentreColumnLabels()); viewport.setIgnoreGapsConsensus(view.isIgnoreGapsinConsensus(), null); @@ -4767,21 +5024,24 @@ public class Jalview2XML viewport.setShowNPFeats(safeBoolean(view.isShowNPfeatureTooltip())); viewport.setShowGroupConsensus(view.isShowGroupConsensus()); viewport.setShowGroupConservation(view.isShowGroupConservation()); + viewport.setShowComplementFeatures(view.isShowComplementFeatures()); + viewport.setShowComplementFeaturesOnTop( + view.isShowComplementFeaturesOnTop()); // recover feature settings if (jm.getFeatureSettings() != null) { - FeatureRenderer fr = af.alignPanel.getSeqPanel().seqCanvas + FeatureRendererModel fr = af.alignPanel.getSeqPanel().seqCanvas .getFeatureRenderer(); FeaturesDisplayed fdi; viewport.setFeaturesDisplayed(fdi = new FeaturesDisplayed()); - String[] renderOrder = new String[jm.getFeatureSettings() - .getSetting().size()]; + String[] renderOrder = new String[jm.getFeatureSettings().getSetting() + .size()]; Map featureColours = new Hashtable<>(); Map featureOrder = new Hashtable<>(); - for (int fs = 0; fs < jm.getFeatureSettings() - .getSetting().size(); fs++) + for (int fs = 0; fs < jm.getFeatureSettings().getSetting() + .size(); fs++) { Setting setting = jm.getFeatureSettings().getSetting().get(fs); String featureType = setting.getType(); @@ -4793,8 +5053,8 @@ public class Jalview2XML .getMatcherSet(); if (filters != null) { - FeatureMatcherSetI filter = Jalview2XML - .parseFilter(featureType, filters); + FeatureMatcherSetI filter = Jalview2XML.parseFilter(featureType, + filters); if (!filter.isEmpty()) { fr.setFeatureFilter(featureType, filter); @@ -4825,8 +5085,8 @@ public class Jalview2XML float min = safeFloat(safeFloat(setting.getMin())); float max = setting.getMax() == null ? 1f : setting.getMax().floatValue(); - FeatureColourI gc = new FeatureColour(minColour, maxColour, - noValueColour, min, max); + FeatureColourI gc = new FeatureColour(maxColour, minColour, + maxColour, noValueColour, min, max); if (setting.getAttributeName().size() > 0) { gc.setAttributeName(setting.getAttributeName().toArray( @@ -4860,8 +5120,7 @@ public class Jalview2XML } else { - featureColours.put(featureType, - new FeatureColour(maxColour)); + featureColours.put(featureType, new FeatureColour(maxColour)); } renderOrder[fs] = featureType; if (setting.getOrder() != null) @@ -4870,7 +5129,7 @@ public class Jalview2XML } else { - featureOrder.put(featureType, new Float( + featureOrder.put(featureType, Float.valueOf( fs / jm.getFeatureSettings().getSetting().size())); } if (safeBoolean(setting.isDisplay())) @@ -4882,7 +5141,7 @@ public class Jalview2XML for (int gs = 0; gs < jm.getFeatureSettings().getGroup().size(); gs++) { Group grp = jm.getFeatureSettings().getGroup().get(gs); - fgtable.put(grp.getName(), new Boolean(grp.isDisplay())); + fgtable.put(grp.getName(), Boolean.valueOf(grp.isDisplay())); } // FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder, // fgtable, featureColours, jms.getFeatureSettings().hasTransparency() ? @@ -5027,7 +5286,7 @@ public class Jalview2XML else { cs = new AnnotationColourGradient(matchedAnnotation, - ColourSchemeProperty.getColourScheme(al, + ColourSchemeProperty.getColourScheme(af.getViewport(), al, viewAnnColour.getColourScheme()), safeInt(viewAnnColour.getAboveThreshold())); } @@ -5220,6 +5479,20 @@ public class Jalview2XML { jalview.datamodel.AlignmentI ds = getDatasetFor( vamsasSet.getDatasetId()); + AlignmentI xtant_ds = ds; + if (xtant_ds == null) + { + // good chance we are about to create a new dataset, but check if we've + // seen some of the dataset sequence IDs before. + // TODO: skip this check if we are working with project generated by + // version 2.11 or later + xtant_ds = checkIfHasDataset(vamsasSet.getSequence()); + if (xtant_ds != null) + { + ds = xtant_ds; + addDatasetRef(vamsasSet.getDatasetId(), ds); + } + } Vector dseqs = null; if (!ignoreUnrefed) { @@ -5231,6 +5504,13 @@ public class Jalview2XML { warn("JAL-3171 regression: Overwriting a dataset reference for an alignment" + " - CDS/Protein crossreference data may be lost"); + if (xtant_ds != null) + { + // This can only happen if the unique sequence set ID was bound to a + // dataset that did not contain any of the sequences in the view + // currently being restored. + warn("JAL-3171 SERIOUS! TOTAL CONFUSION - please consider contacting the Jalview Development team so they can investigate why your project caused this message to be displayed."); + } } ds = seqSetDS; addDatasetRef(vamsasSet.getDatasetId(), ds); @@ -5238,6 +5518,8 @@ public class Jalview2XML } if (ds == null) { + // try even harder to restore dataset + AlignmentI xtantDS = checkIfHasDataset(vamsasSet.getSequence()); // create a list of new dataset sequences dseqs = new Vector(); } @@ -5263,6 +5545,53 @@ public class Jalview2XML // register dataset for the alignment's uniqueSeqSetId for legacy projects addDatasetRef(UNIQSEQSETID + uniqueSeqSetId, ds); } + updateSeqDatasetBinding(vamsasSet.getSequence(), ds); + } + + /** + * XML dataset sequence ID to materialised dataset reference + */ + HashMap seqToDataset = new HashMap<>(); + + /** + * @return the first materialised dataset reference containing a dataset + * sequence referenced in the given view + * @param list + * - sequences from the view + */ + AlignmentI checkIfHasDataset(List list) + { + for (Sequence restoredSeq : list) + { + AlignmentI datasetFor = seqToDataset.get(restoredSeq.getDsseqid()); + if (datasetFor != null) + { + return datasetFor; + } + } + return null; + } + + /** + * Register ds as the containing dataset for the dataset sequences referenced + * by sequences in list + * + * @param list + * - sequences in a view + * @param ds + */ + void updateSeqDatasetBinding(List list, AlignmentI ds) + { + for (Sequence restoredSeq : list) + { + AlignmentI prevDS = seqToDataset.put(restoredSeq.getDsseqid(), ds); + if (prevDS != null && prevDS != ds) + { + warn("Dataset sequence appears in many datasets: " + + restoredSeq.getDsseqid()); + // TODO: try to merge! + } + } } /** @@ -5486,13 +5815,29 @@ public class Jalview2XML return datasetId; } + /** + * Add any saved DBRefEntry's to the sequence. An entry flagged as 'locus' is + * constructed as a special subclass GeneLocus. + * + * @param datasetSequence + * @param sequence + */ private void addDBRefs(SequenceI datasetSequence, Sequence sequence) { for (int d = 0; d < sequence.getDBRef().size(); d++) { DBRef dr = sequence.getDBRef().get(d); - jalview.datamodel.DBRefEntry entry = new jalview.datamodel.DBRefEntry( - dr.getSource(), dr.getVersion(), dr.getAccessionId()); + DBRefEntry entry; + if (dr.isLocus()) + { + entry = new GeneLocus(dr.getSource(), dr.getVersion(), + dr.getAccessionId()); + } + else + { + entry = new DBRefEntry(dr.getSource(), dr.getVersion(), + dr.getAccessionId()); + } if (dr.getMapping() != null) { entry.setMap(addMapping(dr.getMapping())); @@ -5524,15 +5869,16 @@ public class Jalview2XML jalview.datamodel.Mapping jmap = new jalview.datamodel.Mapping(dsto, fr, fto, m.getMapFromUnit().intValue(), m.getMapToUnit().intValue()); - // if (m.getMappingChoice() != null) - // { - // MappingChoice mc = m.getMappingChoice(); + + /* + * (optional) choice of dseqFor or Sequence + */ if (m.getDseqFor() != null) { String dsfor = m.getDseqFor(); if (seqRefIds.containsKey(dsfor)) { - /** + /* * recover from hash */ jmap.setTo(seqRefIds.get(dsfor)); @@ -5542,9 +5888,9 @@ public class Jalview2XML frefedSequence.add(newMappingRef(dsfor, jmap)); } } - else + else if (m.getSequence() != null) { - /** + /* * local sequence definition */ Sequence ms = m.getSequence(); @@ -5866,14 +6212,136 @@ public class Jalview2XML } /** + * Loads any saved PCA viewers + * + * @param jms + * @param ap + */ + protected void loadPCAViewers(JalviewModel model, AlignmentPanel ap) + { + try + { + List pcaviewers = model.getPcaViewer(); + for (PcaViewer viewer : pcaviewers) + { + 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.getRotatableCanvas().setShowLabels(showLabels); + panel.getRotatableCanvas() + .setBgColour(new Color(viewer.getBgColour())); + panel.getRotatableCanvas() + .setApplyToAllViews(viewer.isLinkToAllViews()); + + /* + * load PCA output data + */ + ScoreModelI scoreModel = ScoreModels.getInstance() + .getScoreModel(modelName, ap); + PCA pca = new PCA(null, scoreModel, params); + PcaDataType 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.getPcaModel().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 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.getRotatableCanvas().setPoints(seqPoints, seqPoints.size()); + + /* + * set min-max ranges and scale after setPoints (which recomputes them) + */ + panel.getRotatableCanvas().setScaleFactor(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.getRotatableCanvas().setSeqMinMax(min, max); + + // todo: hold points list in PCAModel only + panel.getPcaModel().setSequencePoints(seqPoints); + + panel.setSelectedDimensionIndex(viewer.getXDim(), X); + panel.setSelectedDimensionIndex(viewer.getYDim(), Y); + panel.setSelectedDimensionIndex(viewer.getZDim(), Z); + + // is this duplication needed? + panel.setTop(seqPoints.size() - 1); + panel.getPcaModel().setTop(seqPoints.size() - 1); + + /* + * add the axes' end points for the display + */ + for (int i = 0; i < 3; i++) + { + Axis axis = viewer.getAxis().get(i); + panel.getRotatableCanvas().getAxisEndPoints()[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 * @param fcol * @return */ - public static Colour marshalColour( - String featureType, FeatureColourI fcol) + public static Colour marshalColour(String featureType, + FeatureColourI fcol) { Colour col = new Colour(); if (fcol.isSimpleColour()) @@ -5934,7 +6402,7 @@ public class Jalview2XML boolean and) { jalview.xml.binding.jalview.FeatureMatcherSet result = new jalview.xml.binding.jalview.FeatureMatcherSet(); - + if (filters.hasNext()) { /* @@ -5984,7 +6452,7 @@ public class Jalview2XML } result.setMatchCondition(matcherModel); } - + return result; } @@ -5995,8 +6463,7 @@ public class Jalview2XML * @param matcherSetModel * @return */ - public static FeatureMatcherSetI parseFilter( - String featureType, + public static FeatureMatcherSetI parseFilter(String featureType, jalview.xml.binding.jalview.FeatureMatcherSet matcherSetModel) { FeatureMatcherSetI result = new FeatureMatcherSet(); @@ -6011,7 +6478,7 @@ public class Jalview2XML featureType, e.getMessage())); // return as much as was parsed up to the error } - + return result; } @@ -6026,8 +6493,7 @@ public class Jalview2XML * @throws IllegalStateException * if AND and OR conditions are mixed */ - protected static void parseFilterConditions( - FeatureMatcherSetI matcherSet, + protected static void parseFilterConditions(FeatureMatcherSetI matcherSet, jalview.xml.binding.jalview.FeatureMatcherSet matcherSetModel, boolean and) { @@ -6049,7 +6515,7 @@ public class Jalview2XML else if (filterBy == FilterBy.BY_SCORE) { matchCondition = FeatureMatcher.byScore(cond, pattern); - + } else if (filterBy == FilterBy.BY_ATTRIBUTE) { @@ -6059,7 +6525,7 @@ public class Jalview2XML matchCondition = FeatureMatcher.byAttribute(cond, pattern, attNames); } - + /* * note this throws IllegalStateException if AND-ing to a * previously OR-ed compound condition, or vice versa @@ -6102,13 +6568,13 @@ public class Jalview2XML public static FeatureColourI parseColour(Colour colourModel) { FeatureColourI colour = null; - + if (colourModel.getMax() != null) { Color mincol = null; Color maxcol = null; Color noValueColour = null; - + try { mincol = new Color(Integer.parseInt(colourModel.getMinRGB(), 16)); @@ -6117,7 +6583,7 @@ public class Jalview2XML { Cache.log.warn("Couldn't parse out graduated feature color.", e); } - + NoValueColour noCol = colourModel.getNoValueColour(); if (noCol == NoValueColour.MIN) { @@ -6127,8 +6593,8 @@ public class Jalview2XML { noValueColour = maxcol; } - - colour = new FeatureColour(mincol, maxcol, noValueColour, + + colour = new FeatureColour(maxcol, mincol, maxcol, noValueColour, safeFloat(colourModel.getMin()), safeFloat(colourModel.getMax())); final List attributeName = colourModel.getAttributeName(); @@ -6166,7 +6632,7 @@ public class Jalview2XML Color color = new Color(Integer.parseInt(colourModel.getRGB(), 16)); colour = new FeatureColour(color); } - + return colour; } }