<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/Plugin.jar"/>
<classpathentry kind="lib" path="lib/jfreesvg-2.1.jar"/>
- <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.launching.macosx.MacOSXType/Java SE 6 [1.6.0_65-b14-462]"/>
<classpathentry kind="output" path="classes"/>
</classpath>
package ext.edu.ucsf.rbvi.strucviz2;
+import jalview.ws.HttpClientUtils;
+
import java.awt.Color;
+import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import org.apache.http.NameValuePair;
+import org.apache.http.message.BasicNameValuePair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
*/
public class ChimeraManager
{
+ private static final boolean debug = false;
+
+ /*
+ * true: use REST API (recommended), false: use stdout/stdin (deprecated)
+ */
+ // TODO remove once definitely happy with using REST
+ private static final boolean USE_REST = true;
+
+ private int chimeraRestPort;
private Process chimera;
- private ListenerThreads chimeraListenerThreads;
+ private ListenerThreads chimeraListenerThread;
private Map<Integer, ChimeraModel> currentModelsMap;
{
this.structureManager = structureManager;
chimera = null;
- chimeraListenerThreads = null;
+ chimeraListenerThread = null;
currentModelsMap = new HashMap<Integer, ChimeraModel>();
}
{
chimera = null;
currentModelsMap.clear();
- chimeraListenerThreads = null;
+ if (USE_REST)
+ {
+ this.chimeraRestPort = 0;
+ }
+ else
+ {
+ chimeraListenerThread.requestStop();
+ chimeraListenerThread = null;
+ }
structureManager.clearOnChimeraExit();
}
List<String> args = new ArrayList<String>();
args.add(chimeraPath);
args.add("--start");
- args.add("ReadStdin");
+ args.add(USE_REST ? "RESTServer" : "ReadStdin");
ProcessBuilder pb = new ProcessBuilder(args);
chimera = pb.start();
error = "";
workingPath = chimeraPath;
- logger.info("Strarting " + chimeraPath);
+ logger.info("Starting " + chimeraPath + " with "
+ + (USE_REST ? "REST API" : "stdin/stdout"));
break;
} catch (Exception e)
{
// If no error, then Chimera was launched successfully
if (error.length() == 0)
{
- // Initialize the listener threads
- chimeraListenerThreads = new ListenerThreads(chimera,
- structureManager);
- chimeraListenerThreads.start();
+ if (USE_REST)
+ {
+ this.chimeraRestPort = getPortNumber();
+ System.out.println("Chimera REST API on port " + chimeraRestPort);
+ }
+ else
+ {
+ // Initialize the listener threads
+ chimeraListenerThread = new ListenerThreads(chimera,
+ structureManager);
+ chimeraListenerThread.start();
+ }
// structureManager.initChimTable();
structureManager.setChimeraPathProperty(workingPath);
// TODO: [Optional] Check Chimera version and show a warning if below 1.8
}
/**
+ * Read and return the port number returned in the reply to --start RESTServer
+ */
+ private int getPortNumber()
+ {
+ int port = 0;
+ InputStream readChan = chimera.getInputStream();
+ BufferedReader lineReader = new BufferedReader(new InputStreamReader(
+ readChan));
+ String response = null;
+ try
+ {
+ // expect: REST server on host 127.0.0.1 port port_number
+ response = lineReader.readLine();
+ String [] tokens = response.split(" ");
+ if (tokens.length == 7 && "port".equals(tokens[5])) {
+ port = Integer.parseInt(tokens[6]);
+ logger.info("Chimera REST service listening on port "
+ + chimeraRestPort);
+ }
+ } catch (Exception e)
+ {
+ logger.error("Failed to get REST port number from " + response + ": "
+ + e.getMessage());
+ } finally
+ {
+ try
+ {
+ lineReader.close();
+ } catch (IOException e2)
+ {
+ }
+ }
+ return port;
+ }
+
+ /**
* Determine the color that Chimera is using for this model.
*
* @param model
*/
public List<String> sendChimeraCommand(String command, boolean reply)
{
- if (!isChimeraLaunched())
+ if (!isChimeraLaunched() || command == null
+ || "".equals(command.trim()))
{
return null;
}
;
}
busy = true;
+ long startTime = System.currentTimeMillis();
try
{
- chimeraListenerThreads.clearResponse(command);
- String text = command.concat("\n");
- // System.out.println("send command to chimera: " + text);
- try
+ if (USE_REST)
{
- // send the command
- chimera.getOutputStream().write(text.getBytes());
- chimera.getOutputStream().flush();
- } catch (IOException e)
- {
- // logger.info("Unable to execute command: " + text);
- // logger.info("Exiting...");
- logger.warn("Unable to execute command: " + text);
- logger.warn("Exiting...");
- clearOnChimeraExit();
- // busy = false;
- return null;
+ return sendRestCommand(command);
}
- if (!reply)
+ else
{
- // busy = false;
- return null;
+ return sendStdinCommand(command, reply);
}
- List<String> rsp = chimeraListenerThreads.getResponse(command);
- // busy = false;
- return rsp;
} finally
{
busy = false;
+ if (debug)
+ {
+ System.out.println("Chimera command took "
+ + (System.currentTimeMillis() - startTime) + "ms: "
+ + command);
+ }
+
+ }
+ }
+
+ /**
+ * Sends the command to Chimera's REST API, and returns any response lines.
+ *
+ * @param command
+ * @return
+ */
+ protected List<String> sendRestCommand(String command)
+ {
+ // TODO start a separate thread to do this so we don't block?
+ String restUrl = "http://127.0.0.1:" + this.chimeraRestPort + "/run";
+ List<NameValuePair> commands = new ArrayList<NameValuePair>(1);
+ commands.add(new BasicNameValuePair("command", command));
+
+ List<String> reply = new ArrayList<String>();
+ BufferedReader response = null;
+ try {
+ response = HttpClientUtils.doHttpUrlPost(restUrl,
+ commands);
+ String line = "";
+ while ((line = response.readLine()) != null) {
+ reply.add(line);
+ }
+ } catch (Exception e)
+ {
+ logger.error("REST call " + command + " failed: " + e.getMessage());
+ } finally
+ {
+ if (response != null)
+ {
+ try
+ {
+ response.close();
+ } catch (IOException e)
+ {
+ }
+ }
+ }
+ return reply;
+ }
+
+ /**
+ * Send a command to stdin of Chimera process, and optionally read any
+ * responses.
+ *
+ * @param command
+ * @param readReply
+ * @return
+ */
+ protected List<String> sendStdinCommand(String command, boolean readReply)
+ {
+ chimeraListenerThread.clearResponse(command);
+ String text = command.concat("\n");
+ try
+ {
+ // send the command
+ chimera.getOutputStream().write(text.getBytes());
+ chimera.getOutputStream().flush();
+ } catch (IOException e)
+ {
+ // logger.info("Unable to execute command: " + text);
+ // logger.info("Exiting...");
+ logger.warn("Unable to execute command: " + text);
+ logger.warn("Exiting...");
+ clearOnChimeraExit();
+ return null;
+ }
+ if (!readReply)
+ {
+ return null;
}
+ List<String> rsp = chimeraListenerThread.getResponse(command);
+ return rsp;
}
public StructureManager getStructureManager()
/**
* Reply listener thread
*/
-public class ListenerThreads extends Thread {
- private InputStream readChan = null;
- private BufferedReader lineReader = null;
- private Process chimera = null;
- private Map<String, List<String>> replyLog = null;
- private Logger logger;
- private StructureManager structureManager = null;
-
- /**
- * Create a new listener thread to read the responses from Chimera
- *
- * @param chimera
- * a handle to the Chimera Process
- * @param log
- * a handle to a List to post the responses to
- * @param chimeraObject
- * a handle to the Chimera Object
- */
- public ListenerThreads(Process chimera, StructureManager structureManager) {
- this.chimera = chimera;
- this.structureManager = structureManager;
- replyLog = new HashMap<String, List<String>>();
- // Get a line-oriented reader
- readChan = chimera.getInputStream();
- lineReader = new BufferedReader(new InputStreamReader(readChan));
- logger = LoggerFactory.getLogger(ext.edu.ucsf.rbvi.strucviz2.port.ListenerThreads.class);
- }
-
- /**
- * Start the thread running
- */
- public void run() {
- // System.out.println("ReplyLogListener running");
- while (true) {
- try {
- chimeraRead();
- } catch (IOException e) {
- logger.warn("UCSF Chimera has exited: " + e.getMessage());
- return;
- }
- }
- }
-
- public List<String> getResponse(String command) {
- List<String> reply;
- // System.out.println("getResponse: "+command);
+public class ListenerThreads extends Thread
+{
+ private BufferedReader lineReader = null;
+
+ private Process chimera = null;
+
+ private Map<String, List<String>> replyLog = null;
+
+ private Logger logger;
+
+ private StructureManager structureManager = null;
+
+ private boolean stopMe = false;
+
+ /**
+ * Create a new listener thread to read the responses from Chimera
+ *
+ * @param chimera
+ * a handle to the Chimera Process
+ * @param structureManager
+ * a handle to the Chimera structure manager
+ */
+ public ListenerThreads(Process chimera, StructureManager structureManager)
+ {
+ this.chimera = chimera;
+ this.structureManager = structureManager;
+ replyLog = new HashMap<String, List<String>>();
+ // Get a line-oriented reader
+ InputStream readChan = chimera.getInputStream();
+ lineReader = new BufferedReader(new InputStreamReader(readChan));
+ logger = LoggerFactory
+ .getLogger(ext.edu.ucsf.rbvi.strucviz2.port.ListenerThreads.class);
+ }
+
+ /**
+ * Start the thread running
+ */
+ public void run()
+ {
+ // System.out.println("ReplyLogListener running");
+ while (!stopMe)
+ {
+ try
+ {
+ chimeraRead();
+ } catch (IOException e)
+ {
+ logger.warn("UCSF Chimera has exited: " + e.getMessage());
+ return;
+ } finally
+ {
+ if (lineReader != null)
+ {
+ try
+ {
+ lineReader.close();
+ } catch (IOException e)
+ {
+ }
+ }
+ }
+ }
+ }
+
+ public List<String> getResponse(String command)
+ {
+ List<String> reply;
+ // System.out.println("getResponse: "+command);
// TODO do we need a maximum wait time before aborting?
- while (!replyLog.containsKey(command)) {
- try {
- Thread.currentThread().sleep(100);
- } catch (InterruptedException e) {
- }
- }
-
- synchronized (replyLog) {
- reply = replyLog.get(command);
- // System.out.println("getResponse ("+command+") = "+reply);
- replyLog.remove(command);
- }
- return reply;
- }
-
- public void clearResponse(String command) {
- try {
- Thread.currentThread().sleep(100);
- } catch (InterruptedException e) {
- }
- if (replyLog.containsKey(command))
+ while (!replyLog.containsKey(command))
+ {
+ try
+ {
+ Thread.currentThread().sleep(100);
+ } catch (InterruptedException e)
+ {
+ }
+ }
+
+ synchronized (replyLog)
{
+ reply = replyLog.get(command);
+ // System.out.println("getResponse ("+command+") = "+reply);
replyLog.remove(command);
}
- return;
- }
+ return reply;
+ }
- /**
- * Read input from Chimera
- *
- * @return a List containing the replies from Chimera
- */
- private void chimeraRead() throws IOException {
- if (chimera == null)
+ public void clearResponse(String command)
+ {
+ try
+ {
+ Thread.currentThread().sleep(100);
+ } catch (InterruptedException e)
+ {
+ }
+ if (replyLog.containsKey(command))
+ {
+ replyLog.remove(command);
+ }
+ return;
+ }
+
+ /**
+ * Read input from Chimera
+ *
+ * @return a List containing the replies from Chimera
+ */
+ private void chimeraRead() throws IOException
+ {
+ if (chimera == null)
{
return;
}
- String line = null;
- while ((line = lineReader.readLine()) != null) {
- // System.out.println("From Chimera-->" + line);
- if (line.startsWith("CMD")) {
- chimeraCommandRead(line.substring(4));
- } else if (line.startsWith("ModelChanged: ")) {
- (new ModelUpdater()).start();
- } else if (line.startsWith("SelectionChanged: ")) {
- (new SelectionUpdater()).start();
- } else if (line.startsWith("Trajectory residue network info:")) {
- (new NetworkUpdater(line)).start();
- }
- }
- return;
- }
-
- private void chimeraCommandRead(String command) throws IOException {
- // Generally -- looking for:
- // CMD command
- // ........
- // END
- // We return the text in between
- List<String> reply = new ArrayList<String>();
- boolean updateModels = false;
- boolean updateSelection = false;
- boolean importNetwork = false;
- String line = null;
-
- synchronized (replyLog) {
- while ((line = lineReader.readLine()) != null) {
- // System.out.println("From Chimera (" + command + ") -->" + line);
- if (line.startsWith("CMD")) {
- logger.warn("Got unexpected command from Chimera: " + line);
-
- } else if (line.startsWith("END")) {
- break;
- }
- if (line.startsWith("ModelChanged: ")) {
- updateModels = true;
- } else if (line.startsWith("SelectionChanged: ")) {
- updateSelection = true;
- } else if (line.length() == 0) {
- continue;
- } else if (!line.startsWith("CMD")) {
- reply.add(line);
- } else if (line.startsWith("Trajectory residue network info:")) {
- importNetwork = true;
- }
- }
- replyLog.put(command, reply);
- }
- if (updateModels)
+ String line = null;
+ while ((line = lineReader.readLine()) != null)
+ {
+ // System.out.println("From Chimera-->" + line);
+ if (line.startsWith("CMD"))
+ {
+ chimeraCommandRead(line.substring(4));
+ }
+ else if (line.startsWith("ModelChanged: "))
+ {
+ (new ModelUpdater()).start();
+ }
+ else if (line.startsWith("SelectionChanged: "))
+ {
+ (new SelectionUpdater()).start();
+ }
+ else if (line.startsWith("Trajectory residue network info:"))
+ {
+ (new NetworkUpdater(line)).start();
+ }
+ }
+ return;
+ }
+
+ private void chimeraCommandRead(String command) throws IOException
+ {
+ // Generally -- looking for:
+ // CMD command
+ // ........
+ // END
+ // We return the text in between
+ List<String> reply = new ArrayList<String>();
+ boolean updateModels = false;
+ boolean updateSelection = false;
+ boolean importNetwork = false;
+ String line = null;
+
+ synchronized (replyLog)
+ {
+ while ((line = lineReader.readLine()) != null)
+ {
+ // System.out.println("From Chimera (" + command + ") -->" + line);
+ if (line.startsWith("CMD"))
+ {
+ logger.warn("Got unexpected command from Chimera: " + line);
+
+ }
+ else if (line.startsWith("END"))
+ {
+ break;
+ }
+ if (line.startsWith("ModelChanged: "))
+ {
+ updateModels = true;
+ }
+ else if (line.startsWith("SelectionChanged: "))
+ {
+ updateSelection = true;
+ }
+ else if (line.length() == 0)
+ {
+ continue;
+ }
+ else if (!line.startsWith("CMD"))
+ {
+ reply.add(line);
+ }
+ else if (line.startsWith("Trajectory residue network info:"))
+ {
+ importNetwork = true;
+ }
+ }
+ replyLog.put(command, reply);
+ }
+ if (updateModels)
{
(new ModelUpdater()).start();
}
- if (updateSelection)
+ if (updateSelection)
{
(new SelectionUpdater()).start();
}
- if (importNetwork) {
- (new NetworkUpdater(line)).start();
- }
- return;
- }
-
- /**
- * Model updater thread
- */
- class ModelUpdater extends Thread {
-
- public ModelUpdater() {
- }
-
- public void run() {
- structureManager.updateModels();
- structureManager.modelChanged();
- }
- }
-
- /**
- * Selection updater thread
- */
- class SelectionUpdater extends Thread {
-
- public SelectionUpdater() {
- }
-
- public void run() {
- try {
- logger.info("Responding to chimera selection");
- structureManager.chimeraSelectionChanged();
- } catch (Exception e) {
- logger.warn("Could not update selection", e);
- }
- }
- }
-
- /**
- * Selection updater thread
- */
- class NetworkUpdater extends Thread {
-
- private String line;
-
- public NetworkUpdater(String line) {
- this.line = line;
- }
-
- public void run() {
- try {
-// ((TaskManager<?, ?>) structureManager.getService(TaskManager.class))
-// .execute(new ImportTrajectoryRINTaskFactory(structureManager, line)
-// .createTaskIterator());
- } catch (Exception e) {
- logger.warn("Could not import trajectory network", e);
- }
- }
- }
+ if (importNetwork)
+ {
+ (new NetworkUpdater(line)).start();
+ }
+ return;
+ }
+
+ /**
+ * Model updater thread
+ */
+ class ModelUpdater extends Thread
+ {
+
+ public ModelUpdater()
+ {
+ }
+
+ public void run()
+ {
+ structureManager.updateModels();
+ structureManager.modelChanged();
+ }
+ }
+
+ /**
+ * Selection updater thread
+ */
+ class SelectionUpdater extends Thread
+ {
+
+ public SelectionUpdater()
+ {
+ }
+
+ public void run()
+ {
+ try
+ {
+ logger.info("Responding to chimera selection");
+ structureManager.chimeraSelectionChanged();
+ } catch (Exception e)
+ {
+ logger.warn("Could not update selection", e);
+ }
+ }
+ }
+
+ /**
+ * Selection updater thread
+ */
+ class NetworkUpdater extends Thread
+ {
+
+ private String line;
+
+ public NetworkUpdater(String line)
+ {
+ this.line = line;
+ }
+
+ public void run()
+ {
+ try
+ {
+ // ((TaskManager<?, ?>) structureManager.getService(TaskManager.class))
+ // .execute(new ImportTrajectoryRINTaskFactory(structureManager, line)
+ // .createTaskIterator());
+ } catch (Exception e)
+ {
+ logger.warn("Could not import trajectory network", e);
+ }
+ }
+ }
+
+ /**
+ * Set a flag that this thread should clean up and exit.
+ */
+ public void requestStop()
+ {
+ this.stopMe = true;
+ }
}
Color getResidueBoxColour(SequenceI sequenceI, int r);
+ Color getResidueColour(SequenceI seq, int position, FeatureRenderer fr);
+
}
*/
package jalview.appletgui;
+import jalview.api.FeatureRenderer;
import jalview.datamodel.AlignmentAnnotation;
import jalview.datamodel.SequenceGroup;
import jalview.datamodel.SequenceI;
return resBoxColour;
}
+ /**
+ * Get the residue colour at the given sequence position - as determined by
+ * the sequence group colour (if any), else the colour scheme, possibly
+ * overridden by a feature colour.
+ *
+ * @param seq
+ * @param position
+ * @param fr
+ * @return
+ */
+ @Override
+ public Color getResidueColour(final SequenceI seq, int position,
+ FeatureRenderer fr)
+ {
+ // TODO replace 8 or so code duplications with calls to this method
+ // (refactored as needed)
+ Color col = getResidueBoxColour(seq, position);
+
+ if (fr != null)
+ {
+ col = fr.findFeatureColour(col, seq, position);
+ }
+ return col;
+ }
+
void getBoxColour(ColourSchemeI cs, SequenceI seq, int i)
{
if (cs != null)
import jalview.api.FeatureRenderer;
import jalview.api.SequenceRenderer;
-import jalview.api.structures.JalviewStructureDisplayI;
import jalview.datamodel.AlignmentI;
import jalview.datamodel.SequenceI;
import jalview.structure.StructureMapping;
import jalview.structure.StructureMappingcommandSet;
import jalview.structure.StructureSelectionManager;
-import jalview.util.Format;
+import jalview.util.ColorUtils;
+import jalview.util.Comparison;
import java.awt.Color;
import java.util.ArrayList;
-import java.util.Hashtable;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
/**
* Routines for generating Chimera commands for Jalview/Chimera binding
SequenceI[][] sequence, SequenceRenderer sr, FeatureRenderer fr,
AlignmentI alignment)
{
+ Map<Color, Map<Integer, Map<String, List<int[]>>>> colourMap = buildColoursMap(
+ ssm, files, sequence, sr, fr, alignment);
- ArrayList<StructureMappingcommandSet> cset = new ArrayList<StructureMappingcommandSet>();
- Hashtable<String,StringBuffer> colranges=new Hashtable<String,StringBuffer>();
+ List<String> colourCommands = buildColourCommands(colourMap);
+
+ StructureMappingcommandSet cs = new StructureMappingcommandSet(
+ ChimeraCommands.class, null,
+ colourCommands.toArray(new String[0]));
+
+ return new StructureMappingcommandSet[]
+ { cs };
+ }
+
+ /**
+ * Traverse the map of colours/models/chains/positions to construct a list of
+ * 'color' commands (one per distinct colour used). The format of each command
+ * is
+ *
+ * <blockquote> color colorname #modelnumber:range.chain e.g. color #00ff00
+ * #0:2.B,4.B,9-12.B|#1:1.A,2-6.A,...
+ *
+ * @see http
+ * ://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/frameatom_spec
+ * .html </pre>
+ *
+ * @param colourMap
+ * @return
+ */
+ protected static List<String> buildColourCommands(
+ Map<Color, Map<Integer, Map<String, List<int[]>>>> colourMap)
+ {
+ /*
+ * This version concatenates all commands into a single String (semi-colon
+ * delimited). If length limit issues arise, refactor to return one color
+ * command per colour.
+ */
+ List<String> commands = new ArrayList<String>();
+ StringBuilder sb = new StringBuilder(256);
+ boolean firstColour = true;
+ for (Color colour : colourMap.keySet())
+ {
+ String colourCode = ColorUtils.toTkCode(colour);
+ if (!firstColour)
+ {
+ sb.append("; ");
+ }
+ sb.append("color ").append(colourCode).append(" ");
+ firstColour = false;
+ boolean firstModelForColour = true;
+ final Map<Integer, Map<String, List<int[]>>> colourData = colourMap.get(colour);
+ for (Integer model : colourData.keySet())
+ {
+ boolean firstPositionForModel = true;
+ if (!firstModelForColour)
+ {
+ sb.append("|");
+ }
+ firstModelForColour = false;
+ sb.append("#").append(model).append(":");
+
+ final Map<String, List<int[]>> modelData = colourData.get(model);
+ for (String chain : modelData.keySet())
+ {
+ for (int[] range : modelData.get(chain))
+ {
+ if (!firstPositionForModel)
+ {
+ sb.append(",");
+ }
+ if (range[0] == range[1])
+ {
+ sb.append(range[0]);
+ }
+ else
+ {
+ sb.append(range[0]).append("-").append(range[1]);
+ }
+ sb.append(".").append(chain);
+ firstPositionForModel = false;
+ }
+ }
+ }
+ }
+ commands.add(sb.toString());
+ return commands;
+ }
+
+ /**
+ * <pre>
+ * Build a data structure which maps contiguous subsequences for each colour.
+ * This generates a data structure from which we can easily generate the
+ * Chimera command for colour by sequence.
+ * Color
+ * Model number
+ * Chain
+ * list of start/end ranges
+ * Ordering is by order of addition (for colours and positions), natural ordering (for models and chains)
+ * </pre>
+ */
+ protected static Map<Color, Map<Integer, Map<String, List<int[]>>>> buildColoursMap(
+ StructureSelectionManager ssm, String[] files,
+ SequenceI[][] sequence, SequenceRenderer sr, FeatureRenderer fr,
+ AlignmentI alignment)
+ {
+ Map<Color, Map<Integer, Map<String, List<int[]>>>> colourMap = new LinkedHashMap<Color, Map<Integer, Map<String, List<int[]>>>>();
+ Color lastColour = null;
for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
{
- float cols[] = new float[4];
StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
- StringBuffer command = new StringBuffer();
- StructureMappingcommandSet smc;
- ArrayList<String> str = new ArrayList<String>();
if (mapping == null || mapping.length < 1)
+ {
continue;
+ }
- int startPos = -1, lastPos = -1, startModel = -1, lastModel = -1;
- String startChain = "", lastChain = "";
- Color lastCol = null;
+ int startPos = -1, lastPos = -1;
+ String lastChain = "";
for (int s = 0; s < sequence[pdbfnum].length; s++)
{
for (int sp, m = 0; m < mapping.length; m++)
{
- if (mapping[m].getSequence() == sequence[pdbfnum][s]
- && (sp = alignment.findIndex(sequence[pdbfnum][s])) > -1)
+ final SequenceI seq = sequence[pdbfnum][s];
+ if (mapping[m].getSequence() == seq
+ && (sp = alignment.findIndex(seq)) > -1)
{
SequenceI asp = alignment.getSequenceAt(sp);
for (int r = 0; r < asp.getLength(); r++)
{
// no mapping to gaps in sequence
- if (jalview.util.Comparison.isGap(asp.getCharAt(r)))
+ if (Comparison.isGap(asp.getCharAt(r)))
{
continue;
}
int pos = mapping[m].getPDBResNum(asp.findPosition(r));
if (pos < 1 || pos == lastPos)
+ {
continue;
+ }
- Color col = sr.getResidueBoxColour(sequence[pdbfnum][s], r);
+ Color colour = sr.getResidueColour(seq, r, fr);
+ final String chain = mapping[m].getChain();
- if (fr != null)
- col = fr.findFeatureColour(col, sequence[pdbfnum][s], r);
- if (lastCol != col || lastPos + 1 != pos
- || pdbfnum != lastModel
- || !mapping[m].getChain().equals(lastChain))
+ /*
+ * Just keep incrementing the end position for this colour range
+ * _unless_ colour, PDB model or chain has changed, or there is a
+ * gap in the mapped residue sequence
+ */
+ final boolean newColour = !colour.equals(lastColour);
+ final boolean nonContig = lastPos + 1 != pos;
+ final boolean newChain = !chain.equals(lastChain);
+ if (newColour || nonContig || newChain)
{
- if (lastCol != null)
+ if (startPos != -1)
{
- addColourRange(colranges, lastCol,startModel,startPos,lastPos,lastChain);
+ addColourRange(colourMap, lastColour, pdbfnum, startPos,
+ lastPos, lastChain);
}
- lastCol = null;
startPos = pos;
- startModel = pdbfnum;
- startChain = mapping[m].getChain();
}
- lastCol = col;
+ lastColour = colour;
lastPos = pos;
- lastModel = pdbfnum;
- lastChain = mapping[m].getChain();
+ lastChain = chain;
}
// final colour range
- if (lastCol != null)
+ if (lastColour != null)
{
- addColourRange(colranges, lastCol,startModel,startPos,lastPos,lastChain);
+ addColourRange(colourMap, lastColour, pdbfnum, startPos,
+ lastPos, lastChain);
}
break;
}
}
}
- // Finally, add the command set ready to be returned.
- StringBuffer coms=new StringBuffer();
- for (String cr:colranges.keySet())
- {
- coms.append("color #"+cr+" "+colranges.get(cr)+";");
- }
- cset.add(new StructureMappingcommandSet(ChimeraCommands.class,
- files[pdbfnum], new String[] { coms.toString() }));
}
- return cset.toArray(new StructureMappingcommandSet[cset.size()]);
+ return colourMap;
}
- private static void addColourRange(Hashtable<String, StringBuffer> colranges, Color lastCol, int startModel,
- int startPos, int lastPos, String lastChain)
+ /**
+ * Helper method to add one contiguous colour range to the colour map.
+ *
+ * @param colourMap
+ * @param colour
+ * @param model
+ * @param startPos
+ * @param endPos
+ * @param chain
+ */
+ protected static void addColourRange(
+ Map<Color, Map<Integer, Map<String, List<int[]>>>> colourMap,
+ Color colour, int model, int startPos, int endPos, String chain)
{
-
- String colstring = ((lastCol.getRed()< 16) ? "0":"")+Integer.toHexString(lastCol.getRed())
- + ((lastCol.getGreen()< 16) ? "0":"")+Integer.toHexString(lastCol.getGreen())
- + ((lastCol.getBlue()< 16) ? "0":"")+Integer.toHexString(lastCol.getBlue());
- StringBuffer currange = colranges.get(colstring);
- if (currange==null)
+ /*
+ * Get/initialize map of data for the colour
+ */
+ Map<Integer, Map<String, List<int[]>>> colourData = colourMap
+ .get(colour);
+ if (colourData == null)
+ {
+ colourMap
+ .put(colour,
+ colourData = new TreeMap<Integer, Map<String, List<int[]>>>());
+ }
+
+ /*
+ * Get/initialize map of data for the colour and model
+ */
+ Map<String, List<int[]>> modelData = colourData.get(model);
+ if (modelData == null)
{
- colranges.put(colstring,currange = new StringBuffer());
+ colourData.put(model, modelData = new TreeMap<String, List<int[]>>());
}
- if (currange.length()>0)
+
+ /*
+ * Get/initialize map of data for colour, model and chain
+ */
+ List<int[]> chainData = modelData.get(chain);
+ if (chainData == null)
{
- currange.append("|");
+ modelData.put(chain, chainData = new ArrayList<int[]>());
}
- currange.append("#" + startModel + ":" + ((startPos==lastPos) ? startPos : startPos + "-"
- + lastPos) + "." + lastChain);
+
+ /*
+ * Add the start/end positions
+ */
+ chainData.add(new int[]
+ { startPos, endPos });
}
}
SequenceStructureBinding, StructureSelectionManagerProvider
{
+
+ private static final boolean debug = false;
+
private static final String PHOSPHORUS = "P";
private static final String ALPHACARBON = "CA";
}
if (selectioncom.length() > 0)
{
- // TODO remove debug output
- System.out.println("Select regions:\n" + selectioncom.toString());
- System.out
- .println("Superimpose command(s):\n" + command.toString());
+ if (debug)
+ {
+ System.out.println("Select regions:\n" + selectioncom.toString());
+ System.out.println("Superimpose command(s):\n"
+ + command.toString());
+ }
allComs.append("~display all; chain @CA|P; ribbon "
+ selectioncom.toString() + ";"+command.toString());
// selcom.append("; ribbons; ");
{
selectioncom.setLength(selectioncom.length() - 1);
}
- System.out.println("Select regions:\n" + selectioncom.toString());
+ if (debug)
+ {
+ System.out.println("Select regions:\n" + selectioncom.toString());
+ }
allComs.append("; ~display all; chain @CA|P; ribbon "
+ selectioncom.toString() + "; focus");
// evalStateCommand("select *; backbone; select "+selcom.toString()+"; cartoons; center "+selcom.toString());
}
}
- boolean debug = false;
-
private void log(String message)
{
System.err.println("## Chimera log: " + message);
*/
package jalview.gui;
+import jalview.api.FeatureRenderer;
import jalview.datamodel.AlignmentAnnotation;
import jalview.datamodel.SequenceGroup;
import jalview.datamodel.SequenceI;
// If EPS graphics, stringWidth will be a double, not an int
double dwidth = fm.getStringBounds("M", g).getWidth();
- monospacedFont = (dwidth == fm.getStringBounds("|", g).getWidth() && (float) av.charWidth == dwidth);
+ monospacedFont = (dwidth == fm.getStringBounds("|", g).getWidth() && av.charWidth == dwidth);
this.renderGaps = renderGaps;
}
+ @Override
public Color getResidueBoxColour(SequenceI seq, int i)
{
allGroups = av.getAlignment().findAllGroups(seq);
}
/**
+ * Get the residue colour at the given sequence position - as determined by
+ * the sequence group colour (if any), else the colour scheme, possibly
+ * overridden by a feature colour.
+ *
+ * @param seq
+ * @param position
+ * @param fr
+ * @return
+ */
+ @Override
+ public Color getResidueColour(final SequenceI seq, int position,
+ FeatureRenderer fr)
+ {
+ // TODO replace 8 or so code duplications with calls to this method
+ // (refactored as needed)
+ Color col = getResidueBoxColour(seq, position);
+
+ if (fr != null)
+ {
+ col = fr.findFeatureColour(col, seq, position);
+ }
+ return col;
+ }
+
+ /**
* DOCUMENT ME!
*
* @param cs
int y1)
{
if (seq == null)
+ {
return; // fix for racecondition
+ }
int i = start;
int length = seq.getLength();
package jalview.schemes;
import jalview.analysis.AAFrequency;
-
-import java.awt.Color;
-import java.util.Map;
-
import jalview.datamodel.AnnotatedCollectionI;
import jalview.datamodel.SequenceCollectionI;
import jalview.datamodel.SequenceI;
+import java.awt.Color;
+import java.util.Map;
+
public class Blosum62ColourScheme extends ResidueColourScheme
{
public Blosum62ColourScheme()
if (max.indexOf(res) > -1)
{
+ // TODO use a constant here?
currentColour = new Color(154, 154, 255);
}
else
if (c > 0)
{
+ // TODO use a constant here?
currentColour = new Color(204, 204, 255);
}
else
}
/**
+ * Convert to Tk colour code format
+ *
+ * @param colour
+ * @return
+ * @see http
+ * ://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/colortool.html#
+ * tkcode
+ */
+ public static final String toTkCode(Color colour)
+ {
+ String colstring = "#" + ((colour.getRed() < 16) ? "0" : "")
+ + Integer.toHexString(colour.getRed())
+ + ((colour.getGreen() < 16) ? "0" : "")
+ + Integer.toHexString(colour.getGreen())
+ + ((colour.getBlue() < 16) ? "0" : "")
+ + Integer.toHexString(colour.getBlue());
+ return colstring;
+ }
+
+ /**
* Returns a colour three shades darker. Note you can't guarantee that
* brighterThan reverses this, as darkerThan may result in black.
*
--- /dev/null
+package jalview.ext.rbvi.chimera;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.awt.Color;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Test;
+
+public class ChimeraCommandsTest
+{
+ @Test
+ public void testAddColourRange()
+ {
+ Map<Color, Map<Integer, Map<String, List<int[]>>>> map = new LinkedHashMap<Color, Map<Integer, Map<String, List<int[]>>>>();
+ ChimeraCommands.addColourRange(map, Color.pink, 1, 2, 4, "A");
+ ChimeraCommands.addColourRange(map, Color.pink, 1, 8, 8, "A");
+ ChimeraCommands.addColourRange(map, Color.pink, 1, 5, 7, "B");
+ ChimeraCommands.addColourRange(map, Color.red, 1, 3, 5, "A");
+ ChimeraCommands.addColourRange(map, Color.red, 0, 1, 4, "B");
+ ChimeraCommands.addColourRange(map, Color.orange, 0, 5, 9, "C");
+
+ // three colours mapped
+ assertEquals(3, map.keySet().size());
+
+ // Red has two models, Pink and Orange one each
+ assertEquals(2, map.get(Color.red).keySet().size());
+ assertEquals(1, map.get(Color.orange).keySet().size());
+ assertEquals(1, map.get(Color.pink).keySet().size());
+
+ // pink model 1 has two chains, red.0 / red.1 / orange.0 one each
+ assertEquals(2, map.get(Color.pink).get(1).keySet().size());
+ assertEquals(1, map.get(Color.red).get(0).keySet().size());
+ assertEquals(1, map.get(Color.red).get(1).keySet().size());
+ assertEquals(1, map.get(Color.orange).get(0).keySet().size());
+
+ // inspect positions
+ List<int[]> posList = map.get(Color.pink).get(1).get("A");
+ assertEquals(2, posList.size());
+ assertTrue(Arrays.equals(new int[]
+ { 2, 4 }, posList.get(0)));
+ assertTrue(Arrays.equals(new int[]
+ { 8, 8 }, posList.get(1)));
+
+ posList = map.get(Color.pink).get(1).get("B");
+ assertEquals(1, posList.size());
+ assertTrue(Arrays.equals(new int[]
+ { 5, 7 }, posList.get(0)));
+
+ posList = map.get(Color.red).get(0).get("B");
+ assertEquals(1, posList.size());
+ assertTrue(Arrays.equals(new int[]
+ { 1, 4 }, posList.get(0)));
+
+ posList = map.get(Color.red).get(1).get("A");
+ assertEquals(1, posList.size());
+ assertTrue(Arrays.equals(new int[]
+ { 3, 5 }, posList.get(0)));
+
+ posList = map.get(Color.orange).get(0).get("C");
+ assertEquals(1, posList.size());
+ assertTrue(Arrays.equals(new int[]
+ { 5, 9 }, posList.get(0)));
+ }
+
+ @Test
+ public void testBuildColourCommands()
+ {
+
+ Map<Color, Map<Integer, Map<String, List<int[]>>>> map = new LinkedHashMap<Color, Map<Integer, Map<String, List<int[]>>>>();
+ ChimeraCommands.addColourRange(map, Color.blue, 0, 2, 5, "A");
+ ChimeraCommands.addColourRange(map, Color.blue, 0, 7, 7, "B");
+ ChimeraCommands.addColourRange(map, Color.blue, 0, 9, 23, "A");
+ ChimeraCommands.addColourRange(map, Color.blue, 1, 1, 1, "A");
+ ChimeraCommands.addColourRange(map, Color.blue, 1, 4, 7, "B");
+ ChimeraCommands.addColourRange(map, Color.yellow, 1, 8, 8, "A");
+ ChimeraCommands.addColourRange(map, Color.yellow, 1, 3, 5, "A");
+ ChimeraCommands.addColourRange(map, Color.red, 0, 3, 5, "A");
+
+ // Colours should appear in the Chimera command in the order in which
+ // they were added; within colour, by model, by chain, and positions as
+ // added
+ String command = ChimeraCommands.buildColourCommands(map).get(0);
+ assertEquals(
+ "color #0000ff #0:2-5.A,9-23.A,7.B|#1:1.A,4-7.B; color #ffff00 #1:8.A,3-5.A; color #ff0000 #0:3-5.A",
+ command);
+ }
+}
--- /dev/null
+package jalview.gui;
+
+import static org.junit.Assert.assertEquals;
+import jalview.datamodel.Alignment;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.Sequence;
+import jalview.datamodel.SequenceI;
+import jalview.schemes.ZappoColourScheme;
+
+import java.awt.Color;
+
+import org.junit.Test;
+
+public class SequenceRendererTest
+{
+
+ @Test
+ public void testGetResidueBoxColour_zappo()
+ {
+ SequenceI seq = new Sequence("name", "MATVLGSPRAPAFF"); // FER1_MAIZE...
+ AlignmentI al = new Alignment(new SequenceI[]
+ { seq });
+ final AlignViewport av = new AlignViewport(al);
+ SequenceRenderer sr = new SequenceRenderer(av);
+ av.setGlobalColourScheme(new ZappoColourScheme());
+
+ // @see ResidueProperties.zappo
+ assertEquals(Color.pink, sr.getResidueColour(seq, 0, null)); // M
+ assertEquals(Color.green, sr.getResidueColour(seq, 2, null)); // T
+ assertEquals(Color.magenta, sr.getResidueColour(seq, 5, null)); // G
+ assertEquals(Color.orange, sr.getResidueColour(seq, 12, null)); // F
+ }
+ // TODO more tests for getResidueBoxColour covering groups, feature rendering,
+ // gaps, overview...
+
+}
ColorUtils.brighterThan(darkColour));
assertNull(ColorUtils.brighterThan(null));
}
+
+ /**
+ * @see http://www.rtapo.com/notes/named_colors.html
+ */
+ @Test
+ public void testToTkCode()
+ {
+ assertEquals("#fffafa", ColorUtils.toTkCode(new Color(255, 250, 250))); // snow
+ assertEquals("#e6e6fa", ColorUtils.toTkCode(new Color(230, 230, 250))); // lavender
+ assertEquals("#dda0dd", ColorUtils.toTkCode(new Color(221, 160, 221))); // plum
+ assertEquals("#800080", ColorUtils.toTkCode(new Color(128, 0, 128))); // purple
+ assertEquals("#00ff00", ColorUtils.toTkCode(new Color(0, 255, 0))); // lime
+ }
}