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