Running detectors on the fly (#2102)

* Draft version

* Removed extra file

* Removed extra code

* Updated icon: magic wand

* Ctrl modifier, fixed some cases when interaction event isn't raised

* Added tooltip description of an interactor

* Locking UI while server fetching

* Removing old code & refactoring

* Fixed couple of bugs

* Updated CHANGELOG.md, updated versions

* Update crosshair.ts

* Minor fixes

* Fixed eslint issues

* Prevent default action

* Added minNegVertices=0 by default, ignored negative points for dextr, fixed context menu in some cases

* On the fly annotations draft

* Initial version of FBRS interactive segmentation

* Fix fbrs model_handler

* Fixed couple of minor bugs

* Added ability to interrupt interaction

* Do not show reid on annotation view

* Prettified UI

* Updated changelog, increased version

* Removed extra files

* Removed extra code

* Fixed changelog

Co-authored-by: Nikita Manovich <nikita.manovich@intel.com>
main
Boris Sekachev 6 years ago committed by GitHub
parent ffb71fb7a2
commit bd143853a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added password reset functionality (<https://github.com/opencv/cvat/pull/2058>)
- Ability to work with data on the fly (https://github.com/opencv/cvat/pull/2007)
- Annotation in process outline color wheel (<https://github.com/opencv/cvat/pull/2084>)
- On the fly annotation using DL detectors (<https://github.com/opencv/cvat/pull/2102>)
- [Datumaro] CLI command for dataset equality comparison (<https://github.com/opencv/cvat/pull/1989>)
- [Datumaro] Merging of datasets with different labels (<https://github.com/opencv/cvat/pull/2098>)

@ -1,6 +1,6 @@
{
"name": "cvat-ui",
"version": "1.9.1",
"version": "1.9.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

@ -1,6 +1,6 @@
{
"name": "cvat-ui",
"version": "1.9.1",
"version": "1.9.2",
"description": "CVAT single-page application",
"main": "src/index.tsx",
"scripts": {

@ -10,6 +10,7 @@ import Select, { OptionProps } from 'antd/lib/select';
import Button from 'antd/lib/button';
import Modal from 'antd/lib/modal';
import Text from 'antd/lib/typography/Text';
import Tabs from 'antd/lib/tabs';
import { Row, Col } from 'antd/lib/grid';
import notification from 'antd/lib/notification';
@ -23,8 +24,14 @@ import {
ObjectType,
ShapeType,
} from 'reducers/interfaces';
import { interactWithCanvas, fetchAnnotationsAsync, updateAnnotationsAsync } from 'actions/annotation-actions';
import {
interactWithCanvas,
fetchAnnotationsAsync,
updateAnnotationsAsync,
createAnnotationsAsync,
} from 'actions/annotation-actions';
import { InteractionResult } from 'cvat-canvas/src/typescript/canvas';
import DetectorRunner from 'components/model-runner-modal/detector-runner';
interface StateToProps {
canvasInstance: Canvas;
@ -35,11 +42,13 @@ interface StateToProps {
isInteraction: boolean;
frame: number;
interactors: Model[];
detectors: Model[];
}
interface DispatchToProps {
onInteractionStart(activeInteractor: Model, activeLabelID: number): void;
updateAnnotations(statesToUpdate: any[]): void;
createAnnotations(sessionInstance: any, frame: number, statesToCreate: any[]): void;
fetchAnnotations(): void;
}
@ -51,10 +60,11 @@ function mapStateToProps(state: CombinedState): StateToProps {
const { instance: jobInstance } = annotation.job;
const { instance: canvasInstance, activeControl } = annotation.canvas;
const { models } = state;
const { interactors } = models;
const { interactors, detectors } = models;
return {
interactors,
detectors,
isInteraction: activeControl === ActiveControl.INTERACTION,
activeLabelID: annotation.drawing.activeLabelID,
labels: annotation.job.labels,
@ -65,19 +75,12 @@ function mapStateToProps(state: CombinedState): StateToProps {
};
}
function mapDispatchToProps(dispatch: any): DispatchToProps {
return {
onInteractionStart(activeInteractor: Model, activeLabelID: number): void {
dispatch(interactWithCanvas(activeInteractor, activeLabelID));
},
updateAnnotations(statesToUpdate: any[]): void {
dispatch(updateAnnotationsAsync(statesToUpdate));
},
fetchAnnotations(): void {
dispatch(fetchAnnotationsAsync());
},
};
}
const mapDispatchToProps = {
onInteractionStart: interactWithCanvas,
updateAnnotations: updateAnnotationsAsync,
fetchAnnotations: fetchAnnotationsAsync,
createAnnotations: createAnnotationsAsync,
};
function convertShapesForInteractor(shapes: InteractionResult[]): number[][] {
const reducer = (acc: number[][], _: number, index: number, array: number[]): number[][] => {
@ -378,9 +381,10 @@ class ToolsControlComponent extends React.PureComponent<Props, State> {
</Select>
</Col>
</Row>
<Row type='flex' align='middle' justify='center'>
<Col offset={4} span={16}>
<Row type='flex' align='middle' justify='end'>
<Col>
<Button
type='primary'
loading={fetching}
className='cvat-tools-interact-button'
disabled={!activeInteractor || fetching}
@ -405,6 +409,60 @@ class ToolsControlComponent extends React.PureComponent<Props, State> {
);
}
private renderDetectorBlock(): JSX.Element {
const {
jobInstance,
detectors,
frame,
fetchAnnotations,
} = this.props;
return (
<DetectorRunner
withCleanup={false}
models={detectors}
task={jobInstance.task}
runInference={async (task: any, model: Model, body: object) => {
try {
this.setState({ fetching: true });
const result = await core.lambda.call(task, model, {
...body,
frame,
});
const states = result
.map((data: any): any => (
new core.classes.ObjectState({
shapeType: data.type,
label: task.labels
.filter(
(label: any): boolean => label.name === data.label,
)[0],
points: data.points,
objectType: ObjectType.SHAPE,
frame,
occluded: false,
source: 'auto',
attributes: {},
zOrder: 0, // TODO: get current z order
})
));
await jobInstance.annotations.put(states);
fetchAnnotations();
} catch (error) {
notification.error({
description: error.toString(),
message: 'Detection error occured',
});
} finally {
this.setState({ fetching: false });
}
}}
/>
);
}
private renderPopoverContent(): JSX.Element {
return (
<div className='cvat-tools-control-popover-content'>
@ -413,8 +471,15 @@ class ToolsControlComponent extends React.PureComponent<Props, State> {
<Text className='cvat-text-color' strong>AI Tools</Text>
</Col>
</Row>
{ this.renderLabelBlock() }
{ this.renderInteractorBlock() }
<Tabs>
<Tabs.TabPane key='interactors' tab='Interactors'>
{ this.renderLabelBlock() }
{ this.renderInteractorBlock() }
</Tabs.TabPane>
<Tabs.TabPane key='detectors' tab='Detectors'>
{ this.renderDetectorBlock() }
</Tabs.TabPane>
</Tabs>
</div>
);
}
@ -443,7 +508,7 @@ class ToolsControlComponent extends React.PureComponent<Props, State> {
return (
<>
<Modal
title='Interaction request'
title='Making a server request'
zIndex={Number.MAX_SAFE_INTEGER}
visible={fetching}
closable={false}

@ -102,10 +102,10 @@
}
.cvat-tools-control-popover-content {
width: 350px;
padding: 10px;
border-radius: 5px;
background: $background-color-2;
width: 270px;
}
.cvat-draw-shape-popover-content {

@ -0,0 +1,319 @@
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
import './styles.scss';
import React, { useState } from 'react';
import { Row, Col } from 'antd/lib/grid';
import Icon from 'antd/lib/icon';
import Select, { OptionProps } from 'antd/lib/select';
import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox';
import Tooltip from 'antd/lib/tooltip';
import Tag from 'antd/lib/tag';
import notification from 'antd/lib/notification';
import Text from 'antd/lib/typography/Text';
import InputNumber from 'antd/lib/input-number';
import { Model, StringObject } from 'reducers/interfaces';
import Button from 'antd/lib/button';
function colorGenerator(): () => string {
const values = [
'magenta', 'green', 'geekblue',
'orange', 'red', 'cyan',
'blue', 'volcano', 'purple',
];
let index = 0;
return (): string => {
const color = values[index++];
if (index >= values.length) {
index = 0;
}
return color;
};
}
const nextColor = colorGenerator();
interface Props {
withCleanup: boolean;
models: Model[];
task: any;
runInference(task: any, model: Model, body: object): void;
}
function DetectorRunner(props: Props): JSX.Element {
const {
task,
models,
withCleanup,
runInference,
} = props;
const [modelID, setModelID] = useState<string | null>(null);
const [mapping, setMapping] = useState<StringObject>({});
const [colors, setColors] = useState<StringObject>({});
const [threshold, setThreshold] = useState<number>(0.5);
const [distance, setDistance] = useState<number>(50);
const [cleanup, setCleanup] = useState<boolean>(false);
const [match, setMatch] = useState<{
model: string | null;
task: string | null;
}>({
model: null,
task: null,
});
const model = models.filter((_model): boolean => _model.id === modelID)[0];
const isDetector = model && model.type === 'detector';
const isReId = model && model.type === 'reid';
const buttonEnabled = model && (model.type === 'reid' || (
model.type === 'detector' && !!Object.keys(mapping).length
));
const modelLabels = (isDetector ? model.labels : [])
.filter((_label: string): boolean => !(_label in mapping));
const taskLabels = (isDetector && !!task
? task.labels.map((label: any): string => label.name) : []
);
if (model && model.type !== 'reid' && !model.labels.length) {
notification.warning({
message: 'The selected model does not include any lables',
});
}
function updateMatch(modelLabel: string | null, taskLabel: string | null): void {
if (match.model && taskLabel) {
const newmatch: { [index: string]: string } = {};
const newcolor: { [index: string]: string } = {};
newmatch[match.model] = taskLabel;
newcolor[match.model] = nextColor();
setColors({ ...colors, ...newcolor });
setMapping({ ...mapping, ...newmatch });
setMatch({ model: null, task: null });
return;
}
if (match.task && modelLabel) {
const newmatch: { [index: string]: string } = {};
const newcolor: { [index: string]: string } = {};
newmatch[modelLabel] = match.task;
newcolor[modelLabel] = nextColor();
setColors({ ...colors, ...newcolor });
setMapping({ ...mapping, ...newmatch });
setMatch({ model: null, task: null });
return;
}
setMatch({
model: modelLabel,
task: taskLabel,
});
}
function renderSelector(
value: string,
tooltip: string,
labels: string[],
onChange: (label: string) => void,
): JSX.Element {
return (
<Tooltip title={tooltip}>
<Select
value={value}
onChange={onChange}
style={{ width: '100%' }}
showSearch
filterOption={(input: string, option: React.ReactElement<OptionProps>) => {
const { children } = option.props;
if (typeof (children) === 'string') {
return children.toLowerCase().includes(input.toLowerCase());
}
return false;
}}
>
{ labels.map((label: string): JSX.Element => (
<Select.Option key={label}>{label}</Select.Option>
)) }
</Select>
</Tooltip>
);
}
return (
<div className='cvat-run-model-content'>
<Row type='flex' align='middle'>
<Col span={4}>Model:</Col>
<Col span={20}>
<Select
placeholder='Select a model'
style={{ width: '100%' }}
onChange={(_modelID: string): void => {
const newmodel = models
.filter((_model): boolean => _model.id === _modelID)[0];
const newcolors: StringObject = {};
const newmapping = task.labels
.reduce((acc: StringObject, label: any): StringObject => {
if (newmodel.labels.includes(label.name)) {
acc[label.name] = label.name;
newcolors[label.name] = nextColor();
}
return acc;
}, {});
setMapping(newmapping);
setColors(newcolors);
setMatch({ model: null, task: null });
setModelID(_modelID);
}}
>
{models.map((_model: Model): JSX.Element => (
<Select.Option key={_model.id}>{_model.name}</Select.Option>
))}
</Select>
</Col>
</Row>
{ isDetector && !!Object.keys(mapping).length && (
Object.keys(mapping).map((modelLabel: string) => (
<Row key={modelLabel} type='flex' justify='start' align='middle'>
<Col span={10}>
<Tag color={colors[modelLabel]}>{modelLabel}</Tag>
</Col>
<Col span={10} offset={1}>
<Tag color={colors[modelLabel]}>{mapping[modelLabel]}</Tag>
</Col>
<Col offset={1}>
<Tooltip title='Remove the mapped values' mouseLeaveDelay={0}>
<Icon
className='cvat-danger-circle-icon'
type='close-circle'
onClick={(): void => {
const newmapping = { ...mapping };
delete newmapping[modelLabel];
setMapping(newmapping);
}}
/>
</Tooltip>
</Col>
</Row>
))
)}
{ isDetector && !!taskLabels.length && !!modelLabels.length && (
<>
<Row type='flex' justify='start' align='middle'>
<Col span={10}>
{renderSelector(
match.model || '',
'Model labels',
modelLabels,
(modelLabel: string) => updateMatch(modelLabel, null),
)}
</Col>
<Col span={10} offset={1}>
{renderSelector(
match.task || '',
'Task labels',
taskLabels,
(taskLabel: string) => updateMatch(null, taskLabel),
)}
</Col>
<Col span={1} offset={1}>
<Tooltip title='Specify a label mapping between model labels and task labels' mouseLeaveDelay={0}>
<Icon className='cvat-info-circle-icon' type='question-circle' />
</Tooltip>
</Col>
</Row>
</>
)}
{ isDetector && withCleanup && (
<div>
<Checkbox
checked={cleanup}
onChange={(e: CheckboxChangeEvent): void => setCleanup(e.target.checked)}
>
Clean old annotations
</Checkbox>
</div>
)}
{ isReId && (
<div>
<Row type='flex' align='middle' justify='start'>
<Col>
<Text>Threshold</Text>
</Col>
<Col offset={1}>
<Tooltip title='Minimum similarity value for shapes that can be merged'>
<InputNumber
min={0.01}
step={0.01}
max={1}
value={threshold}
onChange={(value: number | undefined) => {
if (typeof (value) === 'number') {
setThreshold(value);
}
}}
/>
</Tooltip>
</Col>
</Row>
<Row type='flex' align='middle' justify='start'>
<Col>
<Text>Maximum distance</Text>
</Col>
<Col offset={1}>
<Tooltip title='Maximum distance between shapes that can be merged'>
<InputNumber
placeholder='Threshold'
min={1}
value={distance}
onChange={(value: number | undefined) => {
if (typeof (value) === 'number') {
setDistance(value);
}
}}
/>
</Tooltip>
</Col>
</Row>
</div>
) }
<Row type='flex' align='middle' justify='end'>
<Col>
<Button
disabled={!buttonEnabled}
type='primary'
onClick={() => {
runInference(
task,
model,
model.type === 'detector' ? { mapping, cleanup } : {
threshold,
max_distance: distance,
},
);
}}
>
Annotate
</Button>
</Col>
</Row>
</div>
);
}
export default React.memo(DetectorRunner, (prevProps: Props, nextProps: Props): boolean => (
prevProps.task === nextProps.task
&& prevProps.runInference === nextProps.runInference
&& prevProps.models.length === nextProps.models.length
&& nextProps.models.reduce((acc: boolean, model: Model, index: number): boolean => (
acc && model.id === prevProps.models[index].id
), true)
));

@ -0,0 +1,88 @@
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
import './styles.scss';
import React from 'react';
import { connect } from 'react-redux';
import Modal from 'antd/lib/modal';
import { ThunkDispatch } from 'utils/redux';
import { modelsActions, startInferenceAsync } from 'actions/models-actions';
import { Model, CombinedState } from 'reducers/interfaces';
import DetectorRunner from './detector-runner';
interface StateToProps {
visible: boolean;
task: any;
detectors: Model[];
reid: Model[];
}
interface DispatchToProps {
runInference(task: any, model: Model, body: object): void;
closeDialog(): void;
}
function mapStateToProps(state: CombinedState): StateToProps {
const { models } = state;
const { detectors, reid } = models;
return {
visible: models.visibleRunWindows,
task: models.activeRunTask,
reid,
detectors,
};
}
function mapDispatchToProps(dispatch: ThunkDispatch): DispatchToProps {
return {
runInference(task: any, model: Model, body: object) {
dispatch(startInferenceAsync(task, model, body));
},
closeDialog() {
dispatch(modelsActions.closeRunModelDialog());
},
};
}
function ModelRunnerDialog(props: StateToProps & DispatchToProps): JSX.Element {
const {
reid,
detectors,
task,
visible,
runInference,
closeDialog,
} = props;
const models = [...reid, ...detectors];
return (
<Modal
destroyOnClose
visible={visible}
footer={[]}
onCancel={(): void => closeDialog()}
maskClosable
title='Automatic annotation'
>
<DetectorRunner
withCleanup
models={models}
task={task}
runInference={(...args) => {
closeDialog();
runInference(...args);
}}
/>
</Modal>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps,
)(ModelRunnerDialog);

@ -1,450 +0,0 @@
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
import './styles.scss';
import React from 'react';
import { Row, Col } from 'antd/lib/grid';
import Icon from 'antd/lib/icon';
import Select from 'antd/lib/select';
import Checkbox from 'antd/lib/checkbox';
import Tooltip from 'antd/lib/tooltip';
import Modal from 'antd/lib/modal';
import Tag from 'antd/lib/tag';
import notification from 'antd/lib/notification';
import Text from 'antd/lib/typography/Text';
import InputNumber from 'antd/lib/input-number';
import {
Model,
StringObject,
} from 'reducers/interfaces';
interface Props {
reid: Model[];
detectors: Model[];
activeProcesses: StringObject;
visible: boolean;
taskInstance: any;
closeDialog(): void;
runInference(
taskInstance: any,
model: Model,
body: object,
): void;
}
interface State {
selectedModel: string | null;
cleanup: boolean;
mapping: StringObject;
colors: StringObject;
matching: {
model: string;
task: string;
};
threshold: number;
maxDistance: number;
}
function colorGenerator(): () => string {
const values = [
'magenta', 'green', 'geekblue',
'orange', 'red', 'cyan',
'blue', 'volcano', 'purple',
];
let index = 0;
return (): string => {
const color = values[index++];
if (index >= values.length) {
index = 0;
}
return color;
};
}
const nextColor = colorGenerator();
export default class ModelRunnerModalComponent extends React.PureComponent<Props, State> {
public constructor(props: Props) {
super(props);
this.state = {
selectedModel: null,
mapping: {},
colors: {},
cleanup: false,
matching: {
model: '',
task: '',
},
threshold: 0.5,
maxDistance: 50,
};
}
public componentDidUpdate(prevProps: Props, prevState: State): void {
const {
reid,
detectors,
taskInstance,
visible,
} = this.props;
const { selectedModel } = this.state;
const models = [...reid, ...detectors];
if (!prevProps.visible && visible) {
this.setState({
selectedModel: null,
mapping: {},
matching: {
model: '',
task: '',
},
cleanup: false,
});
}
if (selectedModel && prevState.selectedModel !== selectedModel) {
const selectedModelInstance = models
.filter((model) => model.name === selectedModel)[0];
if (selectedModelInstance.type !== 'reid' && !selectedModelInstance.labels.length) {
notification.warning({
message: 'The selected model does not include any lables',
});
}
let taskLabels: string[] = taskInstance.labels
.map((label: any): string => label.name);
const [defaultMapping, defaultColors]: StringObject[] = selectedModelInstance.labels
.reduce((acc: StringObject[], label): StringObject[] => {
if (taskLabels.includes(label)) {
acc[0][label] = label;
acc[1][label] = nextColor();
taskLabels = taskLabels.filter((_label): boolean => _label !== label);
}
return acc;
}, [{}, {}]);
this.setState({
mapping: defaultMapping,
colors: defaultColors,
});
}
}
private renderModelSelector(): JSX.Element {
const { reid, detectors } = this.props;
const models = [...reid, ...detectors];
return (
<Row type='flex' align='middle'>
<Col span={4}>Model:</Col>
<Col span={19}>
<Select
placeholder='Select a model'
style={{ width: '100%' }}
onChange={(value: string): void => this.setState({
selectedModel: value,
mapping: {},
})}
>
{models.map((model): JSX.Element => (
<Select.Option key={model.name}>
{model.name}
</Select.Option>
))}
</Select>
</Col>
</Row>
);
}
private renderMappingTag(modelLabel: string, taskLabel: string): JSX.Element {
const { colors, mapping } = this.state;
return (
<Row key={`${modelLabel}-${taskLabel}`} type='flex' justify='start' align='middle'>
<Col span={10}>
<Tag color={colors[modelLabel]}>{modelLabel}</Tag>
</Col>
<Col span={10} offset={1}>
<Tag color={colors[modelLabel]}>{taskLabel}</Tag>
</Col>
<Col span={1} offset={1}>
<Tooltip title='Remove the mapped values' mouseLeaveDelay={0}>
<Icon
className='cvat-run-model-dialog-remove-mapping-icon'
type='close-circle'
onClick={(): void => {
const newMapping = { ...mapping };
delete newMapping[modelLabel];
this.setState({
mapping: newMapping,
});
}}
/>
</Tooltip>
</Col>
</Row>
);
}
private renderMappingInputSelector(
value: string,
current: string,
options: string[],
): JSX.Element {
const { matching, mapping, colors } = this.state;
return (
<Select
value={value}
placeholder={`${current} labels`}
style={{ width: '100%' }}
onChange={(selectedValue: string): void => {
const anotherValue = current === 'Model'
? matching.task : matching.model;
if (!anotherValue) {
const newMatching = { ...matching };
if (current === 'Model') {
newMatching.model = selectedValue;
} else {
newMatching.task = selectedValue;
}
this.setState({
matching: newMatching,
});
} else {
const newColors = { ...colors };
const newMapping = { ...mapping };
if (current === 'Model') {
newColors[selectedValue] = nextColor();
newMapping[selectedValue] = anotherValue;
} else {
newColors[anotherValue] = nextColor();
newMapping[anotherValue] = selectedValue;
}
this.setState({
colors: newColors,
mapping: newMapping,
matching: {
task: '',
model: '',
},
});
}
}}
>
{options.map((label: string): JSX.Element => (
<Select.Option key={label}>
{label}
</Select.Option>
))}
</Select>
);
}
private renderMappingInput(
availableModelLabels: string[],
availableTaskLabels: string[],
): JSX.Element {
const { matching } = this.state;
return (
<Row type='flex' justify='start' align='middle'>
<Col span={10}>
{this.renderMappingInputSelector(
matching.model,
'Model',
availableModelLabels,
)}
</Col>
<Col span={10} offset={1}>
{this.renderMappingInputSelector(
matching.task,
'Task',
availableTaskLabels,
)}
</Col>
<Col span={1} offset={1}>
<Tooltip title='Specify a label mapping between model labels and task labels' mouseLeaveDelay={0}>
<Icon className='cvat-info-circle-icon' type='question-circle' />
</Tooltip>
</Col>
</Row>
);
}
private renderReidContent(): JSX.Element {
const { threshold, maxDistance } = this.state;
return (
<div>
<Row type='flex' align='middle' justify='start'>
<Col>
<Text>Threshold</Text>
</Col>
<Col offset={1}>
<Tooltip title='Minimum similarity value for shapes that can be merged'>
<InputNumber
min={0.01}
step={0.01}
max={1}
value={threshold}
onChange={(value: number | undefined) => {
if (typeof (value) === 'number') {
this.setState({
threshold: value,
});
}
}}
/>
</Tooltip>
</Col>
</Row>
<Row type='flex' align='middle' justify='start'>
<Col>
<Text>Maximum distance</Text>
</Col>
<Col offset={1}>
<Tooltip title='Maximum distance between shapes that can be merged'>
<InputNumber
placeholder='Threshold'
min={1}
value={maxDistance}
onChange={(value: number | undefined) => {
if (typeof (value) === 'number') {
this.setState({
maxDistance: value,
});
}
}}
/>
</Tooltip>
</Col>
</Row>
</div>
);
}
private renderContent(): JSX.Element {
const { selectedModel, cleanup, mapping } = this.state;
const { reid, detectors, taskInstance } = this.props;
const models = [...reid, ...detectors];
const model = selectedModel && models
.filter((_model): boolean => _model.name === selectedModel)[0];
const excludedModelLabels: string[] = Object.keys(mapping);
const isDetector = model && model.type === 'detector';
const isReId = model && model.type === 'reid';
const tags = isDetector ? excludedModelLabels
.map((modelLabel: string) => this.renderMappingTag(
modelLabel,
mapping[modelLabel],
)) : [];
const availableModelLabels = model ? model.labels
.filter(
(label: string) => !excludedModelLabels.includes(label),
) : [];
const taskLabels = taskInstance.labels.map(
(label: any) => label.name,
);
const mappingISAvailable = !!availableModelLabels.length
&& !!taskLabels.length;
return (
<div className='cvat-run-model-dialog'>
{ this.renderModelSelector() }
{ isDetector && tags}
{ isDetector
&& mappingISAvailable
&& this.renderMappingInput(availableModelLabels, taskLabels)}
{ isDetector
&& (
<div>
<Checkbox
checked={cleanup}
onChange={(e: any): void => this.setState({
cleanup: e.target.checked,
})}
>
Clean old annotations
</Checkbox>
</div>
)}
{ isReId && this.renderReidContent() }
</div>
);
}
public render(): JSX.Element | false {
const {
selectedModel,
mapping,
cleanup,
threshold,
maxDistance,
} = this.state;
const {
reid,
detectors,
visible,
taskInstance,
runInference,
closeDialog,
} = this.props;
const models = [...reid, ...detectors];
const activeModel = models.filter(
(model): boolean => model.name === selectedModel,
)[0];
const enabledSubmit = !!activeModel && (activeModel.type === 'reid'
|| !!Object.keys(mapping).length);
return (
visible && (
<Modal
closable={false}
okType='primary'
okText='Submit'
onOk={(): void => {
runInference(
taskInstance,
models
.filter((model): boolean => model.name === selectedModel)[0],
activeModel.type === 'detector' ? {
mapping,
cleanup,
} : {
threshold,
max_distance: maxDistance,
},
);
closeDialog();
}}
onCancel={(): void => closeDialog()}
okButtonProps={{ disabled: !enabledSubmit }}
title='Automatic annotation'
visible
>
{ this.renderContent() }
</Modal>
)
);
}
}

@ -4,10 +4,10 @@
@import '../../base.scss';
.cvat-run-model-dialog > div:not(first-child) {
.cvat-run-model-content > div:not(first-child) {
margin-top: 10px;
}
.cvat-run-model-dialog-remove-mapping-icon {
.cvat-run-model-content-remove-mapping-icon {
color: $danger-icon-color;
}

@ -12,7 +12,7 @@ import Result from 'antd/lib/result';
import DetailsContainer from 'containers/task-page/details';
import JobListContainer from 'containers/task-page/job-list';
import ModelRunnerModalContainer from 'containers/model-runner-dialog/model-runner-dialog';
import ModelRunnerModal from 'components/model-runner-modal/model-runner-dialog';
import { Task } from 'reducers/interfaces';
import TopBarComponent from './top-bar';
@ -75,7 +75,7 @@ class TaskPageComponent extends React.PureComponent<Props> {
<JobListContainer task={(task as Task)} />
</Col>
</Row>
<ModelRunnerModalContainer />
<ModelRunnerModal />
</>
);
}

@ -6,7 +6,7 @@ import React from 'react';
import { Row, Col } from 'antd/lib/grid';
import Pagination from 'antd/lib/pagination';
import ModelRunnerModalContainer from 'containers/model-runner-dialog/model-runner-dialog';
import ModelRunnerModal from 'components/model-runner-modal/model-runner-dialog';
import TaskItem from 'containers/tasks-page/task-item';
export interface ContentListProps {
@ -46,7 +46,7 @@ export default function TaskListComponent(props: ContentListProps): JSX.Element
/>
</Col>
</Row>
<ModelRunnerModalContainer />
<ModelRunnerModal />
</>
);
}

@ -1,60 +0,0 @@
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
import { connect } from 'react-redux';
import ModelRunnerModalComponent from 'components/model-runner-modal/model-runner-modal';
import { Model, CombinedState } from 'reducers/interfaces';
import { startInferenceAsync, modelsActions } from 'actions/models-actions';
interface StateToProps {
reid: Model[];
detectors: Model[];
activeProcesses: {
[index: string]: string;
};
taskInstance: any;
visible: boolean;
}
interface DispatchToProps {
runInference(
taskInstance: any,
model: Model,
body: object,
): void;
closeDialog(): void;
}
function mapStateToProps(state: CombinedState): StateToProps {
const { models } = state;
return {
reid: models.reid,
detectors: models.detectors,
activeProcesses: {},
taskInstance: models.activeRunTask,
visible: models.visibleRunWindows,
};
}
function mapDispatchToProps(dispatch: any): DispatchToProps {
return ({
runInference(
taskInstance: any,
model: Model,
body: object,
): void {
dispatch(startInferenceAsync(taskInstance, model, body));
},
closeDialog(): void {
dispatch(modelsActions.closeRunModelDialog());
},
});
}
export default connect(
mapStateToProps,
mapDispatchToProps,
)(ModelRunnerModalComponent);

@ -43,6 +43,10 @@ hr {
color: $info-icon-color;
}
.cvat-danger-circle-icon {
color: $danger-icon-color;
}
#root {
width: 100%;
height: 100%;

Loading…
Cancel
Save