diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b2858d08f81..f172112b0a09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,7 @@ non-ascii paths while adding files from "Connected file share" (issue #4428) - Create manifest with cvat/server docker container command () - Cannot assign a resource to a user who has an organization () - Logs and annotations are not saved when logout from a job page () +- Added "type" field for all the labels, allows to reduce number of controls on annotation view () - Occluded not applied on canvas instantly for a skeleton elements () - Oriented bounding boxes broken with COCO format ss() - Fixed upload resumption in production environments diff --git a/cvat-core/src/enums.ts b/cvat-core/src/enums.ts index bfa114ab1eeb..9d20f28a5752 100644 --- a/cvat-core/src/enums.ts +++ b/cvat-core/src/enums.ts @@ -481,3 +481,16 @@ export enum WebhookSourceType { export enum WebhookContentType { JSON = 'application/json', } + +export enum LabelType { + ANY = 'any', + RECTANGLE = 'rectangle', + POLYGON = 'polygon', + POLYLINE = 'polyline', + POINTS = 'points', + ELLIPSE = 'ellipse', + CUBOID = 'cuboid', + SKELETON = 'skeleton', + MASK = 'mask', + TAG = 'tag', +} diff --git a/cvat-ui/package.json b/cvat-ui/package.json index 0b01b76f1b02..98474315172c 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.43.2", + "version": "1.44.0", "description": "CVAT single-page application", "main": "src/index.tsx", "scripts": { diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx index ce27851cf4a9..e6c4244b335c 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx @@ -1,14 +1,16 @@ // Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT import React, { useState, useEffect } from 'react'; import { connect } from 'react-redux'; import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons'; -import { SelectValue } from 'antd/lib/select'; import Layout, { SiderProps } from 'antd/lib/layout'; import Text from 'antd/lib/typography/Text'; +import { filterApplicableLabels } from 'utils/filter-applicable-labels'; +import { Label } from 'cvat-core-wrapper'; import { Canvas } from 'cvat-canvas-wrapper'; import { Canvas3d } from 'cvat-canvas3d-wrapper'; import { LogType } from 'cvat-logger'; @@ -147,6 +149,7 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX. activatedStateID === null || activatedIndex === -1 ? null : filteredStates[activatedIndex]; const activeAttribute = activeObjectState ? labelAttrMap[activeObjectState.label.id] : null; + const applicableLabels = activeObjectState ? filterApplicableLabels(activeObjectState, labels) : []; if (canvasIsReady) { if (activeObjectState) { @@ -308,12 +311,10 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX. nextObject={nextObject} /> { - const labelName = value as string; - const [newLabel] = labels.filter((_label): boolean => _label.name === labelName); - activeObjectState.label = newLabel; + currentLabel={activeObjectState.label.id} + labels={applicableLabels} + changeLabel={(value: Label): void => { + activeObjectState.label = value; updateAnnotations([activeObjectState]); }} /> diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-basics-edtior.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-basics-edtior.tsx index 63fd0b275ef0..f82e9d3d6acf 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-basics-edtior.tsx +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-basics-edtior.tsx @@ -1,14 +1,16 @@ // Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT import React from 'react'; -import Select, { SelectValue } from 'antd/lib/select'; +import { Label } from 'cvat-core-wrapper'; +import LabelSelector from 'components/label-selector/label-selector'; interface Props { - currentLabel: string; - labels: any[]; - changeLabel(value: SelectValue): void; + currentLabel: number; + labels: Label[]; + changeLabel(value: Label): void; } function ObjectBasicsEditor(props: Props): JSX.Element { @@ -16,15 +18,12 @@ function ObjectBasicsEditor(props: Props): JSX.Element { return (
- +
); } diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/styles.scss b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/styles.scss index a1e51ba4af65..d1e905a96024 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/styles.scss +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/styles.scss @@ -1,4 +1,5 @@ // Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -10,7 +11,7 @@ .attribute-annotation-sidebar:not(.ant-layout-sider-collapsed) { background: $background-color-2; - padding: 5px; + padding: $grid-unit-size; > .ant-layout-sider-children { display: flex; @@ -24,7 +25,7 @@ align-items: center; justify-content: space-between; font-size: 18px; - margin-top: 10px; + margin-top: $grid-unit-size; > span { max-width: 60%; @@ -40,13 +41,13 @@ .cvat-attribute-annotation-sidebar-basics-editor { display: flex; align-items: center; - justify-content: space-between; + justify-content: center; font-size: 18px; - margin: 10px 0; + margin: $grid-unit-size 0; } .attribute-annotations-sidebar-not-found-wrapper { - margin-top: 20px; + margin-top: $grid-unit-size * 3; text-align: center; flex-grow: 10; } @@ -56,7 +57,7 @@ } .attribute-annotation-sidebar-attr-list-wrapper { - margin: 10px 0 10px 10px; + margin: $grid-unit-size 0 $grid-unit-size $grid-unit-size; } .attribute-annotation-sidebar-attr-elem-wrapper { @@ -75,5 +76,5 @@ } .cvat-sidebar-collapse-button-spacer { - height: 32px; + height: $grid-unit-size * 4; } diff --git a/cvat-ui/src/components/annotation-page/canvas/brush-tools.tsx b/cvat-ui/src/components/annotation-page/canvas/brush-tools.tsx index cee633115713..79c947380a9a 100644 --- a/cvat-ui/src/components/annotation-page/canvas/brush-tools.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/brush-tools.tsx @@ -12,7 +12,8 @@ import Icon, { VerticalAlignBottomOutlined } from '@ant-design/icons'; import InputNumber from 'antd/lib/input-number'; import Select from 'antd/lib/select'; -import { getCore } from 'cvat-core-wrapper'; +import { filterApplicableForType } from 'utils/filter-applicable-labels'; +import { getCore, Label, LabelType } from 'cvat-core-wrapper'; import { Canvas, CanvasMode } from 'cvat-canvas-wrapper'; import { BrushIcon, EraserIcon, PolygonMinusIcon, PolygonPlusIcon, @@ -44,6 +45,7 @@ function BrushTools(): React.ReactPortal | null { const [brushForm, setBrushForm] = useState<'circle' | 'square'>('circle'); const [[top, left], setTopLeft] = useState([0, 0]); const [brushSize, setBrushSize] = useState(10); + const [applicableLabels, setApplicableLabels] = useState([]); const [removeUnderlyingPixels, setRemoveUnderlyingPixels] = useState(false); const dragBar = useDraggable( @@ -99,6 +101,10 @@ function BrushTools(): React.ReactPortal | null { } }, [currentTool, brushSize, brushForm, visible, defaultLabelID, editableState]); + useEffect(() => { + setApplicableLabels(filterApplicableForType(LabelType.MASK, labels)); + }, [labels]); + useEffect(() => { const canvasContainer = window.document.getElementsByClassName('cvat-canvas-container')[0]; if (canvasContainer) { @@ -254,9 +260,9 @@ function BrushTools(): React.ReactPortal | null { icon={} onClick={() => setRemoveUnderlyingPixels(!removeUnderlyingPixels)} /> - { !editableState && ( + { !editableState && !!applicableLabels.length && ( { if (Number.isInteger(labelID)) { diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/labels-list.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/labels-list.tsx index 24a571ff0bee..c90ecab7d79d 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/labels-list.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/labels-list.tsx @@ -1,4 +1,5 @@ // Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -6,7 +7,8 @@ import React, { useCallback, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import message from 'antd/lib/message'; -import { CombinedState } from 'reducers'; +import { LabelType } from 'cvat-core-wrapper'; +import { CombinedState, ObjectType } from 'reducers'; import { rememberObject, updateAnnotationsAsync } from 'actions/annotation-actions'; import LabelItemContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/label-item'; import GlobalHotKeys from 'utils/mousetrap-react'; @@ -15,6 +17,8 @@ function LabelsListComponent(): JSX.Element { const dispatch = useDispatch(); const labels = useSelector((state: CombinedState) => state.annotation.job.labels); const activatedStateID = useSelector((state: CombinedState) => state.annotation.annotations.activatedStateID); + const activeShapeType = useSelector((state: CombinedState) => state.annotation.drawing.activeShapeType); + const activeObjectType = useSelector((state: CombinedState) => state.annotation.drawing.activeObjectType); const states = useSelector((state: CombinedState) => state.annotation.annotations.states); const keyMap = useSelector((state: CombinedState) => state.shortcuts.keyMap); @@ -72,14 +76,22 @@ function LabelsListComponent(): JSX.Element { if (Number.isInteger(labelID) && label) { if (Number.isInteger(activatedStateID)) { const activatedState = states.filter((state: any) => state.clientID === activatedStateID)[0]; - if (activatedState) { + const bothAreTags = activatedState.objectType === ObjectType.TAG && label.type === ObjectType.TAG; + const labelIsApplicable = label.type === LabelType.ANY || + activatedState.shapeType === label.type || bothAreTags; + if (activatedState && labelIsApplicable) { activatedState.label = label; dispatch(updateAnnotationsAsync([activatedState])); } } else { - dispatch(rememberObject({ activeLabelID: labelID })); - message.destroy(); - message.success(`Default label was changed to "${label.name}"`); + const bothAreTags = activeObjectType === ObjectType.TAG && label.type === ObjectType.TAG; + const labelIsApplicable = label.type === LabelType.ANY || + activeShapeType === label.type || bothAreTags; + if (labelIsApplicable) { + dispatch(rememberObject({ activeLabelID: labelID })); + message.destroy(); + message.success(`Default label has been changed to "${label.name}"`); + } } } }, diff --git a/cvat-ui/src/components/annotation-page/standard3D-workspace/controls-side-bar/controls-side-bar.tsx b/cvat-ui/src/components/annotation-page/standard3D-workspace/controls-side-bar/controls-side-bar.tsx index 766294629176..f93d33016e96 100644 --- a/cvat-ui/src/components/annotation-page/standard3D-workspace/controls-side-bar/controls-side-bar.tsx +++ b/cvat-ui/src/components/annotation-page/standard3D-workspace/controls-side-bar/controls-side-bar.tsx @@ -1,10 +1,12 @@ // Copyright (C) 2021-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT import React from 'react'; import Layout from 'antd/lib/layout'; import { ActiveControl } from 'reducers'; +import { Label, LabelType } from 'cvat-core-wrapper'; import { Canvas3d as Canvas } from 'cvat-canvas3d-wrapper'; import MoveControl, { Props as MoveControlProps, @@ -20,13 +22,14 @@ import GroupControl, { } from 'components/annotation-page/standard-workspace/controls-side-bar/group-control'; import GlobalHotKeys, { KeyMap } from 'utils/mousetrap-react'; import ControlVisibilityObserver from 'components/annotation-page/standard-workspace/controls-side-bar/control-visibility-observer'; +import { filterApplicableForType } from 'utils/filter-applicable-labels'; interface Props { keyMap: KeyMap; canvasInstance: Canvas; activeControl: ActiveControl; normalizedKeyMap: Record; - labels: any[]; + labels: Label[]; jobInstance: any; repeatDrawShape(): void; redrawShape(): void; @@ -55,6 +58,7 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element { jobInstance, } = props; + const applicableLabels = filterApplicableForType(LabelType.CUBOID, labels); const preventDefault = (event: KeyboardEvent | undefined): void => { if (event) { event.preventDefault(); @@ -74,7 +78,7 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element { }, }; - if (labels.length) { + if (applicableLabels.length) { handlers = { ...handlers, PASTE_SHAPE: (event: KeyboardEvent | undefined) => { @@ -138,7 +142,7 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element { diff --git a/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/shortcuts-select.tsx b/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/shortcuts-select.tsx index d75ced5c1ece..2c8fd4aa2a76 100644 --- a/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/shortcuts-select.tsx +++ b/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/shortcuts-select.tsx @@ -1,14 +1,15 @@ // Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT import React, { useState, useEffect } from 'react'; -import { useSelector } from 'react-redux'; import { Row, Col } from 'antd/lib/grid'; import Text from 'antd/lib/typography/Text'; import Select from 'antd/lib/select'; -import { CombinedState, DimensionType } from 'reducers'; +import { DimensionType } from 'reducers'; +import { Label } from 'cvat-core-wrapper'; import GlobalHotKeys, { KeyMap } from 'utils/mousetrap-react'; import { shift } from 'utils/math'; @@ -18,6 +19,7 @@ interface ShortcutLabelMap { type Props = { onShortcutPress(event: KeyboardEvent | undefined, labelID: number): void; + labels: Label[]; }; const defaultShortcutLabelMap = { @@ -34,8 +36,7 @@ const defaultShortcutLabelMap = { } as ShortcutLabelMap; const ShortcutsSelect = (props: Props): JSX.Element => { - const { onShortcutPress } = props; - const { labels } = useSelector((state: CombinedState) => state.annotation.job); + const { labels, onShortcutPress } = props; const [shortcutLabelMap, setShortcutLabelMap] = useState(defaultShortcutLabelMap); const keyMap: KeyMap = {}; @@ -69,7 +70,7 @@ const ShortcutsSelect = (props: Props): JSX.Element => { if (event) { event.preventDefault(); } - onShortcutPress(event, label.id); + onShortcutPress(event, label.id as number); }; }); diff --git a/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/tag-annotation-sidebar.tsx b/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/tag-annotation-sidebar.tsx index 9e9d4f1e2793..c67258a55541 100644 --- a/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/tag-annotation-sidebar.tsx +++ b/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/tag-annotation-sidebar.tsx @@ -1,4 +1,5 @@ // Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -23,10 +24,11 @@ import { } from 'actions/annotation-actions'; import { Canvas } from 'cvat-canvas-wrapper'; import { Canvas3d } from 'cvat-canvas3d-wrapper'; +import { getCore, Label, LabelType } from 'cvat-core-wrapper'; import { CombinedState, ObjectType } from 'reducers'; +import { filterApplicableForType } from 'utils/filter-applicable-labels'; import { adjustContextImagePosition } from 'components/annotation-page/standard-workspace/context-image/context-image'; import LabelSelector from 'components/label-selector/label-selector'; -import { getCore } from 'cvat-core-wrapper'; import isAbleToChangeFrame from 'utils/is-able-to-change-frame'; import GlobalHotKeys, { KeyMap } from 'utils/mousetrap-react'; import ShortcutsSelect from './shortcuts-select'; @@ -114,12 +116,15 @@ function TagAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.Elemen } }; - const controlsDisabled = !labels.length || frameData.deleted; - const defaultLabelID = labels.length ? labels[0].id : null; + const [applicableLabels, setApplicableLabels] = useState( + filterApplicableForType(LabelType.TAG, labels), + ); + const controlsDisabled = !applicableLabels.length || frameData.deleted; + const defaultLabelID = applicableLabels.length ? applicableLabels[0].id as number : null; const [sidebarCollapsed, setSidebarCollapsed] = useState(false); const [frameTags, setFrameTags] = useState([] as any[]); - const [selectedLabelID, setSelectedLabelID] = useState(defaultLabelID); + const [selectedLabelID, setSelectedLabelID] = useState(defaultLabelID); const [skipFrame, setSkipFrame] = useState(false); useEffect(() => { @@ -128,6 +133,10 @@ function TagAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.Elemen } }, []); + useEffect(() => { + setApplicableLabels(filterApplicableForType(LabelType.TAG, labels)); + }, [labels]); + useEffect(() => { const listener = (event: Event): void => { if ( @@ -211,7 +220,9 @@ function TagAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.Elemen const handlers = { SWITCH_DRAW_MODE: (event: KeyboardEvent | undefined) => { preventDefault(event); - onShortcutPress(event, selectedLabelID); + if (selectedLabelID !== null) { + onShortcutPress(event, selectedLabelID); + } }, }; @@ -259,7 +270,7 @@ function TagAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.Elemen onAddTag(selectedLabelID)} + onClick={() => onAddTag(selectedLabelID as number)} icon={} /> @@ -286,7 +297,7 @@ function TagAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.Elemen - + diff --git a/cvat-ui/src/components/labels-editor/label-form.tsx b/cvat-ui/src/components/labels-editor/label-form.tsx index 169150404e26..e116c1cfca6e 100644 --- a/cvat-ui/src/components/labels-editor/label-form.tsx +++ b/cvat-ui/src/components/labels-editor/label-form.tsx @@ -1,4 +1,5 @@ // Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -13,7 +14,7 @@ import Form, { FormInstance } from 'antd/lib/form'; import Badge from 'antd/lib/badge'; import { Store } from 'antd/lib/form/interface'; -import { RawAttribute } from 'cvat-core-wrapper'; +import { RawAttribute, LabelType } from 'cvat-core-wrapper'; import CVATTooltip from 'components/common/cvat-tooltip'; import ColorPicker from 'components/annotation-page/standard-workspace/objects-side-bar/color-picker'; import { ColorizeIcon } from 'icons'; @@ -78,7 +79,7 @@ export default class LabelForm extends React.Component { name: values.name, id: label ? label.id : idGenerator(), color: values.color, - type: label && label.id as number > 0 ? label?.type : values.type || label?.type || 'any', + type: values.type || label?.type || LabelType.ANY, attributes: (values.attributes || []).map((attribute: Store) => { let attrValues: string | string[] = attribute.values; if (!Array.isArray(attrValues)) { @@ -451,6 +452,37 @@ export default class LabelForm extends React.Component { ); } + private renderLabelTypeInput(): JSX.Element { + const { onSkeletonSubmit } = this.props; + const isSkeleton = !!onSkeletonSubmit; + + const types = Object.values(LabelType) + .filter((type: string) => type !== LabelType.SKELETON); + const { label } = this.props; + const defaultType = isSkeleton ? LabelType.SKELETON : LabelType.ANY; + const value = label ? label.type : defaultType; + + return ( + + + + ); + } + private renderNewAttributeButton(): JSX.Element { return ( @@ -554,7 +586,8 @@ export default class LabelForm extends React.Component { return (
- {this.renderLabelNameInput()} + {this.renderLabelNameInput()} + {this.renderLabelTypeInput()} {this.renderChangeColorButton()} diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx index 8be016b83a7a..4762ed7d355d 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx @@ -23,9 +23,9 @@ import { import ObjectStateItemComponent from 'components/annotation-page/standard-workspace/objects-side-bar/object-item'; import { getColor } from 'components/annotation-page/standard-workspace/objects-side-bar/shared'; import { shift } from 'utils/math'; -import { Label } from 'cvat-core-wrapper'; import { Canvas, CanvasMode } from 'cvat-canvas-wrapper'; import { Canvas3d } from 'cvat-canvas3d-wrapper'; +import { filterApplicableLabels } from 'utils/filter-applicable-labels'; interface OwnProps { readonly: boolean; @@ -326,6 +326,8 @@ class ObjectItemContainer extends React.PureComponent { jobInstance, } = this.props; + const applicableLabels = filterApplicableLabels(objectState, labels); + return ( { attributes={attributes} elements={objectState.elements} normalizedKeyMap={normalizedKeyMap} - labels={labels.filter((label: Label) => ( - [objectState.shapeType || objectState.objectType, 'any'].includes(label.type) - ))} + labels={applicableLabels} colorBy={colorBy} activate={this.activate} remove={this.remove} diff --git a/cvat-ui/src/cvat-core-wrapper.ts b/cvat-ui/src/cvat-core-wrapper.ts index aa2120420a35..16c49c478a9c 100644 --- a/cvat-ui/src/cvat-core-wrapper.ts +++ b/cvat-ui/src/cvat-core-wrapper.ts @@ -8,7 +8,7 @@ import Webhook from 'cvat-core/src/webhook'; import { Label, Attribute, RawAttribute, RawLabel, } from 'cvat-core/src/labels'; -import { ShapeType } from 'cvat-core/src/enums'; +import { ShapeType, LabelType } from 'cvat-core/src/enums'; import { Storage, StorageData } from 'cvat-core/src/storage'; const cvat: any = _cvat; @@ -28,6 +28,7 @@ export { Label, Attribute, ShapeType, + LabelType, Storage, Webhook, }; diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index 15bd9d55fd10..e2d33d5f3a9c 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -81,7 +81,7 @@ const defaultState: AnnotationState = { }, drawing: { activeShapeType: ShapeType.RECTANGLE, - activeLabelID: 0, + activeLabelID: null, activeObjectType: ObjectType.SHAPE, }, annotations: { @@ -222,7 +222,7 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { }, drawing: { ...state.drawing, - activeLabelID: job.labels.length ? job.labels[0].id : null, + activeLabelID: defaultLabel ? defaultLabel.id : null, activeObjectType: job.mode === 'interpolation' ? ObjectType.TRACK : ObjectType.SHAPE, activeShapeType, }, diff --git a/cvat-ui/src/reducers/index.ts b/cvat-ui/src/reducers/index.ts index ae59994cf95f..fd69be0db3a2 100644 --- a/cvat-ui/src/reducers/index.ts +++ b/cvat-ui/src/reducers/index.ts @@ -692,7 +692,7 @@ export interface AnnotationState { activeRectDrawingMethod?: RectDrawingMethod; activeCuboidDrawingMethod?: CuboidDrawingMethod; activeNumOfPoints?: number; - activeLabelID: number; + activeLabelID: number | null; activeObjectType: ObjectType; activeInitialState?: any; }; diff --git a/cvat-ui/src/utils/filter-applicable-labels.ts b/cvat-ui/src/utils/filter-applicable-labels.ts new file mode 100644 index 000000000000..1c1689d40c4e --- /dev/null +++ b/cvat-ui/src/utils/filter-applicable-labels.ts @@ -0,0 +1,24 @@ +// Copyright (C) 2022 CVAT.ai Corporation +// +// SPDX-License-Identifier: MIT + +import { Label, ObjectState, LabelType } from 'cvat-core-wrapper'; + +export function filterApplicableForType(type: LabelType, labels: Label[]): Label[] { + const applicableLabels = labels.filter((label: Label) => ( + [type, 'any'].includes(label.type) + )); + + return applicableLabels; +} + +export function filterApplicableLabels(objectState: ObjectState, labels: Label[]): Label[] { + const applicableLabels = filterApplicableForType((objectState.shapeType || 'tag') as unknown as LabelType, labels); + + // a label the object has at this moment considered like applicable label + if (!applicableLabels.find((label: Label) => label.id === objectState.label.id)) { + return [objectState.label, ...applicableLabels]; + } + + return applicableLabels; +} diff --git a/cvat/apps/engine/serializers.py b/cvat/apps/engine/serializers.py index e30dac1df2ca..163c90d208e8 100644 --- a/cvat/apps/engine/serializers.py +++ b/cvat/apps/engine/serializers.py @@ -135,6 +135,10 @@ def update_instance(validated_data, parent_instance, parent_label=None): except models.Label.DoesNotExist: raise exceptions.NotFound(detail='Not found label with id #{} to change'.format(validated_data['id'])) db_label.name = validated_data.get('name', db_label.name) + updated_type = validated_data.get('type', db_label.type) + if 'skeleton' not in [db_label.type, updated_type]: + # do not permit to change types from/to "skeleton" + db_label.type = updated_type logger.info("{}({}) label was updated".format(db_label.name, db_label.id)) else: db_label = models.Label.objects.create(name=validated_data.get('name'), type=validated_data.get('type'), diff --git a/tests/cypress/integration/actions_tasks/case_72_hotkeys_change_labels.js b/tests/cypress/integration/actions_tasks/case_72_hotkeys_change_labels.js index 598e26c1725c..cdabfcab3e67 100644 --- a/tests/cypress/integration/actions_tasks/case_72_hotkeys_change_labels.js +++ b/tests/cypress/integration/actions_tasks/case_72_hotkeys_change_labels.js @@ -126,7 +126,7 @@ context('Hotkeys to change labels feature.', () => { cy.contains('button', 'Shape').click(); }); cy.get('body').type('{Ctrl}2'); - cy.contains(`Default label was changed to "${secondLabelCurrentVal}"`).should('exist'); + cy.contains(`Default label has been changed to "${secondLabelCurrentVal}"`).should('exist'); cy.get('.cvat-canvas-container').click(500, 500).click(600, 600); cy.get('#cvat-objects-sidebar-state-item-2') .find('.cvat-objects-sidebar-state-item-label-selector')