diff --git a/cvat-canvas/README.md b/cvat-canvas/README.md index 59f4baa6f29f..6b7155f06351 100644 --- a/cvat-canvas/README.md +++ b/cvat-canvas/README.md @@ -40,6 +40,7 @@ Canvas itself handles: interface DrawData { enabled: boolean; shapeType?: string; + rectDrawingMethod?: string; numberOfPoints?: number; initialState?: any; crosshair?: boolean; diff --git a/cvat-canvas/src/typescript/canvasModel.ts b/cvat-canvas/src/typescript/canvasModel.ts index 58e22952d253..8100f579d217 100644 --- a/cvat-canvas/src/typescript/canvasModel.ts +++ b/cvat-canvas/src/typescript/canvasModel.ts @@ -40,6 +40,7 @@ export interface ActiveElement { export interface DrawData { enabled: boolean; shapeType?: string; + rectDrawingMethod?: string; numberOfPoints?: number; initialState?: any; crosshair?: boolean; diff --git a/cvat-canvas/src/typescript/drawHandler.ts b/cvat-canvas/src/typescript/drawHandler.ts index 1d999d9f8b88..2ab5c55776da 100644 --- a/cvat-canvas/src/typescript/drawHandler.ts +++ b/cvat-canvas/src/typescript/drawHandler.ts @@ -160,7 +160,8 @@ export class DrawHandlerImpl implements DrawHandler { if (!this.drawData.initialState) { const { drawInstance } = this; this.drawInstance = null; - if (this.drawData.shapeType === 'rectangle') { + if (this.drawData.shapeType === 'rectangle' + && this.drawData.rectDrawingMethod !== 'by_four_points') { drawInstance.draw('cancel'); } else { drawInstance.draw('done'); @@ -200,6 +201,45 @@ export class DrawHandlerImpl implements DrawHandler { }); } + private drawBoxBy4Points(): void { + let numberOfPoints = 0; + this.drawInstance = (this.canvas as any).polygon() + .addClass('cvat_canvas_shape_drawing').attr({ + 'stroke-width': 0, + opacity: 0, + }).on('drawstart', () => { + // init numberOfPoints as one on drawstart + numberOfPoints = 1; + }).on('drawpoint', (e: CustomEvent) => { + // increase numberOfPoints by one on drawpoint + numberOfPoints += 1; + + // finish if numberOfPoints are exactly four + if (numberOfPoints === 4) { + const bbox = (e.target as SVGPolylineElement).getBBox(); + const [xtl, ytl, xbr, ybr] = this.getFinalRectCoordinates(bbox); + + if ((xbr - xtl) * (ybr - ytl) >= consts.AREA_THRESHOLD) { + this.onDrawDone({ + shapeType: this.drawData.shapeType, + points: [xtl, ytl, xbr, ybr], + }); + } else { + this.onDrawDone(null); + } + } + }).on('undopoint', () => { + if (numberOfPoints > 0) { + numberOfPoints -= 1; + } + }).off('drawdone').on('drawdone', () => { + // close drawing mode without drawing rect + this.onDrawDone(null); + }); + + this.drawPolyshape(); + } + private drawPolyshape(): void { this.drawInstance.attr({ z_order: Number.MAX_SAFE_INTEGER, @@ -536,13 +576,18 @@ export class DrawHandlerImpl implements DrawHandler { this.pastePoints(stringifiedPoints); } } - this.setupPasteEvents(); } else { if (this.drawData.shapeType === 'rectangle') { - this.drawBox(); - // Draw instance was initialized after drawBox(); - this.shapeSizeElement = displayShapeSize(this.canvas, this.text); + if (this.drawData.rectDrawingMethod === 'by_four_points') { + // draw box by extreme clicking + this.drawBoxBy4Points(); + } else { + // default box drawing + this.drawBox(); + // Draw instance was initialized after drawBox(); + this.shapeSizeElement = displayShapeSize(this.canvas, this.text); + } } else if (this.drawData.shapeType === 'polygon') { this.drawPolygon(); } else if (this.drawData.shapeType === 'polyline') { @@ -550,7 +595,6 @@ export class DrawHandlerImpl implements DrawHandler { } else if (this.drawData.shapeType === 'points') { this.drawPoints(); } - this.setupDrawEvents(); } } diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index 6f57ea46a1f1..9d2f340f3588 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -583,6 +583,7 @@ export function drawShape( labelID: number, objectType: ObjectType, points?: number, + rectDrawingMethod?: string, ): AnyAction { let activeControl = ActiveControl.DRAW_RECTANGLE; if (shapeType === ShapeType.POLYGON) { @@ -601,6 +602,7 @@ export function drawShape( objectType, points, activeControl, + rectDrawingMethod, }, }; } diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx index ba43a8c368d4..8fe33802b5b2 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx @@ -6,22 +6,27 @@ import { Select, Button, InputNumber, + Radio, } from 'antd'; +import { RadioChangeEvent } from 'antd/lib/radio'; import Text from 'antd/lib/typography/Text'; import { ShapeType, + RectDrawingMethod, } from 'reducers/interfaces'; interface Props { shapeType: ShapeType; + rectDrawingMethod: RectDrawingMethod; labels: any[]; minimumPoints: number; numberOfPoints?: number; selectedLabeID: number; onChangeLabel(value: string): void; onChangePoints(value: number | undefined): void; + onChangeRectDrawingMethod(event: RadioChangeEvent): void; onDrawTrack(): void; onDrawShape(): void; } @@ -37,6 +42,7 @@ function DrawShapePopoverComponent(props: Props): JSX.Element { onDrawShape, onChangeLabel, onChangePoints, + onChangeRectDrawingMethod, } = props; return ( @@ -71,7 +77,37 @@ function DrawShapePopoverComponent(props: Props): JSX.Element { { - shapeType !== ShapeType.RECTANGLE && ( + shapeType === ShapeType.RECTANGLE ? ( + <> + + + Drawing method + + + + + + + By 2 Points + + + By 4 Points + + + + + + ) : ( Number of points: diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx index 097ac978a73d..cadea2e3c5ed 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx @@ -1,10 +1,12 @@ import React from 'react'; import { connect } from 'react-redux'; +import { RadioChangeEvent } from 'antd/lib/radio'; import { CombinedState, ShapeType, ObjectType, + RectDrawingMethod, } from 'reducers/interfaces'; import { @@ -23,6 +25,7 @@ interface DispatchToProps { labelID: number, objectType: ObjectType, points?: number, + rectDrawingMethod?: string, ): void; } @@ -39,8 +42,9 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { labelID: number, objectType: ObjectType, points?: number, + rectDrawingMethod?: string, ): void { - dispatch(drawShape(shapeType, labelID, objectType, points)); + dispatch(drawShape(shapeType, labelID, objectType, points, rectDrawingMethod)); }, }; } @@ -67,6 +71,7 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps { type Props = StateToProps & DispatchToProps; interface State { + rectDrawingMethod?: string; numberOfPoints?: number; selectedLabelID: number; } @@ -77,6 +82,7 @@ class DrawShapePopoverContainer extends React.PureComponent { super(props); const defaultLabelID = props.labels[0].id; + const defaultRectDrawingMethod = RectDrawingMethod.BY_TWO_POINTS; this.state = { selectedLabelID: defaultLabelID, }; @@ -91,6 +97,9 @@ class DrawShapePopoverContainer extends React.PureComponent { if (shapeType === ShapeType.POINTS) { this.minimumPoints = 1; } + if (shapeType === ShapeType.RECTANGLE) { + this.state.rectDrawingMethod = defaultRectDrawingMethod; + } } private onDraw(objectType: ObjectType): void { @@ -101,6 +110,7 @@ class DrawShapePopoverContainer extends React.PureComponent { } = this.props; const { + rectDrawingMethod, numberOfPoints, selectedLabelID, } = this.state; @@ -108,6 +118,7 @@ class DrawShapePopoverContainer extends React.PureComponent { canvasInstance.cancel(); canvasInstance.draw({ enabled: true, + rectDrawingMethod, numberOfPoints, shapeType, crosshair: shapeType === ShapeType.RECTANGLE, @@ -117,6 +128,12 @@ class DrawShapePopoverContainer extends React.PureComponent { objectType, numberOfPoints); } + private onChangeRectDrawingMethod = (event: RadioChangeEvent): void => { + this.setState({ + rectDrawingMethod: event.target.value, + }); + }; + private onDrawShape = (): void => { this.onDraw(ObjectType.SHAPE); }; @@ -163,6 +180,7 @@ class DrawShapePopoverContainer extends React.PureComponent { numberOfPoints={numberOfPoints} onChangeLabel={this.onChangeLabel} onChangePoints={this.onChangePoints} + onChangeRectDrawingMethod={this.onChangeRectDrawingMethod} onDrawTrack={this.onDrawTrack} onDrawShape={this.onDrawShape} /> diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index c0800d1ce248..2b959fae5e6a 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -338,6 +338,7 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { objectType, points, activeControl, + rectDrawingMethod, } = action.payload; return { @@ -355,6 +356,7 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { activeNumOfPoints: points, activeObjectType: objectType, activeShapeType: shapeType, + activeRectDrawingMethod: rectDrawingMethod, }, }; } diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index 9305ca020212..792cabd5dd36 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -239,6 +239,11 @@ export enum ActiveControl { EDIT = 'edit', } +export enum RectDrawingMethod { + BY_TWO_POINTS = 'by_two_points', + BY_FOUR_POINTS = 'by_four_points' +} + export enum ShapeType { RECTANGLE = 'rectangle', POLYGON = 'polygon', @@ -297,6 +302,7 @@ export interface AnnotationState { }; drawing: { activeShapeType: ShapeType; + activeRectDrawingMethod?: RectDrawingMethod; activeNumOfPoints?: number; activeLabelID: number; activeObjectType: ObjectType;