From 6acda049a8a69ab0217c7ef73c1ffdfb9fea9c7c Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Tue, 3 Mar 2020 20:33:40 +0300 Subject: [PATCH 01/22] Added tag popover template --- .../controls-side-bar/controls-side-bar.tsx | 16 ++- .../controls-side-bar/setup-tag-control.tsx | 48 +++++++++ .../controls-side-bar/setup-tag-popover.tsx | 73 +++++++++++++ .../controls-side-bar/setup-tag-popover.tsx | 101 ++++++++++++++++++ cvat-ui/src/reducers/interfaces.ts | 1 + 5 files changed, 229 insertions(+), 10 deletions(-) create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/setup-tag-control.tsx create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover.tsx create mode 100644 cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover.tsx 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 176c384b44b..9483c148c8f 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 @@ -5,20 +5,14 @@ import React from 'react'; import { - Icon, Layout, - Tooltip, } from 'antd'; import { ActiveControl, - Rotation + Rotation, } from 'reducers/interfaces'; -import { - TagIcon, -} from 'icons'; - import { Canvas, } from 'cvat-canvas'; @@ -32,6 +26,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'; @@ -91,9 +86,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 00000000000..0f711ba1b75 --- /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 00000000000..810b5b5e23b --- /dev/null +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover.tsx @@ -0,0 +1,73 @@ +// 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(): 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/containers/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover.tsx new file mode 100644 index 00000000000..c6d42bf312a --- /dev/null +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover.tsx @@ -0,0 +1,101 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import { connect } from 'react-redux'; + +import { + CombinedState, +} from 'reducers/interfaces'; + +import { Canvas } from 'cvat-canvas'; +import SetupTagPopoverComponent from 'components/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover'; + +interface DispatchToProps { + onTagSetup(): void; +} + +interface StateToProps { + canvasInstance: Canvas; + labels: any[]; +} + +function mapDispatchToProps(dispatch: any): DispatchToProps { + return { + onTagSetup(): void { + dispatch(); + }, + }; +} + +function mapStateToProps(state: CombinedState): StateToProps { + const { + annotation: { + canvas: { + instance: canvasInstance, + }, + job: { + labels, + }, + }, + } = state; + + return { + canvasInstance, + labels, + }; +} + +type Props = StateToProps; + +interface State { + selectedLabelID: number; +} + +class DrawShapePopoverContainer extends React.PureComponent { + constructor(props: Props) { + super(props); + + const defaultLabelID = props.labels[0].id; + this.state = { + selectedLabelID: defaultLabelID, + }; + } + + private onChangeLabel = (value: string): void => { + this.setState({ + selectedLabelID: +value, + }); + }; + + private onSetup(): void { + const { canvasInstance } = this.props; + + canvasInstance.cancel(); + } + + public render(): JSX.Element { + const { + selectedLabelID, + } = this.state; + + const { + labels, + } = this.props; + + return ( + + ); + } +} + +export default connect( + mapStateToProps, + mapDispatchToProps, +)(DrawShapePopoverContainer); diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index 644d85d50c8..0fab4ef581a 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -245,6 +245,7 @@ export enum ActiveControl { DRAW_POLYGON = 'draw_polygon', DRAW_POLYLINE = 'draw_polyline', DRAW_POINTS = 'draw_points', + SETUP_TAG = 'setup_tag', MERGE = 'merge', GROUP = 'group', SPLIT = 'split', From f468c4035fe7e9ca24874ade6062a0d98723b553 Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Thu, 5 Mar 2020 13:37:53 +0300 Subject: [PATCH 02/22] Setup tag forward to the state --- cvat-ui/src/actions/annotation-actions.ts | 15 ++++++++++++ .../controls-side-bar/setup-tag-popover.tsx | 6 +++-- .../controls-side-bar/setup-tag-popover.tsx | 23 ++++++++++++++----- cvat-ui/src/reducers/annotation-reducer.ts | 23 +++++++++++++++++++ 4 files changed, 59 insertions(+), 8 deletions(-) diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index bc49fb02cd7..b3d0eabfe32 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -85,6 +85,7 @@ export enum AnnotationActionTypes { EDIT_SHAPE = 'EDIT_SHAPE', DRAW_SHAPE = 'DRAW_SHAPE', SHAPE_DRAWN = 'SHAPE_DRAWN', + SETUP_TAG = 'SETUP_TAG', RESET_CANVAS = 'RESET_CANVAS', UPDATE_ANNOTATIONS_SUCCESS = 'UPDATE_ANNOTATIONS_SUCCESS', UPDATE_ANNOTATIONS_FAILED = 'UPDATE_ANNOTATIONS_FAILED', @@ -855,6 +856,20 @@ export function drawShape( }; } +export function setupTag( + labelID: number, + objectType: ObjectType, +): AnyAction { + return { + type: AnnotationActionTypes.SETUP_TAG, + payload: { + labelID, + objectType, + activeControl: ActiveControl.SETUP_TAG, + }, + }; +} + export function shapeDrawn(): AnyAction { return { type: AnnotationActionTypes.SHAPE_DRAWN, 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 index 810b5b5e23b..3edaa2aa4a5 100644 --- 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 @@ -17,7 +17,9 @@ interface Props { labels: any[]; selectedLabeID: number; onChangeLabel(value: string): void; - onSetup(): void; + onSetup( + labelID: number, + ): void; } function setupTagPopover(props: Props): JSX.Element { @@ -61,7 +63,7 @@ function setupTagPopover(props: Props): JSX.Element { - diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover.tsx index c6d42bf312a..5714bc0aea9 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover.tsx @@ -7,13 +7,19 @@ import { connect } from 'react-redux'; import { CombinedState, + ObjectType, } from 'reducers/interfaces'; +import { + setupTag +} from 'actions/annotation-actions'; import { Canvas } from 'cvat-canvas'; import SetupTagPopoverComponent from 'components/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover'; interface DispatchToProps { - onTagSetup(): void; + onTagSetup( + labelID: number, + ): void; } interface StateToProps { @@ -23,8 +29,10 @@ interface StateToProps { function mapDispatchToProps(dispatch: any): DispatchToProps { return { - onTagSetup(): void { - dispatch(); + onTagSetup( + labelID: number, + ): void { + dispatch(setupTag(labelID, ObjectType.TAG)); }, }; } @@ -47,7 +55,7 @@ function mapStateToProps(state: CombinedState): StateToProps { }; } -type Props = StateToProps; +type Props = StateToProps & DispatchToProps; interface State { selectedLabelID: number; @@ -69,10 +77,11 @@ class DrawShapePopoverContainer extends React.PureComponent { }); }; - private onSetup(): void { - const { canvasInstance } = this.props; + private onSetup(labelID: number): void { + const { canvasInstance, onTagSetup } = this.props; canvasInstance.cancel(); + onTagSetup(labelID); } public render(): JSX.Element { @@ -84,6 +93,8 @@ class DrawShapePopoverContainer extends React.PureComponent { labels, } = this.props; + this.onSetup = this.onSetup.bind(this); + return ( { }, }; } + case AnnotationActionTypes.SETUP_TAG: { + const { + labelID, + objectType, + activeControl, + } = action.payload; + + return { + ...state, + annotations: { + ...state.annotations, + }, + canvas: { + ...state.canvas, + activeControl, + }, + drawing: { + ...defaultState.drawing, + activeLabelID: labelID, + activeObjectType: objectType, + }, + }; + } case AnnotationActionTypes.MERGE_OBJECTS: { const { enabled } = action.payload; const activeControl = enabled From 3199fbbdf9f340aa796aa15771c92d5099323f54 Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Fri, 6 Mar 2020 16:38:15 +0300 Subject: [PATCH 03/22] Added tag support in new UI (without canvas drawing) --- cvat-canvas/src/typescript/canvasView.ts | 44 +++++++++++------- cvat-core/src/annotations-collection.js | 12 +++-- cvat-ui/src/actions/annotation-actions.ts | 38 ++++++++------- .../objects-side-bar/object-item.tsx | 25 +++++++++- .../controls-side-bar/setup-tag-popover.tsx | 46 ++++++++++++++----- .../objects-side-bar/object-item.tsx | 21 +++++++++ cvat-ui/src/reducers/annotation-reducer.ts | 18 +------- cvat-ui/src/reducers/interfaces.ts | 1 - 8 files changed, 137 insertions(+), 68 deletions(-) diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index 63b26e1dc52..846ac45498d 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -399,8 +399,11 @@ export class CanvasViewImpl implements CanvasView, Listener { this.svgTexts[state.clientID].remove(); } - this.svgShapes[state.clientID].off('click.canvas'); - this.svgShapes[state.clientID].remove(); + const shape = this.svgShapes[state.clientID]; + if (shape) { + shape.off('click.canvas'); + shape.remove(); + } delete this.drawnStates[state.clientID]; } @@ -847,7 +850,7 @@ export class CanvasViewImpl implements CanvasView, Listener { hidden: state.hidden, lock: state.lock, shapeType: state.shapeType, - points: [...state.points], + points: Array.isArray(state.points) ? [...state.points] : [], attributes: { ...state.attributes }, zOrder: state.zOrder, pinned: state.pinned, @@ -892,7 +895,7 @@ export class CanvasViewImpl implements CanvasView, Listener { this.activate(activeElement); } - if (state.points + if (state.points && state.points .some((p: number, id: number): boolean => p !== drawnState.points[id]) ) { const translatedPoints: number[] = translate(state.points); @@ -1021,22 +1024,24 @@ export class CanvasViewImpl implements CanvasView, Listener { const drawnState = this.drawnStates[clientID]; const shape = this.svgShapes[clientID]; - shape.removeClass('cvat_canvas_shape_activated'); + if (shape) { + shape.removeClass('cvat_canvas_shape_activated'); - if (!drawnState.pinned) { - (shape as any).off('dragstart'); - (shape as any).off('dragend'); - (shape as any).draggable(false); - } + if (!drawnState.pinned) { + (shape as any).off('dragstart'); + (shape as any).off('dragend'); + (shape as any).draggable(false); + } - if (drawnState.shapeType !== 'points') { - this.selectize(false, shape); - } + if (drawnState.shapeType !== 'points') { + this.selectize(false, shape); + } - (shape as any).off('resizestart'); - (shape as any).off('resizing'); - (shape as any).off('resizedone'); - (shape as any).resize(false); + (shape as any).off('resizestart'); + (shape as any).off('resizing'); + (shape as any).off('resizedone'); + (shape as any).resize(false); + } // TODO: Hide text only if it is hidden by settings const text = this.svgTexts[clientID]; @@ -1085,6 +1090,11 @@ export class CanvasViewImpl implements CanvasView, Listener { this.activeElement = { ...activeElement }; const shape = this.svgShapes[clientID]; + + if (!shape) { + return; + } + 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 759980dbe2a..0c5b35fda96 100644 --- a/cvat-core/src/annotations-collection.js +++ b/cvat-core/src/annotations-collection.js @@ -810,11 +810,13 @@ '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)) { - minimumDistance = distance; - minimumState = state; + if (!(object instanceof Tag)) { + const distance = object.constructor.distance(state.points, x, y); + if (distance !== null && (minimumDistance === null + || distance < minimumDistance)) { + minimumDistance = distance; + minimumState = state; + } } } diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index b3d0eabfe32..9a265752dd6 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -85,7 +85,6 @@ export enum AnnotationActionTypes { EDIT_SHAPE = 'EDIT_SHAPE', DRAW_SHAPE = 'DRAW_SHAPE', SHAPE_DRAWN = 'SHAPE_DRAWN', - SETUP_TAG = 'SETUP_TAG', RESET_CANVAS = 'RESET_CANVAS', UPDATE_ANNOTATIONS_SUCCESS = 'UPDATE_ANNOTATIONS_SUCCESS', UPDATE_ANNOTATIONS_FAILED = 'UPDATE_ANNOTATIONS_FAILED', @@ -526,10 +525,31 @@ export function editShape(enabled: boolean): AnyAction { } export function copyShape(objectState: any): AnyAction { + const state = getStore().getState(); + + state.annotation.canvas.instance.cancel(); + if (objectState.objectType !== ObjectType.TAG) { + state.annotation.canvas.instance.draw({ + enabled: true, + initialState: objectState, + }); + } + + let activeControl = ActiveControl.CURSOR; + if (objectState.shapeType === ShapeType.RECTANGLE) { + activeControl = ActiveControl.DRAW_RECTANGLE; + } else if (objectState.shapeType === ShapeType.POINTS) { + activeControl = ActiveControl.DRAW_POINTS; + } else if (objectState.shapeType === ShapeType.POLYGON) { + activeControl = ActiveControl.DRAW_POLYGON; + } else if (objectState.shapeType === ShapeType.POLYLINE) { + activeControl = ActiveControl.DRAW_POLYLINE; + } + return { type: AnnotationActionTypes.COPY_SHAPE, payload: { - objectState, + activeControl, }, }; } @@ -856,20 +876,6 @@ export function drawShape( }; } -export function setupTag( - labelID: number, - objectType: ObjectType, -): AnyAction { - return { - type: AnnotationActionTypes.SETUP_TAG, - payload: { - labelID, - objectType, - activeControl: ActiveControl.SETUP_TAG, - }, - }; -} - export function shapeDrawn(): AnyAction { return { type: AnnotationActionTypes.SHAPE_DRAWN, 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 284fc613009..ce0f69dbac9 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), @@ -68,13 +69,13 @@ function ItemMenu( - - @@ -109,6 +110,7 @@ interface ItemTopComponentProps { serverID: number | undefined; labelID: number; labels: any[]; + objectType: ObjectType; type: string; locked: boolean; changeLabel(labelID: string): void; @@ -126,6 +128,7 @@ function ItemTopComponent(props: ItemTopComponentProps): JSX.Element { serverID, labelID, labels, + objectType, type, locked, changeLabel, @@ -159,6 +162,7 @@ function ItemTopComponent(props: ItemTopComponentProps): JSX.Element { overlay={ItemMenu( serverID, locked, + objectType, copy, remove, propagate, @@ -302,6 +306,22 @@ function ItemButtonsComponent(props: ItemButtonsComponentProps): JSX.Element { ); } + if (objectType === ObjectType.TAG) { + return ( + + + + + { locked + ? + : } + + + + + ); + } + return ( @@ -726,6 +746,7 @@ function ObjectItemComponent(props: Props): JSX.Element { clientID={clientID} labelID={labelID} labels={labels} + objectType={objectType} type={type} locked={locked} changeLabel={changeLabel} diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover.tsx index 5714bc0aea9..03e9bff552e 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover.tsx @@ -11,28 +11,28 @@ import { } from 'reducers/interfaces'; import { - setupTag + createAnnotationsAsync, } from 'actions/annotation-actions'; import { Canvas } from 'cvat-canvas'; import SetupTagPopoverComponent from 'components/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover'; +import getCore from 'cvat-core'; +const cvat = getCore(); interface DispatchToProps { - onTagSetup( - labelID: number, - ): void; + onCreateAnnotations(sessionInstance: any, frame: number, states: any[]): void; } interface StateToProps { canvasInstance: Canvas; + jobInstance: any; labels: any[]; + frame: number; } function mapDispatchToProps(dispatch: any): DispatchToProps { return { - onTagSetup( - labelID: number, - ): void { - dispatch(setupTag(labelID, ObjectType.TAG)); + onCreateAnnotations(sessionInstance: any, frame: number, states: any[]): void { + dispatch(createAnnotationsAsync(sessionInstance, frame, states)); }, }; } @@ -45,13 +45,21 @@ function mapStateToProps(state: CombinedState): StateToProps { }, job: { labels, + instance: jobInstance, + }, + player: { + frame: { + number: frame, + }, }, }, } = state; return { canvasInstance, + jobInstance, labels, + frame, }; } @@ -77,11 +85,27 @@ class DrawShapePopoverContainer extends React.PureComponent { }); }; - private onSetup(labelID: number): void { - const { canvasInstance, onTagSetup } = this.props; + private onSetup(): void { + const { + canvasInstance, + onCreateAnnotations, + jobInstance, + frame, + } = this.props; + + const { selectedLabelID } = this.state; canvasInstance.cancel(); - onTagSetup(labelID); + + const state = { + objectType: ObjectType.TAG, + label: jobInstance.task.labels + .filter((label: any) => label.id === selectedLabelID)[0], + frame, + + }; + const objectState = new cvat.classes.ObjectState(state); + onCreateAnnotations(jobInstance, frame, [objectState]); } public render(): JSX.Element { 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 6ced57029d2..0723f4380af 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 @@ -9,10 +9,12 @@ import { ActiveControl, CombinedState, ColorBy, + ObjectType, } from 'reducers/interfaces'; import { collapseObjectItems, changeLabelColorAsync, + createAnnotationsAsync, updateAnnotationsAsync, changeFrameAsync, removeObjectAsync, @@ -23,6 +25,9 @@ import { } from 'actions/annotation-actions'; import ObjectStateItemComponent from 'components/annotation-page/standard-workspace/objects-side-bar/object-item'; +import getCore from 'cvat-core'; + +const cvat = getCore(); interface OwnProps { clientID: number; @@ -47,6 +52,7 @@ interface StateToProps { interface DispatchToProps { changeFrame(frame: number): void; updateState(sessionInstance: any, frameNumber: number, objectState: any): void; + createAnnotations(sessionInstance: any, frameNumber: number, state: any): void collapseOrExpand(objectStates: any[], collapsed: boolean): void; activateObject: (activatedStateID: number | null) => void; removeObject: (sessionInstance: any, objectState: any) => void; @@ -123,6 +129,9 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { updateState(sessionInstance: any, frameNumber: number, state: any): void { dispatch(updateAnnotationsAsync(sessionInstance, frameNumber, [state])); }, + createAnnotations(sessionInstance: any, frameNumber: number, state: any): void { + dispatch(createAnnotationsAsync(sessionInstance, frameNumber, state)); + }, collapseOrExpand(objectStates: any[], collapsed: boolean): void { dispatch(collapseObjectItems(objectStates, collapsed)); }, @@ -215,9 +224,21 @@ class ObjectItemContainer extends React.PureComponent { const { objectState, copyShape, + jobInstance, + frameNumber, + createAnnotations, } = this.props; copyShape(objectState); + if (objectState.objectType === ObjectType.TAG) { + const state = new cvat.classes.ObjectState({ + objectType: objectState.objectType, + label: objectState.label, + frame: frameNumber, + + }); + createAnnotations(jobInstance, frameNumber, [state]); + } }; private propagate = (): void => { diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index 27697cb5a58..3a8776aea0c 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -429,6 +429,7 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { ...state, annotations: { ...state.annotations, + activatedStateID: null, }, canvas: { ...state.canvas, @@ -678,24 +679,9 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { } case AnnotationActionTypes.COPY_SHAPE: { const { - objectState, + activeControl, } = action.payload; - state.canvas.instance.cancel(); - state.canvas.instance.draw({ - enabled: true, - initialState: objectState, - }); - - let activeControl = ActiveControl.DRAW_RECTANGLE; - if (objectState.shapeType === ShapeType.POINTS) { - activeControl = ActiveControl.DRAW_POINTS; - } else if (objectState.shapeType === ShapeType.POLYGON) { - activeControl = ActiveControl.DRAW_POLYGON; - } else if (objectState.shapeType === ShapeType.POLYLINE) { - activeControl = ActiveControl.DRAW_POLYLINE; - } - return { ...state, canvas: { diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index 0fab4ef581a..644d85d50c8 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -245,7 +245,6 @@ export enum ActiveControl { DRAW_POLYGON = 'draw_polygon', DRAW_POLYLINE = 'draw_polyline', DRAW_POINTS = 'draw_points', - SETUP_TAG = 'setup_tag', MERGE = 'merge', GROUP = 'group', SPLIT = 'split', From 4746574ff197cd265a3b1cc904bf8a99e13ecf59 Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Fri, 6 Mar 2020 16:48:57 +0300 Subject: [PATCH 04/22] merge fix --- cvat-ui/src/reducers/annotation-reducer.ts | 24 ---------------------- 1 file changed, 24 deletions(-) diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index fb4669c8d1d..d5de5f6009c 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -422,30 +422,6 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { }, }; } - case AnnotationActionTypes.SETUP_TAG: { - const { - labelID, - objectType, - activeControl, - } = action.payload; - - return { - ...state, - annotations: { - ...state.annotations, - activatedStateID: null, - }, - canvas: { - ...state.canvas, - activeControl, - }, - drawing: { - ...defaultState.drawing, - activeLabelID: labelID, - activeObjectType: objectType, - }, - }; - } case AnnotationActionTypes.REPEAT_DRAW_SHAPE: { const { activeControl } = action.payload; From 8602da72f16fcc4e337664479f819b2a3e397f97 Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Fri, 6 Mar 2020 17:27:36 +0300 Subject: [PATCH 05/22] Fixed copying/pasting actions --- cvat-ui/src/actions/annotation-actions.ts | 61 ++++++++++--------- cvat-ui/src/base.scss | 1 + .../objects-side-bar/object-item.tsx | 36 ++++++----- .../objects-side-bar/styles.scss | 1 + .../objects-side-bar/object-item.tsx | 12 ---- 5 files changed, 56 insertions(+), 55 deletions(-) diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index be23e67f0d5..f02087016fe 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -542,31 +542,10 @@ export function editShape(enabled: boolean): AnyAction { } export function copyShape(objectState: any): AnyAction { - const state = getStore().getState(); - - state.annotation.canvas.instance.cancel(); - if (objectState.objectType !== ObjectType.TAG) { - state.annotation.canvas.instance.draw({ - enabled: true, - initialState: objectState, - }); - } - - let activeControl = ActiveControl.CURSOR; - if (objectState.shapeType === ShapeType.RECTANGLE) { - activeControl = ActiveControl.DRAW_RECTANGLE; - } else if (objectState.shapeType === ShapeType.POINTS) { - activeControl = ActiveControl.DRAW_POINTS; - } else if (objectState.shapeType === ShapeType.POLYGON) { - activeControl = ActiveControl.DRAW_POLYGON; - } else if (objectState.shapeType === ShapeType.POLYLINE) { - activeControl = ActiveControl.DRAW_POLYLINE; - } - return { type: AnnotationActionTypes.COPY_SHAPE, payload: { - activeControl, + objectState, }, }; } @@ -1175,11 +1154,25 @@ 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, + }, + }, + } = 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; @@ -1195,10 +1188,20 @@ export function pasteShapeAsync(): ThunkAction, {}, {}, AnyAction> }); canvasInstance.cancel(); - canvasInstance.draw({ - enabled: true, - initialState, - }); + if (initialState.objectType === ObjectType.TAG) { + const state = new cvat.classes.ObjectState({ + objectType: initialState.objectType, + label: initialState.label, + frame: frameNumber, + + }); + dispatch(createAnnotationsAsync(jobInstance, frameNumber, [state])); + } else { + canvasInstance.draw({ + enabled: true, + initialState, + }); + } } }; } diff --git a/cvat-ui/src/base.scss b/cvat-ui/src/base.scss index 6f545305107..99649cf46e5 100644 --- a/cvat-ui/src/base.scss +++ b/cvat-ui/src/base.scss @@ -22,6 +22,7 @@ $info-icon-color: #0074D9; $objects-bar-tabs-color: #BEBEBE; $objects-bar-icons-color: #242424; // #6E6E6E $active-object-item-background-color: #D8ECFF; +$default-object-colorpicker-item-background-color: #D8D8D8; $slider-color: #1890FF; $monospaced-fonts-stack: Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace; 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 ce0f69dbac9..fbe21a8d42b 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 @@ -719,21 +719,29 @@ function ObjectItemComponent(props: Props): JSX.Element { return (
- + )} + > +
+ + ) : ( +
- )} - > -
- + ) + }
{ const { objectState, copyShape, - jobInstance, - frameNumber, - createAnnotations, } = this.props; copyShape(objectState); - if (objectState.objectType === ObjectType.TAG) { - const state = new cvat.classes.ObjectState({ - objectType: objectState.objectType, - label: objectState.label, - frame: frameNumber, - - }); - createAnnotations(jobInstance, frameNumber, [state]); - } }; private propagate = (): void => { From c85ecb7bf3c9781c3262817939084cdfa0a46987 Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Fri, 6 Mar 2020 17:59:34 +0300 Subject: [PATCH 06/22] Deleted unused objects --- .../standard-workspace/objects-side-bar/object-item.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) 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 9a9382d625d..d3f55c45704 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 @@ -9,7 +9,6 @@ import { ActiveControl, CombinedState, ColorBy, - ObjectType, } from 'reducers/interfaces'; import { collapseObjectItems, @@ -26,9 +25,7 @@ import { } from 'actions/annotation-actions'; import ObjectStateItemComponent from 'components/annotation-page/standard-workspace/objects-side-bar/object-item'; -import getCore from 'cvat-core'; -const cvat = getCore(); interface OwnProps { clientID: number; @@ -53,7 +50,7 @@ interface StateToProps { interface DispatchToProps { changeFrame(frame: number): void; updateState(sessionInstance: any, frameNumber: number, objectState: any): void; - createAnnotations(sessionInstance: any, frameNumber: number, state: any): void + createAnnotations(sessionInstance: any, frameNumber: number, state: any): void; collapseOrExpand(objectStates: any[], collapsed: boolean): void; activateObject: (activatedStateID: number | null) => void; removeObject: (sessionInstance: any, objectState: any) => void; From a99dd38fee470e89237485cd10291a74a228baba Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Tue, 10 Mar 2020 18:58:22 +0300 Subject: [PATCH 07/22] Fix copying and creating tags --- cvat-ui/src/actions/annotation-actions.ts | 94 ++++++++++++++----- .../controls-side-bar/setup-tag-popover.tsx | 36 ++----- .../objects-side-bar/objects-list.tsx | 4 +- cvat-ui/src/reducers/annotation-reducer.ts | 24 +++++ 4 files changed, 100 insertions(+), 58 deletions(-) diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index f02087016fe..6f901cb75e8 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -138,6 +138,7 @@ export enum AnnotationActionTypes { SWITCH_Z_LAYER = 'SWITCH_Z_LAYER', ADD_Z_LAYER = 'ADD_Z_LAYER', SEARCH_ANNOTATIONS_FAILED = 'SEARCH_ANNOTATIONS_FAILED', + ADD_TAG = 'ADD_TAG', } export function addZLayer(): AnyAction { @@ -1151,9 +1152,11 @@ export function searchAnnotationsAsync( }; } -export function pasteShapeAsync(): ThunkAction, {}, {}, AnyAction> { +export function addTagAsync( + labelID: number, + frame: number, +): ThunkAction, {}, {}, AnyAction> { return async (dispatch: ActionCreator): Promise => { - const initialState = getStore().getState().annotation.drawing.activeInitialState; const { canvas: { instance: canvasInstance, @@ -1161,6 +1164,35 @@ export function pasteShapeAsync(): ThunkAction, {}, {}, AnyAction> job: { instance: jobInstance, }, + } = getStore().getState().annotation; + + dispatch({ + type: AnnotationActionTypes.ADD_TAG, + payload: { + labelID, + objectType: ObjectType.TAG, + activeControl: ActiveControl.CURSOR, + }, + }); + + canvasInstance.cancel(); + const objectState = new cvat.classes.ObjectState({ + objectType: ObjectType.TAG, + label: jobInstance.task.labels + .filter((label: any) => label.id === labelID)[0], + frame, + }); + dispatch(createAnnotationsAsync(jobInstance, frame, [objectState])); + }; +} + +export function pasteShapeAsync(): ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + const initialState = getStore().getState().annotation.drawing.activeInitialState; + const { + canvas: { + instance: canvasInstance, + }, player: { frame: { number: frameNumber, @@ -1189,13 +1221,7 @@ export function pasteShapeAsync(): ThunkAction, {}, {}, AnyAction> canvasInstance.cancel(); if (initialState.objectType === ObjectType.TAG) { - const state = new cvat.classes.ObjectState({ - objectType: initialState.objectType, - label: initialState.label, - frame: frameNumber, - - }); - dispatch(createAnnotationsAsync(jobInstance, frameNumber, [state])); + dispatch(addTagAsync(initialState.label.id, frameNumber)); } else { canvasInstance.draw({ enabled: true, @@ -1209,20 +1235,32 @@ 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, + }, + 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({ @@ -1233,12 +1271,16 @@ 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) { + dispatch(addTagAsync(activeLabelID, frameNumber)); + } else { + canvasInstance.draw({ + enabled: true, + rectDrawingMethod: activeRectDrawingMethod, + numberOfPoints: activeNumOfPoints, + shapeType: activeShapeType, + crosshair: activeShapeType === ShapeType.RECTANGLE, + }); + } }; } diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover.tsx index 03e9bff552e..d31d1e7efe4 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover.tsx @@ -7,32 +7,26 @@ import { connect } from 'react-redux'; import { CombinedState, - ObjectType, } from 'reducers/interfaces'; import { - createAnnotationsAsync, + addTagAsync, } from 'actions/annotation-actions'; -import { Canvas } from 'cvat-canvas'; import SetupTagPopoverComponent from 'components/annotation-page/standard-workspace/controls-side-bar/setup-tag-popover'; -import getCore from 'cvat-core'; -const cvat = getCore(); interface DispatchToProps { - onCreateAnnotations(sessionInstance: any, frame: number, states: any[]): void; + onAddTag(labelID: number, frame: number): void; } interface StateToProps { - canvasInstance: Canvas; - jobInstance: any; labels: any[]; frame: number; } function mapDispatchToProps(dispatch: any): DispatchToProps { return { - onCreateAnnotations(sessionInstance: any, frame: number, states: any[]): void { - dispatch(createAnnotationsAsync(sessionInstance, frame, states)); + onAddTag(labelID: number, frame: number): void { + dispatch(addTagAsync(labelID, frame)); }, }; } @@ -40,12 +34,8 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { function mapStateToProps(state: CombinedState): StateToProps { const { annotation: { - canvas: { - instance: canvasInstance, - }, job: { labels, - instance: jobInstance, }, player: { frame: { @@ -56,8 +46,6 @@ function mapStateToProps(state: CombinedState): StateToProps { } = state; return { - canvasInstance, - jobInstance, labels, frame, }; @@ -87,25 +75,13 @@ class DrawShapePopoverContainer extends React.PureComponent { private onSetup(): void { const { - canvasInstance, - onCreateAnnotations, - jobInstance, frame, + onAddTag, } = this.props; const { selectedLabelID } = this.state; - canvasInstance.cancel(); - - const state = { - objectType: ObjectType.TAG, - label: jobInstance.task.labels - .filter((label: any) => label.id === selectedLabelID)[0], - frame, - - }; - const objectState = new cvat.classes.ObjectState(state); - onCreateAnnotations(jobInstance, frame, [objectState]); + onAddTag(selectedLabelID, frame); } public render(): JSX.Element { diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx index 8bc18e0f6d6..6edc95cab44 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx @@ -462,14 +462,14 @@ class ObjectsListContainer extends React.PureComponent { COPY_SHAPE: (event: KeyboardEvent | undefined) => { preventDefault(event); const state = activatedStated(); - if (state && state.objectType !== ObjectType.TAG) { + if (state) { copyShape(state); } }, PROPAGATE_OBJECT: (event: KeyboardEvent | undefined) => { preventDefault(event); const state = activatedStated(); - if (state && state.objectType !== ObjectType.TAG) { + if (state) { propagateObject(state); } }, diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index d5de5f6009c..07298373347 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -437,6 +437,30 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { }, }; } + case AnnotationActionTypes.ADD_TAG: { + const { + labelID, + objectType, + activeControl, + } = action.payload; + + return { + ...state, + annotations: { + ...state.annotations, + activatedStateID: null, + }, + canvas: { + ...state.canvas, + activeControl, + }, + drawing: { + ...defaultState.drawing, + activeLabelID: labelID, + activeObjectType: objectType, + }, + }; + } case AnnotationActionTypes.MERGE_OBJECTS: { const { enabled } = action.payload; const activeControl = enabled From 8b27dbe61327845ce8cddf733c0f557b41d4a81e Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Wed, 11 Mar 2020 14:16:55 +0300 Subject: [PATCH 08/22] Fixed tags color changing and hiding --- cvat-core/src/annotations-objects.js | 5 +++ .../objects-side-bar/object-item.tsx | 37 +++++++------------ .../objects-side-bar/objects-list.tsx | 4 +- 3 files changed, 22 insertions(+), 24 deletions(-) diff --git a/cvat-core/src/annotations-objects.js b/cvat-core/src/annotations-objects.js index 65a30b5663a..245d8e375c1 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/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 fbe21a8d42b..72e2d0e6ebc 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 @@ -719,30 +719,21 @@ function ObjectItemComponent(props: Props): JSX.Element { return (
- { - objectType !== ObjectType.TAG ? ( - - )} - > -
- - ) : ( -
- ) - } - + )} + > +
+
{ const { clientID, lock } = objectState; if (!lock) { - statesHidden = statesHidden && objectState.hidden; + if (objectState.objectType !== ObjectType.TAG) { + statesHidden = statesHidden && objectState.hidden; + } statesLocked = statesLocked && objectState.lock; } const stateCollapsed = clientID in collapsed ? collapsed[clientID] : true; From 89628fbac53cccb63206e3e86c0f519f3f7c851a Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Wed, 11 Mar 2020 14:52:45 +0300 Subject: [PATCH 09/22] Fixed filters with tags --- cvat-core/src/annotations-filter.js | 25 +++++++++-------- .../objects-side-bar/object-item.tsx | 28 +++++++++++-------- 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/cvat-core/src/annotations-filter.js b/cvat-core/src/annotations-filter.js index 62530167557..903067bca3e 100644 --- a/cvat-core/src/annotations-filter.js +++ b/cvat-core/src/annotations-filter.js @@ -165,18 +165,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 !== '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-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 72e2d0e6ebc..dc7952cf371 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 @@ -68,18 +68,22 @@ function ItemMenu( Propagate - - - - - - + { objectType !== ObjectType.TAG && ( + <> + + + + + + + + )} + + {currentAttribute} + {` [${currentIndex + 1}/${attributesCount}]`} + + +
+ ); +} + +export default React.memo(AttributeSwitcher); 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 new file mode 100644 index 00000000000..17a689a997f --- /dev/null +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-basics-edtior.tsx @@ -0,0 +1,43 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import Select, { SelectValue } from 'antd/lib/select'; +import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox'; + +interface Props { + currentLabel: string; + labels: any[]; + occluded: boolean; + setOccluded(event: CheckboxChangeEvent): void; + changeLabel(value: SelectValue): void; +} + +function ObjectBasicsEditor(props: Props): JSX.Element { + const { + currentLabel, + occluded, + labels, + setOccluded, + changeLabel, + } = props; + + return ( +
+ + Occluded +
+ ); +} + +export default React.memo(ObjectBasicsEditor); diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-switcher.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-switcher.tsx new file mode 100644 index 00000000000..9341396feae --- /dev/null +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-switcher.tsx @@ -0,0 +1,48 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import Icon from 'antd/lib/icon'; +import Text from 'antd/lib/typography/Text'; +import Tooltip from 'antd/lib/tooltip'; +import Button from 'antd/lib/button'; + +interface Props { + currentLabel: string; + clientID: number; + occluded: boolean; + objectsCount: number; + currentIndex: number; + nextObject(step: number): void; +} + +function ObjectSwitcher(props: Props): JSX.Element { + const { + currentLabel, + clientID, + objectsCount, + currentIndex, + nextObject, + } = props; + + + const title = `${currentLabel} ${clientID} [${currentIndex + 1}/${objectsCount}]`; + return ( +
+ + + {currentLabel} + {` ${clientID} `} + {`[${currentIndex + 1}/${objectsCount}]`} + + +
+ ); +} + +export default React.memo(ObjectSwitcher); diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-workspace.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-workspace.tsx new file mode 100644 index 00000000000..d4e5fd8de94 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-workspace.tsx @@ -0,0 +1,19 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import './styles.scss'; +import React from 'react'; +import Layout from 'antd/lib/layout'; + +import CanvasWrapperContainer from 'containers/annotation-page/standard-workspace/canvas-wrapper'; +import AttributeAnnotationSidebar from './attribute-annotation-sidebar/attribute-annotation-sidebar'; + +export default function AttributeAnnotationWorkspace(): 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 new file mode 100644 index 00000000000..1f8de8412a3 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/styles.scss @@ -0,0 +1,66 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +@import 'base.scss'; + +.attribute-annotation-workspace.ant-layout { + height: 100%; +} + +.attribute-annotation-sidebar { + background: $background-color-2; + padding: 5px; +} + +.attribute-annotation-sidebar-switcher { + display: flex; + align-items: center; + justify-content: space-between; + font-size: 18px; + margin-top: 10px; + + > span { + max-width: 60%; + text-overflow: ellipsis; + overflow: hidden; + } + + > button > i { + color: $objects-bar-icons-color; + } +} + +.attribute-annotation-sidebar-basics-editor { + display: flex; + align-items: center; + justify-content: space-between; + font-size: 18px; + margin: 10px 0px; +} + +.attribute-annotations-sidebar-not-found-wrapper { + margin-top: 20px; + text-align: center; +} + +.attribute-annotation-sidebar-attr-list-wrapper { + margin: 10px 0px 10px 10px; +} + + +.attribute-annotation-sidebar-attr-elem-wrapper { + display: inline-block; + width: 60%; +} + +.attribute-annotation-sidebar-number-list { + display: flex; + justify-content: space-around; +} + +.attribute-annotation-sidebar-attr-editor { + display: flex; + align-items: center; + justify-content: space-around; +} 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 c1d7efa169c..91a2e20e698 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 @@ -4,18 +4,19 @@ import React from 'react'; import { GlobalHotKeys, KeyMap } from 'react-hotkeys'; +import Slider, { SliderValue } from 'antd/lib/slider'; +import Layout from 'antd/lib/layout'; +import Icon from 'antd/lib/icon'; +import Tooltip from 'antd/lib/tooltip'; -import { - Layout, - Slider, - Icon, - Tooltip, -} from 'antd'; - -import { SliderValue } from 'antd/lib//slider'; -import { ColorBy, GridColor, ObjectType } from 'reducers/interfaces'; import { Canvas } from 'cvat-canvas'; import getCore from 'cvat-core'; +import { + ColorBy, + GridColor, + ObjectType, + Workspace, +} from 'reducers/interfaces'; const cvat = getCore(); @@ -26,6 +27,7 @@ interface Props { canvasInstance: Canvas; jobInstance: any; activatedStateID: number | null; + activatedAttributeID: number | null; selectedStatesID: number[]; annotations: any[]; frameData: any; @@ -48,6 +50,8 @@ interface Props { contrastLevel: number; saturationLevel: number; resetZoom: boolean; + aamZoomMargin: number; + workspace: Workspace; onSetupCanvas: () => void; onDragCanvas: (enabled: boolean) => void; onZoomCanvas: (enabled: boolean) => void; @@ -57,7 +61,7 @@ interface Props { onEditShape: (enabled: boolean) => void; onShapeDrawn: () => void; onResetCanvas: () => void; - onUpdateAnnotations(sessionInstance: any, frame: number, states: any[]): void; + onUpdateAnnotations(states: any[]): void; onCreateAnnotations(sessionInstance: any, frame: number, states: any[]): void; onMergeAnnotations(sessionInstance: any, frame: number, states: any[]): void; onGroupAnnotations(sessionInstance: any, frame: number, states: any[]): void; @@ -113,6 +117,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { brightnessLevel, contrastLevel, saturationLevel, + workspace, } = this.props; if (prevProps.sidebarCollapsed !== sidebarCollapsed) { @@ -161,11 +166,18 @@ export default class CanvasWrapperComponent extends React.PureComponent { } } + if (prevProps.curZLayer !== curZLayer) { + canvasInstance.setZLayer(curZLayer); + } + if (prevProps.annotations !== annotations || prevProps.frameData !== frameData) { this.updateCanvas(); } - if (prevProps.frame !== frameData.number && resetZoom) { + if (prevProps.frame !== frameData.number + && resetZoom + && workspace !== Workspace.ATTRIBUTE_ANNOTATION + ) { canvasInstance.html().addEventListener('canvas.setup', () => { canvasInstance.fit(); }, { once: true }); @@ -176,10 +188,6 @@ export default class CanvasWrapperComponent extends React.PureComponent { this.updateShapesView(); } - if (prevProps.curZLayer !== curZLayer) { - canvasInstance.setZLayer(curZLayer); - } - if (prevProps.frameAngle !== frameAngle) { canvasInstance.rotate(frameAngle); } @@ -188,10 +196,34 @@ export default class CanvasWrapperComponent extends React.PureComponent { } public componentWillUnmount(): void { + const { canvasInstance } = this.props; + + canvasInstance.html().removeEventListener('mousedown', this.onCanvasMouseDown); + canvasInstance.html().removeEventListener('click', this.onCanvasClicked); + canvasInstance.html().removeEventListener('contextmenu', this.onCanvasContextMenu); + canvasInstance.html().removeEventListener('canvas.editstart', this.onCanvasEditStart); + canvasInstance.html().removeEventListener('canvas.edited', this.onCanvasEditDone); + canvasInstance.html().removeEventListener('canvas.dragstart', this.onCanvasDragStart); + canvasInstance.html().removeEventListener('canvas.dragstop', this.onCanvasDragDone); + canvasInstance.html().removeEventListener('canvas.zoomstart', this.onCanvasZoomStart); + canvasInstance.html().removeEventListener('canvas.zoomstop', this.onCanvasZoomDone); + + canvasInstance.html().removeEventListener('canvas.setup', this.onCanvasSetup); + canvasInstance.html().removeEventListener('canvas.canceled', this.onCanvasCancel); + canvasInstance.html().removeEventListener('canvas.find', this.onCanvasFindObject); + canvasInstance.html().removeEventListener('canvas.deactivated', this.onCanvasShapeDeactivated); + canvasInstance.html().removeEventListener('canvas.moved', this.onCanvasCursorMoved); + + canvasInstance.html().removeEventListener('canvas.clicked', this.onCanvasShapeClicked); + canvasInstance.html().removeEventListener('canvas.drawn', this.onCanvasShapeDrawn); + canvasInstance.html().removeEventListener('canvas.merged', this.onCanvasObjectsMerged); + canvasInstance.html().removeEventListener('canvas.groupped', this.onCanvasObjectsGroupped); + canvasInstance.html().addEventListener('canvas.splitted', this.onCanvasTrackSplitted); + window.removeEventListener('resize', this.fitCanvas); } - private onShapeDrawn(event: any): void { + private onCanvasShapeDrawn = (event: any): void => { const { jobInstance, activeLabelID, @@ -222,27 +254,9 @@ export default class CanvasWrapperComponent extends React.PureComponent { state.frame = frame; const objectState = new cvat.classes.ObjectState(state); onCreateAnnotations(jobInstance, frame, [objectState]); - } - - private onShapeEdited(event: any): void { - const { - jobInstance, - frame, - onEditShape, - onUpdateAnnotations, - } = this.props; - - onEditShape(false); - - const { - state, - points, - } = event.detail; - state.points = points; - onUpdateAnnotations(jobInstance, frame, [state]); - } + }; - private onObjectsMerged(event: any): void { + private onCanvasObjectsMerged = (event: any): void => { const { jobInstance, frame, @@ -254,9 +268,9 @@ export default class CanvasWrapperComponent extends React.PureComponent { const { states } = event.detail; onMergeAnnotations(jobInstance, frame, states); - } + }; - private onObjectsGroupped(event: any): void { + private onCanvasObjectsGroupped = (event: any): void => { const { jobInstance, frame, @@ -268,9 +282,9 @@ export default class CanvasWrapperComponent extends React.PureComponent { const { states } = event.detail; onGroupAnnotations(jobInstance, frame, states); - } + }; - private onTrackSplitted(event: any): void { + private onCanvasTrackSplitted = (event: any): void => { const { jobInstance, frame, @@ -282,22 +296,179 @@ export default class CanvasWrapperComponent extends React.PureComponent { const { state } = event.detail; onSplitAnnotations(jobInstance, frame, state); - } + }; private fitCanvas = (): void => { const { canvasInstance } = this.props; canvasInstance.fitCanvas(); }; + private onCanvasMouseDown = (e: MouseEvent): void => { + const { workspace, activatedStateID, onActivateObject } = this.props; + + if ((e.target as HTMLElement).tagName === 'svg') { + if (activatedStateID !== null && workspace !== Workspace.ATTRIBUTE_ANNOTATION) { + onActivateObject(null); + } + } + }; + + private onCanvasClicked = (): void => { + if (document.activeElement instanceof HTMLElement) { + document.activeElement.blur(); + } + }; + + private onCanvasContextMenu = (e: MouseEvent): void => { + const { activatedStateID, onUpdateContextMenu } = this.props; + onUpdateContextMenu(activatedStateID !== null, e.clientX, e.clientY); + }; + + private onCanvasShapeClicked = (e: any): void => { + const { clientID } = e.detail.state; + const sidebarItem = window.document + .getElementById(`cvat-objects-sidebar-state-item-${clientID}`); + if (sidebarItem) { + sidebarItem.scrollIntoView(); + } + }; + + private onCanvasShapeDeactivated = (e: any): void => { + const { onActivateObject, activatedStateID } = this.props; + const { state } = e.detail; + + // when we activate element, canvas deactivates the previous + // and triggers this event + // in this case we do not need to update our state + if (state.clientID === activatedStateID) { + onActivateObject(null); + } + }; + + private onCanvasCursorMoved = async (event: any): Promise => { + const { + jobInstance, + activatedStateID, + workspace, + onActivateObject, + } = this.props; + + if (workspace === Workspace.ATTRIBUTE_ANNOTATION) { + return; + } + + const result = await jobInstance.annotations.select( + event.detail.states, + event.detail.x, + event.detail.y, + ); + + if (result && result.state) { + if (result.state.shapeType === 'polyline' || result.state.shapeType === 'points') { + if (result.distance > MAX_DISTANCE_TO_OPEN_SHAPE) { + return; + } + } + + if (activatedStateID !== result.state.clientID) { + onActivateObject(result.state.clientID); + } + } + }; + + private onCanvasEditStart = (): void => { + const { onActivateObject, onEditShape } = this.props; + onActivateObject(null); + onEditShape(true); + }; + + private onCanvasEditDone = (event: any): void => { + const { + onEditShape, + onUpdateAnnotations, + } = this.props; + + onEditShape(false); + + const { + state, + points, + } = event.detail; + state.points = points; + onUpdateAnnotations([state]); + }; + + private onCanvasDragStart = (): void => { + const { onDragCanvas } = this.props; + onDragCanvas(true); + }; + + private onCanvasDragDone = (): void => { + const { onDragCanvas } = this.props; + onDragCanvas(false); + }; + + private onCanvasZoomStart = (): void => { + const { onZoomCanvas } = this.props; + onZoomCanvas(true); + }; + + private onCanvasZoomDone = (): void => { + const { onZoomCanvas } = this.props; + onZoomCanvas(false); + }; + + private onCanvasSetup = (): void => { + const { onSetupCanvas } = this.props; + onSetupCanvas(); + this.updateShapesView(); + this.activateOnCanvas(); + }; + + private onCanvasCancel = (): void => { + const { onResetCanvas } = this.props; + onResetCanvas(); + }; + + private onCanvasFindObject = async (e: any): Promise => { + const { jobInstance, canvasInstance } = this.props; + + const result = await jobInstance.annotations + .select(e.detail.states, e.detail.x, e.detail.y); + + if (result && result.state) { + if (result.state.shapeType === 'polyline' || result.state.shapeType === 'points') { + if (result.distance > MAX_DISTANCE_TO_OPEN_SHAPE) { + return; + } + } + + canvasInstance.select(result.state); + } + }; + private activateOnCanvas(): void { const { activatedStateID, + activatedAttributeID, canvasInstance, selectedOpacity, + aamZoomMargin, + workspace, + annotations, } = this.props; if (activatedStateID !== null) { - canvasInstance.activate(activatedStateID); + if (workspace === Workspace.ATTRIBUTE_ANNOTATION) { + const [activatedState] = annotations + .filter((state: any): boolean => state.clientID === activatedStateID); + if (activatedState.objectType !== ObjectType.TAG) { + canvasInstance.focus(activatedStateID, aamZoomMargin); + } else { + canvasInstance.fit(); + } + } + canvasInstance.activate(activatedStateID, activatedAttributeID); const el = window.document.getElementById(`cvat_canvas_shape_${activatedStateID}`); if (el) { (el as any as SVGElement).setAttribute('fill-opacity', `${selectedOpacity / 100}`); @@ -358,14 +529,6 @@ export default class CanvasWrapperComponent extends React.PureComponent { gridColor, gridOpacity, canvasInstance, - jobInstance, - onSetupCanvas, - onDragCanvas, - onZoomCanvas, - onResetCanvas, - onActivateObject, - onUpdateContextMenu, - onEditShape, brightnessLevel, contrastLevel, saturationLevel, @@ -396,127 +559,33 @@ export default class CanvasWrapperComponent extends React.PureComponent { } // Events - canvasInstance.html().addEventListener('mousedown', (e: MouseEvent): void => { - const { - activatedStateID, - } = this.props; - - if ((e.target as HTMLElement).tagName === 'svg' && activatedStateID !== null) { - onActivateObject(null); - } - }); - - canvasInstance.html().addEventListener('click', (): void => { - if (document.activeElement) { - (document.activeElement as HTMLElement).blur(); - } - }); - - canvasInstance.html().addEventListener('contextmenu', (e: MouseEvent): void => { - const { - activatedStateID, - } = this.props; - - onUpdateContextMenu(activatedStateID !== null, e.clientX, e.clientY); - }); - - canvasInstance.html().addEventListener('canvas.editstart', (): void => { - onActivateObject(null); - onEditShape(true); - }); - - canvasInstance.html().addEventListener('canvas.setup', (): void => { - onSetupCanvas(); - this.updateShapesView(); - this.activateOnCanvas(); - }); - canvasInstance.html().addEventListener('canvas.setup', () => { + const { activatedStateID, activatedAttributeID } = this.props; canvasInstance.fit(); + canvasInstance.activate(activatedStateID, activatedAttributeID); }, { once: true }); - canvasInstance.html().addEventListener('canvas.canceled', () => { - onResetCanvas(); - }); - - canvasInstance.html().addEventListener('canvas.dragstart', () => { - onDragCanvas(true); - }); - - canvasInstance.html().addEventListener('canvas.dragstop', () => { - onDragCanvas(false); - }); - - canvasInstance.html().addEventListener('canvas.zoomstart', () => { - onZoomCanvas(true); - }); - - canvasInstance.html().addEventListener('canvas.zoomstop', () => { - onZoomCanvas(false); - }); - - canvasInstance.html().addEventListener('canvas.clicked', (e: any) => { - const { clientID } = e.detail.state; - const sidebarItem = window.document - .getElementById(`cvat-objects-sidebar-state-item-${clientID}`); - if (sidebarItem) { - sidebarItem.scrollIntoView(); - } - }); - - canvasInstance.html().addEventListener('canvas.deactivated', (e: any): void => { - const { activatedStateID } = this.props; - const { state } = e.detail; - - // when we activate element, canvas deactivates the previous - // and triggers this event - // in this case we do not need to update our state - if (state.clientID === activatedStateID) { - onActivateObject(null); - } - }); - - canvasInstance.html().addEventListener('canvas.moved', async (event: any): Promise => { - const { activatedStateID } = this.props; - const result = await jobInstance.annotations.select( - event.detail.states, - event.detail.x, - event.detail.y, - ); - - if (result && result.state) { - if (result.state.shapeType === 'polyline' || result.state.shapeType === 'points') { - if (result.distance > MAX_DISTANCE_TO_OPEN_SHAPE) { - return; - } - } - - if (activatedStateID !== result.state.clientID) { - onActivateObject(result.state.clientID); - } - } - }); - - canvasInstance.html().addEventListener('canvas.find', async (e: any) => { - const result = await jobInstance.annotations - .select(e.detail.states, e.detail.x, e.detail.y); - - if (result && result.state) { - if (result.state.shapeType === 'polyline' || result.state.shapeType === 'points') { - if (result.distance > MAX_DISTANCE_TO_OPEN_SHAPE) { - return; - } - } - - canvasInstance.select(result.state); - } - }); - - canvasInstance.html().addEventListener('canvas.edited', this.onShapeEdited.bind(this)); - canvasInstance.html().addEventListener('canvas.drawn', this.onShapeDrawn.bind(this)); - canvasInstance.html().addEventListener('canvas.merged', this.onObjectsMerged.bind(this)); - canvasInstance.html().addEventListener('canvas.groupped', this.onObjectsGroupped.bind(this)); - canvasInstance.html().addEventListener('canvas.splitted', this.onTrackSplitted.bind(this)); + canvasInstance.html().addEventListener('mousedown', this.onCanvasMouseDown); + canvasInstance.html().addEventListener('click', this.onCanvasClicked); + canvasInstance.html().addEventListener('contextmenu', this.onCanvasContextMenu); + canvasInstance.html().addEventListener('canvas.editstart', this.onCanvasEditStart); + canvasInstance.html().addEventListener('canvas.edited', this.onCanvasEditDone); + canvasInstance.html().addEventListener('canvas.dragstart', this.onCanvasDragStart); + canvasInstance.html().addEventListener('canvas.dragstop', this.onCanvasDragDone); + canvasInstance.html().addEventListener('canvas.zoomstart', this.onCanvasZoomStart); + canvasInstance.html().addEventListener('canvas.zoomstop', this.onCanvasZoomDone); + + canvasInstance.html().addEventListener('canvas.setup', this.onCanvasSetup); + canvasInstance.html().addEventListener('canvas.canceled', this.onCanvasCancel); + canvasInstance.html().addEventListener('canvas.find', this.onCanvasFindObject); + canvasInstance.html().addEventListener('canvas.deactivated', this.onCanvasShapeDeactivated); + canvasInstance.html().addEventListener('canvas.moved', this.onCanvasCursorMoved); + + canvasInstance.html().addEventListener('canvas.clicked', this.onCanvasShapeClicked); + canvasInstance.html().addEventListener('canvas.drawn', this.onCanvasShapeDrawn); + canvasInstance.html().addEventListener('canvas.merged', this.onCanvasObjectsMerged); + canvasInstance.html().addEventListener('canvas.groupped', this.onCanvasObjectsGroupped); + canvasInstance.html().addEventListener('canvas.splitted', this.onCanvasTrackSplitted); } public render(): JSX.Element { diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-list-header.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-list-header.tsx index d7406ccd8e1..56b29a26f27 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-list-header.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-list-header.tsx @@ -3,20 +3,14 @@ // SPDX-License-Identifier: MIT import React from 'react'; - -import { - Row, - Col, - Icon, - Select, -} from 'antd'; - +import { Row, Col } from 'antd/lib/grid'; +import Icon from 'antd/lib/icon'; +import Select from 'antd/lib/select'; import Text from 'antd/lib/typography/Text'; -import { SelectValue } from 'antd/lib/select'; +import AnnotationsFiltersInput from 'components/annotation-page/annotations-filters-input'; import { StatesOrdering } from 'reducers/interfaces'; - interface StatesOrderingSelectorComponentProps { statesOrdering: StatesOrdering; changeStatesOrdering(value: StatesOrdering): void; @@ -62,10 +56,7 @@ interface Props { statesLocked: boolean; statesCollapsed: boolean; statesOrdering: StatesOrdering; - annotationsFilters: string[]; - annotationsFiltersHistory: string[]; changeStatesOrdering(value: StatesOrdering): void; - changeAnnotationsFilters(value: SelectValue): void; lockAllStates(): void; unlockAllStates(): void; collapseAllStates(): void; @@ -76,8 +67,6 @@ interface Props { function ObjectListHeader(props: Props): JSX.Element { const { - annotationsFilters, - annotationsFiltersHistory, statesHidden, statesLocked, statesCollapsed, @@ -89,30 +78,13 @@ function ObjectListHeader(props: Props): JSX.Element { expandAllStates, hideAllStates, showAllStates, - changeAnnotationsFilters, } = props; return (
- + diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx index e3af2e3cbd8..5bf4dc0b251 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx @@ -4,7 +4,6 @@ import React from 'react'; -import { SelectValue } from 'antd/lib/select'; import { StatesOrdering } from 'reducers/interfaces'; import ObjectItemContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/object-item'; import ObjectListHeader from './objects-list-header'; @@ -17,10 +16,7 @@ interface Props { statesCollapsed: boolean; statesOrdering: StatesOrdering; sortedStatesID: number[]; - annotationsFilters: string[]; - annotationsFiltersHistory: string[]; changeStatesOrdering(value: StatesOrdering): void; - changeAnnotationsFilters(value: SelectValue): void; lockAllStates(): void; unlockAllStates(): void; collapseAllStates(): void; @@ -37,10 +33,7 @@ function ObjectListComponent(props: Props): JSX.Element { statesCollapsed, statesOrdering, sortedStatesID, - annotationsFilters, - annotationsFiltersHistory, changeStatesOrdering, - changeAnnotationsFilters, lockAllStates, unlockAllStates, collapseAllStates, @@ -56,16 +49,13 @@ function ObjectListComponent(props: Props): JSX.Element { statesLocked={statesLocked} statesCollapsed={statesCollapsed} statesOrdering={statesOrdering} - annotationsFilters={annotationsFilters} changeStatesOrdering={changeStatesOrdering} - changeAnnotationsFilters={changeAnnotationsFilters} lockAllStates={lockAllStates} unlockAllStates={unlockAllStates} collapseAllStates={collapseAllStates} expandAllStates={expandAllStates} hideAllStates={hideAllStates} showAllStates={showAllStates} - annotationsFiltersHistory={annotationsFiltersHistory} />
{ sortedStatesID.map((id: number): JSX.Element => ( diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/styles.scss b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/styles.scss index 81e6ba9c284..07f3c0f6eaf 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/styles.scss +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/styles.scss @@ -69,16 +69,6 @@ > div:nth-child(1) > div:nth-child(1) { height: 32px; - > .ant-select > div { - height: 32px; - > div { - height: 32px; - - ul { - display: flex; - } - } - } } > div:nth-child(2) { diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx index 83e2c70d664..e9bfd6c4e00 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx @@ -17,7 +17,7 @@ import CanvasContextMenuContainer from 'containers/annotation-page/standard-work export default function StandardWorkspaceComponent(): JSX.Element { return ( - + diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss b/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss index ed07a9056f3..e731a27f46e 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss +++ b/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss @@ -4,6 +4,10 @@ @import 'base.scss'; +.cvat-standard-workspace.ant-layout { + height: 100% +} + .cvat-canvas-container { background-color: $background-color-1; } @@ -115,62 +119,3 @@ margin: 0px 5px; } } - -.cvat-canvas-context-menu { - opacity: 0.6; - position: fixed; - width: 300px; - z-index: 10; - max-height: 50%; - overflow-y: auto; - - &:hover { - opacity: 1; - } -} - -.cvat-canvas-z-axis-wrapper { - position: absolute; - background: $background-color-2; - bottom: 10px; - right: 10px; - height: 150px; - z-index: 100; - border-radius: 6px; - opacity: 0.5; - border: 1px solid $border-color-3; - display: flex; - flex-direction: column; - justify-content: space-between; - padding: 3px; - - &:hover { - opacity: 1; - } - - > .ant-slider { - height: 75%; - margin: 5px 3px; - - > .ant-slider-rail { - background-color: #979797; - } - - > .ant-slider-handle { - transform: none !important; - } - } - - > i { - opacity: 0.7; - color: $objects-bar-icons-color; - - &:hover { - opacity: 1; - } - - &:active { - opacity: 0.7; - } - } -} diff --git a/cvat-ui/src/components/annotation-page/styles.scss b/cvat-ui/src/components/annotation-page/styles.scss index 366253b4e28..ac89b303283 100644 --- a/cvat-ui/src/components/annotation-page/styles.scss +++ b/cvat-ui/src/components/annotation-page/styles.scss @@ -213,3 +213,75 @@ width: 15em; } } + + +// TODO: Move canvas from standard workspace and create its own .scss +.cvat-canvas-context-menu { + opacity: 0.6; + position: fixed; + width: 300px; + z-index: 10; + max-height: 50%; + overflow-y: auto; + + &:hover { + opacity: 1; + } +} + +.cvat-canvas-z-axis-wrapper { + position: absolute; + background: $background-color-2; + bottom: 10px; + right: 10px; + height: 150px; + z-index: 100; + border-radius: 6px; + opacity: 0.5; + border: 1px solid $border-color-3; + display: flex; + flex-direction: column; + justify-content: space-between; + padding: 3px; + + &:hover { + opacity: 1; + } + + > .ant-slider { + height: 75%; + margin: 5px 3px; + + > .ant-slider-rail { + background-color: #979797; + } + + > .ant-slider-handle { + transform: none !important; + } + } + + > i { + opacity: 0.7; + color: $objects-bar-icons-color; + + &:hover { + opacity: 1; + } + + &:active { + opacity: 0.7; + } + } +} + +.cvat-annotations-filters-input.ant-select > div { + height: 32px; + > div { + height: 32px; + + ul { + display: flex; + } + } +} diff --git a/cvat-ui/src/components/annotation-page/top-bar/right-group.tsx b/cvat-ui/src/components/annotation-page/top-bar/right-group.tsx index 7e39eff7be1..071279a2942 100644 --- a/cvat-ui/src/components/annotation-page/top-bar/right-group.tsx +++ b/cvat-ui/src/components/annotation-page/top-bar/right-group.tsx @@ -11,17 +11,17 @@ import { Button, } from 'antd'; -import { - InfoIcon, - FullscreenIcon, -} from '../../../icons'; +import { Workspace } from 'reducers/interfaces'; +import { InfoIcon, FullscreenIcon } from '../../../icons'; interface Props { + workspace: Workspace; showStatistics(): void; + changeWorkspace(workspace: Workspace): void; } function RightGroup(props: Props): JSX.Element { - const { showStatistics } = props; + const { showStatistics, changeWorkspace, workspace } = props; return ( @@ -46,9 +46,23 @@ function RightGroup(props: Props): JSX.Element { Info
- + + {Workspace.STANDARD} + + + {Workspace.ATTRIBUTE_ANNOTATION} +
diff --git a/cvat-ui/src/components/annotation-page/top-bar/top-bar.tsx b/cvat-ui/src/components/annotation-page/top-bar/top-bar.tsx index a358720303f..370702fec02 100644 --- a/cvat-ui/src/components/annotation-page/top-bar/top-bar.tsx +++ b/cvat-ui/src/components/annotation-page/top-bar/top-bar.tsx @@ -7,12 +7,12 @@ import React from 'react'; import { Row, Col, - Layout, InputNumber, } from 'antd'; import { SliderValue } from 'antd/lib/slider'; +import { Workspace } from 'reducers/interfaces'; import LeftGroup from './left-group'; import RightGroup from './right-group'; import PlayerNavigation from './player-navigation'; @@ -28,6 +28,8 @@ interface Props { stopFrame: number; undoAction?: string; redoAction?: string; + workspace: Workspace; + changeWorkspace(workspace: Workspace): void; showStatistics(): void; onSwitchPlay(): void; onSaveAnnotation(): void; @@ -55,7 +57,9 @@ export default function AnnotationTopBarComponent(props: Props): JSX.Element { inputFrameRef, startFrame, stopFrame, + workspace, showStatistics, + changeWorkspace, onSwitchPlay, onSaveAnnotation, onPrevFrame, @@ -72,42 +76,44 @@ export default function AnnotationTopBarComponent(props: Props): JSX.Element { } = props; return ( - - - - - - - - - - - - + + + + + + + + + + ); } diff --git a/cvat-ui/src/components/settings-page/workspace-settings.tsx b/cvat-ui/src/components/settings-page/workspace-settings.tsx index 6d265e4ab41..a7a5fd2cbb4 100644 --- a/cvat-ui/src/components/settings-page/workspace-settings.tsx +++ b/cvat-ui/src/components/settings-page/workspace-settings.tsx @@ -93,7 +93,7 @@ export default function WorkspaceSettingsComponent(props: Props): JSX.Element { max={1000} value={aamZoomMargin} onChange={(value: number | undefined): void => { - if (value) { + if (typeof (value) === 'number') { onChangeAAMZoomMargin(value); } }} diff --git a/cvat-ui/src/containers/annotation-page/annotation-page.tsx b/cvat-ui/src/containers/annotation-page/annotation-page.tsx index c66a93049a2..264d1d9450f 100644 --- a/cvat-ui/src/containers/annotation-page/annotation-page.tsx +++ b/cvat-ui/src/containers/annotation-page/annotation-page.tsx @@ -9,9 +9,7 @@ import { RouteComponentProps } from 'react-router'; import AnnotationPageComponent from 'components/annotation-page/annotation-page'; import { getJobAsync } from 'actions/annotation-actions'; -import { - CombinedState, -} from 'reducers/interfaces'; +import { CombinedState, Workspace } from 'reducers/interfaces'; type OwnProps = RouteComponentProps<{ tid: string; @@ -21,6 +19,7 @@ type OwnProps = RouteComponentProps<{ interface StateToProps { job: any | null | undefined; fetching: boolean; + workspace: Workspace; } interface DispatchToProps { @@ -36,12 +35,14 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps { instance: job, fetching, }, + workspace, }, } = state; return { job: !job || jobID === job.id ? job : null, fetching, + workspace, }; } diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx index 0625bece913..e9265bfae3c 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx @@ -39,6 +39,7 @@ import { GridColor, ObjectType, CombinedState, + Workspace, } from 'reducers/interfaces'; import { Canvas } from 'cvat-canvas'; @@ -48,6 +49,7 @@ interface StateToProps { canvasInstance: Canvas; jobInstance: any; activatedStateID: number | null; + activatedAttributeID: number | null; selectedStatesID: number[]; annotations: any[]; frameData: any; @@ -67,6 +69,8 @@ interface StateToProps { contrastLevel: number; saturationLevel: number; resetZoom: boolean; + aamZoomMargin: number; + workspace: Workspace; minZLayer: number; maxZLayer: number; curZLayer: number; @@ -82,7 +86,7 @@ interface DispatchToProps { onGroupObjects: (enabled: boolean) => void; onSplitTrack: (enabled: boolean) => void; onEditShape: (enabled: boolean) => void; - onUpdateAnnotations(sessionInstance: any, frame: number, states: any[]): void; + onUpdateAnnotations(states: any[]): void; onCreateAnnotations(sessionInstance: any, frame: number, states: any[]): void; onMergeAnnotations(sessionInstance: any, frame: number, states: any[]): void; onGroupAnnotations(sessionInstance: any, frame: number, states: any[]): void; @@ -123,6 +127,7 @@ function mapStateToProps(state: CombinedState): StateToProps { annotations: { states: annotations, activatedStateID, + activatedAttributeID, selectedStatesID, zLayer: { cur: curZLayer, @@ -131,6 +136,7 @@ function mapStateToProps(state: CombinedState): StateToProps { }, }, sidebarCollapsed, + workspace, }, settings: { player: { @@ -143,6 +149,9 @@ function mapStateToProps(state: CombinedState): StateToProps { saturationLevel, resetZoom, }, + workspace: { + aamZoomMargin, + }, shapes: { opacity, colorBy, @@ -160,6 +169,7 @@ function mapStateToProps(state: CombinedState): StateToProps { frameAngle: frameAngles[frame - jobInstance.startFrame], frame, activatedStateID, + activatedAttributeID, selectedStatesID, annotations, opacity, @@ -176,9 +186,11 @@ function mapStateToProps(state: CombinedState): StateToProps { contrastLevel, saturationLevel, resetZoom, + aamZoomMargin, curZLayer, minZLayer, maxZLayer, + workspace, }; } @@ -211,8 +223,8 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { onEditShape(enabled: boolean): void { dispatch(editShape(enabled)); }, - onUpdateAnnotations(sessionInstance: any, frame: number, states: any[]): void { - dispatch(updateAnnotationsAsync(sessionInstance, frame, states)); + onUpdateAnnotations(states: any[]): void { + dispatch(updateAnnotationsAsync(states)); }, onCreateAnnotations(sessionInstance: any, frame: number, states: any[]): void { dispatch(createAnnotationsAsync(sessionInstance, frame, states)); @@ -231,7 +243,7 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { dispatch(updateCanvasContextMenu(false, 0, 0)); } - dispatch(activateObject(activatedStateID)); + dispatch(activateObject(activatedStateID, null)); }, onSelectObjects(selectedStatesID: number[]): void { dispatch(selectObjects(selectedStatesID)); diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/label-item.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/label-item.tsx index 2e70ed1bb03..e795eb8d638 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/label-item.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/label-item.tsx @@ -29,7 +29,7 @@ interface StateToProps { } interface DispatchToProps { - updateAnnotations(sessionInstance: any, frameNumber: number, states: any[]): void; + updateAnnotations(states: any[]): void; changeLabelColor(sessionInstance: any, frameNumber: number, label: any, color: string): void; } @@ -68,8 +68,8 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps { function mapDispatchToProps(dispatch: any): DispatchToProps { return { - updateAnnotations(sessionInstance: any, frameNumber: number, states: any[]): void { - dispatch(updateAnnotationsAsync(sessionInstance, frameNumber, states)); + updateAnnotations(states: any[]): void { + dispatch(updateAnnotationsAsync(states)); }, changeLabelColor( sessionInstance: any, @@ -162,8 +162,6 @@ class LabelItemContainer extends React.PureComponent { private switchHidden(value: boolean): void { const { updateAnnotations, - jobInstance, - frameNumber, } = this.props; const { ownObjectStates } = this.state; @@ -171,14 +169,12 @@ class LabelItemContainer extends React.PureComponent { state.hidden = value; } - updateAnnotations(jobInstance, frameNumber, ownObjectStates); + updateAnnotations(ownObjectStates); } private switchLock(value: boolean): void { const { updateAnnotations, - jobInstance, - frameNumber, } = this.props; const { ownObjectStates } = this.state; @@ -186,7 +182,7 @@ class LabelItemContainer extends React.PureComponent { state.lock = value; } - updateAnnotations(jobInstance, frameNumber, ownObjectStates); + updateAnnotations(ownObjectStates); } public render(): JSX.Element { 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 d3f55c45704..fb11fe1de67 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 @@ -49,7 +49,7 @@ interface StateToProps { interface DispatchToProps { changeFrame(frame: number): void; - updateState(sessionInstance: any, frameNumber: number, objectState: any): void; + updateState(objectState: any): void; createAnnotations(sessionInstance: any, frameNumber: number, state: any): void; collapseOrExpand(objectStates: any[], collapsed: boolean): void; activateObject: (activatedStateID: number | null) => void; @@ -57,7 +57,7 @@ interface DispatchToProps { copyShape: (objectState: any) => void; propagateObject: (objectState: any) => void; changeLabelColor(sessionInstance: any, frameNumber: number, label: any, color: string): void; - changeGroupColor(sessionInstance: any, frameNumber: number, group: number, color: string): void; + changeGroupColor(group: number, color: string): void; } function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps { @@ -124,8 +124,8 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { changeFrame(frame: number): void { dispatch(changeFrameAsync(frame)); }, - updateState(sessionInstance: any, frameNumber: number, state: any): void { - dispatch(updateAnnotationsAsync(sessionInstance, frameNumber, [state])); + updateState(state: any): void { + dispatch(updateAnnotationsAsync([state])); }, createAnnotations(sessionInstance: any, frameNumber: number, state: any): void { dispatch(createAnnotationsAsync(sessionInstance, frameNumber, state)); @@ -134,7 +134,7 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { dispatch(collapseObjectItems(objectStates, collapsed)); }, activateObject(activatedStateID: number | null): void { - dispatch(activateObjectAction(activatedStateID)); + dispatch(activateObjectAction(activatedStateID, null)); }, removeObject(sessionInstance: any, objectState: any): void { dispatch(removeObjectAsync(sessionInstance, objectState, true)); @@ -154,13 +154,8 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { ): void { dispatch(changeLabelColorAsync(sessionInstance, frameNumber, label, color)); }, - changeGroupColor( - sessionInstance: any, - frameNumber: number, - group: number, - color: string, - ): void { - dispatch(changeGroupColorAsync(sessionInstance, frameNumber, group, color)); + changeGroupColor(group: number, color: string): void { + dispatch(changeGroupColorAsync(group, color)); }, }; } @@ -392,7 +387,7 @@ class ObjectItemContainer extends React.PureComponent { objectState.color = color; this.commit(); } else if (colorBy === ColorBy.GROUP) { - changeGroupColor(jobInstance, frameNumber, objectState.group.id, color); + changeGroupColor(objectState.group.id, color); } else if (colorBy === ColorBy.LABEL) { changeLabelColor(jobInstance, frameNumber, objectState.label, color); } @@ -421,11 +416,9 @@ class ObjectItemContainer extends React.PureComponent { const { objectState, updateState, - jobInstance, - frameNumber, } = this.props; - updateState(jobInstance, frameNumber, objectState); + updateState(objectState); } public render(): JSX.Element { diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx index 8e1a401ddb0..5279620faa2 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx @@ -6,15 +6,11 @@ import React from 'react'; import { connect } from 'react-redux'; import { GlobalHotKeys, KeyMap } from 'react-hotkeys'; -import { SelectValue } from 'antd/lib/select'; - import ObjectsListComponent from 'components/annotation-page/standard-workspace/objects-side-bar/objects-list'; import { updateAnnotationsAsync, - fetchAnnotationsAsync, removeObjectAsync, changeFrameAsync, - changeAnnotationsFilters as changeAnnotationsFiltersAction, collapseObjectItems, copyShape as copyShapeAction, propagateObject as propagateObjectAction, @@ -42,8 +38,7 @@ interface StateToProps { } interface DispatchToProps { - updateAnnotations(sessionInstance: any, frameNumber: number, states: any[]): void; - changeAnnotationsFilters(sessionInstance: any, filters: string[]): void; + updateAnnotations(states: any[]): void; collapseStates(states: any[], value: boolean): void; removeObject: (sessionInstance: any, objectState: any, force: boolean) => void; copyShape: (objectState: any) => void; @@ -111,19 +106,12 @@ function mapStateToProps(state: CombinedState): StateToProps { function mapDispatchToProps(dispatch: any): DispatchToProps { return { - updateAnnotations(sessionInstance: any, frameNumber: number, states: any[]): void { - dispatch(updateAnnotationsAsync(sessionInstance, frameNumber, states)); + updateAnnotations(states: any[]): void { + dispatch(updateAnnotationsAsync(states)); }, collapseStates(states: any[], collapsed: boolean): void { dispatch(collapseObjectItems(states, collapsed)); }, - changeAnnotationsFilters( - sessionInstance: any, - filters: string[], - ): void { - dispatch(changeAnnotationsFiltersAction(filters)); - dispatch(fetchAnnotationsAsync(sessionInstance)); - }, removeObject(sessionInstance: any, objectState: any, force: boolean): void { dispatch(removeObjectAsync(sessionInstance, objectState, force)); }, @@ -190,15 +178,6 @@ class ObjectsListContainer extends React.PureComponent { }); }; - private onChangeAnnotationsFilters = (value: SelectValue): void => { - const { - jobInstance, - changeAnnotationsFilters, - } = this.props; - const filters = value as string[]; - changeAnnotationsFilters(jobInstance, filters); - }; - private onLockAllStates = (): void => { this.lockAllStates(true); }; @@ -227,28 +206,24 @@ class ObjectsListContainer extends React.PureComponent { const { objectStates, updateAnnotations, - jobInstance, - frameNumber, } = this.props; for (const objectState of objectStates) { objectState.lock = locked; } - updateAnnotations(jobInstance, frameNumber, objectStates); + updateAnnotations(objectStates); } private hideAllStates(hidden: boolean): void { const { objectStates, updateAnnotations, - jobInstance, - frameNumber, } = this.props; for (const objectState of objectStates) { objectState.hidden = hidden; } - updateAnnotations(jobInstance, frameNumber, objectStates); + updateAnnotations(objectStates); } private collapseAllStates(collapsed: boolean): void { @@ -262,12 +237,10 @@ class ObjectsListContainer extends React.PureComponent { public render(): JSX.Element { const { - annotationsFilters, statesHidden, statesLocked, activatedStateID, objectStates, - frameNumber, jobInstance, updateAnnotations, removeObject, @@ -276,7 +249,6 @@ class ObjectsListContainer extends React.PureComponent { changeFrame, maxZLayer, minZLayer, - annotationsFiltersHistory, } = this.props; const { sortedStatesID, @@ -399,7 +371,7 @@ class ObjectsListContainer extends React.PureComponent { const state = activatedStated(); if (state) { state.lock = !state.lock; - updateAnnotations(jobInstance, frameNumber, [state]); + updateAnnotations([state]); } }, SWITCH_ALL_HIDDEN: (event: KeyboardEvent | undefined) => { @@ -411,7 +383,7 @@ class ObjectsListContainer extends React.PureComponent { const state = activatedStated(); if (state) { state.hidden = !state.hidden; - updateAnnotations(jobInstance, frameNumber, [state]); + updateAnnotations([state]); } }, SWITCH_OCCLUDED: (event: KeyboardEvent | undefined) => { @@ -419,7 +391,7 @@ class ObjectsListContainer extends React.PureComponent { const state = activatedStated(); if (state && state.objectType !== ObjectType.TAG) { state.occluded = !state.occluded; - updateAnnotations(jobInstance, frameNumber, [state]); + updateAnnotations([state]); } }, SWITCH_KEYFRAME: (event: KeyboardEvent | undefined) => { @@ -427,7 +399,7 @@ class ObjectsListContainer extends React.PureComponent { const state = activatedStated(); if (state && state.objectType === ObjectType.TRACK) { state.keyframe = !state.keyframe; - updateAnnotations(jobInstance, frameNumber, [state]); + updateAnnotations([state]); } }, SWITCH_OUTSIDE: (event: KeyboardEvent | undefined) => { @@ -435,7 +407,7 @@ class ObjectsListContainer extends React.PureComponent { const state = activatedStated(); if (state && state.objectType === ObjectType.TRACK) { state.outside = !state.outside; - updateAnnotations(jobInstance, frameNumber, [state]); + updateAnnotations([state]); } }, DELETE_OBJECT: (event: KeyboardEvent | undefined) => { @@ -450,7 +422,7 @@ class ObjectsListContainer extends React.PureComponent { const state = activatedStated(); if (state && state.objectType !== ObjectType.TAG) { state.zOrder = minZLayer - 1; - updateAnnotations(jobInstance, frameNumber, [state]); + updateAnnotations([state]); } }, TO_FOREGROUND: (event: KeyboardEvent | undefined) => { @@ -458,7 +430,7 @@ class ObjectsListContainer extends React.PureComponent { const state = activatedStated(); if (state && state.objectType !== ObjectType.TAG) { state.zOrder = maxZLayer + 1; - updateAnnotations(jobInstance, frameNumber, [state]); + updateAnnotations([state]); } }, COPY_SHAPE: (event: KeyboardEvent | undefined) => { @@ -506,10 +478,7 @@ class ObjectsListContainer extends React.PureComponent { {...this.props} statesOrdering={statesOrdering} sortedStatesID={sortedStatesID} - annotationsFilters={annotationsFilters} changeStatesOrdering={this.onChangeStatesOrdering} - changeAnnotationsFilters={this.onChangeAnnotationsFilters} - annotationsFiltersHistory={annotationsFiltersHistory} lockAllStates={this.onLockAllStates} unlockAllStates={this.onUnlockAllStates} collapseAllStates={this.onCollapseAllStates} diff --git a/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx b/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx index 8bcc1f59f87..48695dbed7b 100644 --- a/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx +++ b/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx @@ -22,10 +22,12 @@ import { undoActionAsync, redoActionAsync, searchAnnotationsAsync, + changeWorkspace as changeWorkspaceAction, + activateObject, } from 'actions/annotation-actions'; import AnnotationTopBarComponent from 'components/annotation-page/top-bar/top-bar'; -import { CombinedState, FrameSpeed } from 'reducers/interfaces'; +import { CombinedState, FrameSpeed, Workspace } from 'reducers/interfaces'; interface StateToProps { jobInstance: any; @@ -41,6 +43,7 @@ interface StateToProps { redoAction?: string; autoSave: boolean; autoSaveInterval: number; + workspace: Workspace; } interface DispatchToProps { @@ -51,6 +54,7 @@ interface DispatchToProps { undo(sessionInstance: any, frameNumber: any): void; redo(sessionInstance: any, frameNumber: any): void; searchAnnotations(sessionInstance: any, frameFrom: any, frameTo: any): void; + changeWorkspace(workspace: Workspace): void; } function mapStateToProps(state: CombinedState): StateToProps { @@ -76,6 +80,7 @@ function mapStateToProps(state: CombinedState): StateToProps { canvas: { ready: canvasIsReady, }, + workspace, }, settings: { player: { @@ -103,6 +108,7 @@ function mapStateToProps(state: CombinedState): StateToProps { redoAction: history.redo[history.redo.length - 1], autoSave, autoSaveInterval, + workspace, }; } @@ -130,6 +136,10 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { searchAnnotations(sessionInstance: any, frameFrom: any, frameTo: any): void { dispatch(searchAnnotationsAsync(sessionInstance, frameFrom, frameTo)); }, + changeWorkspace(workspace: Workspace): void { + dispatch(activateObject(null, null)); + dispatch(changeWorkspaceAction(workspace)); + }, }; } @@ -442,8 +452,10 @@ class AnnotationTopBarContainer extends React.PureComponent { frameNumber, undoAction, redoAction, - searchAnnotations, + workspace, canvasIsReady, + searchAnnotations, + changeWorkspace, } = this.props; const preventDefault = (event: KeyboardEvent | undefined): void => { @@ -602,6 +614,8 @@ class AnnotationTopBarContainer extends React.PureComponent { onSliderChange={this.onChangePlayerSliderValue} onInputChange={this.onChangePlayerInputValue} onURLIconClick={this.onURLIconClick} + changeWorkspace={changeWorkspace} + workspace={workspace} playing={playing} saving={saving} savingStatuses={savingStatuses} diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index 83b7a8c0a62..c44dcd848fd 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -12,6 +12,7 @@ import { ActiveControl, ShapeType, ObjectType, + Workspace, } from './interfaces'; const defaultState: AnnotationState = { @@ -54,6 +55,7 @@ const defaultState: AnnotationState = { annotations: { selectedStatesID: [], activatedStateID: null, + activatedAttributeID: null, saving: { uploading: false, statuses: [], @@ -88,6 +90,7 @@ const defaultState: AnnotationState = { sidebarCollapsed: false, appearanceCollapsed: false, tabContentHeight: 0, + workspace: Workspace.STANDARD, }; export default (state = defaultState, action: AnyAction): AnnotationState => { @@ -646,7 +649,11 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { }; } case AnnotationActionTypes.ACTIVATE_OBJECT: { - const { activatedStateID } = action.payload; + const { + activatedStateID, + activatedAttributeID, + } = action.payload; + const { canvas: { activeControl, @@ -663,6 +670,7 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { annotations: { ...state.annotations, activatedStateID, + activatedAttributeID, }, }; } @@ -1041,6 +1049,13 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { }, }; } + case AnnotationActionTypes.CHANGE_WORKSPACE: { + const { workspace } = action.payload; + return { + ...state, + workspace, + }; + } case AnnotationActionTypes.RESET_CANVAS: { return { ...state, diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index 74e36d86c56..e0595b90b9f 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -335,6 +335,7 @@ export interface AnnotationState { annotations: { selectedStatesID: number[]; activatedStateID: number | null; + activatedAttributeID: number | null; collapsed: Record; states: any[]; filters: string[]; @@ -367,6 +368,12 @@ export interface AnnotationState { sidebarCollapsed: boolean; appearanceCollapsed: boolean; tabContentHeight: number; + workspace: Workspace; +} + +export enum Workspace { + STANDARD = 'Standard', + ATTRIBUTE_ANNOTATION = 'Attribute annotation', } export enum GridColor { From ad9b877b6dd86c6c367a46fd91f951248fd8df55 Mon Sep 17 00:00:00 2001 From: Boris Sekachev <40690378+bsekachev@users.noreply.github.com> Date: Tue, 17 Mar 2020 14:19:55 +0300 Subject: [PATCH 15/22] Installation issues for development environment (#1280) * Installation issues * Added ffmpeg --- CONTRIBUTING.md | 4 ++-- cvat-canvas/package-lock.json | 2 +- cvat/requirements/development.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8cb2d483429..40faceb7642 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,7 +15,7 @@ Next steps should work on clear Ubuntu 18.04. - Install necessary dependencies: ```sh -$ sudo apt-get update && apt-get --no-install-recommends install -y nodejs npm curl redis-server python3-dev python3-pip python3-venv libldap2-dev libsasl2-dev +$ sudo apt-get update && sudo apt-get --no-install-recommends install -y ffmpeg build-essential nodejs npm curl redis-server python3-dev python3-pip python3-venv libldap2-dev libsasl2-dev ``` - Install [Visual Studio Code](https://code.visualstudio.com/docs/setup/linux#_debian-and-ubuntu-based-distributions) @@ -28,7 +28,7 @@ git clone https://github.com/opencv/cvat cd cvat && mkdir logs keys python3 -m venv .env . .env/bin/activate -pip install -U pip wheel +pip install -U pip wheel setuptools pip install -r cvat/requirements/development.txt pip install -r datumaro/requirements.txt python manage.py migrate diff --git a/cvat-canvas/package-lock.json b/cvat-canvas/package-lock.json index 46c12b74665..c09c5cb3b43 100644 --- a/cvat-canvas/package-lock.json +++ b/cvat-canvas/package-lock.json @@ -1,6 +1,6 @@ { "name": "cvat-canvas", - "version": "0.1.0", + "version": "0.5.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/cvat/requirements/development.txt b/cvat/requirements/development.txt index fc0333ad0a5..de046528865 100644 --- a/cvat/requirements/development.txt +++ b/cvat/requirements/development.txt @@ -7,7 +7,7 @@ pylint==2.3.1 pylint-django==0.9.4 pylint-plugin-utils==0.2.6 rope==0.11 -wrapt==1.10.11 +wrapt==1.11.1 django-extensions==2.0.6 Werkzeug==0.15.3 snakeviz==0.4.2 From 8b1ec0befc79d2e92921c157a3692885caffc391 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Mar 2020 14:23:30 +0300 Subject: [PATCH 16/22] Bump acorn from 6.3.0 to 6.4.1 in /cvat-ui (#1270) * Bump acorn from 6.3.0 to 6.4.1 in /cvat-ui Bumps [acorn](https://github.com/acornjs/acorn) from 6.3.0 to 6.4.1. - [Release notes](https://github.com/acornjs/acorn/releases) - [Commits](https://github.com/acornjs/acorn/compare/6.3.0...6.4.1) Signed-off-by: dependabot[bot] * Updated CHANGELOG.md Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Boris Sekachev --- CHANGELOG.md | 4 ++-- cvat-ui/package-lock.json | 20 +++++++++----------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5067f6df5fa..08adf7f8d2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - ### Changed -- +- ### Deprecated - @@ -21,7 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - ### Security -- +- Bump acorn from 6.3.0 to 6.4.1 in /cvat-ui ([#1270](https://github.com/opencv/cvat/pull/1270)) ## [0.6.0] - 2020-03-15 ### Added diff --git a/cvat-ui/package-lock.json b/cvat-ui/package-lock.json index 828118cfaaf..f2453993069 100644 --- a/cvat-ui/package-lock.json +++ b/cvat-ui/package-lock.json @@ -1421,9 +1421,9 @@ } }, "acorn": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.3.0.tgz", - "integrity": "sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", + "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==", "dev": true }, "acorn-jsx": { @@ -4293,14 +4293,6 @@ "acorn": "^7.1.0", "acorn-jsx": "^5.1.0", "eslint-visitor-keys": "^1.1.0" - }, - "dependencies": { - "acorn": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz", - "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==", - "dev": true - } } }, "esprima": { @@ -12354,6 +12346,12 @@ "webpack-sources": "^1.4.1" }, "dependencies": { + "acorn": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", + "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", + "dev": true + }, "schema-utils": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", From 4ab3c1af821aae42365a4868582727299314d0a3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Mar 2020 14:24:11 +0300 Subject: [PATCH 17/22] Bump acorn from 6.2.1 to 6.4.1 in /cvat-canvas (#1281) Bumps [acorn](https://github.com/acornjs/acorn) from 6.2.1 to 6.4.1. - [Release notes](https://github.com/acornjs/acorn/releases) - [Commits](https://github.com/acornjs/acorn/compare/6.2.1...6.4.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cvat-canvas/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cvat-canvas/package-lock.json b/cvat-canvas/package-lock.json index c09c5cb3b43..022e4582b77 100644 --- a/cvat-canvas/package-lock.json +++ b/cvat-canvas/package-lock.json @@ -1203,9 +1203,9 @@ } }, "acorn": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.2.1.tgz", - "integrity": "sha512-JD0xT5FCRDNyjDda3Lrg/IxFscp9q4tiYtxE1/nOzlKCk7hIRuYjhq1kCNkbPjMRMZuFq20HNQn1I9k8Oj0E+Q==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", + "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", "dev": true }, "acorn-jsx": { From 55677e03970a61f0bf60453a7a50da0314c86c4c Mon Sep 17 00:00:00 2001 From: zhiltsov-max Date: Tue, 17 Mar 2020 17:56:27 +0300 Subject: [PATCH 18/22] Use source label map for voc export (#1276) * Use source label map for voc export * Add line to changelog --- CHANGELOG.md | 4 +++- cvat/apps/annotation/pascal_voc.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08adf7f8d2a..eb69400edd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - ### Changed -- +- VOC task export now does not use official label map by default, but takes one + from the source task to avoid primary-class and class part name + clashing ([#1275](https://github.com/opencv/cvat/issues/1275)) ### Deprecated - diff --git a/cvat/apps/annotation/pascal_voc.py b/cvat/apps/annotation/pascal_voc.py index 2dd0aa48f51..b6bcfaa33ee 100644 --- a/cvat/apps/annotation/pascal_voc.py +++ b/cvat/apps/annotation/pascal_voc.py @@ -74,7 +74,7 @@ def dump(file_object, annotations): extractor = CvatAnnotationsExtractor('', annotations) extractor = extractor.transform(id_from_image) extractor = Dataset.from_extractors(extractor) # apply lazy transforms - converter = env.make_converter('voc') + converter = env.make_converter('voc', label_map='source') with TemporaryDirectory() as temp_dir: converter(extractor, save_dir=temp_dir) make_zip_archive(temp_dir, file_object) \ No newline at end of file From 936d3059cf72064f397ff72c289d18b6b8ca73c8 Mon Sep 17 00:00:00 2001 From: zhiltsov-max Date: Wed, 18 Mar 2020 14:45:44 +0300 Subject: [PATCH 19/22] [Datumaro] Fix frame matching in video annotations import (#1274) * Add extra frame matching way for videos * Add line to changelog --- CHANGELOG.md | 3 ++- cvat/apps/dataset_manager/bindings.py | 4 ++++ datumaro/datumaro/plugins/yolo_format/extractor.py | 4 +++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb69400edd8..3e62ba4511e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - ### Fixed -- +- Frame name matching for video annotations import - + allowed `frame_XXXXXX[.ext]` format ([#1274](https://github.com/opencv/cvat/pull/1274)) ### Security - Bump acorn from 6.3.0 to 6.4.1 in /cvat-ui ([#1270](https://github.com/opencv/cvat/pull/1270)) diff --git a/cvat/apps/dataset_manager/bindings.py b/cvat/apps/dataset_manager/bindings.py index da37a3048e6..6b531545c38 100644 --- a/cvat/apps/dataset_manager/bindings.py +++ b/cvat/apps/dataset_manager/bindings.py @@ -177,6 +177,8 @@ def __init__(self, url, db_task, user): def match_frame(item, cvat_task_anno): + is_video = cvat_task_anno.meta['task']['mode'] == 'interpolation' + frame_number = None if frame_number is None: try: @@ -193,6 +195,8 @@ def match_frame(item, cvat_task_anno): frame_number = int(item.id) except Exception: pass + if frame_number is None and is_video and item.id.startswith('frame_'): + frame_number = int(item.id[len('frame_'):]) if not frame_number in cvat_task_anno.frame_info: raise Exception("Could not match item id: '%s' with any task frame" % item.id) diff --git a/datumaro/datumaro/plugins/yolo_format/extractor.py b/datumaro/datumaro/plugins/yolo_format/extractor.py index 7840b26c5ca..11e829d4a5b 100644 --- a/datumaro/datumaro/plugins/yolo_format/extractor.py +++ b/datumaro/datumaro/plugins/yolo_format/extractor.py @@ -90,7 +90,9 @@ def __init__(self, config_path, image_info=None): subset = YoloExtractor.Subset(subset_name, self) with open(list_path, 'r') as f: subset.items = OrderedDict( - (osp.splitext(osp.basename(p))[0], p.strip()) for p in f) + (osp.splitext(osp.basename(p.strip()))[0], p.strip()) + for p in f + ) for item_id, image_path in subset.items.items(): image_path = self._make_local_path(image_path) From 95451320979978bb28ec7f4459294bc5cf6e3372 Mon Sep 17 00:00:00 2001 From: zhiltsov-max Date: Wed, 18 Mar 2020 14:54:44 +0300 Subject: [PATCH 20/22] [Datumaro] Allow empty COCO dataset export (#1272) * Allow empty dataset export in coco * Add line to changelog Co-authored-by: Nikita Manovich <40690625+nmanovic@users.noreply.github.com> --- CHANGELOG.md | 1 + .../datumaro/plugins/coco_format/converter.py | 36 +++++++++++-------- datumaro/tests/test_coco_format.py | 5 ++- 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e62ba4511e..03542b30260 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - ### Fixed +- Annotation-less tasks now can be exported as empty datasets in COCO ([#1277](https://github.com/opencv/cvat/issues/1277)) - Frame name matching for video annotations import - allowed `frame_XXXXXX[.ext]` format ([#1274](https://github.com/opencv/cvat/pull/1274)) diff --git a/datumaro/datumaro/plugins/coco_format/converter.py b/datumaro/datumaro/plugins/coco_format/converter.py index 39fe7b15402..403a6a83eb6 100644 --- a/datumaro/datumaro/plugins/coco_format/converter.py +++ b/datumaro/datumaro/plugins/coco_format/converter.py @@ -329,20 +329,24 @@ def save_categories(self, dataset): label_categories = dataset.categories().get(AnnotationType.label) if label_categories is None: return - points_categories = dataset.categories().get(AnnotationType.points) - if points_categories is None: - return - - for idx, kp_cat in points_categories.items.items(): - label_cat = label_categories.items[idx] + point_categories = dataset.categories().get(AnnotationType.points) + for idx, label_cat in enumerate(label_categories.items): cat = { 'id': 1 + idx, 'name': _cast(label_cat.name, str, ''), 'supercategory': _cast(label_cat.parent, str, ''), - 'keypoints': [str(l) for l in kp_cat.labels], - 'skeleton': [int(i) for i in kp_cat.adjacent], + 'keypoints': [], + 'skeleton': [], } + + if point_categories is not None: + kp_cat = point_categories.items.get(idx) + if kp_cat is not None: + cat.update({ + 'keypoints': [str(l) for l in kp_cat.labels], + 'skeleton': [int(i) for i in kp_cat.adjacent], + }) self.categories.append(cat) def save_annotations(self, item): @@ -447,14 +451,19 @@ class _Converter: def __init__(self, extractor, save_dir, tasks=None, save_images=False, segmentation_mode=None, crop_covered=False): - assert tasks is None or isinstance(tasks, (CocoTask, list)) + assert tasks is None or isinstance(tasks, (CocoTask, list, str)) if tasks is None: tasks = list(self._TASK_CONVERTER) elif isinstance(tasks, CocoTask): tasks = [tasks] + elif isinstance(tasks, str): + tasks = [CocoTask[tasks]] else: - for t in tasks: - assert t in CocoTask + for i, t in enumerate(tasks): + if isinstance(t, str): + tasks[i] = CocoTask[t] + else: + assert t in CocoTask, t self._tasks = tasks self._extractor = extractor @@ -546,9 +555,8 @@ def convert(self): task_conv.save_annotations(item) for task, task_conv in task_converters.items(): - if not task_conv.is_empty(): - task_conv.write(osp.join(self._ann_dir, - '%s_%s.json' % (task.name, subset_name))) + task_conv.write(osp.join(self._ann_dir, + '%s_%s.json' % (task.name, subset_name))) class CocoConverter(Converter, CliPlugin): @staticmethod diff --git a/datumaro/tests/test_coco_format.py b/datumaro/tests/test_coco_format.py index 724fdc5a4af..f9340b659e8 100644 --- a/datumaro/tests/test_coco_format.py +++ b/datumaro/tests/test_coco_format.py @@ -632,10 +632,13 @@ def __iter__(self): def categories(self): label_cat = LabelCategories() + point_cat = PointsCategories() for label in range(10): label_cat.add('label_' + str(label)) + point_cat.add(label) return { AnnotationType.label: label_cat, + AnnotationType.points: point_cat, } with TestDir() as test_dir: @@ -651,4 +654,4 @@ def __iter__(self): with TestDir() as test_dir: self._test_save_and_load(TestExtractor(), - CocoConverter(), test_dir) \ No newline at end of file + CocoConverter(tasks='image_info'), test_dir) \ No newline at end of file From 08688b0c3e3e280a0f9a4973aab48a36589de399 Mon Sep 17 00:00:00 2001 From: zhiltsov-max Date: Wed, 18 Mar 2020 14:57:51 +0300 Subject: [PATCH 21/22] [Datumaro] Fix occluded and z_order attributes export (#1271) * Fix occluded and z_order attributes export * Add line to changelog Co-authored-by: Nikita Manovich <40690625+nmanovic@users.noreply.github.com> --- CHANGELOG.md | 1 + cvat/apps/dataset_manager/bindings.py | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03542b30260..0da60518f6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - ### Fixed +- `occluded` and `z_order` annotation attributes are now correctly passed to Datumaro ([#1271](https://github.com/opencv/cvat/pull/1271)) - Annotation-less tasks now can be exported as empty datasets in COCO ([#1277](https://github.com/opencv/cvat/issues/1277)) - Frame name matching for video annotations import - allowed `frame_XXXXXX[.ext]` format ([#1274](https://github.com/opencv/cvat/pull/1274)) diff --git a/cvat/apps/dataset_manager/bindings.py b/cvat/apps/dataset_manager/bindings.py index 6b531545c38..d1d98af279a 100644 --- a/cvat/apps/dataset_manager/bindings.py +++ b/cvat/apps/dataset_manager/bindings.py @@ -91,7 +91,9 @@ def categories(self): @staticmethod def _load_categories(cvat_anno): categories = {} - label_categories = datumaro.LabelCategories() + + label_categories = datumaro.LabelCategories( + attributes=['occluded', 'z_order']) for _, label in cvat_anno.meta['task']['labels']: label_categories.add(label['name']) @@ -144,6 +146,8 @@ def convert_attrs(label, cvat_attrs): anno_group = shape_obj.group anno_label = map_label(shape_obj.label) anno_attr = convert_attrs(shape_obj.label, shape_obj.attributes) + anno_attr['occluded'] = shape_obj.occluded + anno_attr['z_order'] = shape_obj.z_order anno_points = shape_obj.points if shape_obj.type == ShapeType.POINTS: @@ -238,7 +242,7 @@ def import_dm_annotations(dm_dataset, cvat_task_anno): frame=frame_number, label=label_cat.items[ann.label].name, points=ann.points, - occluded=False, + occluded=ann.attributes.get('occluded') == True, group=group_map.get(ann.group, 0), attributes=[cvat_task_anno.Attribute(name=n, value=str(v)) for n, v in ann.attributes.items()], From 731b8967c01847c999f9ec0986e575196513fa18 Mon Sep 17 00:00:00 2001 From: zhiltsov-max Date: Wed, 18 Mar 2020 15:16:26 +0300 Subject: [PATCH 22/22] Fix LabelMe format (#1260) * Fix labelme filenames * Change module path * Add tests for LabelMe * Update test * Fix test * Add line in changelog --- CHANGELOG.md | 1 + cvat/apps/annotation/labelme.py | 8 ++++---- cvat/apps/engine/tests/test_rest_api.py | 15 +++++++++++++++ 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0da60518f6b..e50226d9110 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - ### Fixed +- File names in LabelMe format export are no longer truncated ([#1259](https://github.com/opencv/cvat/issues/1259)) - `occluded` and `z_order` annotation attributes are now correctly passed to Datumaro ([#1271](https://github.com/opencv/cvat/pull/1271)) - Annotation-less tasks now can be exported as empty datasets in COCO ([#1277](https://github.com/opencv/cvat/issues/1277)) - Frame name matching for video annotations import - diff --git a/cvat/apps/annotation/labelme.py b/cvat/apps/annotation/labelme.py index 0128ca73922..baacb388ef0 100644 --- a/cvat/apps/annotation/labelme.py +++ b/cvat/apps/annotation/labelme.py @@ -107,17 +107,17 @@ def dump_frame_anno(frame_annotation): return ET.tostring(root_elem, encoding='unicode', pretty_print=True) def dump_as_labelme_annotation(file_object, annotations): + import os.path as osp from zipfile import ZipFile, ZIP_DEFLATED with ZipFile(file_object, 'w', compression=ZIP_DEFLATED) as output_zip: for frame_annotation in annotations.group_by_frame(): xml_data = dump_frame_anno(frame_annotation) - filename = frame_annotation.name - filename = filename[ : filename.rfind('.')] + '.xml' + filename = osp.splitext(frame_annotation.name)[0] + '.xml' output_zip.writestr(filename, xml_data) def parse_xml_annotations(xml_data, annotations, input_zip): - from cvat.apps.annotation.coco import mask_to_polygon + from datumaro.util.mask_tools import mask_to_polygons from io import BytesIO from lxml import etree as ET import numpy as np @@ -229,7 +229,7 @@ def parse_attributes(attributes_string): mask = input_zip.read(osp.join(_MASKS_DIR, mask_file)) mask = np.asarray(Image.open(BytesIO(mask)).convert('L')) mask = (mask != 0) - polygons = mask_to_polygon(mask) + polygons = mask_to_polygons(mask) for polygon in polygons: ann_items.append(annotations.LabeledShape( diff --git a/cvat/apps/engine/tests/test_rest_api.py b/cvat/apps/engine/tests/test_rest_api.py index d959f71c5b5..f3da0410623 100644 --- a/cvat/apps/engine/tests/test_rest_api.py +++ b/cvat/apps/engine/tests/test_rest_api.py @@ -2655,6 +2655,15 @@ def _get_initial_annotation(annotation_format): "points": [20.0, 0.1, 10, 3.22, 4, 7, 10, 30, 1, 2, 4.44, 5.55], "type": "polygon", "occluded": True + }, + { + "frame": 2, + "label_id": task["labels"][1]["id"], + "group": 1, + "attributes": [], + "points": [4, 7, 10, 30, 4, 5.55], + "type": "polygon", + "occluded": False }] tags_wo_attrs = [{ @@ -2711,6 +2720,12 @@ def _get_initial_annotation(annotation_format): elif annotation_format == "MOT CSV 1.0": annotations["tracks"] = rectangle_tracks_wo_attrs + elif annotation_format == "LabelMe ZIP 3.0 for images": + annotations["shapes"] = rectangle_shapes_with_attrs + \ + rectangle_shapes_wo_attrs + \ + polygon_shapes_wo_attrs + \ + polygon_shapes_with_attrs + return annotations response = self._get_annotation_formats(annotator)