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;