diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9317bd8166..32670496e5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/django/applications/catmaid/static/js/widgets/measurements-table.js b/django/applications/catmaid/static/js/widgets/measurements-table.js
index 8413e05cd6..9b2e76c3e3 100644
--- a/django/applications/catmaid/static/js/widgets/measurements-table.js
+++ b/django/applications/catmaid/static/js/widgets/measurements-table.js
@@ -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');
@@ -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);
@@ -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,
@@ -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]);
@@ -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}]);
}
}
},
@@ -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,
@@ -470,10 +478,21 @@
return `${data}`;
}
},
+ }, {
+ 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();
diff --git a/django/applications/catmaid/static/libs/catmaid/Arbor.js b/django/applications/catmaid/static/libs/catmaid/Arbor.js
index edd93375cb..b411f9509a 100644
--- a/django/applications/catmaid/static/libs/catmaid/Arbor.js
+++ b/django/applications/catmaid/static/libs/catmaid/Arbor.js
@@ -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 0 ? 1 : 0;
+ for (var i=0; i 0 ? 1 : 0;
+ }
+ return radiusCount / (1 + children.length);
+};
diff --git a/django/applications/catmaid/static/libs/catmaid/arbor_parser.js b/django/applications/catmaid/static/libs/catmaid/arbor_parser.js
index b927bf53a0..8bf25916e0 100644
--- a/django/applications/catmaid/static/libs/catmaid/arbor_parser.js
+++ b/django/applications/catmaid/static/libs/catmaid/arbor_parser.js
@@ -37,7 +37,8 @@
ArborParser.prototype.tree = function(rows) {
var arbor = new Arbor(),
- positions = {};
+ positions = {},
+ radii = {};
for (var i=0; i