-
Notifications
You must be signed in to change notification settings - Fork 59
Scripting
CATMAID is written in javascript on the client side. The whole of CATMAID is available at your fingertips if only you knew what to type into the javascript console.
In Google Chrome, push shift+control+j (shift+command+j in MacOSX) or go to the menu "Tools - Javascript Console".
Every widget is an object, and the prototype of that object has an instances array. For example, open a Selection Table by clicking on its icon that looks like this: ['S']
... and then open the Javascript Console, and type:
var tables = SelectionTable.prototype.getInstances();
The tables array should contain one single entry:
var st = tables[0];
The SkeletonSource is an Object that provides an interface for adding and getting skeletons. All widgets that can list and deliver skeletons extend SkeletonSource, and will be listed, when open, in the pulldown menu next to the "Append" button of every widget.
Each skeleton is represented by a SkeletonModel object, which is a simple object holding fields for the ID and its selected state, with separate fields for the visibility of presynaptic sites, postsynaptic sites, meta information like "uncertain end", low-confidence edges etc., for skeleton text tags, and for the skeleton as a whole.
Well-implemented widgets return copies of the SkeletonModel internally representing each skeleton in that widget. To alter the model, you'll have to append it back to the widget. Here is how:
Following from the SelectionTable example, now manually add skeletons to it by selecting them in the canvas (click on an existing skeleton node) and then pushing the "Append" button of the Selection Table widget. Then we can get the SkeletonModel instances representing each Skeleton, in this case just one:
var st = SelectionTable.prototype.getInstances()[0];
var models = st.getSkeletonModels();
Here, models is an Object with skeleton ID as keys and SkeletonModel instances as values. To change the visibility of all the skeletons we will change the selected field of each, along with all other fields, via the method setVisible:
Object.keys(models).forEach(function(skeleton_id) {
models[skeleton_id].setVisible(false);
};
To update the SelectionTable, the modified models must be appended back, which won't duplicate them: will simply read the new values of the updated ones and append new ones if any:
st.append(models);
Now the checkbox of the listed neurons in the Selection Table should have been unticked.
Assuming you have a list of neurons in a Selection widget, and at least a subset of them is selected (their checkboxes are ticked):
// Assuming it is the first one opened:
var st = SelectionTable.prototype.getInstances()[0];
// List of skeleton IDs
var skids = st.getSelectedSkeletons();
// Pick one skeleton at random:
var skid = skids[Math.floor(Math.random() * skids.length)];
// Select the skeleton in the canvas
TracingTool.goToNearestInNeuronOrSkeleton("skeleton", skid);
If the Selection Table is linked to a 3D Viewer (as is the default when the 3D Viewer is open), then this will hide all neurons except the one selected at random:
var selectAtRandom = function() {
var st = SelectionTable.prototype.getInstances()[0];
var models = st.getSkeletonModels();
var skids = Object.keys(models);
var skid = skids[Math.floor(Math.random() * skids.length)];
console.log("Picking: ", skid);
TracingTool.goToNearestInNeuronOrSkeleton("skeleton", skid);
// Leave selected only skid
Object.keys(models).forEach(function(id) {
models[id].setVisible(id == skid);
});
st.append(models);
};
selectAtRandom();
Given the annotation as text, we obtain first its ID from the local cache (which is loaded on startup), and then request from the server the list of neurons along with their associated skeleton IDs:
var selectAtRandom = function(annotation) {
if (!annotation) return alert("What annotation?");
var annot = annotations.annotation_ids[annotation];
if (!annot) return alert("Invalid annotation");
var request = function(url, post, callback) {
requestQueue.register(
django_url + project.id + url, "POST", post,
function(status, text) {
if (200 !== status) return;
var json = $.parseJSON(text);
if (json.error) return alert(json.error);
callback(json);
});
};
request(
"/neuron/query-by-annotations",
{neuron_query_by_annotation: [annot]},
function(json) {
var skids = json.entities.map(function(e) {
return e.skeleton_ids[0];
});
var skid = skids[Math.floor(Math.random() * skids.length)];
console.log("Picking: ", skid);
TracingTool.goToNearestInNeuronOrSkeleton("skeleton", skid);
});
};
To print for each 3D viewer a CSV table with the root node position for every skeleton ID, the following function can be used:
function printRootPositions() {
var viewers = WebGLApplication.prototype.instances;
for (var wid in viewers) {
var w = viewers[wid];
console.log("Root nodes for skeletons in widget '" + w.getName() + "'");
console.log(["Skeleton ID", "Root X", "Root Y", "Root Z"].join(", "));
for (var sid in w.space.content.skeletons) {
var skeleton = w.space.content.skeletons[sid];
var arbor = skeleton.createArbor();
var pos = skeleton.getPositions()[arbor.root];
console.log([skeleton.id, pos.x, pos.y, pos.z].join(", "));
}
}
}
The "run" function fetches the statistics of each skeleton in a SelectionTable and then sorts them. In the example below we use the "n_post" value in the sorter function, descending, but it could be any value, including e.g. number of nodes contributed by a specific user.
The fetch for statistics returns a JSON object with the data displayed in a dialog when pushing the "Info" button in a Selection Table. Open first the javascript console, push the "Info" button for a neuron, and then go to "Network" in the console, click on the row showing the last call (named "contributor_statistics"), and look into what JSON data was returned in the "Preview". It will have values like "n_pre", "n_post", and an object like "node_contributors" that is a map of user ID vs number of skeleton nodes done by that user; or "post_contributors" which is a map of user ID vs number of postsynaptic relations with the skeleton made by that user. Any of these values is accessible below from the "sorter" function.
(To find out the ID of a specific user, type "Users.all()" in the console and look for it by expanding each user. If too many, write a loop over all users to match by e.g. username.)
var run = function() {
// Selection Table to be sorted
var st = SelectionTable.prototype.getInstances()[0];
// Copies of SkeletonModel instances and skeleton IDs in the table
var models = st.getSelectedSkeletonModels();
var skids = Object.keys(models).map(Number);
// Array to accumulate results in
var skid_json = [];
// Function to sort the skeletons according to e.g. n_post,
// aka the number of postsynaptic sites:
var sorter = function(a, b) {
var an = a.json.n_post;
var bn = b.json.n_post;
return an === bn ? 0 : (an < bn ? 1 : -1);
};
// Function to sort the table when having fetch all the data
var sortTable = function() {
// Sort fetched data
skid_json.sort(sorter);
// Recreate, sorted, the data structures of the SelectionTable
var ids = {};
var skeletons = [];
for (var i=0; i<skid_json.length; ++i) {
var skid = skid_json[i].skid;
ids[skid] = i;
skeletons.push(models[skid]);
}
// Replace internal data structures
st.skeletons = skeletons;
st.skeleton_ids = ids;
st.gui.update();
};
// Function to asynchronously fetch statistics for one skeleton from the server
var fetch = function(skid, callback) {
requestQueue.register(django_url + project.id + '/skeleton/' + skid + '/contributor_statistics', 'POST', {}, function(status, text) {
if (200 !== status) return;
var json = $.parseJSON(text);
if (json.error) return console.log(skid, json.error);
callback(skid, json);
});
};
// Callback function: accumulate fetched data into skid_json
var pusher = function(skid, json) {
skid_json.push({skid: skid, json: json});
// Sort the table when done
if (skid_json.length === skids.length) {
sortTable();
}
};
// Fetch statistics for each skeleton
skids.forEach(function(skid) {
console.log("fetching " + skid);
fetch(skid, pusher);
});
};
run();