Class |
Line # |
Actions |
|||
---|---|---|---|---|---|
ChimeraCommands | 42 | 86 | 33 |
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 java.awt.Color; | |
24 | import java.util.ArrayList; | |
25 | import java.util.Arrays; | |
26 | import java.util.List; | |
27 | import java.util.Locale; | |
28 | import java.util.Map; | |
29 | ||
30 | import jalview.structure.AtomSpecModel; | |
31 | import jalview.structure.StructureCommand; | |
32 | import jalview.structure.StructureCommandI; | |
33 | import jalview.structure.StructureCommandsBase; | |
34 | import jalview.util.ColorUtils; | |
35 | ||
36 | /** | |
37 | * Routines for generating Chimera commands for Jalview/Chimera binding | |
38 | * | |
39 | * @author JimP | |
40 | * | |
41 | */ | |
42 | public class ChimeraCommands extends StructureCommandsBase | |
43 | { | |
44 | // https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/focus.html | |
45 | private static final StructureCommand FOCUS_VIEW = new StructureCommand( | |
46 | "focus"); | |
47 | ||
48 | // https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/listen.html#listresattr | |
49 | private static final StructureCommand LIST_RESIDUE_ATTRIBUTES = new StructureCommand( | |
50 | "list resattr"); | |
51 | ||
52 | // https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/stop.html | |
53 | private static final StructureCommand CLOSE_CHIMERA = new StructureCommand( | |
54 | "stop really"); | |
55 | ||
56 | // https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/listen.html | |
57 | private static final StructureCommand STOP_NOTIFY_SELECTION = new StructureCommand( | |
58 | "listen stop selection"); | |
59 | ||
60 | private static final StructureCommand STOP_NOTIFY_MODELS = new StructureCommand( | |
61 | "listen stop models"); | |
62 | ||
63 | // https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/listen.html#listselection | |
64 | private static final StructureCommand GET_SELECTION = new StructureCommand( | |
65 | "list selection level residue"); | |
66 | ||
67 | private static final StructureCommand SHOW_BACKBONE = new StructureCommand( | |
68 | "~display all;~ribbon;chain @CA|P"); | |
69 | ||
70 | private static final StructureCommandI COLOUR_BY_CHARGE = new StructureCommand( | |
71 | "color white;color red ::ASP,GLU;color blue ::LYS,ARG;color yellow ::CYS"); | |
72 | ||
73 | // https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/rainbow.html | |
74 | private static final StructureCommandI COLOUR_BY_CHAIN = new StructureCommand( | |
75 | "rainbow chain"); | |
76 | ||
77 | // Chimera clause to exclude alternate locations in atom selection | |
78 | private static final String NO_ALTLOCS = "&~@.B-Z&~@.2-9"; | |
79 | ||
80 | 4 | @Override |
81 | public StructureCommandI colourResidues(String atomSpec, Color colour) | |
82 | { | |
83 | // https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/color.html | |
84 | 4 | String colourCode = getColourString(colour); |
85 | 4 | return new StructureCommand("color " + colourCode + " " + atomSpec); |
86 | } | |
87 | ||
88 | /** | |
89 | * Returns a colour formatted suitable for use in viewer command syntax | |
90 | * | |
91 | * @param colour | |
92 | * @return | |
93 | */ | |
94 | 8 | protected String getColourString(Color colour) |
95 | { | |
96 | 8 | return ColorUtils.toTkCode(colour); |
97 | } | |
98 | ||
99 | /** | |
100 | * Traverse the map of features/values/models/chains/positions to construct a | |
101 | * list of 'setattr' commands (one per distinct feature type and value). | |
102 | * <p> | |
103 | * The format of each command is | |
104 | * | |
105 | * <pre> | |
106 | * <blockquote> setattr r <featureName> " " #modelnumber:range.chain | |
107 | * e.g. setattr r jv_chain <value> #0:2.B,4.B,9-12.B|#1:1.A,2-6.A,... | |
108 | * </blockquote> | |
109 | * </pre> | |
110 | * | |
111 | * @param featureMap | |
112 | * @return | |
113 | */ | |
114 | 10 | @Override |
115 | public List<StructureCommandI> setAttributes( | |
116 | Map<String, Map<Object, AtomSpecModel>> featureMap) | |
117 | { | |
118 | 10 | List<StructureCommandI> commands = new ArrayList<>(); |
119 | 10 | for (String featureType : featureMap.keySet()) |
120 | { | |
121 | 10 | String attributeName = makeAttributeName(featureType); |
122 | ||
123 | /* | |
124 | * clear down existing attributes for this feature | |
125 | */ | |
126 | // 'problem' - sets attribute to None on all residues - overkill? | |
127 | // commands.add("~setattr r " + attributeName + " :*"); | |
128 | ||
129 | 10 | Map<Object, AtomSpecModel> values = featureMap.get(featureType); |
130 | 10 | for (Object value : values.keySet()) |
131 | { | |
132 | /* | |
133 | * for each distinct value recorded for this feature type, | |
134 | * add a command to set the attribute on the mapped residues | |
135 | * Put values in single quotes, encoding any embedded single quotes | |
136 | */ | |
137 | 12 | AtomSpecModel atomSpecModel = values.get(value); |
138 | 12 | String featureValue = value.toString(); |
139 | 12 | featureValue = featureValue.replaceAll("\\'", "'"); |
140 | 12 | StructureCommandI cmd = setAttribute(attributeName, featureValue, |
141 | atomSpecModel); | |
142 | 12 | commands.add(cmd); |
143 | } | |
144 | } | |
145 | ||
146 | 10 | return commands; |
147 | } | |
148 | ||
149 | /** | |
150 | * Returns a viewer command to set the given residue attribute value on | |
151 | * residues specified by the AtomSpecModel, for example | |
152 | * | |
153 | * <pre> | |
154 | * setatr res jv_chain 'primary' #1:12-34,48-55.B | |
155 | * </pre> | |
156 | * | |
157 | * @param attributeName | |
158 | * @param attributeValue | |
159 | * @param atomSpecModel | |
160 | * @return | |
161 | */ | |
162 | 7 | protected StructureCommandI setAttribute(String attributeName, |
163 | String attributeValue, AtomSpecModel atomSpecModel) | |
164 | { | |
165 | 7 | StringBuilder sb = new StringBuilder(128); |
166 | 7 | sb.append("setattr res ").append(attributeName).append(" '") |
167 | .append(attributeValue).append("' "); | |
168 | 7 | sb.append(getAtomSpec(atomSpecModel, AtomSpecType.RESIDUE_ONLY)); |
169 | 7 | return new StructureCommand(sb.toString()); |
170 | } | |
171 | ||
172 | /** | |
173 | * Makes a prefixed and valid Chimera attribute name. A jv_ prefix is applied | |
174 | * for a 'Jalview' namespace, and any non-alphanumeric character is converted | |
175 | * to an underscore. | |
176 | * | |
177 | * @param featureType | |
178 | * @return | |
179 | * @see https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/setattr.html | |
180 | */ | |
181 | 16 | @Override |
182 | protected String makeAttributeName(String featureType) | |
183 | { | |
184 | 16 | String attName = super.makeAttributeName(featureType); |
185 | ||
186 | /* | |
187 | * Chimera treats an attribute name ending in 'color' as colour-valued; | |
188 | * Jalview doesn't, so prevent this by appending an underscore | |
189 | */ | |
190 | 16 | if (attName.toUpperCase(Locale.ROOT).endsWith("COLOR")) |
191 | { | |
192 | 1 | attName += "_"; |
193 | } | |
194 | ||
195 | 16 | return attName; |
196 | } | |
197 | ||
198 | 2 | @Override |
199 | public StructureCommandI colourByChain() | |
200 | { | |
201 | 2 | return COLOUR_BY_CHAIN; |
202 | } | |
203 | ||
204 | 1 | @Override |
205 | public List<StructureCommandI> colourByCharge() | |
206 | { | |
207 | 1 | return Arrays.asList(COLOUR_BY_CHARGE); |
208 | } | |
209 | ||
210 | 1 | @Override |
211 | public String getResidueSpec(String residue) | |
212 | { | |
213 | 1 | return "::" + residue; |
214 | } | |
215 | ||
216 | 2 | @Override |
217 | public StructureCommandI setBackgroundColour(Color col) | |
218 | { | |
219 | // https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/set.html#bgcolor | |
220 | 2 | return new StructureCommand("set bgColor " + ColorUtils.toTkCode(col)); |
221 | } | |
222 | ||
223 | 1 | @Override |
224 | public StructureCommandI focusView() | |
225 | { | |
226 | 1 | return FOCUS_VIEW; |
227 | } | |
228 | ||
229 | 0 | @Override |
230 | public List<StructureCommandI> showChains(List<String> toShow) | |
231 | { | |
232 | /* | |
233 | * Construct a chimera command like | |
234 | * | |
235 | * ~display #*;~ribbon #*;ribbon :.A,:.B | |
236 | */ | |
237 | 0 | StringBuilder cmd = new StringBuilder(64); |
238 | 0 | boolean first = true; |
239 | 0 | for (String chain : toShow) |
240 | { | |
241 | 0 | String[] tokens = chain.split(":"); |
242 | 0 | if (tokens.length == 2) |
243 | { | |
244 | 0 | String showChainCmd = tokens[0] + ":." + tokens[1]; |
245 | 0 | if (!first) |
246 | { | |
247 | 0 | cmd.append(","); |
248 | } | |
249 | 0 | cmd.append(showChainCmd); |
250 | 0 | first = false; |
251 | } | |
252 | } | |
253 | ||
254 | /* | |
255 | * could append ";focus" to this command to resize the display to fill the | |
256 | * window, but it looks more helpful not to (easier to relate chains to the | |
257 | * whole) | |
258 | */ | |
259 | 0 | final String command = "~display #*; ~ribbon #*; ribbon :" |
260 | + cmd.toString(); | |
261 | 0 | return Arrays.asList(new StructureCommand(command)); |
262 | } | |
263 | ||
264 | 1 | @Override |
265 | public List<StructureCommandI> superposeStructures(AtomSpecModel ref, | |
266 | AtomSpecModel spec, AtomSpecType backbone) | |
267 | { | |
268 | /* | |
269 | * Form Chimera match command to match spec to ref | |
270 | * (the first set of atoms are moved on to the second) | |
271 | * | |
272 | * match #1:1-30.B,81-100.B@CA #0:21-40.A,61-90.A@CA | |
273 | * | |
274 | * @see https://www.cgl.ucsf.edu/chimera/docs/UsersGuide/midas/match.html | |
275 | */ | |
276 | 1 | StringBuilder cmd = new StringBuilder(); |
277 | 1 | String atomSpecAlphaOnly = getAtomSpec(spec, backbone); |
278 | 1 | String refSpecAlphaOnly = getAtomSpec(ref, backbone); |
279 | 1 | cmd.append("match ").append(atomSpecAlphaOnly).append(" ") |
280 | .append(refSpecAlphaOnly); | |
281 | ||
282 | /* | |
283 | * show superposed residues as ribbon | |
284 | */ | |
285 | 1 | String atomSpec = getAtomSpec(spec, AtomSpecType.RESIDUE_ONLY); |
286 | 1 | String refSpec = getAtomSpec(ref, AtomSpecType.RESIDUE_ONLY); |
287 | 1 | cmd.append("; ribbon "); |
288 | 1 | cmd.append(atomSpec).append("|").append(refSpec).append("; focus"); |
289 | ||
290 | 1 | return Arrays.asList(new StructureCommand(cmd.toString())); |
291 | } | |
292 | ||
293 | 1 | @Override |
294 | public StructureCommandI openCommandFile(String path) | |
295 | { | |
296 | // https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/filetypes.html | |
297 | 1 | return new StructureCommand("open cmd:" + path); |
298 | } | |
299 | ||
300 | 1 | @Override |
301 | public StructureCommandI saveSession(String filepath) | |
302 | { | |
303 | // https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/save.html | |
304 | 1 | return new StructureCommand("save " + filepath); |
305 | } | |
306 | ||
307 | /** | |
308 | * Returns the range(s) modelled by {@code atomSpec} formatted as a Chimera | |
309 | * atomspec string, e.g. | |
310 | * | |
311 | * <pre> | |
312 | * #0:15.A,28.A,54.A,70-72.A|#1:2.A,6.A,11.A,13-14.A | |
313 | * </pre> | |
314 | * | |
315 | * where | |
316 | * <ul> | |
317 | * <li>#0 is a model number</li> | |
318 | * <li>15 or 70-72 is a residue number, or range of residue numbers</li> | |
319 | * <li>.A is a chain identifier</li> | |
320 | * <li>residue ranges are separated by comma</li> | |
321 | * <li>atomspecs for distinct models are separated by | (or)</li> | |
322 | * </ul> | |
323 | * | |
324 | * <pre> | |
325 | * | |
326 | * @param model | |
327 | * @param specType | |
328 | * @return | |
329 | * @see https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/frameatom_spec.html | |
330 | */ | |
331 | 41 | @Override |
332 | public String getAtomSpec(AtomSpecModel atomSpec, AtomSpecType specType) | |
333 | { | |
334 | 41 | StringBuilder sb = new StringBuilder(128); |
335 | 41 | boolean firstModel = true; |
336 | 41 | for (String model : atomSpec.getModels()) |
337 | { | |
338 | 60 | if (!firstModel) |
339 | { | |
340 | 21 | sb.append("|"); |
341 | } | |
342 | 60 | firstModel = false; |
343 | 60 | appendModel(sb, model, atomSpec, specType); |
344 | } | |
345 | 41 | return sb.toString(); |
346 | } | |
347 | ||
348 | /** | |
349 | * A helper method to append an atomSpec string for atoms in the given model | |
350 | * | |
351 | * @param sb | |
352 | * @param model | |
353 | * @param atomSpec | |
354 | * @param alphaOnly | |
355 | */ | |
356 | 60 | protected void appendModel(StringBuilder sb, String model, |
357 | AtomSpecModel atomSpec, AtomSpecType specType) | |
358 | { | |
359 | 60 | sb.append("#").append(model).append(":"); |
360 | ||
361 | 60 | boolean firstPositionForModel = true; |
362 | ||
363 | 60 | for (String chain : atomSpec.getChains(model)) |
364 | { | |
365 | 94 | chain = " ".equals(chain) ? chain : chain.trim(); |
366 | ||
367 | 94 | List<int[]> rangeList = atomSpec.getRanges(model, chain); |
368 | 94 | for (int[] range : rangeList) |
369 | { | |
370 | 119 | appendRange(sb, range[0], range[1], chain, firstPositionForModel, |
371 | false); | |
372 | 119 | firstPositionForModel = false; |
373 | } | |
374 | } | |
375 | 60 | if (specType == AtomSpecType.ALPHA) |
376 | { | |
377 | /* | |
378 | * restrict to alpha carbon, no alternative locations | |
379 | * (needed to ensuring matching atom counts for superposition) | |
380 | */ | |
381 | 19 | sb.append("@CA").append(NO_ALTLOCS); |
382 | } | |
383 | 60 | if (specType == AtomSpecType.PHOSPHATE) |
384 | { | |
385 | 0 | sb.append("@P").append(NO_ALTLOCS); |
386 | } | |
387 | } | |
388 | ||
389 | 1 | @Override |
390 | public List<StructureCommandI> showBackbone() | |
391 | { | |
392 | 1 | return Arrays.asList(SHOW_BACKBONE); |
393 | } | |
394 | ||
395 | 1 | @Override |
396 | public StructureCommandI loadFile(String file) | |
397 | { | |
398 | 1 | return new StructureCommand("open " + file); |
399 | } | |
400 | ||
401 | 1 | @Override |
402 | public StructureCommandI openSession(String filepath) | |
403 | { | |
404 | // https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/filetypes.html | |
405 | // this version of the command has no dependency on file extension | |
406 | 1 | return new StructureCommand("open chimera:" + filepath); |
407 | } | |
408 | ||
409 | 1 | @Override |
410 | public StructureCommandI closeViewer() | |
411 | { | |
412 | 1 | return CLOSE_CHIMERA; |
413 | } | |
414 | ||
415 | 1 | @Override |
416 | public List<StructureCommandI> startNotifications(String uri) | |
417 | { | |
418 | // https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/listen.html | |
419 | 1 | List<StructureCommandI> cmds = new ArrayList<>(); |
420 | 1 | cmds.add(new StructureCommand("listen start models url " + uri)); |
421 | 1 | cmds.add(new StructureCommand( |
422 | "listen start select prefix SelectionChanged url " + uri)); | |
423 | 1 | return cmds; |
424 | } | |
425 | ||
426 | 1 | @Override |
427 | public List<StructureCommandI> stopNotifications() | |
428 | { | |
429 | 1 | List<StructureCommandI> cmds = new ArrayList<>(); |
430 | 1 | cmds.add(STOP_NOTIFY_MODELS); |
431 | 1 | cmds.add(STOP_NOTIFY_SELECTION); |
432 | 1 | return cmds; |
433 | } | |
434 | ||
435 | 1 | @Override |
436 | public StructureCommandI getSelectedResidues() | |
437 | { | |
438 | 1 | return GET_SELECTION; |
439 | } | |
440 | ||
441 | 1 | @Override |
442 | public StructureCommandI listResidueAttributes() | |
443 | { | |
444 | 1 | return LIST_RESIDUE_ATTRIBUTES; |
445 | } | |
446 | ||
447 | 1 | @Override |
448 | public StructureCommandI getResidueAttributes(String attName) | |
449 | { | |
450 | // this alternative command | |
451 | // list residues spec ':*/attName' attr attName | |
452 | // doesn't report 'None' values (which is good), but | |
453 | // fails for 'average.bfactor' (which is bad): | |
454 | 1 | return new StructureCommand("list residues attr '" + attName + "'"); |
455 | } | |
456 | ||
457 | 0 | @Override |
458 | public List<StructureCommandI> centerViewOn(List<AtomSpecModel> residues) | |
459 | { | |
460 | // TODO Auto-generated method stub | |
461 | 0 | return null; |
462 | } | |
463 | ||
464 | } |