Tags on frame (#75)

main
Kirill Lakhov 4 years ago committed by GitHub
parent bddd44642d
commit c0c469f824
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Progressbars in CLI for file uploading and downloading (<https://github.com/cvat-ai/cvat/pull/46>)
- `utils/cli` changed to `cvat-cli` package (<https://github.com/cvat-ai/cvat/pull/59>)
- Support custom file name for backup (<https://github.com/cvat-ai/cvat/pull/91>)
- Possibility to display tags on frame (<https://github.com/cvat-ai/cvat/pull/75>)
- Support source and target storages (server part) (<https://github.com/cvat-ai/cvat/pull/28>)
- Tests for import/export annotation, dataset, backup from/to cloud storage (<https://github.com/cvat-ai/cvat/pull/56>)

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

@ -139,7 +139,7 @@ export enum AnnotationActionTypes {
REPEAT_DRAW_SHAPE = 'REPEAT_DRAW_SHAPE',
SHAPE_DRAWN = 'SHAPE_DRAWN',
RESET_CANVAS = 'RESET_CANVAS',
REMEMBER_CREATED_OBJECT = 'REMEMBER_CREATED_OBJECT',
REMEMBER_OBJECT = 'REMEMBER_OBJECT',
UPDATE_ANNOTATIONS_SUCCESS = 'UPDATE_ANNOTATIONS_SUCCESS',
UPDATE_ANNOTATIONS_FAILED = 'UPDATE_ANNOTATIONS_FAILED',
CREATE_ANNOTATIONS_SUCCESS = 'CREATE_ANNOTATIONS_SUCCESS',
@ -156,6 +156,7 @@ export enum AnnotationActionTypes {
COLLAPSE_APPEARANCE = 'COLLAPSE_APPEARANCE',
COLLAPSE_OBJECT_ITEMS = 'COLLAPSE_OBJECT_ITEMS',
ACTIVATE_OBJECT = 'ACTIVATE_OBJECT',
REMOVE_OBJECT = 'REMOVE_OBJECT',
REMOVE_OBJECT_SUCCESS = 'REMOVE_OBJECT_SUCCESS',
REMOVE_OBJECT_FAILED = 'REMOVE_OBJECT_FAILED',
PROPAGATE_OBJECT = 'PROPAGATE_OBJECT',
@ -553,6 +554,16 @@ export function removeObjectAsync(sessionInstance: any, objectState: any, force:
};
}
export function removeObject(objectState: any, force: boolean): AnyAction {
return {
type: AnnotationActionTypes.REMOVE_OBJECT,
payload: {
objectState,
force,
},
};
}
export function editShape(enabled: boolean): AnyAction {
return {
type: AnnotationActionTypes.EDIT_SHAPE,
@ -1195,7 +1206,7 @@ export function rememberObject(createParams: {
activeCuboidDrawingMethod?: CuboidDrawingMethod;
}): AnyAction {
return {
type: AnnotationActionTypes.REMEMBER_CREATED_OBJECT,
type: AnnotationActionTypes.REMEMBER_OBJECT,
payload: createParams,
};
}
@ -1555,6 +1566,7 @@ export function repeatDrawShapeAsync(): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
const {
canvas: { instance: canvasInstance },
annotations: { states },
job: { labels, instance: jobInstance },
player: {
frame: { number: frameNumber },
@ -1614,13 +1626,17 @@ export function repeatDrawShapeAsync(): ThunkAction {
if (canvasInstance instanceof Canvas) {
canvasInstance.cancel();
}
if (activeObjectType === ObjectType.TAG) {
const objectState = new cvat.classes.ObjectState({
objectType: ObjectType.TAG,
label: labels.filter((label: any) => label.id === activeLabelID)[0],
frame: frameNumber,
});
dispatch(createAnnotationsAsync(jobInstance, frameNumber, [objectState]));
const tags = states.filter((objectState: any): boolean => objectState.objectType === ObjectType.TAG);
if (tags.every((objectState: any): boolean => objectState.label.id !== activeLabelID)) {
const objectState = new cvat.classes.ObjectState({
objectType: ObjectType.TAG,
label: labels.filter((label: any) => label.id === activeLabelID)[0],
frame: frameNumber,
});
dispatch(createAnnotationsAsync(jobInstance, frameNumber, [objectState]));
}
} else if (canvasInstance) {
canvasInstance.draw({
enabled: true,

@ -42,6 +42,7 @@ export enum SettingsActionTypes {
SET_SETTINGS = 'SET_SETTINGS',
SWITCH_TOOLS_BLOCKER_STATE = 'SWITCH_TOOLS_BLOCKER_STATE',
SWITCH_SHOWING_DELETED_FRAMES = 'SWITCH_SHOWING_DELETED_FRAMES',
SWITCH_SHOWING_TAGS_ON_FRAME = 'SWITCH_SHOWING_TAGS_ON_FRAME',
}
export function changeShapesOpacity(opacity: number): AnyAction {
@ -350,3 +351,12 @@ export function switchShowingDeletedFrames(showDeletedFrames: boolean): AnyActio
},
};
}
export function switchShowingTagsOnFrame(showTagsOnFrame: boolean): AnyAction {
return {
type: SettingsActionTypes.SWITCH_SHOWING_TAGS_ON_FRAME,
payload: {
showTagsOnFrame,
},
};
}

@ -18,6 +18,7 @@ import { Canvas3d } from 'cvat-canvas3d-wrapper';
import getCore from 'cvat-core-wrapper';
import consts from 'consts';
import CVATTooltip from 'components/common/cvat-tooltip';
import FrameTags from 'components/annotation-page/tag-annotation-workspace/frame-tags';
import ImageSetupsContent from './image-setups-content';
import ContextImage from '../standard-workspace/context-image/context-image';
@ -69,6 +70,7 @@ interface Props {
keyMap: KeyMap;
canvasBackgroundColor: string;
switchableAutomaticBordering: boolean;
showTagsOnFrame: boolean;
onSetupCanvas: () => void;
onDragCanvas: (enabled: boolean) => void;
onZoomCanvas: (enabled: boolean) => void;
@ -780,6 +782,7 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
keyMap,
switchableAutomaticBordering,
automaticBordering,
showTagsOnFrame,
onSwitchAutomaticBordering,
onSwitchZLayer,
onAddZLayer,
@ -842,6 +845,13 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
<PlusCircleOutlined onClick={onAddZLayer} />
</CVATTooltip>
</div>
{showTagsOnFrame ? (
<div className='cvat-canvas-frame-tags'>
<FrameTags />
</div>
) : null}
;
</Layout.Content>
);
}

@ -1,4 +1,4 @@
// Copyright (C) 2020-2021 Intel Corporation
// Copyright (C) 2020-2022 Intel Corporation
//
// SPDX-License-Identifier: MIT
@ -8,6 +8,7 @@ import Button from 'antd/lib/button';
import Text from 'antd/lib/typography/Text';
import LabelSelector from 'components/label-selector/label-selector';
import { PlusOutlined } from '@ant-design/icons';
import CVATTooltip from 'components/common/cvat-tooltip';
interface Props {
@ -37,20 +38,21 @@ function SetupTagPopover(props: Props): JSX.Element {
<Text className='cvat-text-color'>Label</Text>
</Col>
</Row>
<Row justify='center'>
<Col span={24}>
<Row justify='start'>
<Col>
<LabelSelector
style={{ width: '100%' }}
labels={labels}
value={selectedLabelID}
onChange={onChangeLabel}
onEnterPress={() => onSetup(selectedLabelID)}
/>
</Col>
</Row>
<Row justify='space-around'>
<Col span={24}>
<CVATTooltip title={`Press ${repeatShapeShortcut} to add a tag again`}>
<Button onClick={() => onSetup(selectedLabelID)}>Tag</Button>
<Button
type='primary'
className='cvat-add-tag-button'
onClick={() => onSetup(selectedLabelID)}
icon={<PlusOutlined />}
/>
</CVATTooltip>
</Col>
</Row>

@ -1,11 +1,10 @@
// Copyright (C) 2020-2021 Intel Corporation
// Copyright (C) 2020-2022 Intel Corporation
//
// SPDX-License-Identifier: MIT
import React from 'react';
import Menu from 'antd/lib/menu';
import Button from 'antd/lib/button';
import Modal from 'antd/lib/modal';
import Icon, {
LinkOutlined, CopyOutlined, BlockOutlined, RetweetOutlined, DeleteOutlined,
} from '@ant-design/icons';
@ -179,27 +178,14 @@ function SwitchColorItem(props: ItemProps): JSX.Element {
function RemoveItem(props: ItemProps): JSX.Element {
const { toolProps, ...rest } = props;
const { removeShortcut, locked, remove } = toolProps;
const { removeShortcut, remove } = toolProps;
return (
<Menu.Item {...rest}>
<CVATTooltip title={`${removeShortcut}`}>
<Button
type='link'
icon={<DeleteOutlined />}
onClick={(): void => {
if (locked) {
Modal.confirm({
className: 'cvat-modal-confirm',
title: 'Object is locked',
content: 'Are you sure you want to remove it?',
onOk() {
remove();
},
});
} else {
remove();
}
}}
onClick={remove}
>
Remove
</Button>
@ -230,7 +216,9 @@ export default function ItemMenu(props: Props): JSX.Element {
return (
<Menu className='cvat-object-item-menu' selectable={false}>
<CreateURLItem key={MenuKeys.CREATE_URL} toolProps={props} />
{!readonly && <MakeCopyItem key={MenuKeys.COPY} toolProps={props} />}
{!readonly && objectType !== ObjectType.TAG && (
<MakeCopyItem key={MenuKeys.COPY} toolProps={props} />
)}
{!readonly && <PropagateItem key={MenuKeys.PROPAGATE} toolProps={props} />}
{is2D && !readonly && [ShapeType.POLYGON, ShapeType.POLYLINE, ShapeType.CUBOID].includes(shapeType) && (
<SwitchOrientationItem key={MenuKeys.SWITCH_ORIENTATION} toolProps={props} />

@ -0,0 +1,53 @@
// Copyright (C) 2022 Intel Corporation
//
// SPDX-License-Identifier: MIT
import React, { useCallback, useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { CombinedState } from 'reducers/interfaces';
import Modal from 'antd/lib/modal';
import { removeObjectAsync, removeObject as removeObjectAction } from 'actions/annotation-actions';
export default function RemoveConfirmComponent(): JSX.Element | null {
const [visible, setVisible] = useState(false);
const [title, setTitle] = useState('');
const objectState = useSelector((state: CombinedState) => state.annotation.remove.objectState);
const force = useSelector((state: CombinedState) => state.annotation.remove.force);
const jobInstance = useSelector((state: CombinedState) => state.annotation.job.instance);
const dispatch = useDispatch();
const onOk = useCallback(() => {
dispatch(removeObjectAsync(jobInstance, objectState, true));
}, [jobInstance, objectState]);
const onCancel = useCallback(() => {
dispatch(removeObjectAction(null, false));
}, []);
useEffect(() => {
const newVisible = !!objectState && !force && objectState.lock;
setTitle(objectState?.lock ? 'Object is locked' : 'Remove object');
setVisible(newVisible);
if (!newVisible && objectState) {
dispatch(removeObjectAsync(jobInstance, objectState, true));
}
}, [objectState, force]);
return (
<Modal
okType='primary'
okText='Yes'
cancelText='Cancel'
title={title}
visible={visible}
onOk={onOk}
onCancel={onCancel}
className='cvat-modal-confirm'
>
<div>
Are you sure you want to remove it?
</div>
</Modal>
);
}

@ -1,4 +1,4 @@
// Copyright (C) 2020 Intel Corporation
// Copyright (C) 2020-2022 Intel Corporation
//
// SPDX-License-Identifier: MIT
@ -14,6 +14,7 @@ import ObjectsListContainer from 'containers/annotation-page/standard-workspace/
import ObjectSideBarComponent from 'components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar';
import CanvasPointContextMenuComponent from 'components/annotation-page/canvas/canvas-point-context-menu';
import IssueAggregatorComponent from 'components/annotation-page/review/issues-aggregator';
import RemoveConfirmComponent from 'components/annotation-page/standard-workspace/remove-confirm';
export default function StandardWorkspaceComponent(): JSX.Element {
return (
@ -25,6 +26,7 @@ export default function StandardWorkspaceComponent(): JSX.Element {
<CanvasContextMenuContainer />
<CanvasPointContextMenuComponent />
<IssueAggregatorComponent />
<RemoveConfirmComponent />
</Layout>
);
}

@ -1,4 +1,4 @@
// Copyright (C) 2020-2021 Intel Corporation
// Copyright (C) 2020-2022 Intel Corporation
//
// SPDX-License-Identifier: MIT
@ -268,8 +268,7 @@
margin-top: $grid-unit-size;
}
.cvat-setup-tag-popover-content,
.cvat-draw-shape-popover-content {
.cvat-popover-content {
padding: $grid-unit-size;
border-radius: $grid-unit-size;
background: $background-color-2;
@ -278,6 +277,22 @@
> div {
margin-top: $grid-unit-size;
}
}
.cvat-setup-tag-popover-content {
@extend .cvat-popover-content;
.ant-select {
width: 27 * $grid-unit-size;
}
> div:last-child {
margin-bottom: $grid-unit-size;
}
}
.cvat-draw-shape-popover-content {
@extend .cvat-popover-content;
> div:nth-child(3) > div > div {
width: 100%;

@ -1,4 +1,4 @@
// Copyright (C) 2021 Intel Corporation
// Copyright (C) 2021-2022 Intel Corporation
//
// SPDX-License-Identifier: MIT
@ -13,6 +13,7 @@ import ObjectsListContainer from 'containers/annotation-page/standard-workspace/
import PropagateConfirmContainer from 'containers/annotation-page/standard-workspace/propagate-confirm';
import CanvasContextMenuContainer from 'containers/annotation-page/canvas/canvas-context-menu';
import CanvasPointContextMenuComponent from 'components/annotation-page/canvas/canvas-point-context-menu';
import RemoveConfirmComponent from 'components/annotation-page/standard-workspace/remove-confirm';
export default function StandardWorkspace3DComponent(): JSX.Element {
return (
@ -23,6 +24,7 @@ export default function StandardWorkspace3DComponent(): JSX.Element {
<PropagateConfirmContainer />
<CanvasContextMenuContainer />
<CanvasPointContextMenuComponent />
<RemoveConfirmComponent />
</Layout>
);
}

@ -0,0 +1,79 @@
// Copyright (C) 2022 Intel Corporation
//
// SPDX-License-Identifier: MIT
import React, { useState, useEffect } from 'react';
import Tag from 'antd/lib/tag';
import { connect } from 'react-redux';
import { Action } from 'redux';
import { ThunkDispatch } from 'redux-thunk';
import {
removeObject as removeObjectAction,
} from 'actions/annotation-actions';
import { CombinedState, ObjectType } from 'reducers/interfaces';
interface StateToProps {
states: any[];
}
interface DispatchToProps {
removeObject(objectState: any): void;
}
function mapStateToProps(state: CombinedState): StateToProps {
const {
annotation: {
annotations: { states },
},
} = state;
return {
states,
};
}
function mapDispatchToProps(dispatch: ThunkDispatch<CombinedState, {}, Action>): DispatchToProps {
return {
removeObject(objectState: any): void {
dispatch(removeObjectAction(objectState, false));
},
};
}
function FrameTags(props: StateToProps & DispatchToProps): JSX.Element {
const {
states,
removeObject,
} = props;
const [frameTags, setFrameTags] = useState([] as any[]);
const onRemoveState = (objectState: any): void => {
removeObject(objectState);
};
useEffect(() => {
setFrameTags(states.filter((objectState: any): boolean => objectState.objectType === ObjectType.TAG));
}, [states]);
return (
<>
{frameTags.map((tag: any) => (
<Tag
className='cvat-frame-tag'
color={tag.label.color}
onClose={() => {
onRemoveState(tag);
}}
key={tag.clientID}
closable
>
{tag.label.name}
</Tag>
))}
</>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(FrameTags);

@ -1,4 +1,4 @@
// Copyright (C) 2020 Intel Corporation
// Copyright (C) 2020-2022 Intel Corporation
//
// SPDX-License-Identifier: MIT
@ -10,42 +10,79 @@
.cvat-tag-annotation-sidebar:not(.ant-layout-sider-collapsed) {
background: $background-color-2;
padding: 5px;
> div > .ant-row > .ant-col > .ant-tag {
margin: 4px;
}
padding: $grid-unit-size * 0.5;
padding-left: $grid-unit-size * 1.25;
overflow-y: auto;
}
.cvat-tag-annotation-sidebar-label-select {
padding-top: 40px;
padding-bottom: 15px;
padding-top: $grid-unit-size * 1.25;
padding-bottom: $grid-unit-size * 1.8;
> .ant-col > .ant-select {
padding-left: 5px;
width: 230px;
width: $grid-unit-size * 25;
}
}
.cvat-tag-annotation-sidebar-shortcut-help {
padding-top: 15px;
padding-top: $grid-unit-size * 1.8;
text-align: center;
}
.cvat-tag-annotation-sidebar-buttons,
.cvat-tag-annotation-sidebar-checkbox-skip-frame {
padding-bottom: 15px;
padding-bottom: $grid-unit-size * 1.8;
}
.cvat-tag-annotation-label-selects {
padding-top: 10px;
padding-top: $grid-unit-size * 1.25;
.ant-select {
width: 230px;
padding: 5px 10px;
width: $grid-unit-size * 29;
margin: $grid-unit-size * 0.5 0;
}
.cvat-tag-annotation-shortcut-key {
margin-left: $grid-unit-size * 1.25;
}
}
.labels-tag-annotation-sidebar-not-found-wrapper {
margin-top: $grid-unit-size * 4;
}
.cvat-frame-tags {
.ant-tag {
margin: $grid-unit-size * 0.25;
display: inline-flex;
justify-content: center;
align-items: center;
.ant-tag-close-icon {
margin-left: $grid-unit-size * 0.5;
font-size: $grid-unit-size * 1.5;
}
}
}
.cvat-canvas-frame-tags {
@extend .cvat-frame-tags;
position: absolute;
top: $layout-sm-grid-size;
left: $grid-unit-size;
z-index: 3;
.ant-tag {
user-select: none;
}
}
.cvat-tag-annotation-sidebar-tag-label {
margin-top: $grid-unit-size * 1.8;
}
.cvat-add-tag-button {
margin-left: $grid-unit-size * 1.25;
width: $grid-unit-size * 3.5;
height: $grid-unit-size * 3.5;
}

@ -1,4 +1,4 @@
// Copyright (C) 2020-2021 Intel Corporation
// Copyright (C) 2020-2022 Intel Corporation
//
// SPDX-License-Identifier: MIT
@ -8,7 +8,7 @@ import { Row, Col } from 'antd/lib/grid';
import Text from 'antd/lib/typography/Text';
import Select from 'antd/lib/select';
import { CombinedState } from 'reducers/interfaces';
import { CombinedState, DimensionType } from 'reducers/interfaces';
import GlobalHotKeys, { KeyMap } from 'utils/mousetrap-react';
import { shift } from 'utils/math';
@ -17,7 +17,7 @@ interface ShortcutLabelMap {
}
type Props = {
onAddTag(labelID: number): void;
onShortcutPress(event: KeyboardEvent | undefined, labelID: number): void;
};
const defaultShortcutLabelMap = {
@ -34,7 +34,7 @@ const defaultShortcutLabelMap = {
} as ShortcutLabelMap;
const ShortcutsSelect = (props: Props): JSX.Element => {
const { onAddTag } = props;
const { onShortcutPress } = props;
const { labels } = useSelector((state: CombinedState) => state.annotation.job);
const [shortcutLabelMap, setShortcutLabelMap] = useState(defaultShortcutLabelMap);
@ -60,16 +60,16 @@ const ShortcutsSelect = (props: Props): JSX.Element => {
keyMap[key] = {
name: `Setup ${label.name} tag`,
description: `Setup tag with "${label.name}" label`,
sequences: [`${id}`],
sequences: [`${id}`, `shift+${id}`],
action: 'keydown',
applicable: [DimensionType.DIM_2D, DimensionType.DIM_3D],
};
handlers[key] = (event: KeyboardEvent | undefined) => {
if (event) {
event.preventDefault();
}
onAddTag(label.id);
onShortcutPress(event, label.id);
};
});
@ -92,7 +92,6 @@ const ShortcutsSelect = (props: Props): JSX.Element => {
.map((id) => (
<Row key={id}>
<Col>
<Text strong>{`Key ${id}:`}</Text>
<Select
value={`${shortcutLabelMap[Number.parseInt(id, 10)]}`}
onChange={(value: string) => {
@ -110,6 +109,7 @@ const ShortcutsSelect = (props: Props): JSX.Element => {
</Select.Option>
))}
</Select>
<Text code className='cvat-tag-annotation-shortcut-key'>{`Key ${id}`}</Text>
</Col>
</Row>
))}

@ -7,16 +7,17 @@ import { connect } from 'react-redux';
import { Action } from 'redux';
import { ThunkDispatch } from 'redux-thunk';
import { Row, Col } from 'antd/lib/grid';
import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons';
import {
MenuFoldOutlined, MenuUnfoldOutlined, PlusOutlined,
} from '@ant-design/icons';
import Layout, { SiderProps } from 'antd/lib/layout';
import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox/Checkbox';
import Button from 'antd/lib/button/button';
import Text from 'antd/lib/typography/Text';
import Tag from 'antd/lib/tag';
import {
createAnnotationsAsync,
removeObjectAsync,
removeObject as removeObjectAction,
changeFrameAsync,
rememberObject,
} from 'actions/annotation-actions';
@ -44,7 +45,7 @@ interface StateToProps {
}
interface DispatchToProps {
removeObject(jobInstance: any, objectState: any): void;
removeObject(objectState: any): void;
createAnnotations(jobInstance: any, frame: number, objectStates: any[]): void;
changeFrame(frame: number, fillBuffer?: boolean, frameStep?: number): void;
onRememberObject(labelID: number): void;
@ -67,7 +68,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
jobInstance,
labels,
states,
canvasInstance,
canvasInstance: canvasInstance as Canvas | Canvas3d,
frameNumber,
keyMap,
normalizedKeyMap,
@ -83,8 +84,8 @@ function mapDispatchToProps(dispatch: ThunkDispatch<CombinedState, {}, Action>):
createAnnotations(jobInstance: any, frame: number, objectStates: any[]): void {
dispatch(createAnnotationsAsync(jobInstance, frame, objectStates));
},
removeObject(jobInstance: any, objectState: any): void {
dispatch(removeObjectAsync(jobInstance, objectState, true));
removeObject(objectState: any): void {
dispatch(removeObjectAction(objectState, false));
},
onRememberObject(labelID: number): void {
dispatch(rememberObject({ activeObjectType: ObjectType.TAG, activeLabelID: labelID }));
@ -148,7 +149,8 @@ function TagAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.Elemen
}, []);
useEffect(() => {
setFrameTags(states.filter((objectState: any): boolean => objectState.objectType === ObjectType.TAG));
const tags = states.filter((objectState: any): boolean => objectState.objectType === ObjectType.TAG);
setFrameTags(tags);
}, [states]);
const siderProps: SiderProps = {
@ -166,8 +168,9 @@ function TagAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.Elemen
setSelectedLabelID(value.id);
};
const onRemoveState = (objectState: any): void => {
removeObject(jobInstance, objectState);
const onRemoveState = (labelID: number): void => {
const objectState = frameTags.find((tag: any): boolean => tag.label.id === labelID);
if (objectState) removeObject(objectState);
};
const onChangeFrame = (): void => {
@ -179,17 +182,26 @@ function TagAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.Elemen
};
const onAddTag = (labelID: number): void => {
onRememberObject(labelID);
if (frameTags.every((objectState: any): boolean => objectState.label.id !== labelID)) {
onRememberObject(labelID);
const objectState = new cvat.classes.ObjectState({
objectType: ObjectType.TAG,
label: labels.filter((label: any) => label.id === labelID)[0],
frame: frameNumber,
});
const objectState = new cvat.classes.ObjectState({
objectType: ObjectType.TAG,
label: labels.filter((label: any) => label.id === labelID)[0],
frame: frameNumber,
});
createAnnotations(jobInstance, frameNumber, [objectState]);
createAnnotations(jobInstance, frameNumber, [objectState]);
if (skipFrame) onChangeFrame();
}
};
if (skipFrame) onChangeFrame();
const onShortcutPress = (event: KeyboardEvent | undefined, labelID: number): void => {
if (event?.shiftKey) {
onRemoveState(labelID);
} else {
onAddTag(labelID);
}
};
const subKeyMap = {
@ -199,7 +211,7 @@ function TagAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.Elemen
const handlers = {
SWITCH_DRAW_MODE: (event: KeyboardEvent | undefined) => {
preventDefault(event);
onAddTag(selectedLabelID);
onShortcutPress(event, selectedLabelID);
},
};
@ -239,18 +251,25 @@ function TagAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.Elemen
>
{sidebarCollapsed ? <MenuFoldOutlined title='Show' /> : <MenuUnfoldOutlined title='Hide' />}
</span>
<Row justify='start' className='cvat-tag-annotation-sidebar-label-select'>
<Row justify='start' className='cvat-tag-annotation-sidebar-tag-label'>
<Col>
<Text strong>Tag label</Text>
<LabelSelector labels={labels} value={selectedLabelID} onChange={onChangeLabel} />
<Text strong>Tag label:</Text>
</Col>
</Row>
<Row justify='space-around' className='cvat-tag-annotation-sidebar-buttons'>
<Col span={8}>
<Button onClick={() => onAddTag(selectedLabelID)}>Add tag</Button>
</Col>
<Col span={8}>
<Button onClick={onChangeFrame}>Skip frame</Button>
<Row justify='start' className='cvat-tag-annotation-sidebar-label-select'>
<Col>
<LabelSelector
labels={labels}
value={selectedLabelID}
onChange={onChangeLabel}
onEnterPress={onAddTag}
/>
<Button
type='primary'
className='cvat-add-tag-button'
onClick={() => onAddTag(selectedLabelID)}
icon={<PlusOutlined />}
/>
</Col>
</Row>
<Row className='cvat-tag-annotation-sidebar-checkbox-skip-frame'>
@ -265,27 +284,9 @@ function TagAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.Elemen
</Checkbox>
</Col>
</Row>
<Row justify='start' className='cvat-tag-annotation-sidebar-frame-tags'>
<Col>
<Text strong>Frame tags:&nbsp;</Text>
{frameTags.map((tag: any) => (
<Tag
className='cvat-tag-annotation-sidebar-frame-tag-label'
color={tag.label.color}
onClose={() => {
onRemoveState(tag);
}}
key={tag.clientID}
closable
>
{tag.label.name}
</Tag>
))}
</Col>
</Row>
<Row>
<Col>
<ShortcutsSelect onAddTag={onAddTag} />
<ShortcutsSelect onShortcutPress={onShortcutPress} />
</Col>
</Row>
<Row justify='center' className='cvat-tag-annotation-sidebar-shortcut-help'>
@ -295,11 +296,10 @@ function TagAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.Elemen
<Text code>N</Text>
&nbsp;or digits&nbsp;
<Text code>0-9</Text>
&nbsp;to add selected tag
<br />
or&nbsp;
<Text code></Text>
&nbsp;to skip frame
&nbsp;to add selected tag.&nbsp;
Add&nbsp;
<Text code>Shift</Text>
&nbsp;modifier to remove selected tag.
</Text>
</Col>
</Row>

@ -1,4 +1,4 @@
// Copyright (C) 2020 Intel Corporation
// Copyright (C) 2020-2022 Intel Corporation
//
// SPDX-License-Identifier: MIT
@ -7,6 +7,7 @@ import React from 'react';
import Layout from 'antd/lib/layout';
import CanvasWrapperContainer from 'containers/annotation-page/canvas/canvas-wrapper';
import RemoveConfirmComponent from 'components/annotation-page/standard-workspace/remove-confirm';
import TagAnnotationSidebar from './tag-annotation-sidebar/tag-annotation-sidebar';
export default function TagAnnotationWorkspace(): JSX.Element {
@ -14,6 +15,7 @@ export default function TagAnnotationWorkspace(): JSX.Element {
<Layout hasSider className='cvat-tag-annotation-workspace'>
<CanvasWrapperContainer />
<TagAnnotationSidebar />
<RemoveConfirmComponent />
</Layout>
);
}

@ -32,6 +32,7 @@
.cvat-workspace-settings-show-deleted,
.cvat-workspace-settings-approx-poly-threshold,
.cvat-workspace-settings-aam-zoom-margin,
.cvat-workspace-settings-show-frame-tags,
.cvat-workspace-settings-text-settings {
margin-bottom: $grid-unit-size * 3;

@ -29,6 +29,7 @@ interface Props {
textFontSize: number;
textPosition: 'center' | 'auto';
textContent: string;
showTagsOnFrame: boolean;
onSwitchAutoSave(enabled: boolean): void;
onChangeAutoSaveInterval(interval: number): void;
onChangeAAMZoomMargin(margin: number): void;
@ -40,6 +41,7 @@ interface Props {
onChangeTextFontSize(fontSize: number): void;
onChangeTextPosition(position: 'auto' | 'center'): void;
onChangeTextContent(textContent: string[]): void;
onSwitchShowingTagsOnFrame(enabled: boolean): void;
}
function WorkspaceSettingsComponent(props: Props): JSX.Element {
@ -55,6 +57,7 @@ function WorkspaceSettingsComponent(props: Props): JSX.Element {
textFontSize,
textPosition,
textContent,
showTagsOnFrame,
onSwitchAutoSave,
onChangeAutoSaveInterval,
onChangeAAMZoomMargin,
@ -66,6 +69,7 @@ function WorkspaceSettingsComponent(props: Props): JSX.Element {
onChangeTextFontSize,
onChangeTextPosition,
onChangeTextContent,
onSwitchShowingTagsOnFrame,
} = props;
const minAutoSaveInterval = 1;
@ -223,6 +227,22 @@ function WorkspaceSettingsComponent(props: Props): JSX.Element {
<Text type='secondary'>Try to crop polygons automatically when editing</Text>
</Col>
</Row>
<Row className='cvat-workspace-settings-show-frame-tags'>
<Col span={24}>
<Checkbox
className='cvat-text-color'
checked={showTagsOnFrame}
onChange={(event: CheckboxChangeEvent): void => {
onSwitchShowingTagsOnFrame(event.target.checked);
}}
>
Show tags on frame
</Checkbox>
</Col>
<Col span={24}>
<Text type='secondary'>Show frame tags in the corner of the workspace</Text>
</Col>
</Row>
<Row className='cvat-workspace-settings-aam-zoom-margin'>
<Col>
<Text className='cvat-text-color'> Attribute annotation mode (AAM) zoom margin </Text>

@ -1,8 +1,8 @@
// Copyright (C) 2020-2021 Intel Corporation
// Copyright (C) 2020-2022 Intel Corporation
//
// SPDX-License-Identifier: MIT
import React from 'react';
import React, { useEffect, useState } from 'react';
import Select, { SelectProps } from 'antd/lib/select';
// eslint-disable-next-line import/no-extraneous-dependencies
import { OptionData, OptionGroupData } from 'rc-select/lib/interface';
@ -11,11 +11,12 @@ interface Props extends SelectProps<string> {
labels: any[];
value: any | number | null;
onChange: (label: any) => void;
onEnterPress?: (labelID: number) => void;
}
export default function LabelSelector(props: Props): JSX.Element {
const {
labels, value, onChange, ...rest
labels, value, onChange, onEnterPress, ...rest
} = props;
const dinamicProps = value ?
{
@ -23,6 +24,15 @@ export default function LabelSelector(props: Props): JSX.Element {
} :
{};
const [enterPressed, setEnterPressed] = useState(false);
useEffect(() => {
if (enterPressed && onEnterPress) {
onEnterPress(value);
setEnterPressed(false);
}
}, [value, enterPressed]);
return (
<Select
virtual={false}
@ -48,6 +58,11 @@ export default function LabelSelector(props: Props): JSX.Element {
throw new Error(`Label with id ${newValue} was not found within the list`);
}
}}
onInputKeyDown={(event) => {
if (onEnterPress) {
setEnterPressed(event.key === 'Enter');
}
}}
>
{labels.map((label: any) => (
<Select.Option title={label.name} key={label.id} value={label.id}>

@ -95,6 +95,7 @@ interface StateToProps {
switchableAutomaticBordering: boolean;
keyMap: KeyMap;
canvasBackgroundColor: string;
showTagsOnFrame: boolean;
}
interface DispatchToProps {
@ -164,6 +165,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
aamZoomMargin,
showObjectsTextAlways,
showAllInterpolationTracks,
showTagsOnFrame,
automaticBordering,
intelligentPolygonCrop,
textFontSize,
@ -212,6 +214,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
textPosition,
textContent,
showAllInterpolationTracks,
showTagsOnFrame,
curZLayer,
minZLayer,
maxZLayer,

@ -22,6 +22,7 @@ interface StateToProps {
normalizedKeyMap: Record<string, string>;
canvasInstance: Canvas;
jobInstance: any;
states: any[];
labels: any[];
frame: number;
}
@ -45,6 +46,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
const {
annotation: {
canvas: { instance: canvasInstance },
annotations: { states },
job: { instance: jobInstance, labels },
player: {
frame: { number: frame },
@ -54,8 +56,9 @@ function mapStateToProps(state: CombinedState): StateToProps {
} = state;
return {
canvasInstance,
canvasInstance: canvasInstance as Canvas,
jobInstance,
states,
labels,
frame,
normalizedKeyMap,
@ -66,21 +69,51 @@ type Props = StateToProps & DispatchToProps;
interface State {
selectedLabelID: number;
frameTags: any[]
canAddSelectedTag: boolean;
}
class DrawShapePopoverContainer extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
const { states } = props;
const frameTags = states.filter((objectState: any): boolean => objectState.objectType === ObjectType.TAG);
const defaultLabelID = props.labels[0].id;
this.state = {
selectedLabelID: defaultLabelID,
canAddSelectedTag: frameTags.every((objectState: any): boolean => objectState.label.id !== defaultLabelID),
frameTags,
};
}
public componentDidUpdate(prevProps: Props, prevState: State): void {
const { frameTags: prevFrameTags } = prevState;
const { frame: prevFrame } = prevProps;
const { states, frame } = this.props;
const frameTags = states.filter((objectState: any): boolean => objectState.objectType === ObjectType.TAG);
if (prevFrame === frame && prevFrameTags.length === frameTags.length) return;
const { selectedLabelID } = this.state;
const canAddSelectedTag =
frameTags.every((objectState: any): boolean => objectState.label.id !== selectedLabelID);
this.setState({
frameTags,
canAddSelectedTag,
});
}
private onChangeLabel = (value: any): void => {
const { onRememberObject } = this.props;
const { frameTags } = this.state;
const canAddSelectedTag = frameTags.every((objectState: any): boolean => objectState.label.id !== value.id);
onRememberObject(value.id);
this.setState({
selectedLabelID: value.id,
canAddSelectedTag,
});
};
@ -89,19 +122,20 @@ class DrawShapePopoverContainer extends React.PureComponent<Props, State> {
frame, labels, jobInstance, canvasInstance, onAnnotationCreate, onRememberObject,
} = this.props;
const { selectedLabelID } = this.state;
canvasInstance.cancel();
const { selectedLabelID, canAddSelectedTag } = this.state;
if (canAddSelectedTag) {
canvasInstance.cancel();
onRememberObject(selectedLabelID);
onRememberObject(selectedLabelID);
const objectState = new cvat.classes.ObjectState({
objectType: ObjectType.TAG,
label: labels.filter((label: any) => label.id === selectedLabelID)[0],
frame,
});
const objectState = new cvat.classes.ObjectState({
objectType: ObjectType.TAG,
label: labels.filter((label: any) => label.id === selectedLabelID)[0],
frame,
});
onAnnotationCreate(jobInstance, frame, [objectState]);
onAnnotationCreate(jobInstance, frame, [objectState]);
}
};
public render(): JSX.Element {

@ -1,4 +1,4 @@
// Copyright (C) 2021 Intel Corporation
// Copyright (C) 2021-2022 Intel Corporation
//
// SPDX-License-Identifier: MIT
@ -11,12 +11,12 @@ import {
collapseObjectItems,
updateAnnotationsAsync,
changeFrameAsync,
removeObjectAsync,
changeGroupColorAsync,
pasteShapeAsync,
copyShape as copyShapeAction,
activateObject as activateObjectAction,
propagateObject as propagateObjectAction,
removeObject as removeObjectAction,
} from 'actions/annotation-actions';
import {
ActiveControl, CombinedState, ColorBy, ShapeType,
@ -55,7 +55,7 @@ interface DispatchToProps {
updateState(objectState: any): void;
collapseOrExpand(objectStates: any[], collapsed: boolean): void;
activateObject: (activatedStateID: number | null) => void;
removeObject: (sessionInstance: any, objectState: any) => void;
removeObject: (objectState: any) => void;
copyShape: (objectState: any) => void;
propagateObject: (objectState: any) => void;
changeGroupColor(group: number, color: string): void;
@ -120,8 +120,8 @@ function mapDispatchToProps(dispatch: any): DispatchToProps {
activateObject(activatedStateID: number | null): void {
dispatch(activateObjectAction(activatedStateID, null));
},
removeObject(sessionInstance: any, objectState: any): void {
dispatch(removeObjectAsync(sessionInstance, objectState, true));
removeObject(objectState: any): void {
dispatch(removeObjectAction(objectState, false));
},
copyShape(objectState: any): void {
dispatch(copyShapeAction(objectState));
@ -154,11 +154,11 @@ class ObjectItemContainer extends React.PureComponent<Props> {
private remove = (): void => {
const {
objectState, jobInstance, readonly, removeObject,
objectState, readonly, removeObject,
} = this.props;
if (!readonly) {
removeObject(jobInstance, objectState);
removeObject(objectState);
}
};

@ -1,4 +1,4 @@
// Copyright (C) 2020-2021 Intel Corporation
// Copyright (C) 2020-2022 Intel Corporation
//
// SPDX-License-Identifier: MIT
@ -9,12 +9,12 @@ import GlobalHotKeys, { KeyMap } from 'utils/mousetrap-react';
import ObjectsListComponent from 'components/annotation-page/standard-workspace/objects-side-bar/objects-list';
import {
updateAnnotationsAsync,
removeObjectAsync,
changeFrameAsync,
collapseObjectItems,
changeGroupColorAsync,
copyShape as copyShapeAction,
propagateObject as propagateObjectAction,
removeObject as removeObjectAction,
} from 'actions/annotation-actions';
import isAbleToChangeFrame from 'utils/is-able-to-change-frame';
import {
@ -46,7 +46,7 @@ interface StateToProps {
interface DispatchToProps {
updateAnnotations(states: any[]): void;
collapseStates(states: any[], value: boolean): void;
removeObject: (sessionInstance: any, objectState: any, force: boolean) => void;
removeObject: (objectState: any, force: boolean) => void;
copyShape: (objectState: any) => void;
propagateObject: (objectState: any) => void;
changeFrame(frame: number): void;
@ -116,8 +116,8 @@ function mapDispatchToProps(dispatch: any): DispatchToProps {
collapseStates(states: any[], collapsed: boolean): void {
dispatch(collapseObjectItems(states, collapsed));
},
removeObject(sessionInstance: any, objectState: any, force: boolean): void {
dispatch(removeObjectAsync(sessionInstance, objectState, force));
removeObject(objectState: any, force: boolean): void {
dispatch(removeObjectAction(objectState, force));
},
copyShape(objectState: any): void {
dispatch(copyShapeAction(objectState));
@ -248,7 +248,6 @@ class ObjectsListContainer extends React.PureComponent<Props, State> {
statesHidden,
statesLocked,
activatedStateID,
jobInstance,
maxZLayer,
minZLayer,
keyMap,
@ -377,7 +376,7 @@ class ObjectsListContainer extends React.PureComponent<Props, State> {
preventDefault(event);
const state = activatedStated();
if (state && !readonly) {
removeObject(jobInstance, state, event ? event.shiftKey : false);
removeObject(state, event ? event.shiftKey : false);
}
},
CHANGE_OBJECT_COLOR: (event: KeyboardEvent | undefined) => {

@ -17,6 +17,7 @@ import {
switchTextFontSize,
switchTextPosition,
switchTextContent,
switchShowingTagsOnFrame,
} from 'actions/settings-actions';
import { CombinedState } from 'reducers/interfaces';
@ -35,6 +36,7 @@ interface StateToProps {
textFontSize: number;
textPosition: 'auto' | 'center';
textContent: string;
showTagsOnFrame: boolean;
}
interface DispatchToProps {
@ -49,6 +51,7 @@ interface DispatchToProps {
onChangeTextFontSize(fontSize: number): void;
onChangeTextPosition(position: 'auto' | 'center'): void;
onChangeTextContent(textContent: string[]): void;
onSwitchShowingTagsOnFrame(enabled: boolean): void;
}
function mapStateToProps(state: CombinedState): StateToProps {
@ -65,6 +68,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
textFontSize,
textPosition,
textContent,
showTagsOnFrame,
} = workspace;
return {
@ -79,6 +83,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
textFontSize,
textPosition,
textContent,
showTagsOnFrame,
};
}
@ -118,6 +123,9 @@ function mapDispatchToProps(dispatch: any): DispatchToProps {
const serialized = textContent.join(',');
dispatch(switchTextContent(serialized));
},
onSwitchShowingTagsOnFrame(enabled: boolean): void {
dispatch(switchShowingTagsOnFrame(enabled));
},
};
}

@ -103,6 +103,10 @@ const defaultState: AnnotationState = {
objectState: null,
frames: 50,
},
remove: {
objectState: null,
force: false,
},
statistics: {
visible: false,
collecting: false,
@ -466,7 +470,7 @@ export default (state = defaultState, action: AnyAction): AnnotationState => {
},
};
}
case AnnotationActionTypes.REMEMBER_CREATED_OBJECT: {
case AnnotationActionTypes.REMEMBER_OBJECT: {
const { payload } = action;
let { activeControl } = state.canvas;
@ -719,6 +723,17 @@ export default (state = defaultState, action: AnyAction): AnnotationState => {
},
};
}
case AnnotationActionTypes.REMOVE_OBJECT: {
const { objectState, force } = action.payload;
return {
...state,
remove: {
...state.remove,
objectState,
force,
},
};
}
case AnnotationActionTypes.REMOVE_OBJECT_SUCCESS: {
const { objectState, history } = action.payload;
const contextMenuClientID = state.canvas.contextMenu.clientID;
@ -742,6 +757,19 @@ export default (state = defaultState, action: AnyAction): AnnotationState => {
visible: objectState.clientID === contextMenuClientID ? false : contextMenuVisible,
},
},
remove: {
objectState: null,
force: false,
},
};
}
case AnnotationActionTypes.REMOVE_OBJECT_FAILED: {
return {
...state,
remove: {
objectState: null,
force: false,
},
};
}
case AnnotationActionTypes.PASTE_SHAPE: {

@ -631,6 +631,10 @@ export interface AnnotationState {
objectState: any | null;
frames: number;
};
remove: {
objectState: any;
force: boolean;
}
statistics: {
collecting: boolean;
visible: boolean;
@ -705,6 +709,7 @@ export interface WorkspaceSettingsState {
textFontSize: number;
textPosition: 'auto' | 'center';
textContent: string;
showTagsOnFrame: boolean;
}
export interface ShapesSettingsState {

@ -39,6 +39,7 @@ const defaultState: SettingsState = {
algorithmsLocked: false,
buttonVisible: false,
},
showTagsOnFrame: true,
},
player: {
canvasBackgroundColor: '#ffffff',
@ -363,6 +364,15 @@ export default (state = defaultState, action: AnyAction): SettingsState => {
},
};
}
case SettingsActionTypes.SWITCH_SHOWING_TAGS_ON_FRAME: {
return {
...state,
workspace: {
...state.workspace,
showTagsOnFrame: action.payload.showTagsOnFrame,
},
};
}
case BoundariesActionTypes.RESET_AFTER_ERROR:
case AnnotationActionTypes.GET_JOB_SUCCESS: {
const { job } = action.payload;

2
package-lock.json generated

@ -253,7 +253,7 @@
"devDependencies": {}
},
"cvat-ui": {
"version": "1.38.1",
"version": "1.39.0",
"license": "MIT",
"dependencies": {
"@ant-design/icons": "^4.6.3",

@ -1,4 +1,4 @@
// Copyright (C) 2020-2021 Intel Corporation
// Copyright (C) 2020-2022 Intel Corporation
//
// SPDX-License-Identifier: MIT
@ -9,33 +9,29 @@ import { taskName, labelName } from '../../support/const';
context('Tag annotation mode.', () => {
const caseId = '22';
function checkFrameTagsDontExist() {
cy.get('.cvat-canvas-frame-tags span.cvat-frame-tag').should('not.exist');
}
function checkCountFrameTags(countTags) {
if (countTags == 0) {
cy.get('span.cvat-tag-annotation-sidebar-frame-tag-label').should('not.exist');
} else {
cy.get('span.cvat-tag-annotation-sidebar-frame-tag-label').should('have.length', countTags);
}
cy.get('.cvat-canvas-frame-tags span.cvat-frame-tag').should('have.length', countTags);
}
function checkPresenceFrameTags() {
cy.get('.cvat-tag-annotation-sidebar-frame-tags').within(() => {
cy.get('.cvat-tag-annotation-sidebar-frame-tag-label').should('exist');
cy.get('.cvat-canvas-frame-tags').within(() => {
cy.get('.cvat-frame-tag').should('exist');
});
}
function addTag() {
cy.get('.cvat-tag-annotation-sidebar-buttons').contains('Add tag').click();
}
function skipFrame() {
cy.get('.cvat-tag-annotation-sidebar-buttons').contains('Skip frame').click();
cy.get('.cvat-add-tag-button').click();
}
function changeCheckboxAutomaticallyGoToNextFrame(value) {
cy.get('.cvat-tag-annotation-sidebar-checkbox-skip-frame').within(() => {
if (value == 'check') {
if (value === 'check') {
cy.get('[type="checkbox"]').check();
} else if (value == 'uncheck') {
} else if (value === 'uncheck') {
cy.get('[type="checkbox"]').uncheck();
}
});
@ -48,15 +44,7 @@ context('Tag annotation mode.', () => {
describe(`Testing case "${caseId}"`, () => {
it('Go to tag annotation', () => {
cy.changeWorkspace('Tag annotation', labelName);
checkCountFrameTags(0);
});
it('Skip frame', () => {
skipFrame();
cy.checkFrameNum(1);
checkCountFrameTags(0);
cy.goToPreviousFrame(0);
checkCountFrameTags(0);
checkFrameTagsDontExist();
});
it('Add tag', () => {
@ -67,14 +55,37 @@ context('Tag annotation mode.', () => {
it('Set "Automatically go to the next frame" to true and add tag', () => {
cy.goToNextFrame(1);
checkCountFrameTags(0);
checkFrameTagsDontExist();
changeCheckboxAutomaticallyGoToNextFrame('check');
addTag();
cy.checkFrameNum(2);
checkCountFrameTags(0);
checkFrameTagsDontExist();
cy.goToPreviousFrame(1);
checkCountFrameTags(1);
checkPresenceFrameTags();
});
it('Disable show tags on frame', () => {
cy.openSettings();
cy.get('.cvat-settings-modal').within(() => {
cy.contains('Workspace').click();
cy.get('.cvat-workspace-settings-show-frame-tags').within(() => {
cy.get('[type="checkbox"]').uncheck();
});
});
cy.closeSettings();
cy.get('.cvat-canvas-frame-tags').should('not.exist');
});
});
after(() => {
cy.openSettings();
cy.get('.cvat-settings-modal').within(() => {
cy.contains('Workspace').click();
cy.get('.cvat-workspace-settings-show-frame-tags').within(() => {
cy.get('[type="checkbox"]').check();
});
});
cy.closeSettings();
});
});

@ -1,4 +1,4 @@
// Copyright (C) 2020-2021 Intel Corporation
// Copyright (C) 2020-2022 Intel Corporation
//
// SPDX-License-Identifier: MIT
@ -12,11 +12,11 @@ context('Delete unlock/lock object', () => {
const createRectangleShape2Points = {
points: 'By 2 Points',
type: 'Shape',
labelName: labelName,
firstX: 100,
firstY: 100,
secondX: 300,
secondY: 300,
labelName,
};
function lockObject() {
@ -26,7 +26,7 @@ context('Delete unlock/lock object', () => {
}
function deleteObjectViaShortcut(shortcut, stateLockObject) {
if (stateLockObject == 'unlock') {
if (stateLockObject === 'unlock') {
cy.get('.cvat-canvas-container').within(() => {
cy.get('.cvat_canvas_shape').trigger('mousemove').should('have.class', 'cvat_canvas_shape_activated');
});
@ -65,17 +65,20 @@ context('Delete unlock/lock object', () => {
});
}
function checkFailDeleteLockObject(shortcut) {
deleteObjectViaShortcut(shortcut, 'lock');
checkExistObject('exist');
cy.get('.cvat-notification-notice-remove-object-failed').should('exist');
}
function checkExistObject(state) {
cy.get('.cvat_canvas_shape').should(state);
cy.get('.cvat-objects-sidebar-state-item').should(state);
}
function checkFailDeleteLockObject(shortcut) {
deleteObjectViaShortcut(shortcut, 'lock');
checkExistObject('exist');
cy.get('.cvat-modal-confirm').should('exist');
cy.get('.cvat-modal-confirm').within(() => {
cy.contains('Cancel').click();
});
}
before(() => {
cy.openTaskJob(taskName);
});
@ -105,7 +108,7 @@ context('Delete unlock/lock object', () => {
cy.createRectangle(createRectangleShape2Points);
lockObject();
deleteObjectViaGUIFromSidebar();
actionOnConfirmWindow('OK');
actionOnConfirmWindow('Yes');
checkExistObject('not.exist');
});

@ -103,14 +103,13 @@ context('Object make a copy.', () => {
cy.createPolyline(createPolylinesShape);
cy.createEllipse(createEllipseShape);
cy.createPoint(createPointsShape);
cy.createTag(labelName);
});
describe(`Testing case "${caseId}"`, () => {
it('Make a copy via sidebar.', () => {
let coordX = 100;
const coordY = 300;
for (let id = 1; id < countObject + 2; id++) {
for (let id = 1; id < countObject + 1; id++) {
cy.get(`#cvat-objects-sidebar-state-item-${id}`).within(() => {
cy.get('[aria-label="more"]').trigger('mouseover').wait(300); // Wait dropdown menu transition
});
@ -123,15 +122,15 @@ context('Object make a copy.', () => {
});
it('After copying via sidebar, the attributes of the objects are the same.', () => {
checkObjectArrSize(12, 14);
checkObjectArrSize(12, 12);
for (let id = 1; id < countObject; id++) {
// Parameters id 1 equal patameters id 8, 2 to 9, etc.
compareObjectsAttr(`#cvat_canvas_shape_${id}`, `#cvat_canvas_shape_${id + countObject + 1}`);
compareObjectsAttr(`#cvat_canvas_shape_${id}`, `#cvat_canvas_shape_${id + countObject}`);
}
for (let idSidebar = 1; idSidebar < 7; idSidebar++) {
compareObjectsSidebarAttr(
`#cvat-objects-sidebar-state-item-${idSidebar}`,
`#cvat-objects-sidebar-state-item-${idSidebar + countObject + 1}`,
`#cvat-objects-sidebar-state-item-${idSidebar + countObject}`,
); // Parameters sidebar id 1 equal patameters sidebar id 8, 2 to 9, etc.
}
});
@ -165,15 +164,15 @@ context('Object make a copy.', () => {
'After copying via object context menu, the attributes of the objects are the same.',
{ browser: '!firefox' },
() => {
checkObjectArrSize(17, 19); // The point and tag was not copied via the object's context menu
checkObjectArrSize(17, 17); // The point was not copied via the object's context menu
for (let id = 1; id < countObject; id++) {
// Parameters id 1 equal patameters id 13, 2 to 14, etc.
compareObjectsAttr(`#cvat_canvas_shape_${id}`, `#cvat_canvas_shape_${id + countObject + 8}`);
compareObjectsAttr(`#cvat_canvas_shape_${id}`, `#cvat_canvas_shape_${id + 2 * countObject}`);
}
for (let idSidebar = 1; idSidebar < 6; idSidebar++) {
for (let idSidebar = 1; idSidebar < countObject; idSidebar++) {
compareObjectsSidebarAttr(
`#cvat-objects-sidebar-state-item-${idSidebar}`,
`#cvat-objects-sidebar-state-item-${idSidebar + countObject + 8}`,
`#cvat-objects-sidebar-state-item-${idSidebar + 2 * countObject}`,
); // Parameters sidebar id 1 equal patameters sidebar id 15, 2 to 16, etc.
}
},

@ -1,4 +1,4 @@
// Copyright (C) 2021 Intel Corporation
// Copyright (C) 2021-2022 Intel Corporation
//
// SPDX-License-Identifier: MIT
@ -139,12 +139,5 @@ context('Repeat draw feature.', () => {
checkCountShapes(10);
checkShapeType('#cvat-objects-sidebar-state-item-10', 'CUBOID SHAPE');
});
it('Cteate and repeat the creating of the tag.', () => {
cy.createTag(labelName);
repeatDrawningStart(); // Repeat the creating the tag
cy.get('#cvat-objects-sidebar-state-item-12').should('exist');
checkShapeType('#cvat-objects-sidebar-state-item-12', 'TAG');
});
});
});

@ -78,6 +78,8 @@ context('New organization pipeline.', () => {
}
before(() => {
cy.visit('/');
cy.login();
cy.imageGenerator(
imagesFolder,
imageFileName,
@ -90,8 +92,8 @@ context('New organization pipeline.', () => {
imagesCount,
);
cy.createZipArchive(directoryToArchive, archivePath);
cy.logout();
cy.visit('/');
cy.goToRegisterPage();
cy.userRegistration(
secondUser.firstName,

@ -1,4 +1,4 @@
// Copyright (C) 2020-2021 Intel Corporation
// Copyright (C) 2020-2022 Intel Corporation
//
// SPDX-License-Identifier: MIT
@ -16,9 +16,7 @@ context('Check if the UI not to crash after remove a tag', () => {
describe(`Testing issue "${issueId}"`, () => {
it('Add a tag', () => {
cy.changeWorkspace('Tag annotation');
cy.get('.cvat-tag-annotation-sidebar-buttons').within(() => {
cy.get('button').contains('Add tag').click({ force: true });
});
cy.get('.cvat-add-tag-button').click({ force: true });
cy.changeWorkspace('Standard');
});
it('Remove the tag', () => {

@ -35,7 +35,9 @@ Cypress.Commands.add('logout', (username = Cypress.env('user')) => {
});
cy.get('span[aria-label="logout"]').click();
cy.url().should('include', '/auth/login');
cy.intercept('/auth/login').as('login');
cy.visit('/auth/login'); // clear query parameter "next"
cy.wait('@login').then(() => cy.contains('Login').should('exist'));
});
Cypress.Commands.add('userRegistration', (firstName, lastName, userName, emailAddr, password) => {

Loading…
Cancel
Save