React UI: Player updates (#1058)
* Move, zoom integration * Moving, zooming, additional canvas handler * Activating & changing for objects * Improved colors * Saving annotations on the server * Fixed size * Refactoring * Added couple of notifications * Basic shape drawing * Cancel previous drawing * Refactoring * Minor draw improvings * Merge, group, split * Improved colorsmain
parent
247d7f04ec
commit
2cc2e32ff1
@ -0,0 +1,141 @@
|
||||
import * as SVG from 'svg.js';
|
||||
import consts from './consts';
|
||||
|
||||
import {
|
||||
translateToSVG,
|
||||
} from './shared';
|
||||
|
||||
import {
|
||||
Geometry,
|
||||
} from './canvasModel';
|
||||
|
||||
|
||||
export interface ZoomHandler {
|
||||
zoom(): void;
|
||||
cancel(): void;
|
||||
transform(geometry: Geometry): void;
|
||||
}
|
||||
|
||||
export class ZoomHandlerImpl implements ZoomHandler {
|
||||
private onZoomRegion: (x: number, y: number, width: number, height: number) => void;
|
||||
private bindedOnSelectStart: (event: MouseEvent) => void;
|
||||
private bindedOnSelectUpdate: (event: MouseEvent) => void;
|
||||
private bindedOnSelectStop: (event: MouseEvent) => void;
|
||||
private geometry: Geometry;
|
||||
private canvas: SVG.Container;
|
||||
private selectionRect: SVG.Rect | null;
|
||||
private startSelectionPoint: {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
|
||||
private onSelectStart(event: MouseEvent): void {
|
||||
if (!this.selectionRect && event.which === 1) {
|
||||
const point = translateToSVG(
|
||||
(this.canvas.node as any as SVGSVGElement),
|
||||
[event.clientX, event.clientY],
|
||||
);
|
||||
this.startSelectionPoint = {
|
||||
x: point[0],
|
||||
y: point[1],
|
||||
};
|
||||
|
||||
this.selectionRect = this.canvas.rect().addClass('cvat_canvas_zoom_selection');
|
||||
this.selectionRect.attr({
|
||||
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
|
||||
...this.startSelectionPoint,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private getSelectionBox(event: MouseEvent): {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
} {
|
||||
const point = translateToSVG(
|
||||
(this.canvas.node as any as SVGSVGElement),
|
||||
[event.clientX, event.clientY],
|
||||
);
|
||||
const stopSelectionPoint = {
|
||||
x: point[0],
|
||||
y: point[1],
|
||||
};
|
||||
|
||||
const xtl = Math.min(this.startSelectionPoint.x, stopSelectionPoint.x);
|
||||
const ytl = Math.min(this.startSelectionPoint.y, stopSelectionPoint.y);
|
||||
const xbr = Math.max(this.startSelectionPoint.x, stopSelectionPoint.x);
|
||||
const ybr = Math.max(this.startSelectionPoint.y, stopSelectionPoint.y);
|
||||
|
||||
return {
|
||||
x: xtl,
|
||||
y: ytl,
|
||||
width: xbr - xtl,
|
||||
height: ybr - ytl,
|
||||
};
|
||||
}
|
||||
|
||||
private onSelectUpdate(event: MouseEvent): void {
|
||||
if (this.selectionRect) {
|
||||
this.selectionRect.attr({
|
||||
...this.getSelectionBox(event),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private onSelectStop(event: MouseEvent): void {
|
||||
if (this.selectionRect) {
|
||||
const box = this.getSelectionBox(event);
|
||||
this.selectionRect.remove();
|
||||
this.selectionRect = null;
|
||||
this.startSelectionPoint = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
};
|
||||
const threshold = 5;
|
||||
if (box.width > threshold && box.height > threshold) {
|
||||
this.onZoomRegion(box.x, box.y, box.width, box.height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public constructor(
|
||||
onZoomRegion: (x: number, y: number, width: number, height: number) => void,
|
||||
canvas: SVG.Container,
|
||||
geometry: Geometry,
|
||||
) {
|
||||
this.onZoomRegion = onZoomRegion;
|
||||
this.canvas = canvas;
|
||||
this.geometry = geometry;
|
||||
this.selectionRect = null;
|
||||
this.startSelectionPoint = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
};
|
||||
this.bindedOnSelectStart = this.onSelectStart.bind(this);
|
||||
this.bindedOnSelectUpdate = this.onSelectUpdate.bind(this);
|
||||
this.bindedOnSelectStop = this.onSelectStop.bind(this);
|
||||
}
|
||||
|
||||
public zoom(): void {
|
||||
this.canvas.node.addEventListener('mousedown', this.bindedOnSelectStart);
|
||||
this.canvas.node.addEventListener('mousemove', this.bindedOnSelectUpdate);
|
||||
this.canvas.node.addEventListener('mouseup', this.bindedOnSelectStop);
|
||||
}
|
||||
|
||||
public cancel(): void {
|
||||
this.canvas.node.removeEventListener('mousedown', this.bindedOnSelectStart);
|
||||
this.canvas.node.removeEventListener('mousemove', this.bindedOnSelectUpdate);
|
||||
this.canvas.node.removeEventListener('mouseup ', this.bindedOnSelectStop);
|
||||
}
|
||||
|
||||
public transform(geometry: Geometry): void {
|
||||
this.geometry = geometry;
|
||||
if (this.selectionRect) {
|
||||
this.selectionRect.style({
|
||||
'stroke-width': consts.BASE_STROKE_WIDTH / geometry.scale,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,127 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
Icon,
|
||||
Layout,
|
||||
Tooltip,
|
||||
Popover,
|
||||
} from 'antd';
|
||||
|
||||
import {
|
||||
CursorIcon,
|
||||
MoveIcon,
|
||||
RotateIcon,
|
||||
FitIcon,
|
||||
ZoomIcon,
|
||||
RectangleIcon,
|
||||
PolygonIcon,
|
||||
PointIcon,
|
||||
PolylineIcon,
|
||||
TagIcon,
|
||||
MergeIcon,
|
||||
GroupIcon,
|
||||
SplitIcon,
|
||||
} from '../../../icons';
|
||||
|
||||
import {
|
||||
Canvas,
|
||||
Rotation,
|
||||
} from '../../../canvas';
|
||||
|
||||
interface Props {
|
||||
canvasInstance: Canvas;
|
||||
rotateAll: boolean;
|
||||
}
|
||||
|
||||
export default function ControlsSideBarComponent(props: Props): JSX.Element {
|
||||
const {
|
||||
rotateAll,
|
||||
canvasInstance,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<Layout.Sider
|
||||
className='cvat-annotation-page-controls-sidebar'
|
||||
theme='light'
|
||||
width={44}
|
||||
>
|
||||
<Tooltip overlay='Cursor' placement='right'>
|
||||
<Icon component={CursorIcon} />
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip overlay='Move the image' placement='right'>
|
||||
<Icon component={MoveIcon} />
|
||||
</Tooltip>
|
||||
|
||||
<Popover
|
||||
overlayClassName='cvat-annotation-page-controls-rotate'
|
||||
placement='right'
|
||||
content={(
|
||||
<>
|
||||
<Icon
|
||||
className='cvat-annotation-page-controls-rotate-left'
|
||||
onClick={(): void => canvasInstance
|
||||
.rotate(Rotation.ANTICLOCKWISE90, rotateAll)}
|
||||
component={RotateIcon}
|
||||
/>
|
||||
<Icon
|
||||
className='cvat-annotation-page-controls-rotate-right'
|
||||
onClick={(): void => canvasInstance
|
||||
.rotate(Rotation.CLOCKWISE90, rotateAll)}
|
||||
component={RotateIcon}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
trigger='hover'
|
||||
>
|
||||
<Tooltip overlay='Rotate the image' placement='topRight'>
|
||||
<Icon component={RotateIcon} />
|
||||
</Tooltip>
|
||||
</Popover>
|
||||
|
||||
<hr />
|
||||
|
||||
<Tooltip overlay='Fit the image' placement='right'>
|
||||
<Icon component={FitIcon} />
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip overlay='Zoom the image' placement='right'>
|
||||
<Icon component={ZoomIcon} />
|
||||
</Tooltip>
|
||||
|
||||
<hr />
|
||||
|
||||
<Tooltip overlay='Draw a rectangle' placement='right'>
|
||||
<Icon component={RectangleIcon} />
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip overlay='Draw a polygon' placement='right'>
|
||||
<Icon component={PolygonIcon} />
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip overlay='Draw a polyline' placement='right'>
|
||||
<Icon component={PolylineIcon} />
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip overlay='Draw points' placement='right'>
|
||||
<Icon component={PointIcon} />
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip overlay='Setup a tag' placement='right'>
|
||||
<Icon component={TagIcon} />
|
||||
</Tooltip>
|
||||
<hr />
|
||||
<Tooltip overlay='Merge shapes/tracks' placement='right'>
|
||||
<Icon component={MergeIcon} />
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip overlay='Group shapes/tracks' placement='right'>
|
||||
<Icon component={GroupIcon} />
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip overlay='Split a track' placement='right'>
|
||||
<Icon component={SplitIcon} />
|
||||
</Tooltip>
|
||||
</Layout.Sider>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,78 @@
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
Icon,
|
||||
Layout,
|
||||
Tooltip,
|
||||
} from 'antd';
|
||||
|
||||
import {
|
||||
ActiveControl,
|
||||
} from 'reducers/interfaces';
|
||||
|
||||
import {
|
||||
TagIcon,
|
||||
} from 'icons';
|
||||
|
||||
import {
|
||||
Canvas,
|
||||
} from 'cvat-canvas';
|
||||
|
||||
import CursorControl from './cursor-control';
|
||||
import MoveControl from './move-control';
|
||||
import RotateControl from './rotate-control';
|
||||
import FitControl from './fit-control';
|
||||
import ResizeControl from './resize-control';
|
||||
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 MergeControl from './merge-control';
|
||||
import GroupControl from './group-control';
|
||||
import SplitControl from './split-control';
|
||||
|
||||
interface Props {
|
||||
canvasInstance: Canvas;
|
||||
rotateAll: boolean;
|
||||
activeControl: ActiveControl;
|
||||
|
||||
onMergeStart(): void;
|
||||
onGroupStart(): void;
|
||||
onSplitStart(): void;
|
||||
}
|
||||
|
||||
export default function ControlsSideBarComponent(props: Props): JSX.Element {
|
||||
return (
|
||||
<Layout.Sider
|
||||
className='cvat-annotation-page-controls-sidebar'
|
||||
theme='light'
|
||||
width={44}
|
||||
>
|
||||
<CursorControl {...props} />
|
||||
<MoveControl {...props} />
|
||||
<RotateControl {...props} />
|
||||
|
||||
<hr />
|
||||
|
||||
<FitControl {...props} />
|
||||
<ResizeControl {...props} />
|
||||
|
||||
<hr />
|
||||
|
||||
<DrawRectangleControl {...props} />
|
||||
<DrawPolygonControl {...props} />
|
||||
<DrawPolylineControl {...props} />
|
||||
<DrawPointsControl {...props} />
|
||||
|
||||
<Tooltip overlay='Setup a tag' placement='right'>
|
||||
<Icon component={TagIcon} />
|
||||
</Tooltip>
|
||||
|
||||
<hr />
|
||||
|
||||
<MergeControl {...props} />
|
||||
<GroupControl {...props} />
|
||||
<SplitControl {...props} />
|
||||
</Layout.Sider>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
Icon,
|
||||
Tooltip,
|
||||
} from 'antd';
|
||||
|
||||
import {
|
||||
CursorIcon,
|
||||
} from 'icons';
|
||||
|
||||
import {
|
||||
ActiveControl,
|
||||
} from 'reducers/interfaces';
|
||||
|
||||
import {
|
||||
Canvas,
|
||||
} from 'cvat-canvas';
|
||||
|
||||
interface Props {
|
||||
canvasInstance: Canvas;
|
||||
activeControl: ActiveControl;
|
||||
}
|
||||
|
||||
export default function CursorControl(props: Props): JSX.Element {
|
||||
const {
|
||||
canvasInstance,
|
||||
activeControl,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<Tooltip overlay='Cursor' placement='right'>
|
||||
<Icon
|
||||
component={CursorIcon}
|
||||
className={activeControl === ActiveControl.CURSOR
|
||||
? 'cvat-annotation-page-active-control' : ''
|
||||
}
|
||||
onClick={(): void => {
|
||||
if (activeControl !== ActiveControl.CURSOR) {
|
||||
canvasInstance.cancel();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Popover,
|
||||
Icon,
|
||||
} from 'antd';
|
||||
|
||||
import { Canvas } from 'cvat-canvas';
|
||||
import { PointIcon } from 'icons';
|
||||
import {
|
||||
ShapeType,
|
||||
ActiveControl,
|
||||
} from 'reducers/interfaces';
|
||||
|
||||
import DrawShapePopoverContainer from 'containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover';
|
||||
|
||||
interface Props {
|
||||
canvasInstance: Canvas;
|
||||
activeControl: ActiveControl;
|
||||
}
|
||||
|
||||
export default function DrawRectangleControl(props: Props): JSX.Element {
|
||||
const {
|
||||
canvasInstance,
|
||||
activeControl,
|
||||
} = props;
|
||||
|
||||
const dynamcPopoverPros = activeControl === ActiveControl.DRAW_POINTS
|
||||
? {
|
||||
overlayStyle: {
|
||||
display: 'none',
|
||||
},
|
||||
} : {};
|
||||
|
||||
const dynamicIconProps = activeControl === ActiveControl.DRAW_POINTS
|
||||
? {
|
||||
className: 'cvat-annotation-page-active-control',
|
||||
onClick: (): void => {
|
||||
canvasInstance.draw({ enabled: false });
|
||||
},
|
||||
} : {};
|
||||
|
||||
return (
|
||||
<Popover
|
||||
{...dynamcPopoverPros}
|
||||
overlayClassName='cvat-draw-shape-popover'
|
||||
placement='right'
|
||||
content={(
|
||||
<DrawShapePopoverContainer shapeType={ShapeType.POINTS} />
|
||||
)}
|
||||
>
|
||||
<Icon
|
||||
{...dynamicIconProps}
|
||||
component={PointIcon}
|
||||
/>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Popover,
|
||||
Icon,
|
||||
} from 'antd';
|
||||
|
||||
import { Canvas } from 'cvat-canvas';
|
||||
import { PolygonIcon } from 'icons';
|
||||
import {
|
||||
ShapeType,
|
||||
ActiveControl,
|
||||
} from 'reducers/interfaces';
|
||||
|
||||
import DrawShapePopoverContainer from 'containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover';
|
||||
|
||||
interface Props {
|
||||
canvasInstance: Canvas;
|
||||
activeControl: ActiveControl;
|
||||
}
|
||||
|
||||
export default function DrawRectangleControl(props: Props): JSX.Element {
|
||||
const {
|
||||
canvasInstance,
|
||||
activeControl,
|
||||
} = props;
|
||||
|
||||
const dynamcPopoverPros = activeControl === ActiveControl.DRAW_POLYGON
|
||||
? {
|
||||
overlayStyle: {
|
||||
display: 'none',
|
||||
},
|
||||
} : {};
|
||||
|
||||
const dynamicIconProps = activeControl === ActiveControl.DRAW_POLYGON
|
||||
? {
|
||||
className: 'cvat-annotation-page-active-control',
|
||||
onClick: (): void => {
|
||||
canvasInstance.draw({ enabled: false });
|
||||
},
|
||||
} : {};
|
||||
|
||||
return (
|
||||
<Popover
|
||||
{...dynamcPopoverPros}
|
||||
overlayClassName='cvat-draw-shape-popover'
|
||||
placement='right'
|
||||
content={(
|
||||
<DrawShapePopoverContainer shapeType={ShapeType.POLYGON} />
|
||||
)}
|
||||
>
|
||||
<Icon
|
||||
{...dynamicIconProps}
|
||||
component={PolygonIcon}
|
||||
/>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Popover,
|
||||
Icon,
|
||||
} from 'antd';
|
||||
|
||||
import { Canvas } from 'cvat-canvas';
|
||||
import { PolylineIcon } from 'icons';
|
||||
import {
|
||||
ShapeType,
|
||||
ActiveControl,
|
||||
} from 'reducers/interfaces';
|
||||
|
||||
import DrawShapePopoverContainer from 'containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover';
|
||||
|
||||
interface Props {
|
||||
canvasInstance: Canvas;
|
||||
activeControl: ActiveControl;
|
||||
}
|
||||
|
||||
export default function DrawRectangleControl(props: Props): JSX.Element {
|
||||
const {
|
||||
canvasInstance,
|
||||
activeControl,
|
||||
} = props;
|
||||
|
||||
const dynamcPopoverPros = activeControl === ActiveControl.DRAW_POLYLINE
|
||||
? {
|
||||
overlayStyle: {
|
||||
display: 'none',
|
||||
},
|
||||
} : {};
|
||||
|
||||
const dynamicIconProps = activeControl === ActiveControl.DRAW_POLYLINE
|
||||
? {
|
||||
className: 'cvat-annotation-page-active-control',
|
||||
onClick: (): void => {
|
||||
canvasInstance.draw({ enabled: false });
|
||||
},
|
||||
} : {};
|
||||
|
||||
return (
|
||||
<Popover
|
||||
{...dynamcPopoverPros}
|
||||
overlayClassName='cvat-draw-shape-popover'
|
||||
placement='right'
|
||||
content={(
|
||||
<DrawShapePopoverContainer shapeType={ShapeType.POLYLINE} />
|
||||
)}
|
||||
>
|
||||
<Icon
|
||||
{...dynamicIconProps}
|
||||
component={PolylineIcon}
|
||||
/>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Popover,
|
||||
Icon,
|
||||
} from 'antd';
|
||||
|
||||
import { Canvas } from 'cvat-canvas';
|
||||
import { RectangleIcon } from 'icons';
|
||||
import {
|
||||
ShapeType,
|
||||
ActiveControl,
|
||||
} from 'reducers/interfaces';
|
||||
|
||||
import DrawShapePopoverContainer from 'containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover';
|
||||
|
||||
interface Props {
|
||||
canvasInstance: Canvas;
|
||||
activeControl: ActiveControl;
|
||||
}
|
||||
|
||||
export default function DrawRectangleControl(props: Props): JSX.Element {
|
||||
const {
|
||||
canvasInstance,
|
||||
activeControl,
|
||||
} = props;
|
||||
|
||||
const dynamcPopoverPros = activeControl === ActiveControl.DRAW_RECTANGLE
|
||||
? {
|
||||
overlayStyle: {
|
||||
display: 'none',
|
||||
},
|
||||
} : {};
|
||||
|
||||
const dynamicIconProps = activeControl === ActiveControl.DRAW_RECTANGLE
|
||||
? {
|
||||
className: 'cvat-annotation-page-active-control',
|
||||
onClick: (): void => {
|
||||
canvasInstance.draw({ enabled: false });
|
||||
},
|
||||
} : {};
|
||||
|
||||
return (
|
||||
<Popover
|
||||
{...dynamcPopoverPros}
|
||||
overlayClassName='cvat-draw-shape-popover'
|
||||
placement='right'
|
||||
content={(
|
||||
<DrawShapePopoverContainer shapeType={ShapeType.RECTANGLE} />
|
||||
)}
|
||||
>
|
||||
<Icon
|
||||
{...dynamicIconProps}
|
||||
component={RectangleIcon}
|
||||
/>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,186 @@
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
Row,
|
||||
Col,
|
||||
Select,
|
||||
Button,
|
||||
InputNumber,
|
||||
} from 'antd';
|
||||
|
||||
import Text from 'antd/lib/typography/Text';
|
||||
|
||||
import {
|
||||
ShapeType,
|
||||
ObjectType,
|
||||
StringObject,
|
||||
} from 'reducers/interfaces';
|
||||
|
||||
import {
|
||||
Canvas,
|
||||
} from 'cvat-canvas';
|
||||
|
||||
interface Props {
|
||||
canvasInstance: Canvas;
|
||||
shapeType: ShapeType;
|
||||
labels: StringObject;
|
||||
|
||||
onDrawStart(
|
||||
shapeType: ShapeType,
|
||||
labelID: number,
|
||||
objectType: ObjectType,
|
||||
points?: number,
|
||||
): void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
numberOfPoints?: number;
|
||||
selectedLabeID: number;
|
||||
}
|
||||
|
||||
function defineMinimumPoints(shapeType: ShapeType): number {
|
||||
if (shapeType === ShapeType.POLYGON) {
|
||||
return 3;
|
||||
}
|
||||
if (shapeType === ShapeType.POLYLINE) {
|
||||
return 2;
|
||||
}
|
||||
if (shapeType === ShapeType.POINTS) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
export default class DrawShapePopoverComponent extends React.PureComponent<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
const defaultLabelID = +Object.keys(props.labels)[0];
|
||||
this.state = {
|
||||
selectedLabeID: defaultLabelID,
|
||||
};
|
||||
}
|
||||
|
||||
private onChangePoints = (value: number | undefined): void => {
|
||||
this.setState({
|
||||
numberOfPoints: value,
|
||||
});
|
||||
};
|
||||
|
||||
private onChangeLabel = (value: string): void => {
|
||||
this.setState({
|
||||
selectedLabeID: +value,
|
||||
});
|
||||
};
|
||||
|
||||
private onDrawTrackStart = (): void => {
|
||||
this.onDrawStart(ObjectType.TRACK);
|
||||
};
|
||||
|
||||
private onDrawShapeStart = (): void => {
|
||||
this.onDrawStart(ObjectType.SHAPE);
|
||||
};
|
||||
|
||||
private onDrawStart = (objectType: ObjectType): void => {
|
||||
const {
|
||||
numberOfPoints,
|
||||
selectedLabeID,
|
||||
} = this.state;
|
||||
|
||||
const {
|
||||
shapeType,
|
||||
onDrawStart,
|
||||
canvasInstance,
|
||||
} = this.props;
|
||||
|
||||
canvasInstance.cancel();
|
||||
canvasInstance.draw({
|
||||
enabled: true,
|
||||
numberOfPoints,
|
||||
shapeType,
|
||||
crosshair: shapeType === ShapeType.RECTANGLE,
|
||||
});
|
||||
|
||||
onDrawStart(shapeType, selectedLabeID,
|
||||
objectType, numberOfPoints);
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
const {
|
||||
selectedLabeID,
|
||||
} = this.state;
|
||||
|
||||
const {
|
||||
shapeType,
|
||||
labels,
|
||||
} = this.props;
|
||||
|
||||
const minimumPoints = defineMinimumPoints(shapeType);
|
||||
|
||||
return (
|
||||
<div className='cvat-draw-shape-popover-content'>
|
||||
<Row type='flex' justify='start'>
|
||||
<Col>
|
||||
<Text className='cvat-text-color' strong>{`Draw new ${shapeType}`}</Text>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row type='flex' justify='start'>
|
||||
<Col>
|
||||
<Text className='cvat-text-color'>Label</Text>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row type='flex' justify='center'>
|
||||
<Col span={24}>
|
||||
<Select
|
||||
value={labels[selectedLabeID]}
|
||||
onChange={this.onChangeLabel}
|
||||
>
|
||||
{
|
||||
Object.keys(labels).map((key: string) => (
|
||||
<Select.Option
|
||||
key={key}
|
||||
value={key}
|
||||
>
|
||||
{labels[+key]}
|
||||
</Select.Option>
|
||||
))
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
{
|
||||
shapeType !== ShapeType.RECTANGLE && (
|
||||
<Row type='flex' justify='space-around' align='middle'>
|
||||
<Col span={14}>
|
||||
<Text className='cvat-text-color'> Number of points: </Text>
|
||||
</Col>
|
||||
<Col span={10}>
|
||||
<InputNumber
|
||||
onChange={this.onChangePoints}
|
||||
className='cvat-draw-shape-popover-points-selector'
|
||||
min={minimumPoints}
|
||||
step={1}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
<Row type='flex' justify='space-around'>
|
||||
<Col span={12}>
|
||||
<Button
|
||||
onClick={this.onDrawShapeStart}
|
||||
>
|
||||
Shape
|
||||
</Button>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Button
|
||||
onClick={this.onDrawTrackStart}
|
||||
>
|
||||
Track
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
Icon,
|
||||
Tooltip,
|
||||
} from 'antd';
|
||||
|
||||
import {
|
||||
FitIcon,
|
||||
} from 'icons';
|
||||
|
||||
import {
|
||||
Canvas,
|
||||
} from 'cvat-canvas';
|
||||
|
||||
interface Props {
|
||||
canvasInstance: Canvas;
|
||||
}
|
||||
|
||||
export default function FitControl(props: Props): JSX.Element {
|
||||
const {
|
||||
canvasInstance,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<Tooltip overlay='Fit the image' placement='right'>
|
||||
<Icon component={FitIcon} onClick={(): void => canvasInstance.fit()} />
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
Tooltip,
|
||||
Icon,
|
||||
} from 'antd';
|
||||
|
||||
import {
|
||||
GroupIcon,
|
||||
} from 'icons';
|
||||
|
||||
import { Canvas } from 'cvat-canvas';
|
||||
import { ActiveControl } from 'reducers/interfaces';
|
||||
|
||||
interface Props {
|
||||
canvasInstance: Canvas;
|
||||
activeControl: ActiveControl;
|
||||
|
||||
onGroupStart(): void;
|
||||
}
|
||||
|
||||
export default function GroupControl(props: Props): JSX.Element {
|
||||
const {
|
||||
activeControl,
|
||||
canvasInstance,
|
||||
onGroupStart,
|
||||
} = props;
|
||||
|
||||
const dynamicIconProps = activeControl === ActiveControl.GROUP
|
||||
? {
|
||||
className: 'cvat-annotation-page-active-control',
|
||||
onClick: (): void => {
|
||||
canvasInstance.group({ enabled: false });
|
||||
},
|
||||
} : {
|
||||
onClick: (): void => {
|
||||
canvasInstance.cancel();
|
||||
canvasInstance.group({ enabled: true });
|
||||
onGroupStart();
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<Tooltip overlay='Group shapes/tracks' placement='right'>
|
||||
<Icon {...dynamicIconProps} component={GroupIcon} />
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
Tooltip,
|
||||
Icon,
|
||||
} from 'antd';
|
||||
|
||||
import {
|
||||
MergeIcon,
|
||||
} from 'icons';
|
||||
|
||||
import { Canvas } from 'cvat-canvas';
|
||||
import { ActiveControl } from 'reducers/interfaces';
|
||||
|
||||
interface Props {
|
||||
canvasInstance: Canvas;
|
||||
activeControl: ActiveControl;
|
||||
|
||||
onMergeStart(): void;
|
||||
}
|
||||
|
||||
export default function MergeControl(props: Props): JSX.Element {
|
||||
const {
|
||||
activeControl,
|
||||
canvasInstance,
|
||||
onMergeStart,
|
||||
} = props;
|
||||
|
||||
const dynamicIconProps = activeControl === ActiveControl.MERGE
|
||||
? {
|
||||
className: 'cvat-annotation-page-active-control',
|
||||
onClick: (): void => {
|
||||
canvasInstance.merge({ enabled: false });
|
||||
},
|
||||
} : {
|
||||
onClick: (): void => {
|
||||
canvasInstance.cancel();
|
||||
canvasInstance.merge({ enabled: true });
|
||||
onMergeStart();
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<Tooltip overlay='Merge shapes/tracks' placement='right'>
|
||||
<Icon {...dynamicIconProps} component={MergeIcon} />
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
Icon,
|
||||
Tooltip,
|
||||
} from 'antd';
|
||||
|
||||
import {
|
||||
MoveIcon,
|
||||
} from 'icons';
|
||||
|
||||
import {
|
||||
ActiveControl,
|
||||
} from 'reducers/interfaces';
|
||||
|
||||
import {
|
||||
Canvas,
|
||||
} from 'cvat-canvas';
|
||||
|
||||
interface Props {
|
||||
canvasInstance: Canvas;
|
||||
activeControl: ActiveControl;
|
||||
}
|
||||
|
||||
export default function MoveControl(props: Props): JSX.Element {
|
||||
const {
|
||||
canvasInstance,
|
||||
activeControl,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<Tooltip overlay='Move the image' placement='right'>
|
||||
<Icon
|
||||
component={MoveIcon}
|
||||
className={activeControl === ActiveControl.DRAG_CANVAS
|
||||
? 'cvat-annotation-page-active-control' : ''
|
||||
}
|
||||
onClick={(): void => {
|
||||
if (activeControl === ActiveControl.DRAG_CANVAS) {
|
||||
canvasInstance.dragCanvas(false);
|
||||
} else {
|
||||
canvasInstance.cancel();
|
||||
canvasInstance.dragCanvas(true);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
Icon,
|
||||
Tooltip,
|
||||
} from 'antd';
|
||||
|
||||
import {
|
||||
ZoomIcon,
|
||||
} from 'icons';
|
||||
|
||||
import {
|
||||
ActiveControl,
|
||||
} from 'reducers/interfaces';
|
||||
|
||||
import {
|
||||
Canvas,
|
||||
} from 'cvat-canvas';
|
||||
|
||||
interface Props {
|
||||
canvasInstance: Canvas;
|
||||
activeControl: ActiveControl;
|
||||
}
|
||||
|
||||
export default function ResizeControl(props: Props): JSX.Element {
|
||||
const {
|
||||
activeControl,
|
||||
canvasInstance,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<Tooltip overlay='Select a region of interest' placement='right'>
|
||||
<Icon
|
||||
component={ZoomIcon}
|
||||
className={activeControl === ActiveControl.ZOOM_CANVAS
|
||||
? 'cvat-annotation-page-active-control' : ''
|
||||
}
|
||||
onClick={(): void => {
|
||||
if (activeControl === ActiveControl.ZOOM_CANVAS) {
|
||||
canvasInstance.zoomCanvas(false);
|
||||
} else {
|
||||
canvasInstance.cancel();
|
||||
canvasInstance.zoomCanvas(true);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
Icon,
|
||||
Tooltip,
|
||||
Popover,
|
||||
} from 'antd';
|
||||
|
||||
import {
|
||||
RotateIcon,
|
||||
} from 'icons';
|
||||
|
||||
import {
|
||||
Rotation,
|
||||
Canvas,
|
||||
} from 'cvat-canvas';
|
||||
|
||||
interface Props {
|
||||
canvasInstance: Canvas;
|
||||
rotateAll: boolean;
|
||||
}
|
||||
|
||||
export default function RotateControl(props: Props): JSX.Element {
|
||||
const {
|
||||
rotateAll,
|
||||
canvasInstance,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<Popover
|
||||
overlayClassName='cvat-annotation-page-controls-rotate'
|
||||
placement='right'
|
||||
content={(
|
||||
<>
|
||||
<Tooltip overlay='Rotate the image anticlockwise' placement='topRight'>
|
||||
<Icon
|
||||
className='cvat-annotation-page-controls-rotate-left'
|
||||
onClick={(): void => canvasInstance
|
||||
.rotate(Rotation.ANTICLOCKWISE90, rotateAll)}
|
||||
component={RotateIcon}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip overlay='Rotate the image clockwise' placement='topRight'>
|
||||
<Icon
|
||||
className='cvat-annotation-page-controls-rotate-right'
|
||||
onClick={(): void => canvasInstance
|
||||
.rotate(Rotation.CLOCKWISE90, rotateAll)}
|
||||
component={RotateIcon}
|
||||
/>
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
trigger='hover'
|
||||
>
|
||||
<Icon component={RotateIcon} />
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
Tooltip,
|
||||
Icon,
|
||||
} from 'antd';
|
||||
|
||||
import {
|
||||
SplitIcon,
|
||||
} from 'icons';
|
||||
|
||||
import { Canvas } from 'cvat-canvas';
|
||||
import { ActiveControl } from 'reducers/interfaces';
|
||||
|
||||
interface Props {
|
||||
canvasInstance: Canvas;
|
||||
activeControl: ActiveControl;
|
||||
|
||||
onSplitStart(): void;
|
||||
}
|
||||
|
||||
export default function SplitControl(props: Props): JSX.Element {
|
||||
const {
|
||||
activeControl,
|
||||
canvasInstance,
|
||||
onSplitStart,
|
||||
} = props;
|
||||
|
||||
const dynamicIconProps = activeControl === ActiveControl.SPLIT
|
||||
? {
|
||||
className: 'cvat-annotation-page-active-control',
|
||||
onClick: (): void => {
|
||||
canvasInstance.split({ enabled: false });
|
||||
},
|
||||
} : {
|
||||
onClick: (): void => {
|
||||
canvasInstance.cancel();
|
||||
canvasInstance.split({ enabled: true });
|
||||
onSplitStart();
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<Tooltip overlay='Split a track' placement='right'>
|
||||
<Icon {...dynamicIconProps} component={SplitIcon} />
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
@ -1,35 +0,0 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { Canvas } from '../../../canvas';
|
||||
|
||||
import ControlsSideBarComponent from '../../../components/annotation-page/standard-workspace/controls-side-bar';
|
||||
import { CombinedState } from '../../../reducers/interfaces';
|
||||
|
||||
|
||||
interface StateToProps {
|
||||
canvasInstance: Canvas;
|
||||
rotateAll: boolean;
|
||||
}
|
||||
|
||||
function mapStateToProps(state: CombinedState): StateToProps {
|
||||
const {
|
||||
annotation,
|
||||
settings,
|
||||
} = state;
|
||||
|
||||
return {
|
||||
rotateAll: settings.player.rotateAll,
|
||||
canvasInstance: annotation.canvasInstance,
|
||||
};
|
||||
}
|
||||
|
||||
function StandardWorkspaceContainer(props: StateToProps): JSX.Element {
|
||||
return (
|
||||
<ControlsSideBarComponent {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
)(StandardWorkspaceContainer);
|
||||
@ -0,0 +1,79 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { Canvas } from 'cvat-canvas';
|
||||
|
||||
import {
|
||||
mergeObjects,
|
||||
groupObjects,
|
||||
splitTrack,
|
||||
} from 'actions/annotation-actions';
|
||||
import ControlsSideBarComponent from 'components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar';
|
||||
import {
|
||||
ActiveControl,
|
||||
CombinedState,
|
||||
StringObject,
|
||||
} from 'reducers/interfaces';
|
||||
|
||||
interface StateToProps {
|
||||
canvasInstance: Canvas;
|
||||
rotateAll: boolean;
|
||||
activeControl: ActiveControl;
|
||||
labels: StringObject;
|
||||
}
|
||||
|
||||
interface DispatchToProps {
|
||||
onMergeStart(): void;
|
||||
onGroupStart(): void;
|
||||
onSplitStart(): void;
|
||||
}
|
||||
|
||||
function mapStateToProps(state: CombinedState): StateToProps {
|
||||
const {
|
||||
annotation,
|
||||
settings,
|
||||
} = state;
|
||||
|
||||
const {
|
||||
canvasInstance,
|
||||
activeControl,
|
||||
} = annotation;
|
||||
|
||||
const labels = annotation.jobInstance.task.labels
|
||||
.reduce((acc: StringObject, label: any): StringObject => {
|
||||
acc[label.id as number] = label.name;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return {
|
||||
rotateAll: settings.player.rotateAll,
|
||||
canvasInstance,
|
||||
activeControl,
|
||||
labels,
|
||||
};
|
||||
}
|
||||
|
||||
function dispatchToProps(dispatch: any): DispatchToProps {
|
||||
return {
|
||||
onMergeStart(): void {
|
||||
dispatch(mergeObjects());
|
||||
},
|
||||
onGroupStart(): void {
|
||||
dispatch(groupObjects());
|
||||
},
|
||||
onSplitStart(): void {
|
||||
dispatch(splitTrack());
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function StandardWorkspaceContainer(props: StateToProps & DispatchToProps): JSX.Element {
|
||||
return (
|
||||
<ControlsSideBarComponent {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
dispatchToProps,
|
||||
)(StandardWorkspaceContainer);
|
||||
@ -0,0 +1,80 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import {
|
||||
CombinedState,
|
||||
ShapeType,
|
||||
ObjectType,
|
||||
StringObject,
|
||||
} from 'reducers/interfaces';
|
||||
|
||||
import {
|
||||
drawShape,
|
||||
} from 'actions/annotation-actions';
|
||||
import { Canvas } from 'cvat-canvas';
|
||||
import DrawShapePopoverComponent from 'components/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover';
|
||||
|
||||
interface OwnProps {
|
||||
shapeType: ShapeType;
|
||||
}
|
||||
|
||||
interface DispatchToProps {
|
||||
onDrawStart(
|
||||
shapeType: ShapeType,
|
||||
labelID: number,
|
||||
objectType: ObjectType,
|
||||
points?: number,
|
||||
): void;
|
||||
}
|
||||
|
||||
interface StateToProps {
|
||||
canvasInstance: Canvas;
|
||||
shapeType: ShapeType;
|
||||
labels: StringObject;
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch: any): DispatchToProps {
|
||||
return {
|
||||
onDrawStart(
|
||||
shapeType: ShapeType,
|
||||
labelID: number,
|
||||
objectType: ObjectType,
|
||||
points?: number,
|
||||
): void {
|
||||
dispatch(drawShape(shapeType, labelID, objectType, points));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps {
|
||||
const {
|
||||
annotation,
|
||||
} = state;
|
||||
|
||||
const {
|
||||
canvasInstance,
|
||||
} = annotation;
|
||||
|
||||
const labels = annotation.jobInstance.task.labels
|
||||
.reduce((acc: StringObject, label: any): StringObject => {
|
||||
acc[label.id as number] = label.name;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return {
|
||||
...own,
|
||||
canvasInstance,
|
||||
labels,
|
||||
};
|
||||
}
|
||||
|
||||
function DrawShapePopoverContainer(props: DispatchToProps & StateToProps): JSX.Element {
|
||||
return (
|
||||
<DrawShapePopoverComponent {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
)(DrawShapePopoverContainer);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue