Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

React UI: tags support #1241

Merged
merged 14 commits into from
Mar 16, 2020
1 change: 1 addition & 0 deletions cvat-canvas/src/typescript/canvasView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1084,6 +1084,7 @@ export class CanvasViewImpl implements CanvasView, Listener {

this.activeElement = { ...activeElement };
const shape = this.svgShapes[clientID];

let text = this.svgTexts[clientID];
if (!text) {
text = this.addText(state);
Expand Down
8 changes: 5 additions & 3 deletions cvat-core/src/annotations-collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -802,17 +802,19 @@
let minimumState = null;
for (const state of objectStates) {
checkObjectType('object state', state, null, ObjectState);
if (state.outside || state.hidden) continue;
if (state.outside || state.hidden || state.objectType === ObjectType.TAG) {
continue;
}

const object = this.objects[state.clientID];
if (typeof (object) === 'undefined') {
throw new ArgumentError(
'The object has not been saved yet. Call annotations.put([state]) before',
);
}

const distance = object.constructor.distance(state.points, x, y);
if (distance !== null && (minimumDistance === null || distance < minimumDistance)) {
if (distance !== null && (minimumDistance === null
|| distance < minimumDistance)) {
minimumDistance = distance;
minimumState = state;
}
Expand Down
30 changes: 18 additions & 12 deletions cvat-core/src/annotations-filter.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
*/

const jsonpath = require('jsonpath');
const { AttributeType } = require('./enums');
const {
AttributeType,
ObjectType,
} = require('./enums');
const { ArgumentError } = require('./exceptions');


Expand Down Expand Up @@ -165,18 +168,21 @@ class AnnotationsFilter {
let xbr = Number.MIN_SAFE_INTEGER;
let ytl = Number.MAX_SAFE_INTEGER;
let ybr = Number.MIN_SAFE_INTEGER;
let [width, height] = [null, null];

if (state.objectType !== ObjectType.TAG) {
state.points.forEach((coord, idx) => {
if (idx % 2) { // y
ytl = Math.min(ytl, coord);
ybr = Math.max(ybr, coord);
} else { // x
xtl = Math.min(xtl, coord);
xbr = Math.max(xbr, coord);
}
});
[width, height] = [xbr - xtl, ybr - ytl];
}

state.points.forEach((coord, idx) => {
if (idx % 2) { // y
ytl = Math.min(ytl, coord);
ybr = Math.max(ybr, coord);
} else { // x
xtl = Math.min(xtl, coord);
xbr = Math.max(xbr, coord);
}
});

const [width, height] = [xbr - xtl, ybr - ytl];
const attributes = {};
Object.keys(state.attributes).reduce((acc, key) => {
const attr = labelAttributes[key];
Expand Down
5 changes: 5 additions & 0 deletions cvat-core/src/annotations-objects.js
Original file line number Diff line number Diff line change
Expand Up @@ -1139,6 +1139,7 @@
attributes: { ...this.attributes },
label: this.label,
group: this.groupObject,
color: this.color,
updated: this.updated,
frame,
};
Expand Down Expand Up @@ -1171,6 +1172,10 @@
this._saveLock(data.lock);
}

if (updated.color) {
this._saveColor(data.color);
}

this.updateTimestamp(updated);
updated.reset();

Expand Down
117 changes: 85 additions & 32 deletions cvat-ui/src/actions/annotation-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,10 @@ export enum AnnotationActionTypes {
COPY_SHAPE = 'COPY_SHAPE',
PASTE_SHAPE = 'PASTE_SHAPE',
EDIT_SHAPE = 'EDIT_SHAPE',
DRAW_SHAPE = 'DRAW_SHAPE',
REPEAT_DRAW_SHAPE = 'REPEAT_DRAW_SHAPE',
SHAPE_DRAWN = 'SHAPE_DRAWN',
RESET_CANVAS = 'RESET_CANVAS',
REMEMBER_CREATED_OBJECT = 'REMEMBER_CREATED_OBJECT',
UPDATE_ANNOTATIONS_SUCCESS = 'UPDATE_ANNOTATIONS_SUCCESS',
UPDATE_ANNOTATIONS_FAILED = 'UPDATE_ANNOTATIONS_FAILED',
CREATE_ANNOTATIONS_SUCCESS = 'CREATE_ANNOTATIONS_SUCCESS',
Expand Down Expand Up @@ -843,15 +843,17 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
};
}

export function drawShape(
shapeType: ShapeType,
labelID: number,
export function rememberObject(
objectType: ObjectType,
labelID: number,
shapeType?: ShapeType,
points?: number,
rectDrawingMethod?: RectDrawingMethod,
): AnyAction {
let activeControl = ActiveControl.DRAW_RECTANGLE;
if (shapeType === ShapeType.POLYGON) {
let activeControl = ActiveControl.CURSOR;
if (shapeType === ShapeType.RECTANGLE) {
activeControl = ActiveControl.DRAW_RECTANGLE;
} else if (shapeType === ShapeType.POLYGON) {
activeControl = ActiveControl.DRAW_POLYGON;
} else if (shapeType === ShapeType.POLYLINE) {
activeControl = ActiveControl.DRAW_POLYLINE;
Expand All @@ -860,7 +862,7 @@ export function drawShape(
}

return {
type: AnnotationActionTypes.DRAW_SHAPE,
type: AnnotationActionTypes.REMEMBER_CREATED_OBJECT,
payload: {
shapeType,
labelID,
Expand Down Expand Up @@ -1153,12 +1155,28 @@ export function searchAnnotationsAsync(

export function pasteShapeAsync(): ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
const initialState = getStore().getState().annotation.drawing.activeInitialState;
const { instance: canvasInstance } = getStore().getState().annotation.canvas;
const {
canvas: {
instance: canvasInstance,
},
job: {
instance: jobInstance,
},
player: {
frame: {
number: frameNumber,
},
},
drawing: {
activeInitialState: initialState,
},
} = getStore().getState().annotation;

if (initialState) {
let activeControl = ActiveControl.DRAW_RECTANGLE;
if (initialState.shapeType === ShapeType.POINTS) {
let activeControl = ActiveControl.CURSOR;
if (initialState.shapeType === ShapeType.RECTANGLE) {
activeControl = ActiveControl.DRAW_RECTANGLE;
} else if (initialState.shapeType === ShapeType.POINTS) {
activeControl = ActiveControl.DRAW_POINTS;
} else if (initialState.shapeType === ShapeType.POLYGON) {
activeControl = ActiveControl.DRAW_POLYGON;
Expand All @@ -1174,31 +1192,57 @@ export function pasteShapeAsync(): ThunkAction<Promise<void>, {}, {}, AnyAction>
});

canvasInstance.cancel();
canvasInstance.draw({
enabled: true,
initialState,
});
if (initialState.objectType === ObjectType.TAG) {
const objectState = new cvat.classes.ObjectState({
objectType: ObjectType.TAG,
label: initialState.label,
attributes: initialState.attributes,
frame: frameNumber,
});
dispatch(createAnnotationsAsync(jobInstance, frameNumber, [objectState]));
} else {
canvasInstance.draw({
enabled: true,
initialState,
});
}
}
};
}

export function repeatDrawShapeAsync(): ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
const {
activeShapeType,
activeNumOfPoints,
activeRectDrawingMethod,
} = getStore().getState().annotation.drawing;

const { instance: canvasInstance } = getStore().getState().annotation.canvas;
canvas: {
instance: canvasInstance,
},
job: {
labels,
instance: jobInstance,
},
player: {
frame: {
number: frameNumber,
},
},
drawing: {
activeObjectType,
activeLabelID,
activeShapeType,
activeNumOfPoints,
activeRectDrawingMethod,
},
} = getStore().getState().annotation;

let activeControl = ActiveControl.DRAW_RECTANGLE;
if (activeShapeType === ShapeType.POLYGON) {
let activeControl = ActiveControl.CURSOR;
if (activeShapeType === ShapeType.RECTANGLE) {
activeControl = ActiveControl.DRAW_RECTANGLE;
} else if (activeShapeType === ShapeType.POINTS) {
activeControl = ActiveControl.DRAW_POINTS;
} else if (activeShapeType === ShapeType.POLYGON) {
activeControl = ActiveControl.DRAW_POLYGON;
} else if (activeShapeType === ShapeType.POLYLINE) {
activeControl = ActiveControl.DRAW_POLYLINE;
} else if (activeShapeType === ShapeType.POINTS) {
activeControl = ActiveControl.DRAW_POINTS;
}

dispatch({
Expand All @@ -1209,12 +1253,21 @@ export function repeatDrawShapeAsync(): ThunkAction<Promise<void>, {}, {}, AnyAc
});

canvasInstance.cancel();
canvasInstance.draw({
enabled: true,
rectDrawingMethod: activeRectDrawingMethod,
numberOfPoints: activeNumOfPoints,
shapeType: activeShapeType,
crosshair: activeShapeType === ShapeType.RECTANGLE,
});
if (activeObjectType === ObjectType.TAG) {
const objectState = new cvat.classes.ObjectState({
objectType: ObjectType.TAG,
label: labels.filter((label: any) => label.id === activeLabelID)[0],
frame: frameNumber,
});
dispatch(createAnnotationsAsync(jobInstance, frameNumber, [objectState]));
} else {
canvasInstance.draw({
enabled: true,
rectDrawingMethod: activeRectDrawingMethod,
numberOfPoints: activeNumOfPoints,
shapeType: activeShapeType,
crosshair: activeShapeType === ShapeType.RECTANGLE,
});
}
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,8 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
} = this.props;

if (frameData !== null) {
canvasInstance.setup(frameData, annotations);
canvasInstance.setup(frameData, annotations
.filter((e) => e.objectType !== ObjectType.TAG));
canvasInstance.rotate(frameAngle);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,14 @@ import React from 'react';
import { GlobalHotKeys, KeyMap } from 'react-hotkeys';

import {
Icon,
Layout,
Tooltip,
} from 'antd';

import {
ActiveControl,
Rotation,
} from 'reducers/interfaces';

import {
TagIcon,
} from 'icons';

import {
Canvas,
} from 'cvat-canvas';
Expand All @@ -33,6 +27,7 @@ import DrawRectangleControl from './draw-rectangle-control';
import DrawPolygonControl from './draw-polygon-control';
import DrawPolylineControl from './draw-polyline-control';
import DrawPointsControl from './draw-points-control';
import SetupTagControl from './setup-tag-control';
import MergeControl from './merge-control';
import GroupControl from './group-control';
import SplitControl from './split-control';
Expand Down Expand Up @@ -221,9 +216,10 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element {
isDrawing={activeControl === ActiveControl.DRAW_POINTS}
/>

<Tooltip title='Setup a tag' placement='right'>
<Icon component={TagIcon} style={{ pointerEvents: 'none', opacity: 0.5 }} />
</Tooltip>
<SetupTagControl
canvasInstance={canvasInstance}
isDrawing={false}
/>

<hr />

Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
<Popover
{...dynamcPopoverPros}
placement='right'
overlayClassName='cvat-draw-shape-popover'
content={(
<SetupTagPopoverContainer />
)}
>
<Icon
component={TagIcon}
/>
</Popover>
);
}

export default React.memo(SetupTagControl);
Loading