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