diff --git a/web_external/ImagesGallery/Facets/imagesFacetCategorical.pug b/web_external/ImagesGallery/Facets/imagesFacetCategorical.pug index ce15b50b..4d9686dd 100644 --- a/web_external/ImagesGallery/Facets/imagesFacetCategorical.pug +++ b/web_external/ImagesGallery/Facets/imagesFacetCategorical.pug @@ -10,7 +10,7 @@ block content a.isic-images-facet-all-include i.icon-plus-squared span.isic-images-facet-bin-name Select all - for bin in bins + each bin in bins div a.isic-images-facet-bin(data-bin-label=bin.label, data-toggle=popover) i.icon-check diff --git a/web_external/Segmentation/SegmentationTaskView.js b/web_external/Segmentation/SegmentationTaskView.js new file mode 100644 index 00000000..49bd3fc6 --- /dev/null +++ b/web_external/Segmentation/SegmentationTaskView.js @@ -0,0 +1,95 @@ +import View from '../view'; +import ImageModel from '../models/ImageModel'; +import SegmentationCollection from '../collections/SegmentationCollection'; +import {SegmentationImageViewerWidget} from '../common/Viewer/ImageViewerWidget'; + +import SegmentationTaskTemplate from './segmentationTask.pug'; +import './segmentationTask.styl'; + +import DetailSegmentationTaskTemplate from './detailSegmentationTask.pug'; + + +const SegmentationTaskView = View.extend({ + initialize: function (settings) { + this.image = new ImageModel({ + _id: '581cd7319fc3c13dcd0e1ed6' + }); + this.image + .once('g:fetched', () => { + this.segmentations = new SegmentationCollection(); + this.segmentations + .once('g:changed', () => { + this.segmentation = this.segmentations.at(0); + g_segmentation = this.segmentation; + + this.render(); + }) + .fetch({ + imageId: this.image.id, + limit: 0 + }); + }) + .fetch(); + + // if (girder.currentUser.getSegmentationSkill() !== null) { + // this.segmentationTasks = new TaskCollection(); + // this.segmentationTasks.altUrl = 'task/me/segmentation'; + // this.segmentationTasks.pageLimit = Number.MAX_SAFE_INTEGER; + // + // this.taskSegmentationView = new TasksGroupView({ + // title: 'Lesion Segmentation', + // subtitle: 'Segment boundaries between lesion and normal skin', + // linkPrefix: girder.apiRoot + '/task/me/segmentation/redirect?datasetId=', + // resourceName: 'dataset', + // collection: this.segmentationTasks, + // parentView: this + // }); + // } + // this.render(); + }, + + render: function () { + this.$el.html(SegmentationTaskTemplate({ + segmentations: this.segmentations.models, + formatDate: this.formatDate + })); + + this.imageViewerWidget = new SegmentationImageViewerWidget({ + el: this.$('.isic-segmentation-task-viewer'), + model: this.image, + parentView: this + }).render(); + + this.imageViewerWidget.on('loaded', () => { + this.imageViewerWidget.addSegmentation(this.segmentation); + }); + + return this; + } +}); + +const DetailSegmentationTaskView = View.extend({ + events: { + + }, + + initialize: function (settings) { + this.segmentation = settings.segmentation; + + this.segmentation + .once('g:fetched', () => { + this.render(); + }) + .fetch(); + }, + + render: function () { + this.$el.html(DetailSegmentationTaskTemplate({ + segmentation: this.segmentation, + formatDate: this.formatDate + })); + return this; + } +}); + +export default SegmentationTaskView; diff --git a/web_external/Segmentation/detailSegmentationTask.pug b/web_external/Segmentation/detailSegmentationTask.pug new file mode 100644 index 00000000..2c5cf8df --- /dev/null +++ b/web_external/Segmentation/detailSegmentationTask.pug @@ -0,0 +1,2 @@ +h1= segmentation.id +span Created: #{formatDate(segmentation.get('created'))} diff --git a/web_external/Segmentation/segmentationTask.pug b/web_external/Segmentation/segmentationTask.pug new file mode 100644 index 00000000..07675f99 --- /dev/null +++ b/web_external/Segmentation/segmentationTask.pug @@ -0,0 +1,24 @@ +.container-fluid + .row + .col-sm-3 + .isic-listing-container + each segmentation, index in segmentations + .isic-listing-panel-group.panel-group(id=`panel-group-${segmentation.id}`) + .isic-listing-panel.panel.panel-default + .isic-listing-panel-heading.panel-heading( + data-toggle='collapse', + data-parent=`panel-group-${segmentation.id}`, + data-target=`panel-${segmentation.id}`) + a.collapsed + block heading + = segmentation.id + span.isic-listing-panel-heading-indicator.pull-right + i.icon-right-open + .isic-listing-panel-collapse.panel-collapse.collapse( + id=`panel-${segmentation.id}`, + data-model-index=index) + .isic-listing-panel-body.panel-body + span Created: #{formatDate(segmentation.get('created'))} + + .col-sm-9 + .isic-segmentation-task-viewer diff --git a/web_external/Segmentation/segmentationTask.styl b/web_external/Segmentation/segmentationTask.styl new file mode 100644 index 00000000..56034b93 --- /dev/null +++ b/web_external/Segmentation/segmentationTask.styl @@ -0,0 +1,3 @@ +.isic-segmentation-task-viewer + height 100vh + width 100% diff --git a/web_external/common/Viewer/ImageViewerWidget.js b/web_external/common/Viewer/ImageViewerWidget.js index 7e1d33f0..a5062177 100644 --- a/web_external/common/Viewer/ImageViewerWidget.js +++ b/web_external/common/Viewer/ImageViewerWidget.js @@ -112,6 +112,7 @@ const ImageViewerWidget = View.extend({ this.renderedModelId = this.model.id; this._createViewer(); + this.trigger('loaded'); return this; }, @@ -138,4 +139,44 @@ const ImageViewerWidget = View.extend({ } }); +const SegmentationImageViewerWidget = ImageViewerWidget.extend({ + addSegmentation: function (segmentation) { + if (this.segmentationLayer) { + this.removeSegmentation(); + } + + this.segmentationLayer = this.viewer.createLayer('feature', { + features: ['quad'] + }); + this.segmentationLayer.opacity(0.2); + this.segmentationLayer.moveToTop(); + + this.segmentationFeature = this.segmentationLayer.createFeature('quad', { + selectionAPI: false + }).data([{ + ul: {x: 0, y: 0}, + lr: {x: this.sizeX, y: this.sizeY}, + // TODO: fetch this with Girder auth headers + image: `${girder.apiRoot}/segmentation/${segmentation.id}/mask` + }]).draw(); + }, + + removeSegmentation: function () { + this.viewer.deleteLayer(this.segmentationLayer); + this.segmentationLayer = null; + this.segmentationFeature = null; + }, + + showSegmentation: function () { + this.segmentationFeature.visible(false); + this.segmentationFeature.draw(); + }, + + hideSegmentation: function () { + this.segmentationFeature.visible(true); + this.segmentationFeature.draw(); + } +}); + export default ImageViewerWidget; +export {SegmentationImageViewerWidget}; diff --git a/web_external/routes.js b/web_external/routes.js index 999ae6f8..cc9973fc 100644 --- a/web_external/routes.js +++ b/web_external/routes.js @@ -202,3 +202,9 @@ import TasksView from './Tasks/TasksView'; router.route('tasks', 'tasks', () => { navigateToIfTermsAccepted(TasksView); }); + +// Segmentation +import SegmentationTaskView from './Segmentation/SegmentationTaskView'; +router.route('tasks/segmentation/:id', 'segmentationTask', (id) => { + navigateToIfTermsAccepted(SegmentationTaskView); +});