Scripting

Tom Kazimiers edited this page Oct 6, 2018 · 43 revisions

First step: open the Javascript console

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".

Accessing the content of widgets

Each widget is an Object

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 = CATMAID.SelectionTable.prototype.getInstances();

The tables array should contain one single entry:

var st = tables[0];

Alternatively, the focused widget (the one with a blue status bar above it) can always be quickly accessed for convenience:

var st = CATMAID.front();

All widgets that list skeletons extend SkeletonSource

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 = CATMAID.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.

Selecting neurons at random from a list

Select a neuron at random from a list of neurons in a Selection Table

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 = CATMAID.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
CATMAID.TracingTool.goToNearestInNeuronOrSkeleton("skeleton", skid);

Select a neuron at random from a Selection Table, hiding the others

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 = CATMAID.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);
    CATMAID.TracingTool.goToNearestInNeuronOrSkeleton("skeleton", skid);
    // Leave selected only skid
    Object.keys(models).forEach(function(id) {
        models[id].setVisible(id == skid);
    }); 
    st.append(models);
};

selectAtRandom();

Select a neuron at random from a list of neurons that are all annotated with the same annotation

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 = CATMAID.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(
    "/annotations/query-targets",
    {annotated_with: [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);
      CATMAID.TracingTool.goToNearestInNeuronOrSkeleton("skeleton", skid);
    });
};

Add a list of skeleton IDs to a SelectionTable

We create model without name and with yellow color. The neuron name will be picked up by CATMAID automatically anyway.

var skids = [16627838, 14898295, 15722008];
var models = {};
skids.forEach(function(skid) {
  models[skid] = new CATMAID.SkeletonModel(skid, "", new THREE.Color().setRGB(1,1,0));
});

CATMAID.SelectionTable.prototype.getFirstInstance().append(models);

Get root node positions of skeletons in 3D viewers

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 = CATMAID.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(", "));
    }
  }
}

Print the Strahler index distribution of all neurons in 3D viewers

The following can be used to print a Strahler index distribution per skeleton for all skeletons in all open 3D viewers. A Strahler distribution is the number of segments for each Strahler number in a given skeleton.

function printStrahlerDistributionFrom3DViewer() {
  var viewers = CATMAID.WebGLApplication.prototype.instances;
  for (var wid in viewers) {
    var w = viewers[wid];
    console.log("Strahler distribution for skeletons in widget '" + w.getName() + "'");
    for (var sid in w.space.content.skeletons) {
      var skeleton = w.space.content.skeletons[sid];
      var arbor = skeleton.createArbor();
      // We only care about the topology, therefore create a new working arbor
      // that only contains the root, branches and leafs.
      var topoArbor = arbor.topologicalCopy();
      // Get Strahler numbers for nodes of topology arbor and build distribution
      var strahlerNumbers = topoArbor.strahlerAnalysis();
      var distribution = {};
      var maxStrahler = strahlerNumbers[topoArbor.root];
      for (var nodeId in strahlerNumbers) {
        var s = strahlerNumbers[nodeId];
        if (s in distribution) {
          ++distribution[s]
        } else {
          distribution[s] = 1;
        }
      }
      // The count of the root strahler numnber has to be reduced by one,
      // because the Strahler number is associated with the edge to the parent,
      // which the root node doesn't have.
      --distribution[maxStrahler];

      console.log("Strahler distribution for Skeleton #" + sid, distribution);
    }
  }
}

printStrahlerDistributionFrom3DViewer();

Sort skeletons in a Selection Table by number of postsynaptic sites or contributor

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 = CATMAID.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();

Hide or alter the properties of elements in the 3D Viewer

The 3D Viewer is coded by the WebGLApplication prototype. The top fields of an instance include the "space" that holds onto the "content", the latter including the map of skeleton IDs vs instances of WebGLApplication.prototype.Skeleton.

A "Skeleton" is an object that holds the geometry (vertices, lines, spheres) and material (color, etc.) of the skeleton that represents a neuron in the 3D Viewer.

One of the members of a Skeleton is the "actor", an object that contains the three kinds of THREE.Line geometry objects: the "neurite" (the cable), the "postsynaptic_to" (a collection of cyan lines, one for each postsynaptic relation), and the "presynaptic_to" (a collection of red lines, one for each presynaptic relation). For the later two, each line starts at a skeleton node and ends at the coordinates of the connector that represents the synapse.

Each of these "Line" is an object (THREE.Line) that extends THREE.Geometry, containing members such as "visible" (used below) and "vertices" (the list of pairs of Vector3 instances, each representing a point in space in project coordinates).

Below, we acquire the first instance of a 3D Viewer, declare a variable "sks" that points to its table of skeleton IDs vs Skeleton instances, and then iterate the latter to set the visibility of its postsynaptic lines to false, hiding them:

var w = CATMAID.WebGLApplication.prototype.getFirstInstance();
var sks = w.space.content.skeletons;
Object.keys(sks).forEach(function(skid) {
  sks[skid].actor.postsynaptic_to.visible = false;
});
w.space.render();

Given that the geometry is in project coordinates, we can retrieve the position of all skeleton nodes by directly reading them from the vertices of the Line object that defines its skeleton. Remember that the Line object is an array of Vector3, where each pair of consecutive Vector3 instances defines an edge of the skeleton (so there's lots of repeats).

var w = CATMAID.WebGLApplication.prototype.getFirstInstance();
var sks = w.space.content.skeletons;

// The selected skeleton in the 2D canvas
var skid = SkeletonAnnotations.getActiveSkeletonId();
var sk = sks[skid];

// Equivalent to the function sk.getPositions(), expanded here for illustration:
var positions = {};
sk.geometry.neurite.vertices.forEach(function(v) {
  positions[v.node_id] = v;
});

To measure the cable length of an arbor you could do:

// An instance of Arbor as defined in Arbor.js
var arbor = sk.createArbor();
var cable = arbor.cableLength(sk.getPositions());

The value will be identical to that obtained by the "Measure" button in a Selection widget.

For creative renderings of the 3D Viewer it is useful to know how to modify the transparency of e.g. the spheres that represent the somas of the neurons. The "radiusVolumes" object of the Skeleton instance contains all spheres that result from a skeleton node having a radius larger than zero (and its neighboring nodes not having it over zero, otherwise it would be defined as a cylinder), and these include the somas. The spheres are keyed by skeleton treenode ID.

Here is a script to hide all spheres, including the somas. The spheres for pre- and postsynaptic locations are not included in the "radiusVolumes" but in the "synapticSpheres" object, so the same approach would work for hiding/showing those instead:

var sks = CATMAID.WebGLApplication.prototype.getFirstInstance().space.content.skeletons;
Object.keys(sks).forEach(function(skid) {
  var sk = sks[skid];
  Object.keys(sk.radiusVolumes).forEach(function(node_id) {
    var sphere = sk.radiusVolumes[node_id];
    sphere.visible = false;
  });
});

If instead of hiding the spheres the goal is to make them transparent, replace this:

sphere.visible = false;

with this:

sphere.material.transparent = true;
sphere.material.opacity = 0.4;

Note that the "material" contains other properties such as "color", which is an instance of THREE.Color.

And finally don't forget to render: either rotate the view with the mouse (or zoom, etc.), or run this:

w.space.render();

Graph widget: select nodes that connect to one or more selected nodes

Select one or more nodes in the Graph widget by clicking on it (shift-click to select more than one, or click and drag to draw a box that selects multiple nodes). Then run the following function:

var selectConnected = function(min_in_synapses, min_out_synapses) {
  var gg = CATMAID.GroupGraph.prototype.getFirstInstance();
  var selected = gg.getSelectedSkeletonModels();
  var skids = Object.keys(selected);
  if (0 === skids.length) return alert("None selected!");
  
  // For each selected node
  skids.forEach(function(skid) {
    var node = gg.cy.nodes('#' + skid);
    var neighbors = node.neighborhood().nodes().toArray();
    
    // For each neighbor of a selected node
    neighbors.forEach(function(neighbor) {
      var to = neighbor.edgesTo(node);
      if (to && to.data('weight') >= min_in_synapses) {
        neighbor.select();
      }
      var from = node.edgesTo(neighbor);
      if (from && from.data('weight') >= min_out_synapses) {
        neighbor.select();
      }
    });
  });
};

// Select upstream nodes that make 10 or more synapses onto selected nodes
selectConnected(10, 0);

3D Viewer: find the bounding box of all synapses of all skeletons in the 3D Viewer

var w = CATMAID.WebGLApplication.prototype.getFirstInstance();
var sks = w.space.content.skeletons;

// Bounding box of synaptic sites:
var box = {x0: Number.MAX_VALUE,
           y0: Number.MAX_VALUE,
           z0: Number.MAX_VALUE,
           x1: -Number.MAX_VALUE,
           y1: -Number.MAX_VALUE,
           z1: -Number.MAX_VALUE};

// Iterate every skeleton
Object.keys(sks).forEach(function(skid) {
  var sk = sks[skid];
  // Skip hidden skeletons
  if (!sk.visible) return;
  // Iterate over both pre- and postsynaptic sites
  sk.synapticTypes.forEach(function(type) {
    // The geometry is an array of even length, where every consecutive
    // pair of THREE.Vector3 entries defines a line to represent
    // the relation in space from the skeleton treenode to the connector.
    var vs = sk.geometry[type].vertices;
    // Pick the second THREE.Vector3 of the pair describing the line
    // (the first one is the connector)
    for (var i=0, l=vs.length; i<l; i+=2) {
      var v = vs[i+1]; // v is in project coordinates (nanometers)
      this.x0 = Math.min(this.x0, v.x);
      this.y0 = Math.min(this.y0, v.y);
      this.z0 = Math.min(this.z0, v.z);
      this.x1 = Math.max(this.x1, v.x);
      this.y1 = Math.max(this.y1, v.y);
      this.z1 = Math.max(this.z1, v.z);
    }
  }, this);
}, box);

console.log(box);

3D Viewer: obtain locations of synapses

Exports into a CSV file the location of all synapses of all visible neurons in the 3D Viewer. The first and second columns contain the name of the neuron and whether the synapse is pre- or postsynaptic. The location, in nanometers, is that of the skeleton node involved in the synaptic relation (not of the connector). Modifying the script below to export the connector location is trivial: modify the filtering function to return the odd indices rather than the even ones.

// Export synapse locations of neurons in a 3D Viewer
var readSynapseLocations = function(index) {
  // Assyme 1-based index if present
  index = index ? index - 1 : 0;
  var wa = CATMAID.WebGLApplication.prototype.getInstances()[index];
  var sks = wa.space.content.skeletons;
  var ns = CATMAID.NeuronNameService.getInstance();

  var csv = Object.keys(sks)
    .map(function(skid) { return {sk: sks[skid], name: ns.getName(skid)}; })
    .filter(function(s) { return s.sk.visible; })
    .map(function(s) {
      return s.sk.synapticTypes.map(function(type) {
        return s.sk.geometry[type].vertices
          .filter(function(v, i) { return 0 !== i % 2}) // odd indices are nodes, even are connectors
          .map(function(v) { return [s.name, type, v.x, v.y, v.z].join(','); })
          .join('\n');
      }).join('\n');
    }).join('\n');

  saveAs(new Blob([csv], {type : 'text/csv'}), "synapse-coords.csv");
};

readSynapseLocations();

Count number of postsynaptic sites on a fraction of a neuron

For example, the number of postsynaptic sites on the dendrite.

First, add the neuron to the Graph widget, select it, got to the "Subgraphs" tab and split it with "Axon and dendrite" (any other splitting would work too).

Then, assuming there is only one Graph widget open, run this script:

var gg = CATMAID.GroupGraph.prototype.getFirstInstance();

gg.cy.nodes().each(function(i, node) {
  if (node.selected()) {
    var props = node.data();
    var us = props.upstream_skids;
    if (!us) {
      console.log("Ignoring unsplit neuron " + props.label);
      return;
    }
    var count_post = 0;
    Object.keys(us).forEach(function(skid) {
      count_post += us[skid].length;
    });
    console.log(props.label + ": " + count_post);
  }
});

What the above does:

  1. Find the selected node(s), which represents a fraction of the neuron.
  2. Look into its properties ("props"), which, for split neurons only, contains two maps: the "upstream_skids" and the "downstream_skids". These are normally used for growing the neuron (i.e. loading its partners). The maps are made of treenode IDs (the skeleton nodes) as keys and arrays of partner skeleton IDs at that node.
  3. Sum the lengths of all the arrays, as the means to count the number of postsynaptic sites.
  4. Print it to the console.

Export CSV file with number of postsynaptic sites on the dendritic arbors only

First, load a number of neurons into the Graph Widget, select them all and go to the "Subgraphs" tab and push "Axon and dendrite". Then open the console and run this script, which will export a CSV file containing two columns: the name, and the number of postsynaptic sites.

var csv = gg.cy.nodes().toArray().reduce(function(list, node) {
  var d = node.data();
  if (d.label.endsWith('[dendrite]')) {
    var n_postsynaptic_sites = Object.keys(d.upstream_skids).reduce(function(count, skid) {
      return count + d.upstream_skids[skid].length;
    }, 0);
    list.push(d.label + "," + n_postsynaptic_sites);
  }
  return list;
}, []).join("\n");

saveAs(new Blob([csv], {type: 'text/csv'}), "counts of postsynaptic sites on dendrites.csv");

The above can also be done in the 3D Viewer, when using the "axon and dendrite" coloring mode. When an arbor cannot be split, it is rendered in dark grey color in the 3D Viewer, and the code below will list its total number of postsynaptic sites.

The code below exports to a CSV file with 3 columns: the neuron name, the number of postsynaptic sites on the dendrite and the total number of postsynaptic sites.

This script can easily be modified to count postsynaptic sites on the axon, or presynaptic sites on the dendrite or the axon. Note that the number of presynaptic sites is that of presynaptic relations to connectors, so with polyadic synapses it will be smaller than the total number of output synapses.

// Assumes the 3D Viewer uses the "axon and dendrite" coloring mode.

(function() {
  "use strict";
  var w = CATMAID.WebGLApplication.prototype.getFirstInstance();
  var sks = w.space.content.skeletons;
  
  var nns = CATMAID.NeuronNameService.getInstance();
  
  var csv = Object.keys(sks).reduce(function(rows, skid) {
    var skeleton = sks[skid];
    var vs = skeleton.geometry["postsynaptic_to"].vertices;
    var row = ['"' + nns.getName(skid) + '"', null, vs.length / 2];
    
    if (skeleton.axon) {
      var axonic = skeleton.axon.nodes();
      var count = 0;
      for (var i=1; i<vs.length; i+=2) {
        // If not in the axon, it's in the dendrite
        if (!axonic[vs[i].node_id]) {
          count += 1;
        }
      }
      row[1] = count;
    } else {
      // Axon was not computable: count them all
      console.log("WARNING, unsplittable neuron: " + row[0]);
      row[1] = row[2];
    }
    rows.push(row.join(','));
    return rows;
  }, [["neuron_name", "dendritic_postsynaptic_sites", "total_postsynaptic_sites"]]);
  
  saveAs(new Blob([csv.join('\n')], {type: 'text/csv'}), "dendritic_postsynaptic_counts.csv");
})();

Count number of postsynaptic sites near a skeleton node

This script will count the number of postsynaptic sites within an arbitrary cable distance of the active node. It assumes that the 3D Viewer is open, displaying the skeleton to analyze, with the active node belonging to that skeleton.

To visualize the distance covered, choose the shading "near active node" and type in the same distance as below (e.g. 80000 nm) in the "Near active node" field under the "Shading parameters" tab. That same distance you typed in will be used below.

To count presynaptic sites instead, see below where it says "CHANGE to 0 to count presynaptic instead".

var countPostsynapticSitesWithin = function(sk, active_node, max_distance) {
  var copy = sk.arbor.clone();
  copy.reroot(active_node);
  var distances = copy.nodesDistanceTo(
          copy.root,
          function(paren, child) {
            return sk.positions[paren].distanceTo(sk.positions[child])
          }
      ).distances;
  return copy.nodesArray().reduce(function(sum, node) {
    if (distances[node] > max_distance) return sum;
    var relations = sk.synapse_map[node];
    if (!relations) return sum;
    return relations.reduce(function(s, relation) {
      return 1 === relation.type ? s + 1 : s; // CHANGE to 0 to count presynaptic instead
    }, sum);
  }, 0);
};

var pickArbor = function(WA, skeleton_id) {
  var sks = WA.space.content.skeletons,
      sk = sks[skeleton_id];
  return {arbor: sk.createArbor(),
          positions: sk.getPositions(),
          synapse_map: sk.createSynapseMap()};
};

var WA = CATMAID.WebGLApplication.prototype.getFirstInstance();

countPostsynapticSitesWithin(
  pickArbor(
    WA,
    SkeletonAnnotations.getActiveSkeletonId()),
  SkeletonAnnotations.getActiveNodeId(),
  WA.options.distance_to_active_node); // or type a number in nanometers

Count the number of presynaptic sites and the number of presynaptic connectors on an axon

The set of skeleton nodes belonging to the axon are computed using the same algorithm that generates the "axon and dendrite" coloring mode in the 3D Viewer.

First load some neurons into the 3D Viewer, then run the script below.

w = CATMAID.WebGLApplication.prototype.getFirstInstance();
sks = w.space.content.skeletons;

skeleton_ids = Object.keys(sks);

fetchSkeletons(
  skeleton_ids,
  function(skid) {
    // Fetch only the synapses
    return django_url + project.id + '/' + skid
           + '/0/1/0/compact-arbor'; },
  function(skid) { return {}; },
  function(skid, json) {
    // How to read the json[1] array containing all presynaptic and postsynaptic connections:
    // row[0]: the treenode ID of this skeleton
    // row[1]: the confidence in the relation from this skeleton treenode to connector
    // row[2]: the connector ID
    // row[3]: the confidence in the relation from the connector to the treenode in the partner skeleton
    // row[4]: the treenode ID of the partner skeleton
    // row[5]: the partner skeleton ID
    // row[6]: relation between this skeleton treenode and the connector (0 for presynaptic and 1 for postsynaptic)
    // row[7]: relation between the connector and the partner skeleton treenode (0 for presynaptic and 1 for postsynaptic)

    // Find the skeleton nodes that belong to the axon
    var axon = sks[skid].splitByFlowCentrality(json);
    var axon_nodes = axon.nodes();
    var axon_node_count = Object.keys(axon_nodes).length;
    var total_node_count = sks[skid].geometry["neurite"].vertices.length / 2;
    
    console.log("Number of nodes in axon: ", axon_node_count);
    console.log("Number of nodes in dendrite: ", total_node_count - axon_node_count);

    var ap = new CATMAID.ArborParser();
    ap.arbor = sks[skid].createArbor();
    ap.synapses(json[1]); // pass only the array with one connection per entry

    console.log("Total number of output connectors", ap.n_output_connectors);
    console.log("Total number of input connectors", ap.n_output_connectors);

    var axon_n_output_synapses = json[1].filter(function(row) {
      return 0 == row[6] && row[0] in axon_nodes; }
    ).length;
    console.log("Number of output synapses in the axon:", axon_n_output_synapses);

    var axon_output_connectors = json[1].reduce(function(set, row) {
      if (0 == row[6] && row[0] in axon_nodes) {
        set[row[2]] = true;
      }
      return set;
    }, {});
    console.log("Number of output connectors in the axon:", Object.keys(axon_output_connectors).length);

  },
  function(skid) {
    console.log("Could not load", skid);
  },
  function() {
    console.log("done");
  });

Change the function for defining the width of an edge in the Graph widget

The Graph widget displays edges between nodes with a width that is a function of the number of synapses from one node (the presynaptic neuron) to the other (the postsynaptic neuron). The default function is the square root ("sqrt"), and can be changed in the properties dialog (in the Main tab). Other functions listed are the identity (linear), the log and the log in base 10.

To change this function, one way to do it is to replace the GroupGraph's edgeWidthFn, like this:

var gg = CATMAID.GroupGraph.prototype.getFirstInstance();
gg.edgeWidthFn = function() {
  return function(n_synapses) {
    // A function with a profile intermediate between sqrt and log.
    // Search Google for: "sqrt(x), sqrt(x) / 1.5, log(x)" to see them plotted.
    return Math.sqrt(n_synapses) / 1.5;
  };
};

Now, push the "Refresh" button, and the edges will be recomputed using the function above. Note that this change is not permanent, and will only affect the one instance of GroupGraph altered (the first opened Graph widget, in this case). And also note that the internal property (e.g. "sqrt", etc.) will be ignored, being always replaced by the function above.

Sort by name neuron nodes within a circular graph layout in the Graph widget

If you add a number of neurons to the Graph widget, each will be represented by a node. In the "Graph" tab there's a pulldown menu listing different types of graph layouts. Select the "circle", push "Re-layout", and now observe how nodes are sorted at random.

To sort nodes along the circle by name, run this script:

/* Starting from a circular node layout in the Graph widget, 
 * sort nodes by their text label starting from the top.
 * Performs binary sorting, so e.g. "MBE11a" will appear before "MBE2a".
 * To define a custom sorting, this could be done by providing an object that
 * defines a mapping between node labels and node index, and use it
 * to look up the index within the sort function, to compare indices instead of labels.
 * 
 * Albert Cardona - 20160713
 */
(function() {
  var g = CATMAID.GroupGraph.prototype.getFirstInstance();

  // A list of nodes
  var nodes = g.cy.nodes().toArray();

  // Sort by label
  nodes.sort(function(node1, node2) {
    var label1 = node1.data("label");
    var label2 = node2.data("label");
    return label1 < label2 ? -1 : 1;
  });

  // Find bounding box of current node coordinates
  var mm = nodes.reduce(function(mm, node) {
    var pos = node.position();
    mm.minX = Math.min(mm.minX, pos.x);
    mm.minY = Math.min(mm.minY, pos.y);
    mm.maxX = Math.max(mm.maxX, pos.x);
    mm.maxY = Math.max(mm.maxY, pos.y);
    return mm;
  }, {minX: Number.MAX_VALUE,
      minY: Number.MAX_VALUE,
      maxX: 0,
      maxY: 0});

  // Center of bounding box and radius
  var radius = (mm.maxX - mm.minX) / 2;
  var cx = mm.minX + radius;
  var cy = mm.minY + radius;
  // Angle increment, in radians: defines the direction of rotation of node layout.
  // (Make it negative to rotate clockwise.)
  var inc = (2 * Math.PI) / nodes.length;
  // Starting point in the circle: at the top (12 o'clock).
  var offset = Math.PI / 2;

  // Layout nodes by increasing angle from 0 to 2 * Math.PI
  nodes.forEach((node, i) => node.position({x: cx + Math.cos(offset + i * inc) * radius,
                                            y: cy + Math.sin(offset + i * inc) * radius}));

})();

If you'd rather sort them following a specific order, create a map relating the node labels with their index in a list, like this:

var table = {"DAN-a1": 0,
             "DAN-a2": 1,
             "DAN-b1": 2,
             ...
            };

... and then modify the sort function above to get these indices:

  // Sort by label
  nodes.sort(function(node1, node2) {
    var label1 = node1.data("label");
    var label2 = node2.data("label");
    var index1 = table[label1];
    var index2 = table[label2];
    return index1 < index2 ? -1 : 1;
  });

Measure distance on a skeleton

To measure the distance between two arbitrary treenodes on a skeleton, the following code can be used:

(function(CATMAID) {

  /**
   * Get the cable length in nanometers between two nodes. If the passed in
   * nodes don't exist in the passed in skeleton or there is no connection
   * between the nodes, the returned Promise will be rejected with an error.
   *
   * @params {Number} projectId  The project to operate in
   * @params {Number} skeletonId The skeleton the nodes of interest are part of
   * @params {Number} nodeA      The first node for the distance calculation
   * @params {Number} nodeB      The second node for the distance calculation
   * @returns {Promise} Resolves in either the distance between nodeA and nodeB
   *                    or null if no distance could be computed.
   */
  CATMAID.distanceBetweenNodes = function(projectId, skeletonId, nodeA, nodeB) {

    // Corner case, node A == node B, no extra work is needed, the distance is zero.
    if (nodeA == nodeB) {
      return Promise.resolve(0);
    }

    return CATMAID.fetch(projectId + '/skeletons/' + skeletonId + '/compact-detail')
      .then(function(skeletonDetail) {
         let arborParser = new CATMAID.ArborParser();
         arborParser.init('compact-skeleton', skeletonDetail);

          // Make sure, nodes A and B are actually part of the skeleton.
          if (!arborParser.positions[nodeA]) {
            throw new CATMAID.ValueError("Node " + nodeA + " is not part of skeleton " + skeletonId);
          }
          if (!arborParser.positions[nodeB]) {
            throw new CATMAID.ValueError("Node " + nodeB + " is not part of skeleton " + skeletonId);
          }

         return arborParser;
      })
      .then(function(arborParser) {
        let arbor = arborParser.arbor;
        let positions = arborParser.positions;

        // Reroot arbor to node A for easy upstream traversal from node B.
        arbor.reroot(nodeA);

        // Compuet distance from node B to upstream node A.
        let distance = 0;
        let childPosition = positions[nodeB];
        let parent = arbor.edges[nodeB];
        while (parent) {
          let parentPosition = positions[parent];
          distance += childPosition.distanceTo(parentPosition);

          // If the current parent node is found, return with the calculated length.
          if (parent == nodeA) {
            return distance;
          }

          parent = arbor.edges[parent];
          childPosition = parentPosition;
        }

        return null;
      });
  };

})(CATMAID);

Paste the above code into a CATMAID console, get the IDs of the two treenodes that you want to measure the distance between and call the newly created function after replacing <projectId>, <skeletonId>, <nodeId1> and <nodeId2> with the respective values, node order doesn't matter:

CATMAID.distanceBetweenNodes(<projectId>, <skeletonId>, <nodeId1>, <nodeId2>)
  .then(function(distance) {
    console.log("The distance between the nodes is: " + distance + 'nm');
  })
  .catch(CATMAID.handleError);
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.