Skip to content

Commit

Permalink
Measurement table: add column for radius based volume estimate
Browse files Browse the repository at this point in the history
A new column is added, which shows the estimated volume based on the
available radius information. The volume is computed by simply adding
all volumes of the cylinders and cones formed around each edge. To make
interpretation of that number easier, a percentage is shown as well for
each entry that reflects how many nodes of the respective skeleton have
a radius assigned.
  • Loading branch information
tomka committed Jul 26, 2023
1 parent 9bba054 commit 61c6adb
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 8 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,14 @@ Data views:
- Stacks are now explicitly returned and rendered in order they are linked to
projects (projects/ API).

Measurement widget:

- A new column is added, which shows the estimated volume based on the available
radius information. The volume is computed by simply adding all volumes of the
cylinders and cones formed around each edge. To make interpretation of that
number easier, a percentage is shown as well for each entry that reflects how
many nodes of the respective skeleton have a radius assigned.

Miscellaneous:

- The built-in API docs (/apis endpoint) now supports deep links, which allows
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
let labels = ['Neuron', 'Skeleton',
'Raw cable (nm)', 'Smooth cable (nm)', 'Lower-bound cable (nm)',
'N inputs', 'N outputs', 'N presynaptic sites', 'N nodes',
'N branch nodes', 'N end nodes'];
'N branch nodes', 'N end nodes', 'Est. radius volume (nm^3)'];

if (forceAll || (this.applyFilterRules && this.filterRules.length > 0)) {
labels.splice(2, 0, 'Fragment start', 'Fragment');
Expand Down Expand Up @@ -229,6 +229,7 @@
let ap = new CATMAID.ArborParser().init('compact-arbor', json),
arbor = ap.arbor,
positions = ap.positions,
radii = ap.radii,
api = models[skid].api,
name = CATMAID.NeuronNameService.getInstance(api).getName(skid);

Expand All @@ -244,6 +245,7 @@
let fractionArborParser = new CATMAID.ArborParser();
fractionArborParser.arbor = fractionArbor;
fractionArborParser.positions = positions;
fractionArborParser.radii = radii;
fractionArborParser.synapses(json[1], true);

let raw_cable = Math.round(fractionArbor.cableLength(positions)) | 0,
Expand All @@ -255,12 +257,14 @@
n_nodes = fractionArbor.countNodes(),
be = fractionArbor.findBranchAndEndNodes(),
n_branching = be.n_branches,
n_ends = be.ends.length;
n_ends = be.ends.length,
radius_volume = fractionArbor.estimateRadiusVolume(positions, radii),
radius_ratio = Math.floor(100.0 * fractionArbor.radiusRatio(radii));

let fragmentRow = [SkeletonMeasurementsTable.prototype._makeStringLink(name, skid),
skid, fractionArbor.root, `${i+1}/${fractions.length}`,
raw_cable, smooth_cable, lower_bound_cable, n_inputs, n_outputs,
n_presynaptic_sites, n_nodes, n_branching, n_ends];
n_presynaptic_sites, n_nodes, n_branching, n_ends, {volume: radius_volume, ratio: radius_ratio}];
if (self.aggregateFragments) {
if (aggRow) {
aggRow[2].push(fragmentRow[2]);
Expand Down Expand Up @@ -301,11 +305,14 @@
n_nodes = arbor.countNodes(),
be = arbor.findBranchAndEndNodes(),
n_branching = be.n_branches,
n_ends = be.ends.length;
n_ends = be.ends.length,
radius_volume = arbor.estimateRadiusVolume(positions, radii),
radius_ratio = Math.floor(100.0 * arbor.radiusRatio(radii));

rows.push([SkeletonMeasurementsTable.prototype._makeStringLink(name, skid),
skid, arbor.root, '1/1', raw_cable, smooth_cable, lower_bound_cable,
n_inputs, n_outputs, n_presynaptic_sites, n_nodes, n_branching, n_ends]);
n_inputs, n_outputs, n_presynaptic_sites, n_nodes,
n_branching, n_ends, {volume: radius_volume, ratio: radius_ratio}]);
}
}
},
Expand Down Expand Up @@ -439,7 +446,8 @@
let labels = this.getLabels(true);
let self = this;

this.table = $('table#skeleton_measurements_table' + this.widgetID).DataTable({
const tableId = 'table#skeleton_measurements_table' + this.widgetID;
this.table = $(tableId).DataTable({
destroy: true,
dom: '<"H"lr>t<"F"ip>',
processing: true,
Expand Down Expand Up @@ -470,10 +478,21 @@
return `<a href="#" data-role="select-node" data-node-id="${data}">${data}</a>`;
}
},
}, {
targets: -1,
render: function(data, type, row, meta) {
return `${Math.round(data.volume)} (${data.ratio}%)`;
},
}],
createdRow: function(row, data, index) {
row.dataset.skeletonId = data[1];
}
},
initComplete: function(settings) {
let headers = $(tableId).find('thead th');
headers[headers.length - 1].title = 'The estimated volume in nm^3 based on the sum of all ' +
'cylinders/cones formed by all skeleton edges with their node radii. The percentage shows how ' +
'many nodes have a radius attached';
},
})
.on('draw.dt', (function() {
this.highlightActiveSkeleton();
Expand Down
30 changes: 30 additions & 0 deletions django/applications/catmaid/static/libs/catmaid/Arbor.js
Original file line number Diff line number Diff line change
Expand Up @@ -2334,3 +2334,33 @@ Arbor.prototype.collapseArtifactualBranches = function(tags, callback) {
}
}
};

/**
* Sum the volume of all cylinders/cones between nodes of an arbor.
*/
Arbor.prototype.estimateRadiusVolume = function(positions, radii) {
var children = this.childrenArray(),
sum = 0;
for (var i=0; i<children.length; ++i) {
const node = children[i];
const dist = positions[node].distanceTo(positions[this.edges[node]]);
const r1 = Math.max(0, radii[node] || 0);
const r2 = Math.max(0, radii[this.edges[node]] || 0);
const vol = (1/3) * dist * Math.PI * (r1 * r1 + r1 * r2 + r2 * r2);
sum += vol;
}
return sum;
};

/**
* The ratio of nodes with radius versus those without.
*/
Arbor.prototype.radiusRatio = function(radii) {
var children = this.childrenArray(),
radiusCount = radii[this.root] && radii[this.root] > 0 ? 1 : 0;
for (var i=0; i<children.length; ++i) {
const node = children[i];
radiusCount += radii[node] && radii[node] > 0 ? 1 : 0;
}
return radiusCount / (1 + children.length);
};
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,21 @@

ArborParser.prototype.tree = function(rows) {
var arbor = new Arbor(),
positions = {};
positions = {},
radii = {};
for (var i=0; i<rows.length; ++i) {
var row = rows[i],
node = row[0],
paren = row[1];
if (paren) arbor.edges[node] = paren;
else arbor.root = node;
positions[node] = new THREE.Vector3(row[3], row[4], row[5]);
radii[node] = row[6];
}

this.arbor = arbor;
this.positions = positions;
this.radii = radii;
return this;
};

Expand Down

0 comments on commit 61c6adb

Please sign in to comment.