Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Add markups to image histograms #561

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 122 additions & 11 deletions server/models/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,9 @@ def load(self, id, level=AccessType.ADMIN, user=None, objectId=True, force=False
raise

def getHistograms(self, filterQuery, user):
# Avoid circular import
from .study import Study

# Define facets
categorialFacets = [
'meta.datasetId',
Expand Down Expand Up @@ -406,15 +409,7 @@ def getHistograms(self, filterQuery, user):
'folderId': {'$in': [
dataset['folderId'] for dataset in Dataset().list(user=user)]}
}
if filterQuery:
query = {
'$and': [
folderQuery,
filterQuery
]
}
else:
query = folderQuery

facetStages = {
'__passedFilters__': [
{'$count': 'count'}],
Expand Down Expand Up @@ -442,9 +437,125 @@ def getHistograms(self, filterQuery, user):
'default': None
}}
]
facetStages['markups'] = [
{
'$unwind': '$markups'
},
{
'$group': {
'_id': '$markups',
'count': {'$sum': 1}
}
},
{
'$project': {
'_id': False,
'label': '$_id',
'count': True
}
}
]

histogram = next(self.collection.aggregate([
{'$match': query},
{'$facet': facetStages}
{
'$match': (
{
'$and': [
folderQuery,
filterQuery
]
}
if filterQuery else
folderQuery
)
},
{
'$lookup': {
'from': 'annotation',
'localField': '_id',
'foreignField': 'imageId',
'as': 'annotations'
}
},
{
'$project': {
'_id': True,
'name': True,
'meta': True,
'markups': {
# (6) Concatinate all annotations' markup IDs together, eliminating
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo, should be "Concatenate"

# duplicates
'$reduce': {
'input': {
# (2) Extract only the markup from each annotation
'$map': {
'input': {
# (1) Remove incomplete, flagged, or inaccessable
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo, should be "inaccessible"

# annotations
'$filter': {
'input': '$annotations',
'cond': {
'$and': [
'$$this.stopTime',
{
'$eq': [
'$$this.status',
'ok'
]
},
{
'$in': [
'$$this.studyId',
[
study['_id']
for study in Study().list(user=user)
]
]
}
]
}
}
},
'as': 'annotation',
'in': '$$annotation.markups'
}
},
'initialValue': [],
'in': {
# (6a) Eliminate duplicates when extending array
'$setUnion': [
'$$value',
{
# (5) Make an array of just markup IDs
'$map': {
'input': {
# (4) Exclude false / empty markup
# TODO: if an annotation has no markups, mark it as a special value
'$filter': {
'input': {
# (3) Convert the markup object to an array
# of markupItem
# '$$this' uses the (6) context, which is
# the output of (2)
'$objectToArray': '$$this'
},
'as': 'markupItem',
'cond': '$$markupItem.v.present'
}
},
'as': 'markupItem',
'in': '$$markupItem.k'
}
}
]
}
}
}
}
},
{
'$facet': facetStages
}
]))

# Fix up the pipeline result
Expand Down
169 changes: 169 additions & 0 deletions web_external/ImagesGallery/Facets/ImagesFacetView.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,19 @@ import DatasetCollection from '../../collections/DatasetCollection';
import View from '../../view';

import HistogramScale from './HistogramScale';
import masterFeatures from '../../masterFeatures.json';

/* eslint-disable import/order */
import ImagesFacetHistogramTemplate from './imagesFacetHistogram.pug';
import ImagesFacetCategoricalTemplate from './imagesFacetCategorical.pug';
import ImagesFacetMarkupsTemplate from './imagesFacetMarkups.pug';
import checkImageUrl from '!url-loader!svg-fill-loader!./check.svg?fill=#999999'; // eslint-disable-line import/no-webpack-loader-syntax
import dashImageUrl from '!url-loader!svg-fill-loader!./dash.svg?fill=#999999'; // eslint-disable-line import/no-webpack-loader-syntax
import exImageUrl from '!url-loader!svg-fill-loader!./ex.svg?fill=#999999'; // eslint-disable-line import/no-webpack-loader-syntax
/* eslint-enable import/order */

import ImagesFacetMarkupLevelTemplate from './imagesFacetMarkupLevel.pug';

const ImagesFacetView = View.extend({
className: 'isic-images-facet',

Expand Down Expand Up @@ -151,6 +155,8 @@ const ImagesFacetHistogramView = ImagesFacetView.extend({
}));
this._renderHistogram();
this._applyInitialCollapseState();

return this;
},

_renderHistogram: function () {
Expand Down Expand Up @@ -459,6 +465,8 @@ const ImagesFacetCategoricalView = ImagesFacetView.extend({
this._applyInitialCollapseState();
this._rerenderCounts();
this._rerenderSelections();

return this;
},

_rerenderCounts: function () {
Expand Down Expand Up @@ -552,6 +560,8 @@ const ImagesFacetCategoricalDatasetView = ImagesFacetCategoricalView.extend({
'show': 100
}
});

return this;
},

_getBinTitle: function (completeBin) {
Expand All @@ -570,6 +580,158 @@ const ImagesFacetCategoricalTagsView = ImagesFacetCategoricalView.extend({
}
});

const ImagesFacetMarkupGroupView = View.extend({
/**
* @param {ImagesFacetModel} settings.completeFacet
* @param {ImagesFacetModel} settings.filteredFacet
* @param {String[]} settings.featureIds
* @param {Number} settings.depth
* @param {String} settings.featureLevelLabel
*/
initialize: function (settings) {
this.completeFacet = settings.completeFacet;
this.filteredFacet = settings.filteredFacet;

this.featureIds = settings.featureIds;
this.depth = settings.depth;
this.featureLevelLabel = settings.featureLevelLabel;

this.childViews = _.chain(this.featureIds)
.groupBy((featureId) => {
const featureLevelLabels = featureId.split(' : ');
const childFeatureLevelLabel = featureLevelLabels[this.depth + 1];
return childFeatureLevelLabel;
})
.map((childFeatureIds, childFeatureLevelLabel) => {
const childDepth = this.depth + 1;

if (childFeatureIds.length === 1
&& childFeatureIds[0].split(' : ').length - 1 === childDepth) {
// If this is the single and last child, then it's a leaf
return new ImagesFacetMarkupLeafView({
completeFacet: this.completeFacet,
filteredFacet: this.filteredFacet,
featureId: childFeatureIds[0],
depth: childDepth,
featureLevelLabel: childFeatureLevelLabel,
parentView: this
});
} else {
return new ImagesFacetMarkupGroupView({
completeFacet: this.completeFacet,
filteredFacet: this.filteredFacet,
featureIds: childFeatureIds,
depth: childDepth,
featureLevelLabel: childFeatureLevelLabel,
parentView: this
});
}
})
.value();
},

render: function () {
this.$el.html(ImagesFacetMarkupLevelTemplate({
featureLevelLabel: this.featureLevelLabel,
count: 0
}));

_.each(this.childViews, (childView) => {
childView
.render()
// To avoid selecting deep children, use '.children'
.$el.appendTo(this.$el.children('.isic-images-facet-childMarkups'));
});

this.completeCount = this.childViews.reduce(
(sum, childView) => sum + childView.completeCount,
0
);
this.$el
.children('.isic-images-facet-bin')
.children('.isic-images-facet-bin-count')
.text(this.completeCount);

return this;
}
});

const ImagesFacetMarkupLeafView = View.extend({
/**
* @param {ImagesFacetModel} settings.completeFacet
* @param {ImagesFacetModel} settings.filteredFacet
* @param {String} settings.featureId
* @param {Number} settings.depth
* @param {String} settings.featureLevelLabel
*/
initialize: function (settings) {
this.completeFacet = settings.completeFacet;
this.filteredFacet = settings.filteredFacet;

this.featureId = settings.featureId;
this.depth = settings.depth;
this.featureLevelLabel = settings.featureLevelLabel;
},

render: function () {
const getFeatureBinCount = (facet) => {
const featureBin = _.findWhere(facet.get('bins'), {label: this.featureId});
const count = featureBin ? featureBin.count : 0;
return count;
};
this.completeCount = getFeatureBinCount(this.completeFacet);
this.filteredCount = getFeatureBinCount(this.filteredFacet);

this.$el.html(ImagesFacetMarkupLevelTemplate({
featureLevelLabel: this.featureLevelLabel,
count: this.completeCount
}));

return this;
}
});

const ImagesFacetMarkupsView = ImagesFacetView.extend({
initialize: function (settings) {
ImagesFacetView.prototype.initialize.call(this, settings);

const featureIds = _.pluck(masterFeatures, 'id');
this.childViews = _.chain(featureIds)
.groupBy((featureId) => {
const featureLevelLabels = featureId.split(' : ');
const childFeatureLevelLabel = featureLevelLabels[0];
return childFeatureLevelLabel;
})
.map((childFeatureIds, childFeatureLevelLabel) => {
return new ImagesFacetMarkupGroupView({
completeFacet: this.completeFacet,
filteredFacet: this.filteredFacet,
featureIds: childFeatureIds,
depth: 0,
featureLevelLabel: childFeatureLevelLabel,
parentView: this
});
})
.value();
},


render: function () {
this.$el.html(ImagesFacetMarkupsTemplate({
title: this.title,
facetContentId: this.facetContentId,
}));

_.each(this.childViews, (childView) => {
childView
.render()
.$el.appendTo(this.$('.isic-images-facet-markups-content'));
});

return this;
}
});

const FACET_SCHEMA = {
'meta.datasetId': {
FacetView: ImagesFacetCategoricalDatasetView,
Expand Down Expand Up @@ -722,6 +884,13 @@ const FACET_SCHEMA = {
coerceToType: 'string',
title: 'Tags',
collapsed: true
},
'markups': {
FacetView: ImagesFacetMarkupsView,
FacetFilter: CategoricalFacetFilter,
coerceToType: 'string',
title: 'Feature Markups',
collapsed: true
}
};

Expand Down
2 changes: 2 additions & 0 deletions web_external/ImagesGallery/Facets/ImagesFacetsPane.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ const ImagesFacetsPane = View.extend({
headerEl = this.$('.isic-images-facets-clinical');
} else if (facetId.startsWith('meta.acquisition')) {
headerEl = this.$('.isic-images-facets-acquisition');
} else if (facetId.startsWith('markups')) {
headerEl = this.$('.isic-images-facets-markup');
} else {
headerEl = this.$('.isic-images-facets-database');
}
Expand Down
6 changes: 6 additions & 0 deletions web_external/ImagesGallery/Facets/imagesFacetMarkupLevel.pug
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
a.isic-images-facet-bin
//i.icon-check
span.isic-images-facet-bin-name= featureLevelLabel
span.isic-images-facet-bin-count= count

.isic-images-facet-childMarkups
4 changes: 4 additions & 0 deletions web_external/ImagesGallery/Facets/imagesFacetMarkups.pug
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
extends imagesFacet

block content
.isic-images-facet-markups-content
Loading