diff --git a/django/applications/catmaid/control/volume.py b/django/applications/catmaid/control/volume.py index d09d4cf306..1a2f79f3c2 100644 --- a/django/applications/catmaid/control/volume.py +++ b/django/applications/catmaid/control/volume.py @@ -8,6 +8,7 @@ from django.conf import settings from catmaid.control.authentication import requires_user_role, user_can_edit +from catmaid.control.common import get_request_list from catmaid.models import UserRole, Project, Volume from catmaid.serializers import VolumeSerializer @@ -728,3 +729,34 @@ def intersects(request, project_id, volume_id): return JsonResponse({ 'intersects': result[0] }) + +@api_view(['POST']) +@requires_user_role([UserRole.Browse]) +def get_volume_entities(request, project_id): + """Retrieve a mapping of volume IDs to entity (class instance) IDs. + --- + parameters: + - name: volume_ids + description: A list of volume IDs to map + paramType: query + """ + volume_ids = get_request_list(request.POST, 'volume_ids', map_fn=int) + + cursor = connection.cursor() + cursor.execute(""" + SELECT vci.volume_id, vci.class_instance_id + FROM volume_class_instance vci + JOIN UNNEST(%(volume_ids)s::int[]) volume(id) + ON volume.id = vci.volume_id + WHERE project_id = %(project_id)s + AND relation_id = ( + SELECT id FROM relation + WHERE relation_name = 'model_of' + AND project_id = %(project_id)s + ) + """, { + 'volume_ids': volume_ids, + 'project_id': project_id + }) + + return JsonResponse(dict(cursor.fetchall())) diff --git a/django/applications/catmaid/static/js/widgets/volumewidget.js b/django/applications/catmaid/static/js/widgets/volumewidget.js index 43c3751ddd..efe517942d 100644 --- a/django/applications/catmaid/static/js/widgets/volumewidget.js +++ b/django/applications/catmaid/static/js/widgets/volumewidget.js @@ -85,6 +85,12 @@ openFile.onclick = hiddenFileButton.click.bind(hiddenFileButton); controls.appendChild(openFile); + var annotate = document.createElement('button'); + annotate.appendChild(document.createTextNode('Annotate')); + annotate.setAttribute('title', 'Annotate all selected volumes'); + annotate.onclick = this.annotateSelectedVolumes.bind(this); + controls.appendChild(annotate); + let self = this; CATMAID.DOM.appendNumericField( controls, @@ -144,7 +150,7 @@ table.style.width = "100%"; var header = table.createTHead(); var hrow = header.insertRow(0); - var columns = ['Name', 'Id', 'Comment', 'User', 'Creation time', + var columns = ['', 'Name', 'Id', 'Comment', 'User', 'Creation time', 'Editor', 'Edition time', 'Action']; columns.forEach(function(c) { hrow.insertCell().appendChild(document.createTextNode(c)); @@ -171,6 +177,12 @@ .catch(CATMAID.handleError); }, columns: [ + { + render: function(data, type, row, meta) { + return ''; + } + }, {data: "title"}, {data: "id"}, {data: "comment"}, @@ -197,6 +209,12 @@ 'Export STL' } ], + }) + .on('change', 'input[data-role=select]', function() { + var table = $(this).closest('table'); + var tr = $(this).closest('tr'); + var data = $(table).DataTable().row(tr).data(); + data.selected = this.checked; }); // Remove volume if 'remove' was clicked @@ -331,7 +349,7 @@ // Display a volume if clicked var self = this; - $(table).on('click', 'tbody td', function() { + $(table).on('dblclick', 'tbody td', function() { var tr = $(this).closest("tr"); var volume = self.datatable.row(tr).data(); self.loadVolume(volume.id) @@ -623,6 +641,39 @@ }; + /** + * Annotate all currently selected volumes. + */ + VolumeManagerWidget.prototype.annotateSelectedVolumes = function() { + if (!this.datatable) { + return; + } + + let allVolumes = this.datatable.rows({'search': 'applied' }).data().toArray(); + let selectedVolumeIds = allVolumes.filter(function(v) { + return v.selected; + }).map(function(v) { + return v.id; + }); + + if (selectedVolumeIds.length === 0) { + CATMAID.warn("No volumes selected"); + return; + } + + // Retrieve class instance IDs for volumes + CATMAID.fetch(project.id + '/volumes/entities/', 'POST', { + volume_ids: selectedVolumeIds + }) + .then(function(ciMapping) { + return CATMAID.annotate(Object.values(ciMapping)); + }) + .then(function() { + CATMAID.msg("Success", "Annotations added"); + }) + .catch(CATMAID.handleError); + }; + var getVolumeType = function(volume) { if (volume instanceof CATMAID.AlphaShapeVolume) { return "alphashape"; diff --git a/django/applications/catmaid/urls.py b/django/applications/catmaid/urls.py index 3cf6100240..ef758e1679 100644 --- a/django/applications/catmaid/urls.py +++ b/django/applications/catmaid/urls.py @@ -495,6 +495,7 @@ url(r'^(?P\d+)/volumes/$', volume.volume_collection), url(r'^(?P\d+)/volumes/add$', record_view("volumes.create")(volume.add_volume)), url(r'^(?P\d+)/volumes/import$', volume.import_volumes), + url(r'^(?P\d+)/volumes/entities/$', volume.get_volume_entities), url(r'^(?P\d+)/volumes/(?P\d+)/$', volume.volume_detail), url(r'^(?P\d+)/volumes/(?P\d+)/intersect$', volume.intersects), url(r'^(?P\d+)/volumes/(?P\d+)/export\.(?P\w+)', volume.export_volume),