diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index d026c524..24e9923f 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -1084,6 +1084,7 @@ export class CanvasViewImpl implements CanvasView, Listener { this.activeElement = { ...activeElement }; const shape = this.svgShapes[clientID]; + let text = this.svgTexts[clientID]; if (!text) { text = this.addText(state); diff --git a/cvat-core/src/annotations-collection.js b/cvat-core/src/annotations-collection.js index a3e49f15..309d387d 100644 --- a/cvat-core/src/annotations-collection.js +++ b/cvat-core/src/annotations-collection.js @@ -802,7 +802,9 @@ let minimumState = null; for (const state of objectStates) { checkObjectType('object state', state, null, ObjectState); - if (state.outside || state.hidden) continue; + if (state.outside || state.hidden || state.objectType === ObjectType.TAG) { + continue; + } const object = this.objects[state.clientID]; if (typeof (object) === 'undefined') { @@ -810,9 +812,9 @@ 'The object has not been saved yet. Call annotations.put([state]) before', ); } - const distance = object.constructor.distance(state.points, x, y); - if (distance !== null && (minimumDistance === null || distance < minimumDistance)) { + if (distance !== null && (minimumDistance === null + || distance < minimumDistance)) { minimumDistance = distance; minimumState = state; } diff --git a/cvat-core/src/annotations-filter.js b/cvat-core/src/annotations-filter.js index 62530167..8a00cab2 100644 --- a/cvat-core/src/annotations-filter.js +++ b/cvat-core/src/annotations-filter.js @@ -8,7 +8,10 @@ */ const jsonpath = require('jsonpath'); -const { AttributeType } = require('./enums'); +const { + AttributeType, + ObjectType, +} = require('./enums'); const { ArgumentError } = require('./exceptions'); @@ -165,18 +168,21 @@ class AnnotationsFilter { let xbr = Number.MIN_SAFE_INTEGER; let ytl = Number.MAX_SAFE_INTEGER; let ybr = Number.MIN_SAFE_INTEGER; + let [width, height] = [null, null]; + + if (state.objectType !== ObjectType.TAG) { + state.points.forEach((coord, idx) => { + if (idx % 2) { // y + ytl = Math.min(ytl, coord); + ybr = Math.max(ybr, coord); + } else { // x + xtl = Math.min(xtl, coord); + xbr = Math.max(xbr, coord); + } + }); + [width, height] = [xbr - xtl, ybr - ytl]; + } - state.points.forEach((coord, idx) => { - if (idx % 2) { // y - ytl = Math.min(ytl, coord); - ybr = Math.max(ybr, coord); - } else { // x - xtl = Math.min(xtl, coord); - xbr = Math.max(xbr, coord); - } - }); - - const [width, height] = [xbr - xtl, ybr - ytl]; const attributes = {}; Object.keys(state.attributes).reduce((acc, key) => { const attr = labelAttributes[key]; diff --git a/cvat-core/src/annotations-objects.js b/cvat-core/src/annotations-objects.js index 65a30b56..245d8e37 100644 --- a/cvat-core/src/annotations-objects.js +++ b/cvat-core/src/annotations-objects.js @@ -1139,6 +1139,7 @@ attributes: { ...this.attributes }, label: this.label, group: this.groupObject, + color: this.color, updated: this.updated, frame, }; @@ -1171,6 +1172,10 @@ this._saveLock(data.lock); } + if (updated.color) { + this._saveColor(data.color); + } + this.updateTimestamp(updated); updated.reset(); diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index 44aeb752..8e41642a 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -84,10 +84,10 @@ export enum AnnotationActionTypes { COPY_SHAPE = 'COPY_SHAPE', PASTE_SHAPE = 'PASTE_SHAPE', EDIT_SHAPE = 'EDIT_SHAPE', - DRAW_SHAPE = 'DRAW_SHAPE', REPEAT_DRAW_SHAPE = 'REPEAT_DRAW_SHAPE', SHAPE_DRAWN = 'SHAPE_DRAWN', RESET_CANVAS = 'RESET_CANVAS', + REMEMBER_CREATED_OBJECT = 'REMEMBER_CREATED_OBJECT', UPDATE_ANNOTATIONS_SUCCESS = 'UPDATE_ANNOTATIONS_SUCCESS', UPDATE_ANNOTATIONS_FAILED = 'UPDATE_ANNOTATIONS_FAILED', CREATE_ANNOTATIONS_SUCCESS = 'CREATE_ANNOTATIONS_SUCCESS', @@ -843,15 +843,17 @@ ThunkAction, {}, {}, AnyAction> { }; } -export function drawShape( - shapeType: ShapeType, - labelID: number, +export function rememberObject( objectType: ObjectType, + labelID: number, + shapeType?: ShapeType, points?: number, rectDrawingMethod?: RectDrawingMethod, ): AnyAction { - let activeControl = ActiveControl.DRAW_RECTANGLE; - if (shapeType === ShapeType.POLYGON) { + let activeControl = ActiveControl.CURSOR; + if (shapeType === ShapeType.RECTANGLE) { + activeControl = ActiveControl.DRAW_RECTANGLE; + } else if (shapeType === ShapeType.POLYGON) { activeControl = ActiveControl.DRAW_POLYGON; } else if (shapeType === ShapeType.POLYLINE) { activeControl = ActiveControl.DRAW_POLYLINE; @@ -860,7 +862,7 @@ export function drawShape( } return { - type: AnnotationActionTypes.DRAW_SHAPE, + type: AnnotationActionTypes.REMEMBER_CREATED_OBJECT, payload: { shapeType, labelID, @@ -1153,12 +1155,28 @@ export function searchAnnotationsAsync( export function pasteShapeAsync(): ThunkAction, {}, {}, AnyAction> { return async (dispatch: ActionCreator): Promise => { - const initialState = getStore().getState().annotation.drawing.activeInitialState; - const { instance: canvasInstance } = getStore().getState().annotation.canvas; + const { + canvas: { + instance: canvasInstance, + }, + job: { + instance: jobInstance, + }, + player: { + frame: { + number: frameNumber, + }, + }, + drawing: { + activeInitialState: initialState, + }, + } = getStore().getState().annotation; if (initialState) { - let activeControl = ActiveControl.DRAW_RECTANGLE; - if (initialState.shapeType === ShapeType.POINTS) { + let activeControl = ActiveControl.CURSOR; + if (initialState.shapeType === ShapeType.RECTANGLE) { + activeControl = ActiveControl.DRAW_RECTANGLE; + } else if (initialState.shapeType === ShapeType.POINTS) { activeControl = ActiveControl.DRAW_POINTS; } else if (initialState.shapeType === ShapeType.POLYGON) { activeControl = ActiveControl.DRAW_POLYGON; @@ -1174,10 +1192,20 @@ export function pasteShapeAsync(): ThunkAction, {}, {}, AnyAction> }); canvasInstance.cancel(); - canvasInstance.draw({ - enabled: true, - initialState, - }); + if (initialState.objectType === ObjectType.TAG) { + const objectState = new cvat.classes.ObjectState({ + objectType: ObjectType.TAG, + label: initialState.label, + attributes: initialState.attributes, + frame: frameNumber, + }); + dispatch(createAnnotationsAsync(jobInstance, frameNumber, [objectState])); + } else { + canvasInstance.draw({ + enabled: true, + initialState, + }); + } } }; } @@ -1185,20 +1213,36 @@ export function pasteShapeAsync(): ThunkAction, {}, {}, AnyAction> export function repeatDrawShapeAsync(): ThunkAction, {}, {}, AnyAction> { return async (dispatch: ActionCreator): Promise => { const { - activeShapeType, - activeNumOfPoints, - activeRectDrawingMethod, - } = getStore().getState().annotation.drawing; - - const { instance: canvasInstance } = getStore().getState().annotation.canvas; + canvas: { + instance: canvasInstance, + }, + job: { + labels, + instance: jobInstance, + }, + player: { + frame: { + number: frameNumber, + }, + }, + drawing: { + activeObjectType, + activeLabelID, + activeShapeType, + activeNumOfPoints, + activeRectDrawingMethod, + }, + } = getStore().getState().annotation; - let activeControl = ActiveControl.DRAW_RECTANGLE; - if (activeShapeType === ShapeType.POLYGON) { + let activeControl = ActiveControl.CURSOR; + if (activeShapeType === ShapeType.RECTANGLE) { + activeControl = ActiveControl.DRAW_RECTANGLE; + } else if (activeShapeType === ShapeType.POINTS) { + activeControl = ActiveControl.DRAW_POINTS; + } else if (activeShapeType === ShapeType.POLYGON) { activeControl = ActiveControl.DRAW_POLYGON; } else if (activeShapeType === ShapeType.POLYLINE) { activeControl = ActiveControl.DRAW_POLYLINE; - } else if (activeShapeType === ShapeType.POINTS) { - activeControl = ActiveControl.DRAW_POINTS; } dispatch({ @@ -1209,12 +1253,21 @@ export function repeatDrawShapeAsync(): ThunkAction, {}, {}, AnyAc }); canvasInstance.cancel(); - canvasInstance.draw({ - enabled: true, - rectDrawingMethod: activeRectDrawingMethod, - numberOfPoints: activeNumOfPoints, - shapeType: activeShapeType, - crosshair: activeShapeType === ShapeType.RECTANGLE, - }); + 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])); + } else { + canvasInstance.draw({ + enabled: true, + rectDrawingMethod: activeRectDrawingMethod, + numberOfPoints: activeNumOfPoints, + shapeType: activeShapeType, + crosshair: activeShapeType === ShapeType.RECTANGLE, + }); + } }; } diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx index 1b5d7508..c1d7efa1 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx @@ -345,7 +345,8 @@ export default class CanvasWrapperComponent extends React.PureComponent { } = this.props; if (frameData !== null) { - canvasInstance.setup(frameData, annotations); + canvasInstance.setup(frameData, annotations + .filter((e) => e.objectType !== ObjectType.TAG)); canvasInstance.rotate(frameAngle); } } diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx index 7deeabb4..bf151997 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx @@ -6,9 +6,7 @@ import React from 'react'; import { GlobalHotKeys, KeyMap } from 'react-hotkeys'; import { - Icon, Layout, - Tooltip, } from 'antd'; import { @@ -16,10 +14,6 @@ import { Rotation, } from 'reducers/interfaces'; -import { - TagIcon, -} from 'icons'; - import { Canvas, } from 'cvat-canvas'; @@ -33,6 +27,7 @@ import DrawRectangleControl from './draw-rectangle-control'; import DrawPolygonControl from './draw-polygon-control'; import DrawPolylineControl from './draw-polyline-control'; import DrawPointsControl from './draw-points-control'; +import SetupTagControl from './setup-tag-control'; import MergeControl from './merge-control'; import GroupControl from './group-control'; import SplitControl from './split-control'; @@ -221,9 +216,10 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element { isDrawing={activeControl === ActiveControl.DRAW_POINTS} /> - - - +
diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/setup-tag-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/setup-tag-control.tsx new file mode 100644 index 00000000..0f711ba1 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/setup-tag-control.tsx @@ -0,0 +1,48 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import { + Popover, + Icon, +} from 'antd'; + +import { Canvas } from 'cvat-canvas'; +import { TagIcon } from 'icons'; + +import SetupTagPopoverContainer from 'containers/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover'; + +interface Props { + canvasInstance: Canvas; + isDrawing: boolean; +} + +function SetupTagControl(props: Props): JSX.Element { + const { + isDrawing, + } = props; + + const dynamcPopoverPros = isDrawing ? { + overlayStyle: { + display: 'none', + }, + } : {}; + + return ( + + )} + > + + + ); +} + +export default React.memo(SetupTagControl); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover.tsx new file mode 100644 index 00000000..3edaa2aa --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover.tsx @@ -0,0 +1,75 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; + +import { + Row, + Col, + Select, + Button, +} from 'antd'; + +import Text from 'antd/lib/typography/Text'; + +interface Props { + labels: any[]; + selectedLabeID: number; + onChangeLabel(value: string): void; + onSetup( + labelID: number, + ): void; +} + +function setupTagPopover(props: Props): JSX.Element { + const { + labels, + selectedLabeID, + onChangeLabel, + onSetup, + } = props; + + return ( +
+ + + Setup tag + + + + + Label + + + + + + + + + + + + +
+ ); +} + +export default React.memo(setupTagPopover); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx index 284fc613..dc7952cf 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx @@ -43,6 +43,7 @@ import { function ItemMenu( serverID: number | undefined, locked: boolean, + objectType: ObjectType, copy: (() => void), remove: (() => void), propagate: (() => void), @@ -67,18 +68,22 @@ function ItemMenu( Propagate - - - - - - + { objectType !== ObjectType.TAG && ( + <> + + + + + + + + )}