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
parent
ffb71fb7a2
commit
bd143853a5
@ -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>
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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);
|
|
||||||
Loading…
Reference in New Issue