Skip to content

Commit

Permalink
Neuron similarity widget: add option to open similarity in regular wi…
Browse files Browse the repository at this point in the history
…ndow

Rather than showing the result in a dialog, the new Neuron Similarity
Detail Widget is now shown by default. It shows detailed information of
a NBLAST similarity result.
  • Loading branch information
tomka committed Oct 3, 2018
1 parent 7462ffb commit a49a992
Show file tree
Hide file tree
Showing 2 changed files with 314 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,294 @@
/* -*- mode: espresso; espresso-indent-level: 2; indent-tabs-mode: nil -*- */
/* vim: set softtabstop=2 shiftwidth=2 tabstop=2 expandtab: */

(function(CATMAID) {

"use strict";

/**
* Create a new Neuron Similarity Result Widget. It lists neuron similarities.
*/
var NeuronSimilarityDetailWidget = function(options) {
options = options || {};

this.widgetID = this.registerInstance();
this.idPrefix = "neuron-similarity-detail" + this.widgetID + '-';

// The currently selected similarity query result.
this.similarity = null;
// Whether or not only positive scores (i.e. matches) should be displayed.
this.onlyPositiveScores = true;
// Show a top N of result matches
this.showTopN = 10;

// We expect the content DOM element to be available after initialization.
this.content = null;

if (options.similarityId) {
this.setSimilarityFromId(options.similarityId);
}
};

$.extend(NeuronSimilarityDetailWidget.prototype, new InstanceRegistry());

NeuronSimilarityDetailWidget.prototype.getName = function() {
return "Neuron Similarity Detail " + this.widgetID;
};

NeuronSimilarityDetailWidget.prototype.destroy = function() {
this.unregisterInstance();
CATMAID.NeuronNameService.getInstance().unregister(this);
};

NeuronSimilarityDetailWidget.prototype.getWidgetConfiguration = function() {
return {
controlsID: this.idPrefix + 'controls',
createControls: function(controls) {
let self = this;

// Update point cloud list
var initSimilarityList = function() {
return CATMAID.Similarity.listAllSkeletonSimilarities(project.id)
.then(function(json) {
let similarityId = self.similarity ? self.similarity.id : null;
var similarities = json.sort(function(a, b) {
return CATMAID.tools.compareStrings(a.name, b.name);
}).map(function(similarity) {
let entry = {
title: similarity.name + ' (' + similarity.id + ')',
value: similarity.id
};
if (similarity.id == similarityId) {
entry.checked = true;
}
return entry;
});
var selectedSimilarity = self.similarityId;
// Create actual element based on the returned data
var node = CATMAID.DOM.createRadioSelect('Similarity', similarities,
selectedSimilarity, true);
// Add a selection handler
node.onchange = function(e) {
var similarityId = parseInt(e.target.value, 10);
self.setSimilarityFromId(similarityId);
};
return node;
});
};

CATMAID.DOM.appendElement(controls, {
type: 'button',
label: 'Refresh',
onclick: self.refresh.bind(self),
});

// Create async selection and wrap it in container to have handle on initial
// DOM location
var similaritySelection = CATMAID.DOM.createAsyncPlaceholder(initSimilarityList());
var similaritySelectionWrapper = controls.appendChild(document.createElement('span'));
similaritySelectionWrapper.appendChild(similaritySelection);

// Replace point cloud selection wrapper children with new select
var refreshSimilarityList = function() {
while (0 !== similaritySelectionWrapper.children.length) {
similaritySelectionWrapper.removeChild(similaritySelectionWrapper.children[0]);
}
var pointcloudSelection = CATMAID.DOM.createAsyncPlaceholder(initSimilarityList());
similaritySelectionWrapper.appendChild(pointcloudSelection);
};

CATMAID.DOM.appendElement(controls, {
type: 'checkbox',
label: 'Only positive scores',
value: self.onlyPositiveScores,
onclick: function() {
self.onlyPositiveScores = this.checked;
self.refresh();
}
});

CATMAID.DOM.appendElement(controls, {
type: 'numeric',
label: 'Top N results',
title: 'Show only the top N matches for a query, zero shows all results',
length: 3,
value: self.showTopN,
onchange: function() {
let value = parseInt(this.value, 10);
if (value !== undefined && !Number.isNaN(value)) {
self.showTopN = value;
}
},
});
},
contentID: this.idPrefix + 'content',
createContent: function(content) {
this.content = content;
},
init: function() {
this.refresh();
},
};
};

/**
* Set a new similarity object to be the active similarity object.
*/
NeuronSimilarityDetailWidget.prototype.setSimilarity = function(similarity) {
this.similarity = similarity;
let targetModels = CATMAID.Similarity.getReferencedSkeletonModels(similarity);
CATMAID.NeuronNameService.getInstance().registerAll(this, targetModels)
.then((function() {
this.refresh();
}).bind(this))
.catch(CATMAID.handleError);
};

NeuronSimilarityDetailWidget.prototype.setSimilarityFromId = function(similarityId) {
CATMAID.Similarity.getSimilarity(project.id, similarityId)
.then((function(similarity) {
this.setSimilarity(similarity);
}).bind(this))
.catch(CATMAID.handleError);
};

NeuronSimilarityDetailWidget.prototype.refresh = function() {
// Clear content
while (this.content.lastChild) {
this.content.removeChild(this.content.lastChild);
}

// Reset message and if no similarity is set, set a message and return
// early.
this.content.dataset.msg = '';
if (!this.similarity) {
this.content.dataset.msg = 'Please select a similarity query result';
return;
}

let table = this.content.appendChild(document.createElement('table'));

let thead = table.appendChild(document.createElement('thead'));
let theadTr = thead.appendChild(document.createElement('tr'));
let theadTh1 = theadTr.appendChild(document.createElement('th'));
theadTh1.appendChild(document.createTextNode('Query ' + this.similarity.query_type));
let theadTh2 = theadTr.appendChild(document.createElement('th'));
theadTh2.appendChild(document.createTextNode('Top 10 target ' + this.similarity.target_type + 's'));
let tbody = table.appendChild(document.createElement('tbody'));

let pointClouds = {};

NeuronSimilarityDetailWidget.createSimilarityTable(this.similarity,
this.onlyPositiveScores, pointClouds, table);
};

NeuronSimilarityDetailWidget.createSimilarityTable = function(similarity,
matchesOnly, pointClouds, table) {
if (!table) {
table = document.createElement('table');
}

let getQueryName;
if (similarity.query_type === 'skeleton') {
getQueryName = function(element) {
return CATMAID.NeuronNameService.getInstance().getName(element);
};
} else if (similarity.query_type === 'pointcloud') {
getQueryName = function(element) {
let pc = pointClouds[element];
return pc ? pc.name : (element + ' (not found)');
};
} else {
getQueryName = function(element) {
return element;
};
}

let getTargetName;
if (similarity.target_type === 'skeleton') {
getTargetName = function(element) {
return CATMAID.NeuronNameService.getInstance().getName(element);
};
} else if (similarity.target_type === 'pointcloud') {
getTargetName = function(element) {
let pc = pointClouds[element];
return pc ? pc.name : (element + ' (not found)');
};
} else {
getTargetName = function(element) {
return element;
};
}

let collectEntries = function(target, element, i) {
if (element >= 0) {
target.push([getTargetName(similarity.target_objects[i]), element]);
}
return target;
};

let compareEntriesDesc = function(a, b) {
if (a[1] > b[1]) return -1;
if (a[1] < b[1]) return 1;
return 0;
};

let dataAboveZero = similarity.query_objects.map(function(qskid, i) {
let sortedMatches = similarity.scoring[i].reduce(collectEntries, []).sort(compareEntriesDesc);
return [qskid, sortedMatches];
});

$(table).DataTable({
dom: 'lfrtip',
data: dataAboveZero,
order: [],
columns: [{
orderable: true,
class: 'cm-center',
render: function(data, type, row, meta) {
return `<a href="#" data-skeleton-id="${row[0]}" data-role="select-skeleton">${getQueryName(row[0])}</a>`;
}
}, {
orderable: false,
class: 'cm-left',
render: function(data, type, row, meta) {
if (row[1].length > 0) {
let nTop10Elements = Math.min(10, row[1].length);
let elements = ['<span class="result-list">'];
for (let i=0; i<nTop10Elements; ++i) {
let entry = row[1][i];
elements.push(`<span class="result-element"><span>${i+1}.</span><a href="#" data-skeleton-id="${entry[0]}" data-role="select-skeleton">${entry[0]}</a> (${entry[1]})</span>`);
}
elements.push('</span>');
return elements.join('');
} else {
return '(no match)';
}
}
}]
}).on('click', 'a[data-role=select-skeleton]', function() {
let skeletonId = parseInt(this.dataset.skeletonId, 10);
CATMAID.TracingTool.goToNearestInNeuronOrSkeleton('skeleton', skeletonId);
});

if (matchesOnly) {
$(table).DataTable().columns(1).search('^(?!.*no match).*$', true, false, true).draw();
}

return table;
};


// Export into CATMAID namespace
CATMAID.NeuronSimilarityDetailWidget = NeuronSimilarityDetailWidget;


// Register widget with CATMAID
CATMAID.registerWidget({
name: "Neuron similarity detail",
description: "Show details of a neuron similarity query",
key: "neuron-similarity-detail",
creator: NeuronSimilarityDetailWidget,
});

})(CATMAID);
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
this.showOnlyMatchesInResult = true;
// Whether or not the results are displayed in a dialog (rather than a
// window).
this.resultMode = 'dialog';
this.resultMode = 'window';

this.mode = 'similarity';
this.modes = ['similarity', 'configrations', 'pointclouds'];
Expand Down Expand Up @@ -391,6 +391,22 @@
type: 'button',
label: 'Refresh',
onclick: widget.refresh.bind(widget),
}, {
id: widget.idPrefix + '-result-mode',
type: 'select',
label: 'View',
title: 'Whether to view results in a window or a dialog',
value: widget.resultMode,
entries: [{
title: 'Window',
value: 'window'
}, {
title: 'Dialog',
value: 'dialog'
}],
onchange: function() {
widget.resultMode = this.value;
}
}, {
type: 'child',
element: newScoringSection,
Expand Down Expand Up @@ -1732,20 +1748,7 @@
NeuronSimilarityWidget.prototype.showSimilarity = function(similarity) {
let self = this;
if (this.resultMode === 'dialog') {
let targetModels = {};
if (similarity.target_type === 'skeleton') {
similarity.target_objects.reduce(function(o, to) {
o[to] = new CATMAID.SkeletonModel(to);
return o;
}, targetModels);
}
if (similarity.query_type === 'skeleton') {
similarity.query_objects.reduce(function(o, to) {
o[to] = new CATMAID.SkeletonModel(to);
return o;
}, targetModels);
}

let targetModels = CATMAID.Similarity.getReferencedSkeletonModels(similarity);
let needsPointclouds = similarity.query_type === 'pointcloud' ||
similarity.target_type === 'pointcloud';

Expand All @@ -1772,7 +1775,8 @@
};

NeuronSimilarityWidget.showSimilarityWindow = function(similarity) {
let widgetInfo = CATMAID.WindowMaker.create('neuron-similarity-list');
let widgetInfo = CATMAID.WindowMaker.create('neuron-similarity-detail');
widgetInfo.widget.setSimilarity(similarity);
};

/**
Expand Down

0 comments on commit a49a992

Please sign in to comment.