From 61c6adb1511737530d1bade119cc0ae6a659dff9 Mon Sep 17 00:00:00 2001 From: Tom Kazimiers Date: Wed, 26 Jul 2023 14:29:30 +0200 Subject: [PATCH] Measurement table: add column for radius based volume estimate 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. --- CHANGELOG.md | 8 +++++ .../static/js/widgets/measurements-table.js | 33 +++++++++++++++---- .../catmaid/static/libs/catmaid/Arbor.js | 30 +++++++++++++++++ .../static/libs/catmaid/arbor_parser.js | 5 ++- 4 files changed, 68 insertions(+), 8 deletions(-) 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