Skip to content

React UI: tags support #1241

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

Merged
merged 14 commits into from
Mar 16, 2020
44 changes: 27 additions & 17 deletions cvat-canvas/src/typescript/canvasView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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];
}

Expand Down Expand Up @@ -846,7 +849,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,
Expand Down Expand Up @@ -891,7 +894,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);
Expand Down Expand Up @@ -1020,22 +1023,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];
Expand Down Expand Up @@ -1084,6 +1089,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);
Expand Down
12 changes: 7 additions & 5 deletions cvat-core/src/annotations-collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}

Expand Down
25 changes: 14 additions & 11 deletions cvat-core/src/annotations-filter.js
Original file line number Diff line number Diff line change
Expand Up @@ -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];
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
114 changes: 90 additions & 24 deletions cvat-ui/src/actions/annotation-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -1151,14 +1152,59 @@ export function searchAnnotationsAsync(
};
}

export function addTagAsync(
labelID: number,
frame: number,
): ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
const {
canvas: {
instance: canvasInstance,
},
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<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,
},
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;
Expand All @@ -1174,31 +1220,47 @@ export function pasteShapeAsync(): ThunkAction<Promise<void>, {}, {}, AnyAction>
});

canvasInstance.cancel();
canvasInstance.draw({
enabled: true,
initialState,
});
if (initialState.objectType === ObjectType.TAG) {
dispatch(addTagAsync(initialState.label.id, frameNumber));
} 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,
},
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 +1271,16 @@ 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) {
dispatch(addTagAsync(activeLabelID, frameNumber));
} else {
canvasInstance.draw({
enabled: true,
rectDrawingMethod: activeRectDrawingMethod,
numberOfPoints: activeNumOfPoints,
shapeType: activeShapeType,
crosshair: activeShapeType === ShapeType.RECTANGLE,
});
}
};
}
1 change: 1 addition & 0 deletions cvat-ui/src/base.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
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
Loading