Skip to content
Draft
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
9 changes: 5 additions & 4 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
WSI Superpixel Guided Labeling
==============================

WSI Superpixel Guided Labeling is a `Girder 3 <https://github.com/girder>`_ plugin designed to be used in conjunction with `HistomicsUI <https://github.com/DigitalSlideArchive/HistomicsUI>`_ and `HistomicsTK <https://github.com/DigitalSlideArchive/HistomicsTK>`_ to facilitate active learning on whole slide images.
WSI Superpixel Guided Labeling is a `Girder 5 <https://github.com/girder>`_ plugin designed to be used in conjunction with `HistomicsUI <https://github.com/DigitalSlideArchive/HistomicsUI>`_ and `HistomicsTK <https://github.com/DigitalSlideArchive/HistomicsTK>`_ to facilitate active learning on whole slide images.

This plugin leverages the output of certain HistomicsTK/SlicerCLI jobs to allow end users to label superpixel regions of whole slide images to be used as input for machine learning algorithms.

Expand All @@ -13,13 +13,14 @@ Once the appropriate data is generated, a new view becomes available for labelin
Installation
------------

The recommended way to use this plugin is by adding it to the Digital Slide Archive's ``docker-compose`` deployment. First, check out both this repository and ``digital_slide_archive`` from Github, if you do not yet have a running instance of the Digital Slide Archive.
The recommended way to use this plugin is by adding it to the Digital Slide Archive's ``docker-compose`` deployment. First, check out both this repository and `digital_slide_archive <https://github.com/DigitalSlideArchive/digital_slide_archive>`_ from Github, if you do not yet have a running instance of the Digital Slide Archive.

If you don't already use a provisioning yaml file as part of your DSA deployment, you'll want to create one, e.g. ``provision.local.yaml``. Make sure this file contains the following: ::

pip:
- /opt/wsi-superpixel-guided-labeling
rebuild-client: True
- /opt/wsi-superpixel-guided-labeling
shell:
- cd /opt/wsi-superpixel-guided-labeling/wsi_superpixel_guided_labeling/web_client && npm install && npm run build
resources:
- model: collection
name: Tasks
Expand Down
11 changes: 5 additions & 6 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
readme = readme_file.read()

requirements = [
'girder>=3.0.0a1'
'girder>=5.0.0a2'
]

setup(
Expand All @@ -23,22 +23,21 @@
description='Perform active learning with superpixel classification.',
install_requires=[
'histomicsui',
'large_image[tiff,openslide,memcached,openjpeg,converter]>=1.20.6',
'girder-large-image-annotation>=1.20.6',
'girder-slicer-cli-web>=1.2.3',
'large_image[tiff,openslide,memcached,openjpeg,converter]',
'girder-large-image-annotation'
],
license='Apache Software License 2.0',
long_description=readme,
long_description_content_type='text/x-rst',
include_package_data=True,
keywords='girder-plugin, wsi_superpixel_guided_labeling',
packages=find_packages(exclude=['test', 'test.*']),
url='https://github.com/DigitalSlideArchive/wsi_superpixel_guided_labeling',
url='https://github.com/DigitalSlideArchive/wsi-superpixel-guided-labeling',
version='0.1.0',
zip_safe=False,
entry_points={
'girder.plugin': [
'wsi_superpixel_guided_labeling = wsi_superpixel_guided_labeling:GirderPlugin'
'wsi_superpixel_guided_labeling = wsi_superpixel_guided_labeling:WSISuperpixelGuidedLabelingPlugin'
]
}
)
15 changes: 11 additions & 4 deletions wsi_superpixel_guided_labeling/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
from girder import plugin
from pathlib import Path

from girder import plugin

class GirderPlugin(plugin.GirderPlugin):
class WSISuperpixelGuidedLabelingPlugin(plugin.GirderPlugin):
DISPLAY_NAME = 'WSI Superpixel Guided Labeling'
CLIENT_SOURCE_PATH = 'web_client'

def load(self, info):
# add plugin loading logic here
plugin.getPlugin('histomicsui').load(info)
# add plugin loading logic here
plugin.registerPluginStaticContent(
plugin='wsi_superpixel_guided_labeling',
css=['/style.css'],
js=['/girder-plugin-wsi-superpixel-guided-labeling.umd.js'],
staticDir=Path(__file__).parent / 'web_client' / 'dist',
tree=info['serverRoot'],
)
15 changes: 9 additions & 6 deletions wsi_superpixel_guided_labeling/web_client/main.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import router from '@girder/histomicsui/router';
import { registerPluginNamespace } from '@girder/core/pluginUtils';
import { exposePluginConfig } from '@girder/core/utilities/PluginUtils';
import girderEvents from '@girder/core/events';
const events = girder.events;
const { registerPluginNamespace } = girder.pluginUtils;
const { exposePluginConfig } = girder.utilities.PluginUtils;

import ActiveLearningView from './views/body/ActiveLearningView';
import './views/itemAndFolderList';
Expand All @@ -15,6 +14,10 @@ const configRoute = `plugins/${pluginName}/config`;
registerPluginNamespace(pluginName, WSISuperpixelGuidedLabeling);
exposePluginConfig(pluginName, configRoute);

router.route('active-learning', 'active-learning', function () {
girderEvents.trigger('g:navigateTo', ActiveLearningView, {});
// g:appload.before runs after all plugin static files have been loaded
events.on('g:appload.before', () => {
const router = girder.plugins.histomicsui.router;
router.route('active-learning', 'active-learning', function () {
events.trigger('g:navigateTo', ActiveLearningView, {});
});
});
39 changes: 16 additions & 23 deletions wsi_superpixel_guided_labeling/web_client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,44 +3,34 @@
"version": "0.1.0",
"private": true,
"description": "Perform active learning with superpixel classification.",
"homepage": "https://github.com/DigitalSlideArchive/wsi_superpixel_guided_labeling",
"homepage": "https://github.com/DigitalSlideArchive/wsi-superpixel-guided-labeling",
"license": "Apache-2.0",
"peerDependencies": {
"@girder/core": "*",
"@girder/histomicsui": "*"
},
"girderPlugin": {
"name": "wsi_superpixel_guided_labeling",
"main": "./main.js",
"dependencies": [
"histomicsui",
"large_image",
"large_image_annotation",
"slicer_cli_web"
],
"webpack": "webpack.helper"
},
"main": "./main.js",
"scripts": {
"lint": "eslint . && pug-lint . && stylint",
"format": "eslint --cache --fix ."
"format": "eslint --cache --fix .",
"dev": "vite",
"build": "vite build"
},
"dependencies": {
"@girder/core": "^5.0.0-beta.2",
"@girder/histomicsui": "file:///opt/HistomicsUI/histomicsui/web_client",
"@vue/compiler-sfc": "^3.2.33",
"backbone.localstorage": "^2.0.2",
"bootstrap-submenu": "^2.0.4",
"copy-webpack-plugin": "^4.5.2",
"js-yaml": "^4.1.0",
"petite-vue": "^0.4.1",
"sinon": "^2.1.0",
"tinycolor2": "^1.4.1",
"url-search-params-polyfill": "^8.1.1",
"vue": "~2.6.14",
"vue-template-compiler": "~2.6.14",
"vue-loader": "~15.9.8",
"webpack": "^3"
"vue": "^2.7.16",
"vue-template-compiler": "~2.6.14"
},
"devDependencies": {
"@girder/eslint-config": "*",
"@girder/pug-lint-config": "*",
"@types/node": "^20.11.24",
"@vitejs/plugin-vue2": "^2.2.0",
"eslint": "^8.20.0",
"eslint-config-semistandard": "17.0.0",
"eslint-config-standard": "^17.0.0",
Expand All @@ -49,8 +39,11 @@
"eslint-plugin-n": "^15.2.4",
"eslint-plugin-promise": "^6.0.0",
"eslint-plugin-vue": "^9.10.0",
"pug": "^3.0.3",
"pug-lint": "^2",
"stylint": "^2"
"stylint": "^2",
"stylus": "^0.64.0",
"vite": "^5.0.0"
},
"eslintConfig": {
"extends": [
Expand Down
8 changes: 8 additions & 0 deletions wsi_superpixel_guided_labeling/web_client/tsconfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"include": ["./vite-env.d.ts", "./**/*"],
"exclude": ["./dist/**/*"],
"compilerOptions": {
"allowJs": true,
"outDir": "./dist",
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { wrap } from '@girder/core/utilities/PluginUtils';
import HeaderImageView from '@girder/histomicsui/views/layout/HeaderImageView.js';
const { wrap } = girder.utilities.PluginUtils;
const HeaderImageView = girder.plugins.histomicsui.views.layout.HeaderImageView;

wrap(HeaderImageView, 'render', function (render) {
render.call(this);
Expand Down
4 changes: 2 additions & 2 deletions wsi_superpixel_guided_labeling/web_client/views/HeaderView.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { wrap } from '@girder/core/utilities/PluginUtils';
import HeaderView from '@girder/histomicsui/views/layout/HeaderView';
const { wrap } = girder.utilities.PluginUtils;
const HeaderView = girder.plugins.histomicsui.views.layout.HeaderView;

wrap(HeaderView, 'render', function (render) {
const isActiveLearning = window.location.href.includes('active-learning');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
/* global $, __webpack_public_path__ */
import View from '@girder/core/views/View';
import { restRequest, getApiRoot } from '@girder/core/rest';
import { confirm } from '@girder/core/dialog';
import _ from 'underscore';

import router from '@girder/histomicsui/router';
import FolderCollection from '@girder/core/collections/FolderCollection';
import AnnotationModel from '@girder/large_image_annotation/models/AnnotationModel';
import ItemCollection from '@girder/core/collections/ItemCollection';
import JobStatus from '@girder/jobs/JobStatus.js';
import { parse } from '@girder/slicer_cli_web/parser';
const $ = girder.$;
const View = girder.views.View;
const { restRequest, getApiRoot } = girder.rest;
const { confirm } = girder.dialog;
const _ = girder._;

const router = girder.plugins.histomicsui.router;
const FolderCollection = girder.collections.FolderCollection;
const AnnotationModel = girder.plugins.large_image_annotation.models.AnnotationModel;
const ItemCollection = girder.collections.ItemCollection;
const JobStatus = girder.plugins.jobs.JobStatus;
const { parse } = girder.plugins.slicer_cli_web.parser;

import learningTemplate from '../../templates/body/activeLearningView.pug';
import ActiveLearningGlobalContainer from '../vue/components/ActiveLearningGlobalContainer.vue';
Expand All @@ -20,10 +20,9 @@ import { debounce, isValidNumber } from '../vue/components/utils.js';

import '../../stylesheets/body/learning.styl';

const yaml = require('js-yaml');
import * as yaml from 'js-yaml';
// Only necessary until we have native support for Promises with es6.
// Used for Promise.all() and Promise.resolve() support.
const Promise = require('bluebird');

const epochRegex = /epoch (\d+)/i;

Expand Down Expand Up @@ -269,33 +268,23 @@ const ActiveLearningView = View.extend({
this.vueApp.$destroy();
}
const el = this.$('.h-active-learning-container').get(0);
// eslint-disable-next-line
const root = (__webpack_public_path__ || '/status/built').replace(/\/$/, '');
const geojsUrl = root + '/plugins/large_image/extra/geojs.js';
// Make sure geojs is available, as it required by the image viewer widgets
$.ajax({
url: geojsUrl,
dataType: 'script',
cache: true
}).done(() => {
const imageNamesById = {};
_.forEach(Object.keys(this.imageItemsById), (imageId) => {
imageNamesById[imageId] = this.imageItemsById[imageId].name;
});
this.vueApp = new ActiveLearningGlobalContainer({
el,
propsData: {
backboneParent: this,
imageNamesById,
annotationsByImageId: this.annotationsByImageId,
certaintyMetrics: this.certaintyMetrics,
featureShapes: this.featureShapes,
apiRoot: getApiRoot(),
currentAverageCertainty: this.currentAverageCertainty,
availableImages: this.availableImages,
categoryMap: this.categoryMap
}
});
const imageNamesById = {};
_.forEach(Object.keys(this.imageItemsById), (imageId) => {
imageNamesById[imageId] = this.imageItemsById[imageId].name;
});
this.vueApp = new ActiveLearningGlobalContainer({
el,
propsData: {
backboneParent: this,
imageNamesById,
annotationsByImageId: this.annotationsByImageId,
certaintyMetrics: this.certaintyMetrics,
featureShapes: this.featureShapes,
apiRoot: getApiRoot(),
currentAverageCertainty: this.currentAverageCertainty,
availableImages: this.availableImages,
categoryMap: this.categoryMap
}
});
},

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { wrap } from '@girder/core/utilities/PluginUtils';
import ItemListWidget from '@girder/core/views/widgets/ItemListWidget';
import FolderListWidget from '@girder/core/views/widgets/FolderListWidget';
import _ from 'underscore';
import { restRequest } from '@girder/core/rest';
const {wrap} = girder.utilities.PluginUtils;
const ItemListWidget = girder.views.widgets.ItemListWidget;
const FolderListWidget = girder.views.widgets.FolderListWidget;
const {restRequest} = girder.rest;

const _ = girder._;

const specialFolders = ['Annotations', 'Models', 'Features'];

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script>
import _ from 'underscore';
const _ = girder._;

import ActiveLearningFilmStripCard from './ActiveLearningFilmStripCard.vue';
import ActiveLearningStats from './ActiveLearningStats.vue';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script>
import Vue from 'vue';
import _ from 'underscore';
const _ = girder._;

import { store, nextCard } from '../store';
import { isValidNumber, updateMetadata } from '../utils';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<script>
import Vue from 'vue';
import _ from 'underscore';
const _ = girder._;

import { confirm } from '@girder/core/dialog';
import ColorPickerInput from '@girder/histomicsui/vue/components/ColorPickerInput.vue';
const { confirm } = girder.dialog;
const ColorPickerInput = girder.plugins.histomicsui.vue.ColorPickerInput;

import { store, assignHotkey, nextCard, previousCard, updatePixelmapLayerStyle } from './store.js';
import { boundaryColor, comboHotkeys, viewMode, activeLearningSteps } from './constants.js';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script>
import Vue from 'vue';
import _ from 'underscore';
const _ = girder._;

import { store } from '../store';
import { isValidNumber, updateMetadata } from '../utils';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script>
import Vue from 'vue';
import _ from 'underscore';
const _ = girder._;

import ActiveLearningReviewCard from './ActiveLearningReviewCard.vue';
import ActiveLearningLabeling from '../ActiveLearningLabeling.vue';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@ export default Vue.extend({
return this.radius > 0 && this.magnification > 0;
}
},
mounted() {
this.certaintyChoice = this.certaintyMetrics[0];
this.featureChoice = this.featureShapes[0];
},
methods: {
generateInitialSuperpixels() {
this.backboneParent.generateInitialSuperpixels(
Expand All @@ -29,6 +25,24 @@ export default Vue.extend({
this.featureChoice
);
}
},
watch: {
certaintyMetrics: {
handler() {
if (this.certaintyChoice === '' && this.certaintyMetrics) {
this.certaintyChoice = this.certaintyMetrics[0];
}
},
immediate: true
},
featureShapes: {
handler() {
if (this.featureChoice === '' && this.featureShapes) {
this.featureChoice = this.featureShapes[0];
}
},
immediate: true
}
}
});
</script>
Expand Down
Loading