React UI: Semi-automatic segmentation (#1398)

* implemented checked

* Implemented plugin

* Added dialog windows

* Updated changelo

* Added cancel request
main
Boris Sekachev 6 years ago committed by GitHub
parent 5e21b4acce
commit a237c66474
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Dedicated message with clarifications when share is unmounted (https://github.com/opencv/cvat/pull/1373)
- Ability to create one tracked point (https://github.com/opencv/cvat/pull/1383)
- Tutorial: instructions for CVAT over HTTPS
- Added deep extreme cut (semi-automatic segmentation) to the new UI (https://github.com/opencv/cvat/pull/1398)
### Changed
- Increase preview size of a task till 256, 256 on the server

@ -33,6 +33,7 @@ export function checkPluginsAsync(): ThunkAction {
GIT_INTEGRATION: false,
TF_ANNOTATION: false,
TF_SEGMENTATION: false,
DEXTR_SEGMENTATION: false,
};
const promises: Promise<boolean>[] = [
@ -41,15 +42,12 @@ export function checkPluginsAsync(): ThunkAction {
PluginChecker.check(SupportedPlugins.GIT_INTEGRATION),
PluginChecker.check(SupportedPlugins.TF_ANNOTATION),
PluginChecker.check(SupportedPlugins.TF_SEGMENTATION),
PluginChecker.check(SupportedPlugins.DEXTR_SEGMENTATION),
];
const values = await Promise.all(promises);
[plugins.ANALYTICS] = values;
[, plugins.AUTO_ANNOTATION] = values;
[,, plugins.GIT_INTEGRATION] = values;
[,,, plugins.TF_ANNOTATION] = values;
[,,,, plugins.TF_SEGMENTATION] = values;
[plugins.ANALYTICS, plugins.AUTO_ANNOTATION, plugins.GIT_INTEGRATION,
plugins.TF_ANNOTATION, plugins.TF_SEGMENTATION, plugins.DEXTR_SEGMENTATION] = values;
dispatch(pluginActions.checkedAllPlugins(plugins));
};
}

@ -0,0 +1,90 @@
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
import React, { useState } from 'react';
import { connect } from 'react-redux';
import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox';
import Tooltip from 'antd/lib/tooltip';
import { Canvas } from 'cvat-canvas';
import { CombinedState } from 'reducers/interfaces';
import { activate as activatePlugin, deactivate as deactivatePlugin } from 'utils/dextr-utils';
interface StateToProps {
pluginEnabled: boolean;
canvasInstance: Canvas;
}
interface DispatchToProps {
activate(canvasInstance: Canvas): void;
deactivate(canvasInstance: Canvas): void;
}
function mapStateToProps(state: CombinedState): StateToProps {
const {
plugins: {
list,
},
annotation: {
canvas: {
instance: canvasInstance,
},
},
} = state;
return {
canvasInstance,
pluginEnabled: list.DEXTR_SEGMENTATION,
};
}
function mapDispatchToProps(): DispatchToProps {
return {
activate(canvasInstance: Canvas): void {
activatePlugin(canvasInstance);
},
deactivate(canvasInstance: Canvas): void {
deactivatePlugin(canvasInstance);
},
};
}
function DEXTRPlugin(props: StateToProps & DispatchToProps): JSX.Element | null {
const {
pluginEnabled,
canvasInstance,
activate,
deactivate,
} = props;
const [pluginActivated, setActivated] = useState(false);
return (
pluginEnabled ? (
<Tooltip title='Make AI polygon from at least 4 extreme points using deep extreme cut'>
<Checkbox
style={{ marginTop: 5 }}
checked={pluginActivated}
onChange={(event: CheckboxChangeEvent): void => {
setActivated(event.target.checked);
if (event.target.checked) {
activate(canvasInstance);
} else {
deactivate(canvasInstance);
}
}}
>
Make AI polygon
</Checkbox>
</Tooltip>
) : null
);
}
export default connect(
mapStateToProps,
mapDispatchToProps,
)(DEXTRPlugin);
// TODO: Add dialog window with cancel button

@ -3,7 +3,6 @@
// SPDX-License-Identifier: MIT
import React from 'react';
import { Row, Col } from 'antd/lib/grid';
import Select from 'antd/lib/select';
import Button from 'antd/lib/button';
@ -15,6 +14,7 @@ import Text from 'antd/lib/typography/Text';
import { RectDrawingMethod } from 'cvat-canvas';
import { ShapeType } from 'reducers/interfaces';
import { clamp } from 'utils/math';
import DEXTRPlugin from './dextr-plugin';
interface Props {
shapeType: ShapeType;
@ -81,6 +81,9 @@ function DrawShapePopoverComponent(props: Props): JSX.Element {
</Select>
</Col>
</Row>
{
shapeType === ShapeType.POLYGON && <DEXTRPlugin />
}
{
shapeType === ShapeType.RECTANGLE ? (
<>

@ -75,6 +75,7 @@ export enum SupportedPlugins {
AUTO_ANNOTATION = 'AUTO_ANNOTATION',
TF_ANNOTATION = 'TF_ANNOTATION',
TF_SEGMENTATION = 'TF_SEGMENTATION',
DEXTR_SEGMENTATION = 'DEXTR_SEGMENTATION',
ANALYTICS = 'ANALYTICS',
}

@ -4,6 +4,7 @@
import { PluginsActionTypes, PluginActions } from 'actions/plugins-actions';
import { registerGitPlugin } from 'utils/git-utils';
import { registerDEXTRPlugin } from 'utils/dextr-utils';
import {
PluginsState,
} from './interfaces';
@ -16,6 +17,7 @@ const defaultState: PluginsState = {
AUTO_ANNOTATION: false,
TF_ANNOTATION: false,
TF_SEGMENTATION: false,
DEXTR_SEGMENTATION: false,
ANALYTICS: false,
},
};
@ -39,6 +41,10 @@ export default function (
registerGitPlugin();
}
if (!state.list.DEXTR_SEGMENTATION && list.DEXTR_SEGMENTATION) {
registerDEXTRPlugin();
}
return {
...state,
initialized: true,

@ -0,0 +1,254 @@
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
import getCore from 'cvat-core';
import { Canvas } from 'cvat-canvas';
import { ShapeType, RQStatus } from 'reducers/interfaces';
const core = getCore();
const baseURL = core.config.backendAPI.slice(0, -7);
interface DEXTRPlugin {
name: string;
description: string;
cvat: {
classes: {
Job: {
prototype: {
annotations: {
put: {
enter(self: any, objects: any[]): Promise<void>;
};
};
};
};
};
};
data: {
canceled: boolean;
enabled: boolean;
};
}
interface Point {
x: number;
y: number;
}
const antModalRoot = document.createElement('div');
const antModalMask = document.createElement('div');
antModalMask.classList.add('ant-modal-mask');
const antModalWrap = document.createElement('div');
antModalWrap.classList.add('ant-modal-wrap');
antModalWrap.setAttribute('role', 'dialog');
const antModal = document.createElement('div');
antModal.classList.add('ant-modal');
antModal.style.width = '300px';
antModal.style.top = '40%';
antModal.setAttribute('role', 'document');
const antModalContent = document.createElement('div');
antModalContent.classList.add('ant-modal-content');
const antModalBody = document.createElement('div');
antModalBody.classList.add('ant-modal-body');
antModalBody.style.textAlign = 'center';
const antModalSpan = document.createElement('span');
antModalSpan.innerText = 'Segmentation request is being processed';
antModalSpan.style.display = 'block';
const antModalButton = document.createElement('button');
antModalButton.disabled = true;
antModalButton.classList.add('ant-btn', 'ant-btn-primary');
antModalButton.style.width = '100px';
antModalButton.style.margin = '10px auto';
const antModalButtonSpan = document.createElement('span');
antModalButtonSpan.innerText = 'Cancel';
antModalBody.append(antModalSpan, antModalButton);
antModalButton.append(antModalButtonSpan);
antModalContent.append(antModalBody);
antModal.append(antModalContent);
antModalWrap.append(antModal);
antModalRoot.append(antModalMask, antModalWrap);
function serverRequest(
plugin: DEXTRPlugin,
jid: number,
frame: number,
points: number[],
): Promise<number[]> {
return new Promise((resolve, reject) => {
const reducer = (acc: Point[], _: number, index: number, array: number[]): Point[] => {
if (!(index % 2)) { // 0, 2, 4
acc.push({
x: array[index],
y: array[index + 1],
});
}
return acc;
};
const reducedPoints = points.reduce(reducer, []);
core.server.request(
`${baseURL}/dextr/create/${jid}`, {
method: 'POST',
data: JSON.stringify({
frame,
points: reducedPoints,
}),
headers: {
'Content-Type': 'application/json',
},
},
).then(() => {
const timeoutCallback = (): void => {
core.server.request(
`${baseURL}/dextr/check/${jid}`, {
method: 'GET',
},
).then((response: any) => {
const { status } = response;
if (status === RQStatus.finished) {
resolve(response.result.split(/\s|,/).map((coord: string) => +coord));
} else if (status === RQStatus.failed) {
reject(new Error(response.stderr));
} else if (status === RQStatus.unknown) {
reject(new Error('Unknown DEXTR status has been received'));
} else {
if (status === RQStatus.queued) {
antModalButton.disabled = false;
}
if (!plugin.data.canceled) {
setTimeout(timeoutCallback, 1000);
} else {
core.server.request(
`${baseURL}/dextr/cancel/${jid}`, {
method: 'GET',
},
).then(() => {
resolve(points);
}).catch((error: Error) => {
reject(error);
});
}
}
}).catch((error: Error) => {
reject(error);
});
};
setTimeout(timeoutCallback, 1000);
}).catch((error: Error) => {
reject(error);
});
});
// start checking
}
const plugin: DEXTRPlugin = {
name: 'Deep extreme cut',
description: 'Plugin allows to get a polygon from extreme points using AI',
cvat: {
classes: {
Job: {
prototype: {
annotations: {
put: {
async enter(self: DEXTRPlugin, objects: any[]): Promise<void> {
try {
if (self.data.enabled) {
document.body.append(antModalRoot);
const promises: Record<number, Promise<number[]>> = {};
for (let i = 0; i < objects.length; i++) {
if (objects[i].points.length >= 8) {
promises[i] = serverRequest(
self,
(this as any).id,
objects[i].frame,
objects[i].points,
);
} else {
promises[i] = new Promise((resolve) => {
resolve(objects[i].points);
});
}
}
const transformed = await Promise
.all(Object.values(promises));
for (let i = 0; i < objects.length; i++) {
// eslint-disable-next-line no-param-reassign
objects[i] = new core.classes.ObjectState({
frame: objects[i].frame,
objectType: objects[i].objectType,
label: objects[i].label,
shapeType: ShapeType.POLYGON,
points: transformed[i],
occluded: objects[i].occluded,
zOrder: objects[i].zOrder,
});
}
}
return;
} catch (error) {
throw new core.exceptions.PluginError(error.toString());
} finally {
// eslint-disable-next-line no-param-reassign
self.data.canceled = false;
antModalButton.disabled = true;
document.body.removeChild(antModalRoot);
}
},
},
},
},
},
},
},
data: {
canceled: false,
enabled: false,
},
};
antModalButton.onclick = () => {
plugin.data.canceled = true;
};
export function activate(canvasInstance: Canvas): void {
if (!plugin.data.enabled) {
// eslint-disable-next-line no-param-reassign
canvasInstance.draw = (drawData: any): void => {
if (drawData.enabled && drawData.shapeType === ShapeType.POLYGON
&& (typeof (drawData.numberOfPoints) === 'undefined' || drawData.numberOfPoints >= 4)
&& (typeof (drawData.initialState) === 'undefined')
) {
const patchedData = { ...drawData };
patchedData.shapeType = ShapeType.POINTS;
patchedData.crosshair = true;
Object.getPrototypeOf(canvasInstance)
.draw.call(canvasInstance, patchedData);
} else {
Object.getPrototypeOf(canvasInstance)
.draw.call(canvasInstance, drawData);
}
};
plugin.data.enabled = true;
}
}
export function deactivate(canvasInstance: Canvas): void {
if (plugin.data.enabled) {
// eslint-disable-next-line no-param-reassign
canvasInstance.draw = Object.getPrototypeOf(canvasInstance).draw;
plugin.data.enabled = false;
}
}
export function registerDEXTRPlugin(): void {
core.plugins.register(plugin);
}

@ -35,6 +35,9 @@ class PluginChecker {
case SupportedPlugins.TF_SEGMENTATION: {
return isReachable(`${serverHost}/tensorflow/segmentation/meta/get`, 'OPTIONS');
}
case SupportedPlugins.DEXTR_SEGMENTATION: {
return isReachable(`${serverHost}/dextr/enabled`, 'GET');
}
case SupportedPlugins.ANALYTICS: {
return isReachable(`${serverHost}/analytics/app/kibana`, 'GET');
}

@ -8,5 +8,6 @@ from . import views
urlpatterns = [
path('create/<int:jid>', views.create),
path('cancel/<int:jid>', views.cancel),
path('check/<int:jid>', views.check)
]
path('check/<int:jid>', views.check),
path('enabled', views.enabled)
]

@ -123,3 +123,6 @@ def check(request, jid):
except Exception as ex:
slogger.job[jid].error("can't check a dextr request for the job {}".format(jid), exc_info=True)
return HttpResponseBadRequest(str(ex))
def enabled(request):
return HttpResponse()

Loading…
Cancel
Save