diff --git a/cvat/apps/dextr_segmentation/static/dextr_segmentation/js/enginePlugin.js b/cvat/apps/dextr_segmentation/static/dextr_segmentation/js/enginePlugin.js index 548f19f64aa..7384f33b9eb 100644 --- a/cvat/apps/dextr_segmentation/static/dextr_segmentation/js/enginePlugin.js +++ b/cvat/apps/dextr_segmentation/static/dextr_segmentation/js/enginePlugin.js @@ -54,7 +54,8 @@ window.addEventListener('DOMContentLoaded', () => { Object.defineProperty(instance, 'defaultType', { get: () => instance._defaultType, set: (type) => { - if (!['box', 'points', 'polygon', 'polyline', 'auto_segmentation'].includes(type)) { + if (!['box', 'box_by_4_points', 'points', 'polygon', + 'polyline', 'auto_segmentation'].includes(type)) { throw Error(`Unknown shape type found ${type}`); } instance._defaultType = type; diff --git a/cvat/apps/documentation/static/documentation/images/gif016.gif b/cvat/apps/documentation/static/documentation/images/gif016.gif new file mode 100644 index 00000000000..e43ff2c94de Binary files /dev/null and b/cvat/apps/documentation/static/documentation/images/gif016.gif differ diff --git a/cvat/apps/documentation/static/documentation/images/image134.jpg b/cvat/apps/documentation/static/documentation/images/image134.jpg new file mode 100644 index 00000000000..7bed8c80577 Binary files /dev/null and b/cvat/apps/documentation/static/documentation/images/image134.jpg differ diff --git a/cvat/apps/documentation/user_guide.md b/cvat/apps/documentation/user_guide.md index eb518814180..594e64c4161 100644 --- a/cvat/apps/documentation/user_guide.md +++ b/cvat/apps/documentation/user_guide.md @@ -25,6 +25,7 @@ - [Annotation mode (advanced)](#annotation-mode-advanced) - [Interpolation mode (advanced)](#interpolation-mode-advanced) - [Attribute annotation mode (advanced)](#attribute-annotation-mode-advanced) + - [Annotation with box by 4 points](#annotation-with-box-by-4-points) - [Annotation with polygons](#annotation-with-polygons) - [Annotation with polylines](#annotation-with-polylines) - [Annotation with points](#annotation-with-points) @@ -934,6 +935,20 @@ To navigate between objects (pedestrians in the case), use the following shortcu By default, objects in the mode are zoomed. Check ``Open Menu`` —> ``Settings`` —> ``AAM Zoom Margin`` to adjust that. +## Annotation with box by 4 points +It is an efficient method of bounding box annotation, proposed +[here](https://arxiv.org/pdf/1708.02750.pdf). +Before starting, you need to be sure that ``Box by 4 points`` is selected. + +![](static/documentation/images/image134.jpg) + +Press ``N`` for entering drawing mode. Click exactly four extreme points: +the top, bottom, left- and right-most physical points on the object. +Drawing is automatically completed right after clicking the fourth point. +Press ``Esc`` to cancel editing. + +![](static/documentation/images/gif016.gif) + ## Annotation with polygons It is used for semantic / instance segmentation. diff --git a/cvat/apps/engine/static/engine/js/shapeCreator.js b/cvat/apps/engine/static/engine/js/shapeCreator.js index b7841bfe1e2..d9da2ba21b3 100644 --- a/cvat/apps/engine/static/engine/js/shapeCreator.js +++ b/cvat/apps/engine/static/engine/js/shapeCreator.js @@ -54,7 +54,8 @@ class ShapeCreatorModel extends Listener { } // FIXME: In the future we have to make some generic solution - if (this._defaultMode === 'interpolation' && ['box', 'points'].includes(this._defaultType)) { + if (this._defaultMode === 'interpolation' + && ['box', 'points', 'box_by_4_points'].includes(this._defaultType)) { data.shapes = []; data.shapes.push(Object.assign({}, result, data)); this._shapeCollection.add(data, `interpolation_${this._defaultType}`); @@ -125,7 +126,7 @@ class ShapeCreatorModel extends Listener { } set defaultType(type) { - if (!['box', 'points', 'polygon', 'polyline'].includes(type)) { + if (!['box', 'box_by_4_points', 'points', 'polygon', 'polyline'].includes(type)) { throw Error(`Unknown shape type found ${type}`); } this._defaultType = type; @@ -234,8 +235,8 @@ class ShapeCreatorView { // FIXME: In the future we have to make some generic solution const mode = this._modeSelector.prop('value'); const type = $(e.target).prop('value'); - if (type !== 'box' && !(type === 'points' && this._polyShapeSize === 1) - && mode !== 'annotation') { + if (type !== 'box' && type !== 'box_by_4_points' + && !(type === 'points' && this._polyShapeSize === 1) && mode !== 'annotation') { this._modeSelector.prop('value', 'annotation'); this._controller.setDefaultShapeMode('annotation'); showMessage('Only the annotation mode allowed for the shape'); @@ -252,7 +253,7 @@ class ShapeCreatorView { const mode = $(e.target).prop('value'); const type = this._typeSelector.prop('value'); if (mode !== 'annotation' && !(type === 'points' && this._polyShapeSize === 1) - && type !== 'box') { + && type !== 'box' && type !== 'box_by_4_points') { this._typeSelector.prop('value', 'box'); this._controller.setDefaultShapeType('box'); showMessage('Only boxes and single point allowed in the interpolation mode'); @@ -492,7 +493,7 @@ class ShapeCreatorView { ytl, xbr, ybr, - } + }; if (this._mode === 'interpolation') { box.outside = false; @@ -511,6 +512,67 @@ class ShapeCreatorView { } }); break; + case 'box_by_4_points': + let numberOfPoints = 0; + this._drawInstance = this._frameContent.polyline().draw({ snapToGrid: 0.1 }) + .addClass('shapeCreation').attr({ + 'stroke-width': 0, + }).on('drawstart', () => { + // init numberOfPoints as one on drawstart + numberOfPoints = 1; + }).on('drawpoint', (e) => { + // increase numberOfPoints by one on drawpoint + numberOfPoints += 1; + + // finish if numberOfPoints are exactly four + if (numberOfPoints === 4) { + let actualPoints = window.cvat.translate.points.canvasToActual(e.target.getAttribute('points')); + actualPoints = PolyShapeModel.convertStringToNumberArray(actualPoints); + const { frameWidth, frameHeight } = window.cvat.player.geometry; + + // init bounding box + const box = { + 'xtl': frameWidth, + 'ytl': frameHeight, + 'xbr': 0, + 'ybr': 0 + }; + + for (const point of actualPoints) { + // clamp point + point.x = Math.clamp(point.x, 0, frameWidth); + point.y = Math.clamp(point.y, 0, frameHeight); + + // update bounding box + box.xtl = Math.min(point.x, box.xtl); + box.ytl = Math.min(point.y, box.ytl); + box.xbr = Math.max(point.x, box.xbr); + box.ybr = Math.max(point.y, box.ybr); + } + + if ((box.ybr - box.ytl) * (box.xbr - box.xtl) >= AREA_TRESHOLD) { + if (this._mode === 'interpolation') { + box.outside = false; + } + // finish drawing + this._controller.finish(box, this._type); + } + this._controller.switchCreateMode(true); + } + }).on('undopoint', () => { + if (numberOfPoints > 0) { + numberOfPoints -= 1; + } + }).off('drawdone').on('drawdone', () => { + if (numberOfPoints !== 4) { + showMessage('Click exactly four extreme points for an object'); + this._controller.switchCreateMode(true); + } else { + throw Error('numberOfPoints is exactly four, but box drawing did not finish.'); + } + }); + this._createPolyEvents(); + break; case 'points': this._drawInstance = this._frameContent.polyline().draw({ snapToGrid: 0.1 }) .addClass('shapeCreation').attr({ diff --git a/cvat/apps/engine/static/engine/js/shapes.js b/cvat/apps/engine/static/engine/js/shapes.js index a95b0306ded..1212b92b372 100644 --- a/cvat/apps/engine/static/engine/js/shapes.js +++ b/cvat/apps/engine/static/engine/js/shapes.js @@ -3313,6 +3313,10 @@ function buildShapeModel(data, type, clientID, color) { switch (type) { case 'interpolation_box': case 'annotation_box': + case 'interpolation_box_by_4_points': + case 'annotation_box_by_4_points': + // convert type into 'box' if 'box_by_4_points' + type = type.replace('_by_4_points', ''); return new BoxModel(data, type, clientID, color); case 'interpolation_points': case 'annotation_points': diff --git a/cvat/apps/engine/templates/engine/annotation.html b/cvat/apps/engine/templates/engine/annotation.html index b437e38ac31..70feb5c4b74 100644 --- a/cvat/apps/engine/templates/engine/annotation.html +++ b/cvat/apps/engine/templates/engine/annotation.html @@ -437,6 +437,7 @@