JAL-1609 wip on Chimera colour by sequence
[jalview.git] / src / jalview / ext / rbvi / chimera / ChimeraCommands.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
3  * Copyright (C) 2014 The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.ext.rbvi.chimera;
22
23 import jalview.api.FeatureRenderer;
24 import jalview.api.SequenceRenderer;
25 import jalview.datamodel.AlignmentI;
26 import jalview.datamodel.SequenceI;
27 import jalview.structure.StructureMapping;
28 import jalview.structure.StructureMappingcommandSet;
29 import jalview.structure.StructureSelectionManager;
30 import jalview.util.Comparison;
31
32 import java.awt.Color;
33 import java.io.File;
34 import java.io.FileOutputStream;
35 import java.io.IOException;
36 import java.util.ArrayList;
37 import java.util.LinkedHashMap;
38 import java.util.List;
39 import java.util.Map;
40
41 /**
42  * Routines for generating Chimera commands for Jalview/Chimera binding
43  * 
44  * @author JimP
45  * 
46  */
47 public class ChimeraCommands
48 {
49
50   /**
51    * utility to construct the commands to colour chains by the given alignment
52    * for passing to Chimera
53    * 
54    * @returns Object[] { Object[] { <model being coloured>,
55    * 
56    */
57   public static StructureMappingcommandSet[] getColourBySequenceCommand(
58           StructureSelectionManager ssm, String[] files,
59           SequenceI[][] sequence, SequenceRenderer sr, FeatureRenderer fr,
60           AlignmentI alignment)
61   {
62     String defAttrPath = null;
63     FileOutputStream fos = null;
64     try
65     {
66       File outFile = File.createTempFile("jalviewdefattr", ".xml");
67       outFile.deleteOnExit();
68       defAttrPath = outFile.getPath();
69       fos = new FileOutputStream(outFile);
70       fos.write("attribute: jalviewclr\n".getBytes());
71     } catch (IOException e1)
72     {
73       e1.printStackTrace();
74     }
75     List<StructureMappingcommandSet> cset = new ArrayList<StructureMappingcommandSet>();
76
77     /*
78      * Map of { colour, positionSpecs}
79      */
80     Map<String, StringBuilder> colranges = new LinkedHashMap<String, StringBuilder>();
81     StringBuilder setAttributes = new StringBuilder(256);
82     String lastColour = "none";
83     Color lastCol = null;
84     for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
85     {
86       boolean startModel = true;
87       StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
88
89       if (mapping == null || mapping.length < 1)
90       {
91         continue;
92       }
93
94       int startPos = -1, lastPos = -1;
95       String lastChain = "";
96       for (int s = 0; s < sequence[pdbfnum].length; s++)
97       {
98         for (int sp, m = 0; m < mapping.length; m++)
99         {
100           final SequenceI seq = sequence[pdbfnum][s];
101           if (mapping[m].getSequence() == seq
102                   && (sp = alignment.findIndex(seq)) > -1)
103           {
104             SequenceI asp = alignment.getSequenceAt(sp);
105             for (int r = 0; r < asp.getLength(); r++)
106             {
107               // no mapping to gaps in sequence
108               if (Comparison.isGap(asp.getCharAt(r)))
109               {
110                 continue;
111               }
112               int pos = mapping[m].getPDBResNum(asp.findPosition(r));
113
114               if (pos < 1 || pos == lastPos)
115               {
116                 continue;
117               }
118
119               Color col = getResidueColour(seq, r, sr, fr);
120               /*
121                * Just keep incrementing the end position for this colour range
122                * _unless_ colour, PDB model or chain has changed, or there is a
123                * gap in the mapped residue sequence
124                */
125               final boolean newColour = !col.equals(lastCol);
126               final boolean nonContig = lastPos + 1 != pos;
127               final boolean newChain = !mapping[m].getChain().equals(lastChain);
128               if (newColour || nonContig || startModel || newChain)
129               {
130                 if (/* lastCol != null */startPos != -1)
131                 {
132                   addColourRange(colranges, lastCol, pdbfnum, startPos,
133                           lastPos, lastChain, startModel);
134                   startModel = false;
135                 }
136                 // lastCol = null;
137                 startPos = pos;
138               }
139               lastCol = col;
140               lastPos = pos;
141               // lastModel = pdbfnum;
142               lastChain = mapping[m].getChain();
143             }
144             // final colour range
145             if (lastCol != null)
146             {
147               addColourRange(colranges, lastCol, pdbfnum, startPos,
148                       lastPos, lastChain, false);
149             }
150             break;
151           }
152         }
153       }
154     }
155       try
156       {
157       lastColour = buildColourCommands(cset, colranges,
158                 fos, setAttributes);
159       } catch (IOException e)
160       {
161         e.printStackTrace();
162       }
163
164     try
165     {
166       fos.close();
167     } catch (IOException e)
168     {
169       e.printStackTrace();
170     }
171
172     /*
173      * Send a rangeColor command, preceded by either defattr or setattr,
174      * whichever we end up preferring!
175      * 
176      * rangecolor requires a minimum of two attribute values to operate on
177      */
178     StringBuilder rangeColor = new StringBuilder(256);
179     rangeColor.append("rangecolor jalviewclr");
180     int colourId = 0;
181     for (String colour : colranges.keySet())
182     {
183       colourId++;
184       rangeColor.append(" " + colourId + " " + colour);
185     }
186     String rangeColorCommand = rangeColor.toString();
187     if (rangeColorCommand.split(" ").length < 5)
188     {
189       rangeColorCommand += " max " + lastColour;
190     }
191     final String defAttrCommand = "defattr " + defAttrPath
192             + " raiseTool false";
193     final String setAttrCommand = setAttributes.toString();
194     final String attrCommand = false ? defAttrCommand : setAttrCommand;
195     cset.add(new StructureMappingcommandSet(ChimeraCommands.class, null,
196             new String[]
197             { attrCommand /* , rangeColorCommand */}));
198
199     return cset.toArray(new StructureMappingcommandSet[cset.size()]);
200   }
201
202   /**
203    * Get the residue colour at the given sequence position - as determined by
204    * the sequence group colour (if any), else the colour scheme, possibly
205    * overridden by a feature colour.
206    * 
207    * @param seq
208    * @param position
209    * @param sr
210    * @param fr
211    * @return
212    */
213   protected static Color getResidueColour(final SequenceI seq,
214           int position, SequenceRenderer sr, FeatureRenderer fr)
215   {
216     Color col = sr.getResidueBoxColour(seq, position);
217
218     if (fr != null)
219     {
220       col = fr.findFeatureColour(col, seq, position);
221     }
222     return col;
223   }
224
225   /**
226    * Helper method to build the colour commands for one PDBfile.
227    * 
228    * @param cset
229    *          the list of commands to be added to
230    * @param colranges
231    *          the map of colours to residue positions already determined
232    * @param fos
233    *          file to write 'defattr' commands to
234    * @param setAttributes
235    * @throws IOException
236    */
237   protected static String buildColourCommands(
238           List<StructureMappingcommandSet> cset,
239           Map<String, StringBuilder> colranges,
240           FileOutputStream fos, StringBuilder setAttributes)
241           throws IOException
242   {
243     int colourId = 0;
244     String lastColour = null;
245     for (String colour : colranges.keySet())
246     {
247       lastColour = colour;
248       colourId++;
249       /*
250        * Using color command directly is slow for larger structures.
251        * setAttributes.append("color #" + colour + " " + colranges.get(colour)+
252        * ";");
253        */
254       setAttributes.append("color " + colour + " " + colranges.get(colour)
255               + ";");
256       final String atomSpec = new String(colranges.get(colour));
257       // setAttributes.append("setattr r jalviewclr " + colourId + " "
258       // + atomSpec + ";");
259       fos.write(("\t" + atomSpec + "\t" + colourId + "\n").getBytes());
260     }
261     return lastColour;
262   }
263
264   /**
265    * Helper method to record a range of positions of the same colour.
266    * 
267    * @param colranges
268    * @param colour
269    * @param model
270    * @param startPos
271    * @param endPos
272    * @param chain
273    * @param changeModel
274    */
275   private static void addColourRange(Map<String, StringBuilder> colranges,
276           Color colour, int model, int startPos, int endPos, String chain,
277           boolean startModel)
278   {
279     String colstring = "#" + ((colour.getRed() < 16) ? "0" : "")
280             + Integer.toHexString(colour.getRed())
281             + ((colour.getGreen()< 16) ? "0":"")+Integer.toHexString(colour.getGreen())
282             + ((colour.getBlue()< 16) ? "0":"")+Integer.toHexString(colour.getBlue());
283     StringBuilder currange = colranges.get(colstring);
284     if (currange == null)
285     {
286       colranges.put(colstring, currange = new StringBuilder(256));
287     }
288     /*
289      * Format as (e.g.) #0:1-3.A,5.A,7-10.A,...#1:1-4.B,..etc
290      */
291     // if (currange.length() > 0)
292     // {
293     // currange.append("|");
294     // }
295     // currange.append("#" + model + ":" + ((startPos==endPos) ? startPos :
296     // startPos + "-"
297     // + endPos) + "." + chain);
298     if (currange.length() == 0)
299     {
300       currange.append("#" + model + ":");
301     }
302     else if (startModel)
303     {
304       currange.append(",#" + model + ":");
305     }
306     else
307     {
308       currange.append(",");
309     }
310     final String rangeSpec = (startPos == endPos) ? Integer
311             .toString(startPos) : (startPos + "-" + endPos);
312     currange.append(rangeSpec + "." + chain);
313   }
314
315 }