Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added intelligent function when paste labels to another task #4161

Merged
merged 14 commits into from
Jan 17, 2022
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Order in an annotation file(<https://github.com/openvinotoolkit/cvat/pull/4087>)
- Fixed task data upload progressbar (<https://github.com/openvinotoolkit/cvat/pull/4134>)
- Email in org invitations is case sensitive (<https://github.com/openvinotoolkit/cvat/pull/4153>)
- Added intelligent function when paste labels to another task (<https://github.com/openvinotoolkit/cvat/pull/4161>)
- Bug: canvas is busy when start playing, start resizing a shape and do not release the mouse cursor (<https://github.com/openvinotoolkit/cvat/pull/4151>)


### Security
- Updated ELK to 6.8.22 which uses log4j 2.17.0 (<https://github.com/openvinotoolkit/cvat/pull/4052>)

Expand Down
3 changes: 1 addition & 2 deletions cvat-core/src/session.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 2019-2021 Intel Corporation
// Copyright (C) 2019-2022 Intel Corporation
//
// SPDX-License-Identifier: MIT

Expand Down Expand Up @@ -1434,7 +1434,6 @@
},
},
/**
* After task has been created value can be appended only.
* @name labels
* @type {module:API.cvat.classes.Label[]}
* @memberof module:API.cvat.classes.Task
Expand Down
4 changes: 2 additions & 2 deletions cvat-ui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion cvat-ui/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cvat-ui",
"version": "1.33.1",
"version": "1.33.2",
"description": "CVAT single-page application",
"main": "src/index.tsx",
"scripts": {
Expand Down
6 changes: 3 additions & 3 deletions cvat-ui/src/components/labels-editor/common.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 2021 Intel Corporation
// Copyright (C) 2021-2022 Intel Corporation
//
// SPDX-License-Identifier: MIT

Expand Down Expand Up @@ -64,11 +64,11 @@ export function validateParsedLabel(label: Label): void {
);
}

if (typeof label.color !== 'string') {
if (label.color && typeof label.color !== 'string') {
throw new Error(`Label "${label.name}". Label color must be a string. Got ${typeof label.color}`);
}

if (!label.color.match(/^#[0-9a-fA-F]{6}$|^$/)) {
if (label.color && !label.color.match(/^#[0-9a-fA-F]{6}$|^$/)) {
throw new Error(
`Label "${label.name}". ` +
`Type of label color must be only a valid color string. Got value ${label.color}`,
Expand Down
17 changes: 12 additions & 5 deletions cvat-ui/src/components/labels-editor/label-form.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 2020-2021 Intel Corporation
// Copyright (C) 2020-2022 Intel Corporation
//
// SPDX-License-Identifier: MIT

Expand Down Expand Up @@ -205,13 +205,20 @@ export default class LabelForm extends React.Component<Props> {
);
}

private renderBooleanValueInput(fieldInstance: any, attr: Attribute | null): JSX.Element {
private renderBooleanValueInput(fieldInstance: any): JSX.Element {
const { key } = fieldInstance;
const value = attr ? attr.values[0] : 'false';

return (
<CVATTooltip title='Specify a default value'>
<Form.Item name={[key, 'values']} fieldKey={[fieldInstance.fieldKey, 'values']} initialValue={value}>
<Form.Item
rules={[
{
required: true,
message: 'Please, specify a default value',
}]}
name={[key, 'values']}
fieldKey={[fieldInstance.fieldKey, 'values']}
>
<Select className='cvat-attribute-values-input'>
<Select.Option value='false'>False</Select.Option>
<Select.Option value='true'>True</Select.Option>
Expand Down Expand Up @@ -354,7 +361,7 @@ export default class LabelForm extends React.Component<Props> {
if ([AttributeType.SELECT, AttributeType.RADIO].includes(type)) {
element = this.renderAttributeValuesInput(fieldInstance, attr);
} else if (type === AttributeType.CHECKBOX) {
element = this.renderBooleanValueInput(fieldInstance, attr);
element = this.renderBooleanValueInput(fieldInstance);
} else if (type === AttributeType.NUMBER) {
element = this.renderNumberRangeInput(fieldInstance, attr);
} else {
Expand Down
33 changes: 2 additions & 31 deletions cvat-ui/src/components/labels-editor/labels-editor.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
// Copyright (C) 2020-2021 Intel Corporation
// Copyright (C) 2020-2022 Intel Corporation
//
// SPDX-License-Identifier: MIT

import './styles.scss';
import React from 'react';
import Tabs from 'antd/lib/tabs';
import Button from 'antd/lib/button';
import Text from 'antd/lib/typography/Text';
import ModalConfirm from 'antd/lib/modal/confirm';
import copy from 'copy-to-clipboard';
import {
CopyOutlined, EditOutlined, BuildOutlined, ExclamationCircleOutlined,
EditOutlined, BuildOutlined, ExclamationCircleOutlined,
} from '@ant-design/icons';

import CVATTooltip from 'components/common/cvat-tooltip';
import RawViewer from './raw-viewer';
import ConstructorViewer from './constructor-viewer';
import ConstructorCreator from './constructor-creator';
Expand Down Expand Up @@ -207,32 +204,6 @@ export default class LabelsEditor extends React.PureComponent<LabelsEditorProps,
defaultActiveKey='2'
type='card'
tabBarStyle={{ marginBottom: '0px' }}
tabBarExtraContent={(
<CVATTooltip title='Copied to clipboard!' trigger='click'>
<Button
type='link'
icon={<CopyOutlined />}
onClick={(): void => {
copy(
JSON.stringify(
savedLabels.concat(unsavedLabels).map((label): any => ({
...label,
id: undefined,
attributes: label.attributes.map((attribute): any => ({
...attribute,
id: undefined,
})),
})),
null,
4,
),
);
}}
>
Copy
</Button>
</CVATTooltip>
)}
>
<Tabs.TabPane
tab={(
Expand Down
129 changes: 110 additions & 19 deletions cvat-ui/src/components/labels-editor/raw-viewer.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 2020-2021 Intel Corporation
// Copyright (C) 2020-2022 Intel Corporation
//
// SPDX-License-Identifier: MIT

Expand All @@ -7,7 +7,10 @@ import { Row, Col } from 'antd/lib/grid';
import Input from 'antd/lib/input';
import Button from 'antd/lib/button';
import Form, { FormInstance, RuleObject } from 'antd/lib/form';
import Tag from 'antd/lib/tag';
import Modal from 'antd/lib/modal';
import { Store } from 'antd/lib/form/interface';
import Paragraph from 'antd/lib/typography/Paragraph';

import CVATTooltip from 'components/common/cvat-tooltip';
import {
Expand Down Expand Up @@ -44,6 +47,21 @@ interface Props {
onSubmit: (labels: Label[]) => void;
}

function convertLabels(labels: Label[]): Label[] {
return labels.map(
(label: any): Label => ({
...label,
id: label.id < 0 ? undefined : label.id,
attributes: label.attributes.map(
(attribute: any): Attribute => ({
...attribute,
id: attribute.id < 0 ? undefined : attribute.id,
}),
),
}),
);
}

export default class RawViewer extends React.PureComponent<Props> {
private formRef: RefObject<FormInstance>;

Expand All @@ -52,49 +70,122 @@ export default class RawViewer extends React.PureComponent<Props> {
this.formRef = React.createRef<FormInstance>();
}

public componentDidUpdate(prevProps: Props): void {
const { labels } = this.props;
if (JSON.stringify(prevProps.labels) !== JSON.stringify(labels) && this.formRef.current) {
const convertedLabels = convertLabels(labels);
const textLabels = JSON.stringify(convertedLabels, null, 2);
this.formRef.current.setFieldsValue({ labels: textLabels });
}
}

private handleSubmit = (values: Store): void => {
const { onSubmit } = this.props;
const { onSubmit, labels } = this.props;
const parsed = JSON.parse(values.labels);

const labelIDs: number[] = [];
const attrIDs: number[] = [];
for (const label of parsed) {
label.id = label.id || idGenerator();
if (label.id >= 0) {
labelIDs.push(label.id);
}
for (const attr of label.attributes) {
attr.id = attr.id || idGenerator();
if (attr.id >= 0) {
attrIDs.push(attr.id);
}
}
}
onSubmit(parsed);

const deletedLabels = labels.filter((_label: Label) => _label.id >= 0 && !labelIDs.includes(_label.id));
const deletedAttributes = labels
.reduce((acc: Attribute[], _label) => [...acc, ..._label.attributes], [])
.filter((_attr: Attribute) => _attr.id >= 0 && !attrIDs.includes(_attr.id));

if (deletedLabels.length || deletedAttributes.length) {
Modal.confirm({
title: 'You are going to remove existing labels/attributes',
content: (
<>
{deletedLabels.length ? (
<Paragraph>
Following labels are going to be removed:
<div>
{deletedLabels
.map((_label: Label) => <Tag color={_label.color}>{_label.name}</Tag>)}
</div>

</Paragraph>
) : null}
{deletedAttributes.length ? (
<Paragraph>
Following attributes are going to be removed:
<div>
{deletedAttributes.map((_attr: Attribute) => <Tag>{_attr.name}</Tag>)}
</div>
</Paragraph>
) : null}
<Paragraph type='danger'>All related annotations will be destroyed. Continue?</Paragraph>
</>
),
okText: 'Delete existing data',
okButtonProps: {
danger: true,
},
onOk: () => {
onSubmit(parsed);
},
closable: true,
});
} else {
onSubmit(parsed);
}
};

public render(): JSX.Element {
const { labels } = this.props;
const convertedLabels = labels.map(
(label: any): Label => ({
...label,
id: label.id < 0 ? undefined : label.id,
attributes: label.attributes.map(
(attribute: any): Attribute => ({
...attribute,
id: attribute.id < 0 ? undefined : attribute.id,
}),
),
}),
);

const convertedLabels = convertLabels(labels);
const textLabels = JSON.stringify(convertedLabels, null, 2);
return (
<Form layout='vertical' onFinish={this.handleSubmit} ref={this.formRef}>
<Form.Item name='labels' initialValue={textLabels} rules={[{ validator: validateLabels }]}>
<Input.TextArea rows={5} className='cvat-raw-labels-viewer' />
<Input.TextArea
onPaste={(e: React.ClipboardEvent) => {
const data = e.clipboardData.getData('text');
const element = window.document.getElementsByClassName('cvat-raw-labels-viewer')[0] as HTMLTextAreaElement;
if (element && this.formRef.current) {
const { selectionStart, selectionEnd } = element;
// remove all "id": <number>,
let replaced = data.replace(/[\s]*"id":[\s]?[-{0-9}]+[,]?/g, '');
if (replaced !== data) {
// remove all carriage characters (textarea value does not contain them)
replaced = replaced.replace(/\r/g, '');
const value = this.formRef.current.getFieldValue('labels');
const updatedValue = value
.substr(0, selectionStart) + replaced + value.substr(selectionEnd);
this.formRef.current.setFieldsValue({ labels: updatedValue });
setTimeout(() => {
element.setSelectionRange(selectionEnd, selectionEnd);
});
e.preventDefault();
}
}
}}
rows={5}
className='cvat-raw-labels-viewer'
/>
</Form.Item>
<Row justify='start' align='middle'>
<Col>
<CVATTooltip title='Save labels and return'>
<CVATTooltip title='Save labels'>
<Button style={{ width: '150px' }} type='primary' htmlType='submit'>
Done
</Button>
</CVATTooltip>
</Col>
<Col offset={1}>
<CVATTooltip title='Do not save the label and return'>
<CVATTooltip title='Reset all changes'>
<Button
type='primary'
danger
Expand Down
Loading