bece4ebe730004d7dc061bacffe0c3225f61f1ce
[jalview.git] / src / jalview / ext / rbvi / chimera / ChimeraCommands.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ 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.ColorUtils;
31 import jalview.util.Comparison;
32
33 import java.awt.Color;
34 import java.util.ArrayList;
35 import java.util.LinkedHashMap;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.TreeMap;
39
40 /**
41  * Routines for generating Chimera commands for Jalview/Chimera binding
42  * 
43  * @author JimP
44  * 
45  */
46 public class ChimeraCommands
47 {
48
49   /**
50    * utility to construct the commands to colour chains by the given alignment
51    * for passing to Chimera
52    * 
53    * @returns Object[] { Object[] { <model being coloured>,
54    * 
55    */
56   public static StructureMappingcommandSet[] getColourBySequenceCommand(
57           StructureSelectionManager ssm, String[] files,
58           SequenceI[][] sequence, SequenceRenderer sr, FeatureRenderer fr,
59           AlignmentI alignment)
60   {
61     Map<Color, Map<Integer, Map<String, List<int[]>>>> colourMap = buildColoursMap(
62             ssm, files, sequence, sr, fr, alignment);
63
64     List<String> colourCommands = buildColourCommands(colourMap);
65
66     StructureMappingcommandSet cs = new StructureMappingcommandSet(
67             ChimeraCommands.class, null,
68             colourCommands.toArray(new String[0]));
69
70     return new StructureMappingcommandSet[] { cs };
71   }
72
73   /**
74    * Traverse the map of colours/models/chains/positions to construct a list of
75    * 'color' commands (one per distinct colour used). The format of each command
76    * is
77    * 
78    * <pre>
79    * <blockquote> color colorname #modelnumber:range.chain e.g. color #00ff00
80    * #0:2.B,4.B,9-12.B|#1:1.A,2-6.A,...
81    * </blockquote>
82    * </pre>
83    * 
84    * @param colourMap
85    * @return
86    * @see http
87    *      ://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/frameatom_spec
88    *      .html
89    */
90   protected static List<String> buildColourCommands(
91           Map<Color, Map<Integer, Map<String, List<int[]>>>> colourMap)
92   {
93     /*
94      * This version concatenates all commands into a single String (semi-colon
95      * delimited). If length limit issues arise, refactor to return one color
96      * command per colour.
97      */
98     List<String> commands = new ArrayList<String>();
99     StringBuilder sb = new StringBuilder(256);
100     boolean firstColour = true;
101     for (Color colour : colourMap.keySet())
102     {
103       String colourCode = ColorUtils.toTkCode(colour);
104       if (!firstColour)
105       {
106         sb.append("; ");
107       }
108       sb.append("color ").append(colourCode).append(" ");
109       firstColour = false;
110       boolean firstModelForColour = true;
111       final Map<Integer, Map<String, List<int[]>>> colourData = colourMap
112               .get(colour);
113       for (Integer model : colourData.keySet())
114       {
115         boolean firstPositionForModel = true;
116         if (!firstModelForColour)
117         {
118           sb.append("|");
119         }
120         firstModelForColour = false;
121         sb.append("#").append(model).append(":");
122
123         final Map<String, List<int[]>> modelData = colourData.get(model);
124         for (String chain : modelData.keySet())
125         {
126           boolean hasChain = !"".equals(chain.trim());
127           for (int[] range : modelData.get(chain))
128           {
129             if (!firstPositionForModel)
130             {
131               sb.append(",");
132             }
133             if (range[0] == range[1])
134             {
135               sb.append(range[0]);
136             }
137             else
138             {
139               sb.append(range[0]).append("-").append(range[1]);
140             }
141             if (hasChain)
142             {
143               sb.append(".").append(chain);
144             }
145             firstPositionForModel = false;
146           }
147         }
148       }
149     }
150     commands.add(sb.toString());
151     return commands;
152   }
153
154   /**
155    * <pre>
156    * Build a data structure which maps contiguous subsequences for each colour. 
157    * This generates a data structure from which we can easily generate the 
158    * Chimera command for colour by sequence.
159    * Color
160    *     Model number
161    *         Chain
162    *             list of start/end ranges
163    * Ordering is by order of addition (for colours and positions), natural ordering (for models and chains)
164    * </pre>
165    */
166   protected static Map<Color, Map<Integer, Map<String, List<int[]>>>> buildColoursMap(
167           StructureSelectionManager ssm, String[] files,
168           SequenceI[][] sequence, SequenceRenderer sr, FeatureRenderer fr,
169           AlignmentI alignment)
170   {
171     Map<Color, Map<Integer, Map<String, List<int[]>>>> colourMap = new LinkedHashMap<Color, Map<Integer, Map<String, List<int[]>>>>();
172     Color lastColour = null;
173     for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
174     {
175       StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
176
177       if (mapping == null || mapping.length < 1)
178       {
179         continue;
180       }
181
182       int startPos = -1, lastPos = -1;
183       String lastChain = "";
184       for (int s = 0; s < sequence[pdbfnum].length; s++)
185       {
186         for (int sp, m = 0; m < mapping.length; m++)
187         {
188           final SequenceI seq = sequence[pdbfnum][s];
189           if (mapping[m].getSequence() == seq
190                   && (sp = alignment.findIndex(seq)) > -1)
191           {
192             SequenceI asp = alignment.getSequenceAt(sp);
193             for (int r = 0; r < asp.getLength(); r++)
194             {
195               // no mapping to gaps in sequence
196               if (Comparison.isGap(asp.getCharAt(r)))
197               {
198                 continue;
199               }
200               int pos = mapping[m].getPDBResNum(asp.findPosition(r));
201
202               if (pos < 1 || pos == lastPos)
203               {
204                 continue;
205               }
206
207               Color colour = sr.getResidueColour(seq, r, fr);
208               final String chain = mapping[m].getChain();
209
210               /*
211                * Just keep incrementing the end position for this colour range
212                * _unless_ colour, PDB model or chain has changed, or there is a
213                * gap in the mapped residue sequence
214                */
215               final boolean newColour = !colour.equals(lastColour);
216               final boolean nonContig = lastPos + 1 != pos;
217               final boolean newChain = !chain.equals(lastChain);
218               if (newColour || nonContig || newChain)
219               {
220                 if (startPos != -1)
221                 {
222                   addColourRange(colourMap, lastColour, pdbfnum, startPos,
223                           lastPos, lastChain);
224                 }
225                 startPos = pos;
226               }
227               lastColour = colour;
228               lastPos = pos;
229               lastChain = chain;
230             }
231             // final colour range
232             if (lastColour != null)
233             {
234               addColourRange(colourMap, lastColour, pdbfnum, startPos,
235                       lastPos, lastChain);
236             }
237             // break;
238           }
239         }
240       }
241     }
242     return colourMap;
243   }
244
245   /**
246    * Helper method to add one contiguous colour range to the colour map.
247    * 
248    * @param colourMap
249    * @param colour
250    * @param model
251    * @param startPos
252    * @param endPos
253    * @param chain
254    */
255   protected static void addColourRange(
256           Map<Color, Map<Integer, Map<String, List<int[]>>>> colourMap,
257           Color colour, int model, int startPos, int endPos, String chain)
258   {
259     /*
260      * Get/initialize map of data for the colour
261      */
262     Map<Integer, Map<String, List<int[]>>> colourData = colourMap
263             .get(colour);
264     if (colourData == null)
265     {
266       colourMap
267               .put(colour,
268                       colourData = new TreeMap<Integer, Map<String, List<int[]>>>());
269     }
270
271     /*
272      * Get/initialize map of data for the colour and model
273      */
274     Map<String, List<int[]>> modelData = colourData.get(model);
275     if (modelData == null)
276     {
277       colourData.put(model, modelData = new TreeMap<String, List<int[]>>());
278     }
279
280     /*
281      * Get/initialize map of data for colour, model and chain
282      */
283     List<int[]> chainData = modelData.get(chain);
284     if (chainData == null)
285     {
286       modelData.put(chain, chainData = new ArrayList<int[]>());
287     }
288
289     /*
290      * Add the start/end positions
291      */
292     chainData.add(new int[] { startPos, endPos });
293   }
294
295 }