From fad68b83059564875dcd0fc98beae83fe0a13c7e Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Fri, 20 Dec 2019 14:33:45 +0300 Subject: [PATCH 1/8] Temporary solution --- .../engine/static/engine/js/shapeCreator.js | 181 +++++++++++++++++- cvat/apps/engine/static/engine/stylesheet.css | 14 ++ 2 files changed, 185 insertions(+), 10 deletions(-) diff --git a/cvat/apps/engine/static/engine/js/shapeCreator.js b/cvat/apps/engine/static/engine/js/shapeCreator.js index e688476244c..87d4dde690e 100644 --- a/cvat/apps/engine/static/engine/js/shapeCreator.js +++ b/cvat/apps/engine/static/engine/js/shapeCreator.js @@ -30,6 +30,7 @@ class ShapeCreatorModel extends Listener { this._defaultLabel = null; this._currentFrame = null; this._createEvent = null; + this._positionsCache = null; this._shapeCollection = shapeCollection; } @@ -90,12 +91,14 @@ class ShapeCreatorModel extends Listener { } else if (window.cvat.mode === 'creation') { window.cvat.mode = null; + this._positionsCache = null; } } else { this._createMode = false; if (window.cvat.mode === 'creation') { window.cvat.mode = null; + this._positionsCache = null; if (this._createEvent) { this._createEvent.close(); this._createEvent = null; @@ -106,6 +109,31 @@ class ShapeCreatorModel extends Listener { this.notify(); } + get currentShapes() { + if (!this._positionsCache) { + this._positionsCache = this._shapeCollection.currentShapes + .filter((shape) => !shape.model.removed) + .map((shape) => { + const pos = shape.interpolation.position; + if (!('points' in pos)) { + return { + points: window.cvat.translate.points + .actualToCanvas(`${pos.xtl},${pos.ytl} ${pos.xbr},${pos.ybr}`), + color: shape.model.color.shape, + }; + } + + return { + points: window.cvat.translate.points + .actualToCanvas(pos.points), + color: shape.model.color.shape, + }; + }); + } + + return this._positionsCache; + } + get saveCurrent() { return this._saveCurrent; } @@ -177,6 +205,10 @@ class ShapeCreatorController { finish(result) { this._model.finish(result); } + + get currentShapes() { + return this._model.currentShapes; + } } class ShapeCreatorView { @@ -203,6 +235,7 @@ class ShapeCreatorView { this._mode = null; this._cancel = false; this._scale = 1; + this._autoBordering = false; let shortkeys = window.cvat.config.shortkeys; this._createButton.attr('title', ` @@ -285,6 +318,114 @@ class ShapeCreatorView { }); } + _rescaleBorderPoints() { + if (this._groups) { + this._groups.forEach((group) => { + Array.from(group.children).forEach((circle) => { + circle.setAttribute('r', 5 / this._scale); + circle.setAttribute('stroke-width', 1 / this._scale); + }); + }); + } + } + + _removeBorderMarkers() { + if (this._groups) { + this._groups.forEach((group) => { + Array.from(group.children).forEach((circle) => { + circle.removeEventListener('click', circle.clickListener); + }); + + group.remove(); + }); + + this._groups = null; + } + } + + _drawBorderMarkers() { + const namespace = 'http://www.w3.org/2000/svg'; + const groups = this._controller.currentShapes.reduce((acc, shape) => { + const group = window.document.createElementNS(namespace, 'g'); + const clicks = []; + shape.points.split(/\s/) + .map((point, idx, points) => { + const [x, y] = point.split(','); + const circle = window.document.createElementNS(namespace, 'circle'); + circle.classList.add('shape-creator-border-point'); + circle.classList.add('shape-creator-border-point-active'); + circle.setAttribute('fill', shape.color); + circle.setAttribute('stroke', 'black'); + circle.setAttribute('stroke-width', 1 / this._scale); + circle.setAttribute('cx', +x); + circle.setAttribute('cy', +y); + circle.setAttribute('r', 5 / this._scale); // TODO: base, scalable radius + circle.clickListener = (e) => { + e.stopPropagation(); + if (clicks.includes(idx)) { + circle.classList.add('shape-creator-border-point-active'); + } else { + clicks.push(idx); + + if (clicks.length > 2) { + const s = Math.sign(clicks[2] - clicks[1]); + const fakeEvent = {}; + // eslint-disable-next-line + for (const prop in e) { + fakeEvent[prop] = e[prop]; + } + + const border = []; + for (let i = clicks[0]; ; i += s) { + if (i < 0) { + i = points.length - 1; + } else if (i === points.length) { + i = 0; + } + + border.push(points[i]); + + if (i === clicks[clicks.length - 1]) { + // put the last element twice + // specific of svg.draw.js + border.push(points[i]); + break; + } + } + + // remove the latest cursor position from drawing array + this._drawInstance.array().valueOf().pop(); + for (const borderPoint of border) { + const [_x, _y] = borderPoint.split(','); + let pt = this._frameContent.node.createSVGPoint(); + pt.x = +_x; + pt.y = +_y; + pt = pt.matrixTransform(this._frameContent.node.getScreenCTM()); + fakeEvent.clientX = pt.x; + fakeEvent.clientY = pt.y; + this._drawInstance.draw('point', new MouseEvent(e.type, fakeEvent)); + } + + while (clicks.length > 0) { + clicks.pop(); + } + } + } + }; + + circle.addEventListener('click', circle.clickListener); + + return circle; + }).forEach((circle) => group.appendChild(circle)); + acc.push(group); + return acc; + }, []); + + this._groups = groups; + this._groups + .forEach((group) => this._frameContent.node.appendChild(group)); + } + _createPolyEvents() { // If number of points for poly shape specified, use it. @@ -335,6 +476,18 @@ class ShapeCreatorView { numberOfPoints ++; }); + $('body').on('keydown.shapeCreator', (e) => { + if (e.ctrlKey && e.keyCode === 17) { + if (this._autoBordering) { + this._removeBorderMarkers(); + this._autoBordering = false; + } else { + this._drawBorderMarkers(); + this._autoBordering = true; + } + } + }); + this._frameContent.on('mousedown.shapeCreator', (e) => { if (e.which === 3) { let lenBefore = this._drawInstance.array().value.length; @@ -366,6 +519,10 @@ class ShapeCreatorView { }); this._drawInstance.on('drawstop', () => { + $('body').off('keydown.shapeCreator'); + this._autoBordering = false; + this._removeBorderMarkers(); + this._frameContent.off('mousedown.shapeCreator'); this._frameContent.off('mousemove.shapeCreator'); }); @@ -413,7 +570,7 @@ class ShapeCreatorView { let sizeUI = null; switch(this._type) { case 'box': - this._drawInstance = this._frameContent.rect().draw({snapToGrid: 0.1}).addClass('shapeCreation').attr({ + this._drawInstance = this._frameContent.rect().draw({ snapToGrid: 0.1 }).addClass('shapeCreation').attr({ 'stroke-width': STROKE_WIDTH / this._scale, }).on('drawstop', function(e) { if (this._cancel) return; @@ -456,9 +613,10 @@ class ShapeCreatorView { }); break; case 'points': - this._drawInstance = this._frameContent.polyline().draw({snapToGrid: 0.1}).addClass('shapeCreation').attr({ - 'stroke-width': 0, - }); + this._drawInstance = this._frameContent.polyline().draw({ snapToGrid: 0.1 }) + .addClass('shapeCreation').attr({ + 'stroke-width': 0, + }); this._createPolyEvents(); break; case 'polygon': @@ -469,9 +627,10 @@ class ShapeCreatorView { this._controller.switchCreateMode(true); return; } - this._drawInstance = this._frameContent.polygon().draw({snapToGrid: 0.1}).addClass('shapeCreation').attr({ - 'stroke-width': STROKE_WIDTH / this._scale, - }); + this._drawInstance = this._frameContent.polygon().draw({ snapToGrid: 0.1 }) + .addClass('shapeCreation').attr({ + 'stroke-width': STROKE_WIDTH / this._scale, + }); this._createPolyEvents(); break; case 'polyline': @@ -482,9 +641,10 @@ class ShapeCreatorView { this._controller.switchCreateMode(true); return; } - this._drawInstance = this._frameContent.polyline().draw({snapToGrid: 0.1}).addClass('shapeCreation').attr({ - 'stroke-width': STROKE_WIDTH / this._scale, - }); + this._drawInstance = this._frameContent.polyline().draw({ snapToGrid: 0.1 }) + .addClass('shapeCreation').attr({ + 'stroke-width': STROKE_WIDTH / this._scale, + }); this._createPolyEvents(); break; default: @@ -580,6 +740,7 @@ class ShapeCreatorView { this._scale = player.geometry.scale; if (this._drawInstance) { this._rescaleDrawPoints(); + this._rescaleBorderPoints(); if (this._aim) { this._aim.x.attr('stroke-width', STROKE_WIDTH / this._scale); this._aim.y.attr('stroke-width', STROKE_WIDTH / this._scale); diff --git a/cvat/apps/engine/static/engine/stylesheet.css b/cvat/apps/engine/static/engine/stylesheet.css index 8dd8e8dd726..2fee0dafcb2 100644 --- a/cvat/apps/engine/static/engine/stylesheet.css +++ b/cvat/apps/engine/static/engine/stylesheet.css @@ -224,6 +224,20 @@ cursor: w-resize; } +.shape-creator-border-point { + opacity: 0.75; +} + +.shape-creator-border-point-active:hover { + opacity: 1; + fill: red; +} + +.shape-creator-border-point-active:active { + opacity: 0.75; + fill: red; +} + .shapeText { font-size: 0.12em; fill: white; From e25aa2d1855b5f448ffd20ef7cfa1a5e41801713 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Fri, 20 Dec 2019 14:44:02 +0300 Subject: [PATCH 2/8] Improved solution --- cvat/apps/engine/static/engine/js/shapeCreator.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cvat/apps/engine/static/engine/js/shapeCreator.js b/cvat/apps/engine/static/engine/js/shapeCreator.js index 87d4dde690e..b6782d45ca2 100644 --- a/cvat/apps/engine/static/engine/js/shapeCreator.js +++ b/cvat/apps/engine/static/engine/js/shapeCreator.js @@ -401,11 +401,14 @@ class ShapeCreatorView { pt.x = +_x; pt.y = +_y; pt = pt.matrixTransform(this._frameContent.node.getScreenCTM()); - fakeEvent.clientX = pt.x; - fakeEvent.clientY = pt.y; - this._drawInstance.draw('point', new MouseEvent(e.type, fakeEvent)); + + this._drawInstance.array().valueOf().push([+_x, +_y]); } + this._drawInstance.remember('_paintHandler').drawCircles(); + this._drawInstance.plot(this._drawInstance.array().valueOf()); + this._rescaleDrawPoints(); + while (clicks.length > 0) { clicks.pop(); } From b0ccf44efa863d5be44f5f341339c8d8b6da59a8 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Fri, 20 Dec 2019 20:48:57 +0300 Subject: [PATCH 3/8] Working solution --- .../engine/static/engine/js/shapeCreator.js | 94 +++++++++++++++---- cvat/apps/engine/static/engine/stylesheet.css | 12 ++- 2 files changed, 84 insertions(+), 22 deletions(-) diff --git a/cvat/apps/engine/static/engine/js/shapeCreator.js b/cvat/apps/engine/static/engine/js/shapeCreator.js index b6782d45ca2..909fde203c1 100644 --- a/cvat/apps/engine/static/engine/js/shapeCreator.js +++ b/cvat/apps/engine/static/engine/js/shapeCreator.js @@ -344,16 +344,19 @@ class ShapeCreatorView { } _drawBorderMarkers() { + const accounter = { + clicks: [], + shapeID: null, + }; + const namespace = 'http://www.w3.org/2000/svg'; - const groups = this._controller.currentShapes.reduce((acc, shape) => { + const groups = this._controller.currentShapes.reduce((acc, shape, shapeID, shapes) => { const group = window.document.createElementNS(namespace, 'g'); - const clicks = []; shape.points.split(/\s/) - .map((point, idx, points) => { + .map((point, pointID, points) => { const [x, y] = point.split(','); const circle = window.document.createElementNS(namespace, 'circle'); circle.classList.add('shape-creator-border-point'); - circle.classList.add('shape-creator-border-point-active'); circle.setAttribute('fill', shape.color); circle.setAttribute('stroke', 'black'); circle.setAttribute('stroke-width', 1 / this._scale); @@ -362,13 +365,65 @@ class ShapeCreatorView { circle.setAttribute('r', 5 / this._scale); // TODO: base, scalable radius circle.clickListener = (e) => { e.stopPropagation(); - if (clicks.includes(idx)) { - circle.classList.add('shape-creator-border-point-active'); + if (accounter.shapeID !== null && accounter.shapeID !== shapeID) { + if (accounter.clicks.length === 2) { + const pointToAdd = shapes[accounter.shapeID].points.split(/\s/)[accounter.clicks[1]]; + const [_x, _y] = pointToAdd.split(','); + this._drawInstance.array().valueOf().pop(); + this._drawInstance.array().valueOf().push([+_x, +_y]); + this._drawInstance.array().valueOf().push([+_x, +_y]); + this._drawInstance.remember('_paintHandler').drawCircles(); + this._drawInstance.plot(this._drawInstance.array().valueOf()); + this._rescaleDrawPoints(); + } + + while (accounter.clicks.length > 0) { + const resetID = accounter.clicks.pop(); + groups[accounter.shapeID] + .children[resetID].classList.remove('shape-creator-border-point-direction'); + } + } + + accounter.shapeID = shapeID; + if (accounter.clicks.includes(pointID)) { + const resetID = accounter.clicks + .splice(accounter.clicks.indexOf(pointID), 1); + group.children[resetID[0]].classList.remove('shape-creator-border-point-direction'); } else { - clicks.push(idx); + accounter.clicks.push(pointID); + + // up current group to work with its points easy + this._frameContent.node.appendChild(group); + if (accounter.clicks.length === 1) { + if (!this._drawInstance.remember('_paintHandler').startPoint) { + this._drawInstance.draw('point', e); + this._drawInstance.array().valueOf().pop(); + } + + this._drawInstance.array().valueOf().pop(); + const borderPoint = points[pointID]; + const [_x, _y] = borderPoint.split(','); + this._drawInstance.array().valueOf().push([+_x, +_y]); + this._drawInstance.array().valueOf().push([+_x, +_y]); + + this._drawInstance.remember('_paintHandler').drawCircles(); + this._drawInstance.plot(this._drawInstance.array().valueOf()); + this._rescaleDrawPoints(); + } else if (accounter.clicks.length === 2) { + circle.classList.add('shape-creator-border-point-direction'); + } else { + // const s = (accounter.clicks[1] > accounter.clicks[0] + // && accounter.clicks[1] > accounter.clicks[2]) + // || (accounter.clicks[1] < accounter.clicks[0] + // && accounter.clicks[1] < accounter.clicks[2]) + // ? -1 : Math.sign(accounter.clicks[2] - accounter.clicks[0]); + + // TODO: ADD TWO POINTS TO A BOX + + const s = Math.sign(accounter.clicks[2] - accounter.clicks[0]) + * Math.sign(accounter.clicks[1] - accounter.clicks[0]) + * Math.sign(accounter.clicks[2] - accounter.clicks[1]); - if (clicks.length > 2) { - const s = Math.sign(clicks[2] - clicks[1]); const fakeEvent = {}; // eslint-disable-next-line for (const prop in e) { @@ -376,7 +431,7 @@ class ShapeCreatorView { } const border = []; - for (let i = clicks[0]; ; i += s) { + for (let i = accounter.clicks[0]; ; i += s) { if (i < 0) { i = points.length - 1; } else if (i === points.length) { @@ -385,7 +440,7 @@ class ShapeCreatorView { border.push(points[i]); - if (i === clicks[clicks.length - 1]) { + if (i === accounter.clicks[accounter.clicks.length - 1]) { // put the last element twice // specific of svg.draw.js border.push(points[i]); @@ -393,15 +448,15 @@ class ShapeCreatorView { } } + if (!this._drawInstance.remember('_paintHandler').startPoint) { + this._drawInstance.draw('point', e); + this._drawInstance.array().valueOf().pop(); + } + // remove the latest cursor position from drawing array this._drawInstance.array().valueOf().pop(); for (const borderPoint of border) { const [_x, _y] = borderPoint.split(','); - let pt = this._frameContent.node.createSVGPoint(); - pt.x = +_x; - pt.y = +_y; - pt = pt.matrixTransform(this._frameContent.node.getScreenCTM()); - this._drawInstance.array().valueOf().push([+_x, +_y]); } @@ -409,9 +464,12 @@ class ShapeCreatorView { this._drawInstance.plot(this._drawInstance.array().valueOf()); this._rescaleDrawPoints(); - while (clicks.length > 0) { - clicks.pop(); + while (accounter.clicks.length > 0) { + const resetID = accounter.clicks.pop(); + group.children[resetID].classList.remove('shape-creator-border-point-direction'); } + + accounter.shapeID = null; } } }; diff --git a/cvat/apps/engine/static/engine/stylesheet.css b/cvat/apps/engine/static/engine/stylesheet.css index 2fee0dafcb2..cc84f73c4d6 100644 --- a/cvat/apps/engine/static/engine/stylesheet.css +++ b/cvat/apps/engine/static/engine/stylesheet.css @@ -225,19 +225,23 @@ } .shape-creator-border-point { - opacity: 0.75; + opacity: 0.35; } -.shape-creator-border-point-active:hover { +.shape-creator-border-point:hover { opacity: 1; fill: red; } -.shape-creator-border-point-active:active { - opacity: 0.75; +.shape-creator-border-point:active { + opacity: 0.35; fill: red; } +.shape-creator-border-point-direction { + fill: blueviolet; +} + .shapeText { font-size: 0.12em; fill: white; From 3fdfa0e6a3b0cb4f23ddc7fe1b02f2204c34fadf Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Mon, 23 Dec 2019 11:29:58 +0300 Subject: [PATCH 4/8] Working of border sticker in edit --- .../engine/static/engine/js/annotationUI.js | 2 +- .../engine/static/engine/js/borderSticker.js | 223 ++++++++++++++++++ .../static/engine/js/polyshapeEditor.js | 40 +++- .../engine/static/engine/js/shapeCreator.js | 216 ++--------------- cvat/apps/engine/static/engine/js/shapes.js | 3 +- cvat/apps/engine/static/engine/stylesheet.css | 4 +- .../engine/templates/engine/annotation.html | 1 + 7 files changed, 282 insertions(+), 207 deletions(-) create mode 100644 cvat/apps/engine/static/engine/js/borderSticker.js diff --git a/cvat/apps/engine/static/engine/js/annotationUI.js b/cvat/apps/engine/static/engine/js/annotationUI.js index 2b5a0e95f53..b1469728687 100644 --- a/cvat/apps/engine/static/engine/js/annotationUI.js +++ b/cvat/apps/engine/static/engine/js/annotationUI.js @@ -583,7 +583,7 @@ function buildAnnotationUI(jobData, taskData, imageMetaData, annotationData, ann const shapeCreatorController = new ShapeCreatorController(shapeCreatorModel); const shapeCreatorView = new ShapeCreatorView(shapeCreatorModel, shapeCreatorController); - const polyshapeEditorModel = new PolyshapeEditorModel(); + const polyshapeEditorModel = new PolyshapeEditorModel(shapeCollectionModel); const polyshapeEditorController = new PolyshapeEditorController(polyshapeEditorModel); const polyshapeEditorView = new PolyshapeEditorView(polyshapeEditorModel, polyshapeEditorController); diff --git a/cvat/apps/engine/static/engine/js/borderSticker.js b/cvat/apps/engine/static/engine/js/borderSticker.js new file mode 100644 index 00000000000..7bfc43f2290 --- /dev/null +++ b/cvat/apps/engine/static/engine/js/borderSticker.js @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2019 Intel Corporation + * + * SPDX-License-Identifier: MIT + */ + +/* exported BorderSticker */ + +class BorderSticker { + constructor(drawInstance, frameContent, shapes, scale) { + this._drawInstance = drawInstance; + this._frameContent = frameContent; + this._shapes = this._formatShapes(shapes); + this._enabled = false; + this._groups = null; + this._scale = scale; + + this._drawBorderMarkers(); + } + + _formatShapes(shapes) { + return shapes + .filter((shape) => !shape.model.removed) + .map((shape) => { + const pos = shape.interpolation.position; + if (!('points' in pos)) { + return { + points: window.cvat.translate.points + .actualToCanvas(`${pos.xtl},${pos.ytl} ${pos.xbr},${pos.ybr}`), + color: shape.model.color.shape, + }; + } + + return { + points: window.cvat.translate.points + .actualToCanvas(pos.points), + color: shape.model.color.shape, + }; + }); + } + + _rescaleDrawPoints() { + if (this._drawInstance) { + this._drawInstance.remember('_paintHandler').set.members.forEach((el) => { + el.attr('stroke-width', 1 / this._scale).attr('r', 2.5 / this._scale); + }); + } + } + + _removeBorderMarkers() { + if (this._groups) { + this._groups.forEach((group) => { + Array.from(group.children).forEach((circle) => { + circle.removeEventListener('click', circle.clickListener); + }); + + group.remove(); + }); + + this._groups = null; + } + } + + _drawBorderMarkers() { + const accounter = { + clicks: [], + shapeID: null, + }; + + const namespace = 'http://www.w3.org/2000/svg'; + const groups = this._shapes.reduce((acc, shape, shapeID, shapes) => { + const group = window.document.createElementNS(namespace, 'g'); + shape.points.split(/\s/) + .map((point, pointID, points) => { + const [x, y] = point.split(','); + const circle = window.document.createElementNS(namespace, 'circle'); + circle.classList.add('shape-creator-border-point'); + circle.setAttribute('fill', shape.color); + circle.setAttribute('stroke', 'black'); + circle.setAttribute('stroke-width', 1 / this._scale); + circle.setAttribute('cx', +x); + circle.setAttribute('cy', +y); + circle.setAttribute('r', 5 / this._scale); + circle.clickListener = (e) => { + e.stopPropagation(); + if (accounter.shapeID !== null && accounter.shapeID !== shapeID) { + if (accounter.clicks.length === 2) { + const pointToAdd = shapes[accounter.shapeID].points.split(/\s/)[accounter.clicks[1]]; + const [_x, _y] = pointToAdd.split(','); + this._drawInstance.array().valueOf().pop(); + this._drawInstance.array().valueOf().push([+_x, +_y]); + this._drawInstance.array().valueOf().push([+_x, +_y]); + this._drawInstance.remember('_paintHandler').drawCircles(); + this._drawInstance.plot(this._drawInstance.array().valueOf()); + this._rescaleDrawPoints(); + } + + while (accounter.clicks.length > 0) { + const resetID = accounter.clicks.pop(); + groups[accounter.shapeID] + .children[resetID].classList.remove('shape-creator-border-point-direction'); + } + } + + accounter.shapeID = shapeID; + if (accounter.clicks.includes(pointID)) { + const resetID = accounter.clicks + .splice(accounter.clicks.indexOf(pointID), 1); + group.children[resetID[0]].classList.remove('shape-creator-border-point-direction'); + } else { + accounter.clicks.push(pointID); + + // up current group to work with its points easy + this._frameContent.node.appendChild(group); + if (accounter.clicks.length === 1) { + if (!this._drawInstance.remember('_paintHandler').startPoint) { + this._drawInstance.draw('point', e); + this._drawInstance.array().valueOf().pop(); + } + + this._drawInstance.array().valueOf().pop(); + const borderPoint = points[pointID]; + const [_x, _y] = borderPoint.split(','); + this._drawInstance.array().valueOf().push([+_x, +_y]); + this._drawInstance.array().valueOf().push([+_x, +_y]); + + this._drawInstance.remember('_paintHandler').drawCircles(); + this._drawInstance.plot(this._drawInstance.array().valueOf()); + this._rescaleDrawPoints(); + } else if (accounter.clicks.length === 2) { + circle.classList.add('shape-creator-border-point-direction'); + } else { + // const s = (accounter.clicks[1] > accounter.clicks[0] + // && accounter.clicks[1] > accounter.clicks[2]) + // || (accounter.clicks[1] < accounter.clicks[0] + // && accounter.clicks[1] < accounter.clicks[2]) + // ? -1 : Math.sign(accounter.clicks[2] - accounter.clicks[0]); + + // TODO: ADD TWO POINTS TO A BOX + + const s = Math.sign(accounter.clicks[2] - accounter.clicks[0]) + * Math.sign(accounter.clicks[1] - accounter.clicks[0]) + * Math.sign(accounter.clicks[2] - accounter.clicks[1]); + + const fakeEvent = {}; + // eslint-disable-next-line + for (const prop in e) { + fakeEvent[prop] = e[prop]; + } + + const border = []; + for (let i = accounter.clicks[0]; ; i += s) { + if (i < 0) { + i = points.length - 1; + } else if (i === points.length) { + i = 0; + } + + border.push(points[i]); + + if (i === accounter.clicks[accounter.clicks.length - 1]) { + // put the last element twice + // specific of svg.draw.js + border.push(points[i]); + break; + } + } + + if (!this._drawInstance.remember('_paintHandler').startPoint) { + this._drawInstance.draw('point', e); + this._drawInstance.array().valueOf().pop(); + } + + // remove the latest cursor position from drawing array + this._drawInstance.array().valueOf().pop(); + for (const borderPoint of border) { + const [_x, _y] = borderPoint.split(','); + this._drawInstance.array().valueOf().push([+_x, +_y]); + } + + this._drawInstance.remember('_paintHandler').drawCircles(); + this._drawInstance.plot(this._drawInstance.array().valueOf()); + this._rescaleDrawPoints(); + + while (accounter.clicks.length > 0) { + const resetID = accounter.clicks.pop(); + group.children[resetID].classList.remove('shape-creator-border-point-direction'); + } + + accounter.shapeID = null; + } + } + }; + + circle.addEventListener('click', circle.clickListener); + + return circle; + }).forEach((circle) => group.appendChild(circle)); + acc.push(group); + return acc; + }, []); + + this._groups = groups; + this._groups + .forEach((group) => this._frameContent.node.appendChild(group)); + } + + disable() { + this._removeBorderMarkers(); + } + + scale(scale) { + this._scale = scale; + if (this._groups) { + this._groups.forEach((group) => { + Array.from(group.children).forEach((circle) => { + circle.setAttribute('r', 5 / scale); + circle.setAttribute('stroke-width', 1 / scale); + }); + }); + } + } +} diff --git a/cvat/apps/engine/static/engine/js/polyshapeEditor.js b/cvat/apps/engine/static/engine/js/polyshapeEditor.js index e39345808e4..cc159e30eba 100644 --- a/cvat/apps/engine/static/engine/js/polyshapeEditor.js +++ b/cvat/apps/engine/static/engine/js/polyshapeEditor.js @@ -12,16 +12,18 @@ PolyShapeModel:false STROKE_WIDTH:false SVG:false + BorderSticker:false */ "use strict"; class PolyshapeEditorModel extends Listener { - constructor() { + constructor(shapeCollection) { super("onPolyshapeEditorUpdate", () => this); this._modeName = 'poly_editing'; this._active = false; + this._shapeCollection = shapeCollection; this._data = { points: null, color: null, @@ -29,10 +31,11 @@ class PolyshapeEditorModel extends Listener { oncomplete: null, type: null, event: null, + id: null, }; } - edit(type, points, color, start, event, oncomplete) { + edit(type, points, color, start, event, oncomplete, id) { if (!this._active && !window.cvat.mode) { window.cvat.mode = this._modeName; this._active = true; @@ -42,6 +45,7 @@ class PolyshapeEditorModel extends Listener { this._data.oncomplete = oncomplete; this._data.type = type; this._data.event = event; + this._data.id = id; this.notify(); } else if (this._active) { @@ -84,6 +88,10 @@ class PolyshapeEditorModel extends Listener { get data() { return this._data; } + + get currentShapes() { + return this._shapeCollection.currentShapes; + } } @@ -99,6 +107,10 @@ class PolyshapeEditorController { cancel() { this._model.cancel(); } + + get currentShapes() { + return this._model.currentShapes; + } } @@ -338,6 +350,20 @@ class PolyshapeEditorView { this._controller.finish(PolyShapeModel.convertNumberArrayToString(resultPoints)); }); } + + $('body').on('keydown.shapeEditor', (e) => { + if (e.ctrlKey && e.keyCode === 17) { + if (this._borderSticker) { + this._borderSticker.disable(); + this._borderSticker = null; + } else { + this._borderSticker = new BorderSticker(this._correctLine, this._frameContent, + this._controller.currentShapes + .filter((shape) => shape.model.id !== this._data.id), + this._scale); + } + } + }); } _endEdit() { @@ -361,6 +387,12 @@ class PolyshapeEditorView { this._frameContent.off('mousemove.polyshapeEditor'); this._frameContent.off('mousedown.polyshapeEditor'); this._frameContent.off('contextmenu.polyshapeEditor'); + + $('body').off('keydown.shapeCreator'); + if (this._borderSticker) { + this._borderSticker.disable(); + this._borderSticker = null; + } } @@ -379,6 +411,10 @@ class PolyshapeEditorView { if (this._scale != scale) { this._scale = scale; + if (this._borderSticker) { + this._borderSticker.scale(this._scale); + } + let strokeWidth = this._data && this._data.type === 'points' ? 0 : STROKE_WIDTH / this._scale; let pointRadius = POINT_RADIUS / this._scale; diff --git a/cvat/apps/engine/static/engine/js/shapeCreator.js b/cvat/apps/engine/static/engine/js/shapeCreator.js index 909fde203c1..478955cf8cc 100644 --- a/cvat/apps/engine/static/engine/js/shapeCreator.js +++ b/cvat/apps/engine/static/engine/js/shapeCreator.js @@ -16,6 +16,7 @@ showMessage:false STROKE_WIDTH:false SVG:false + BorderSticker: false */ "use strict"; @@ -110,28 +111,7 @@ class ShapeCreatorModel extends Listener { } get currentShapes() { - if (!this._positionsCache) { - this._positionsCache = this._shapeCollection.currentShapes - .filter((shape) => !shape.model.removed) - .map((shape) => { - const pos = shape.interpolation.position; - if (!('points' in pos)) { - return { - points: window.cvat.translate.points - .actualToCanvas(`${pos.xtl},${pos.ytl} ${pos.xbr},${pos.ybr}`), - color: shape.model.color.shape, - }; - } - - return { - points: window.cvat.translate.points - .actualToCanvas(pos.points), - color: shape.model.color.shape, - }; - }); - } - - return this._positionsCache; + return this._shapeCollection.currentShapes; } get saveCurrent() { @@ -235,7 +215,7 @@ class ShapeCreatorView { this._mode = null; this._cancel = false; this._scale = 1; - this._autoBordering = false; + this._borderSticker = null; let shortkeys = window.cvat.config.shortkeys; this._createButton.attr('title', ` @@ -318,176 +298,6 @@ class ShapeCreatorView { }); } - _rescaleBorderPoints() { - if (this._groups) { - this._groups.forEach((group) => { - Array.from(group.children).forEach((circle) => { - circle.setAttribute('r', 5 / this._scale); - circle.setAttribute('stroke-width', 1 / this._scale); - }); - }); - } - } - - _removeBorderMarkers() { - if (this._groups) { - this._groups.forEach((group) => { - Array.from(group.children).forEach((circle) => { - circle.removeEventListener('click', circle.clickListener); - }); - - group.remove(); - }); - - this._groups = null; - } - } - - _drawBorderMarkers() { - const accounter = { - clicks: [], - shapeID: null, - }; - - const namespace = 'http://www.w3.org/2000/svg'; - const groups = this._controller.currentShapes.reduce((acc, shape, shapeID, shapes) => { - const group = window.document.createElementNS(namespace, 'g'); - shape.points.split(/\s/) - .map((point, pointID, points) => { - const [x, y] = point.split(','); - const circle = window.document.createElementNS(namespace, 'circle'); - circle.classList.add('shape-creator-border-point'); - circle.setAttribute('fill', shape.color); - circle.setAttribute('stroke', 'black'); - circle.setAttribute('stroke-width', 1 / this._scale); - circle.setAttribute('cx', +x); - circle.setAttribute('cy', +y); - circle.setAttribute('r', 5 / this._scale); // TODO: base, scalable radius - circle.clickListener = (e) => { - e.stopPropagation(); - if (accounter.shapeID !== null && accounter.shapeID !== shapeID) { - if (accounter.clicks.length === 2) { - const pointToAdd = shapes[accounter.shapeID].points.split(/\s/)[accounter.clicks[1]]; - const [_x, _y] = pointToAdd.split(','); - this._drawInstance.array().valueOf().pop(); - this._drawInstance.array().valueOf().push([+_x, +_y]); - this._drawInstance.array().valueOf().push([+_x, +_y]); - this._drawInstance.remember('_paintHandler').drawCircles(); - this._drawInstance.plot(this._drawInstance.array().valueOf()); - this._rescaleDrawPoints(); - } - - while (accounter.clicks.length > 0) { - const resetID = accounter.clicks.pop(); - groups[accounter.shapeID] - .children[resetID].classList.remove('shape-creator-border-point-direction'); - } - } - - accounter.shapeID = shapeID; - if (accounter.clicks.includes(pointID)) { - const resetID = accounter.clicks - .splice(accounter.clicks.indexOf(pointID), 1); - group.children[resetID[0]].classList.remove('shape-creator-border-point-direction'); - } else { - accounter.clicks.push(pointID); - - // up current group to work with its points easy - this._frameContent.node.appendChild(group); - if (accounter.clicks.length === 1) { - if (!this._drawInstance.remember('_paintHandler').startPoint) { - this._drawInstance.draw('point', e); - this._drawInstance.array().valueOf().pop(); - } - - this._drawInstance.array().valueOf().pop(); - const borderPoint = points[pointID]; - const [_x, _y] = borderPoint.split(','); - this._drawInstance.array().valueOf().push([+_x, +_y]); - this._drawInstance.array().valueOf().push([+_x, +_y]); - - this._drawInstance.remember('_paintHandler').drawCircles(); - this._drawInstance.plot(this._drawInstance.array().valueOf()); - this._rescaleDrawPoints(); - } else if (accounter.clicks.length === 2) { - circle.classList.add('shape-creator-border-point-direction'); - } else { - // const s = (accounter.clicks[1] > accounter.clicks[0] - // && accounter.clicks[1] > accounter.clicks[2]) - // || (accounter.clicks[1] < accounter.clicks[0] - // && accounter.clicks[1] < accounter.clicks[2]) - // ? -1 : Math.sign(accounter.clicks[2] - accounter.clicks[0]); - - // TODO: ADD TWO POINTS TO A BOX - - const s = Math.sign(accounter.clicks[2] - accounter.clicks[0]) - * Math.sign(accounter.clicks[1] - accounter.clicks[0]) - * Math.sign(accounter.clicks[2] - accounter.clicks[1]); - - const fakeEvent = {}; - // eslint-disable-next-line - for (const prop in e) { - fakeEvent[prop] = e[prop]; - } - - const border = []; - for (let i = accounter.clicks[0]; ; i += s) { - if (i < 0) { - i = points.length - 1; - } else if (i === points.length) { - i = 0; - } - - border.push(points[i]); - - if (i === accounter.clicks[accounter.clicks.length - 1]) { - // put the last element twice - // specific of svg.draw.js - border.push(points[i]); - break; - } - } - - if (!this._drawInstance.remember('_paintHandler').startPoint) { - this._drawInstance.draw('point', e); - this._drawInstance.array().valueOf().pop(); - } - - // remove the latest cursor position from drawing array - this._drawInstance.array().valueOf().pop(); - for (const borderPoint of border) { - const [_x, _y] = borderPoint.split(','); - this._drawInstance.array().valueOf().push([+_x, +_y]); - } - - this._drawInstance.remember('_paintHandler').drawCircles(); - this._drawInstance.plot(this._drawInstance.array().valueOf()); - this._rescaleDrawPoints(); - - while (accounter.clicks.length > 0) { - const resetID = accounter.clicks.pop(); - group.children[resetID].classList.remove('shape-creator-border-point-direction'); - } - - accounter.shapeID = null; - } - } - }; - - circle.addEventListener('click', circle.clickListener); - - return circle; - }).forEach((circle) => group.appendChild(circle)); - acc.push(group); - return acc; - }, []); - - this._groups = groups; - this._groups - .forEach((group) => this._frameContent.node.appendChild(group)); - } - - _createPolyEvents() { // If number of points for poly shape specified, use it. // Dicrement number on draw new point events. Drawstart trigger when create first point @@ -539,12 +349,12 @@ class ShapeCreatorView { $('body').on('keydown.shapeCreator', (e) => { if (e.ctrlKey && e.keyCode === 17) { - if (this._autoBordering) { - this._removeBorderMarkers(); - this._autoBordering = false; + if (this._borderSticker) { + this._borderSticker.disable(); + this._borderSticker = null; } else { - this._drawBorderMarkers(); - this._autoBordering = true; + this._borderSticker = new BorderSticker(this._drawInstance, this._frameContent, + this._controller.currentShapes, this._scale); } } }); @@ -581,8 +391,10 @@ class ShapeCreatorView { this._drawInstance.on('drawstop', () => { $('body').off('keydown.shapeCreator'); - this._autoBordering = false; - this._removeBorderMarkers(); + if (this._borderSticker) { + this._borderSticker.disable(); + this._borderSticker = null; + } this._frameContent.off('mousedown.shapeCreator'); this._frameContent.off('mousemove.shapeCreator'); @@ -801,7 +613,9 @@ class ShapeCreatorView { this._scale = player.geometry.scale; if (this._drawInstance) { this._rescaleDrawPoints(); - this._rescaleBorderPoints(); + if (this._borderSticker) { + this._borderSticker.scale(this._scale); + } if (this._aim) { this._aim.x.attr('stroke-width', STROKE_WIDTH / this._scale); this._aim.y.attr('stroke-width', STROKE_WIDTH / this._scale); diff --git a/cvat/apps/engine/static/engine/js/shapes.js b/cvat/apps/engine/static/engine/js/shapes.js index 2ff5c4e96ac..46cff530180 100644 --- a/cvat/apps/engine/static/engine/js/shapes.js +++ b/cvat/apps/engine/static/engine/js/shapes.js @@ -3017,7 +3017,8 @@ class PolyShapeView extends ShapeView { this._uis.shape.attr('points', points); this._controller.updatePosition(window.cvat.player.frames.current, this._buildPosition()); } - } + }, + this._controller.id ); } } diff --git a/cvat/apps/engine/static/engine/stylesheet.css b/cvat/apps/engine/static/engine/stylesheet.css index cc84f73c4d6..6e0d7b658d5 100644 --- a/cvat/apps/engine/static/engine/stylesheet.css +++ b/cvat/apps/engine/static/engine/stylesheet.css @@ -225,7 +225,7 @@ } .shape-creator-border-point { - opacity: 0.35; + opacity: 0.55; } .shape-creator-border-point:hover { @@ -234,7 +234,7 @@ } .shape-creator-border-point:active { - opacity: 0.35; + opacity: 0.55; fill: red; } diff --git a/cvat/apps/engine/templates/engine/annotation.html b/cvat/apps/engine/templates/engine/annotation.html index 521a5d2d394..86ffc8c051c 100644 --- a/cvat/apps/engine/templates/engine/annotation.html +++ b/cvat/apps/engine/templates/engine/annotation.html @@ -41,6 +41,7 @@ + From 45035ee756e66e3752e24746344f0573d503f87f Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Mon, 23 Dec 2019 12:35:37 +0300 Subject: [PATCH 5/8] Significantly improved precise --- .../static/engine/js/polyshapeEditor.js | 40 +++++++++++++++---- cvat/apps/engine/static/engine/js/shapes.js | 3 +- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/cvat/apps/engine/static/engine/js/polyshapeEditor.js b/cvat/apps/engine/static/engine/js/polyshapeEditor.js index cc159e30eba..e38cf399c22 100644 --- a/cvat/apps/engine/static/engine/js/polyshapeEditor.js +++ b/cvat/apps/engine/static/engine/js/polyshapeEditor.js @@ -31,11 +31,12 @@ class PolyshapeEditorModel extends Listener { oncomplete: null, type: null, event: null, + startPoint: null, id: null, }; } - edit(type, points, color, start, event, oncomplete, id) { + edit(type, points, color, start, startPoint, e, oncomplete, id) { if (!this._active && !window.cvat.mode) { window.cvat.mode = this._modeName; this._active = true; @@ -44,7 +45,8 @@ class PolyshapeEditorModel extends Listener { this._data.start = start; this._data.oncomplete = oncomplete; this._data.type = type; - this._data.event = event; + this._data.event = e; + this._data.startPoint = startPoint; this._data.id = id; this.notify(); } @@ -77,6 +79,7 @@ class PolyshapeEditorModel extends Listener { this._data.oncomplete = null; this._data.type = null; this._data.event = null; + this._data.startPoint = null; this.notify(); } } @@ -192,6 +195,15 @@ class PolyshapeEditorView { return offset; } + _addRawPoint(x, y) { + this._correctLine.array().valueOf().pop(); + this._correctLine.array().valueOf().push([x, y]); + this._correctLine.array().valueOf().push([x, y]); + this._correctLine.remember('_paintHandler').drawCircles(); + this._correctLine.plot(this._correctLine.array().valueOf()); + this._rescaleDrawPoints(); + } + _startEdit() { this._frame = window.cvat.player.frames.current; let strokeWidth = this._data.type === 'points' ? 0 : STROKE_WIDTH / this._scale; @@ -234,13 +246,19 @@ class PolyshapeEditorView { } + const [prevPointX, prevPointY] = this._data.startPoint + .split(',').map((el) => +el); let prevPoint = { - x: this._data.event.clientX, - y: this._data.event.clientY + x: prevPointX, + y: prevPointY, }; + // draw/remove initial point just to initialize data structures this._correctLine.draw('point', this._data.event); - this._rescaleDrawPoints(); + this._correctLine.array().valueOf().pop(); + + this._addRawPoint(prevPointX, prevPointY); + this._frameContent.on('mousemove.polyshapeEditor', (e) => { if (e.shiftKey && this._data.type != 'points') { let delta = Math.sqrt(Math.pow(e.clientX - prevPoint.x, 2) + Math.pow(e.clientY - prevPoint.y, 2)); @@ -286,7 +304,15 @@ class PolyshapeEditorView { }).on('mousedown', (e) => { if (e.which != 1) return; let currentPoints = PolyShapeModel.convertStringToNumberArray(this._data.points); - let correctPoints = PolyShapeModel.convertStringToNumberArray(this._correctLine.attr('points')); + // replace the latest point from the event + // (which has not precise coordinates, to precise coordinates) + let correctPoints = this._correctLine + .attr('points') + .split(/\s/) + .slice(0, -1); + correctPoints = correctPoints.concat([`${instance.attr('cx')},${instance.attr('cy')}`]).join(' '); + correctPoints = PolyShapeModel.convertStringToNumberArray(correctPoints); + let resultPoints = []; if (this._data.type === 'polygon') { @@ -388,7 +414,7 @@ class PolyshapeEditorView { this._frameContent.off('mousedown.polyshapeEditor'); this._frameContent.off('contextmenu.polyshapeEditor'); - $('body').off('keydown.shapeCreator'); + $('body').off('keydown.shapeEditor'); if (this._borderSticker) { this._borderSticker.disable(); this._borderSticker = null; diff --git a/cvat/apps/engine/static/engine/js/shapes.js b/cvat/apps/engine/static/engine/js/shapes.js index 46cff530180..ddfff6a185b 100644 --- a/cvat/apps/engine/static/engine/js/shapes.js +++ b/cvat/apps/engine/static/engine/js/shapes.js @@ -3007,7 +3007,8 @@ class PolyShapeView extends ShapeView { // Run edit mode PolyShapeView.editor.edit(this._controller.type.split('_')[1], - this._uis.shape.attr('points'), this._color, index, e, + this._uis.shape.attr('points'), this._color, index, + this._uis.shape.attr('points').split(/\s/)[index], e, (points) => { this._uis.shape.removeClass('hidden'); if (this._uis.points) { From 3e4ec54b188e7863ca6f5d1e788af399f8682676 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Mon, 23 Dec 2019 13:41:02 +0300 Subject: [PATCH 6/8] Added double click event --- .../engine/static/engine/js/borderSticker.js | 152 +++++++++--------- .../static/engine/js/polyshapeEditor.js | 2 + .../engine/static/engine/js/shapeCreator.js | 6 +- 3 files changed, 82 insertions(+), 78 deletions(-) diff --git a/cvat/apps/engine/static/engine/js/borderSticker.js b/cvat/apps/engine/static/engine/js/borderSticker.js index 7bfc43f2290..f6881ca5ad2 100644 --- a/cvat/apps/engine/static/engine/js/borderSticker.js +++ b/cvat/apps/engine/static/engine/js/borderSticker.js @@ -26,7 +26,8 @@ class BorderSticker { if (!('points' in pos)) { return { points: window.cvat.translate.points - .actualToCanvas(`${pos.xtl},${pos.ytl} ${pos.xbr},${pos.ybr}`), + .actualToCanvas(`${pos.xtl},${pos.ytl} ${pos.xbr},${pos.ytl}` + + ` ${pos.xbr},${pos.ybr} ${pos.xtl},${pos.ybr}`), color: shape.model.color.shape, }; } @@ -52,6 +53,7 @@ class BorderSticker { this._groups.forEach((group) => { Array.from(group.children).forEach((circle) => { circle.removeEventListener('click', circle.clickListener); + circle.removeEventListener('dblclick', circle.doubleClickListener); }); group.remove(); @@ -62,7 +64,7 @@ class BorderSticker { } _drawBorderMarkers() { - const accounter = { + this._accounter = { clicks: [], shapeID: null, }; @@ -81,11 +83,14 @@ class BorderSticker { circle.setAttribute('cx', +x); circle.setAttribute('cy', +y); circle.setAttribute('r', 5 / this._scale); + circle.doubleClickListener = (e) => { + e.stopPropagation(); + }; circle.clickListener = (e) => { e.stopPropagation(); - if (accounter.shapeID !== null && accounter.shapeID !== shapeID) { - if (accounter.clicks.length === 2) { - const pointToAdd = shapes[accounter.shapeID].points.split(/\s/)[accounter.clicks[1]]; + if (this._accounter.shapeID !== null && this._accounter.shapeID !== shapeID) { + if (this._accounter.clicks.length === 2) { + const pointToAdd = shapes[this._accounter.shapeID].points.split(/\s/)[this._accounter.clicks[1]]; const [_x, _y] = pointToAdd.split(','); this._drawInstance.array().valueOf().pop(); this._drawInstance.array().valueOf().push([+_x, +_y]); @@ -95,104 +100,101 @@ class BorderSticker { this._rescaleDrawPoints(); } - while (accounter.clicks.length > 0) { - const resetID = accounter.clicks.pop(); - groups[accounter.shapeID] + while (this._accounter.clicks.length > 0) { + const resetID = this._accounter.clicks.pop(); + groups[this._accounter.shapeID] .children[resetID].classList.remove('shape-creator-border-point-direction'); } } - accounter.shapeID = shapeID; - if (accounter.clicks.includes(pointID)) { - const resetID = accounter.clicks - .splice(accounter.clicks.indexOf(pointID), 1); - group.children[resetID[0]].classList.remove('shape-creator-border-point-direction'); - } else { - accounter.clicks.push(pointID); - - // up current group to work with its points easy - this._frameContent.node.appendChild(group); - if (accounter.clicks.length === 1) { - if (!this._drawInstance.remember('_paintHandler').startPoint) { - this._drawInstance.draw('point', e); - this._drawInstance.array().valueOf().pop(); - } + this._accounter.shapeID = shapeID; + + this._accounter.clicks.push(pointID); + + // up current group to work with its points easy + this._frameContent.node.appendChild(group); + if (this._accounter.clicks.length === 1) { + if (!this._drawInstance.remember('_paintHandler').startPoint) { + this._drawInstance.draw('point', e); + this._drawInstance.array().valueOf().pop(); + } + this._drawInstance.array().valueOf().pop(); + const borderPoint = points[pointID]; + const [_x, _y] = borderPoint.split(','); + this._drawInstance.array().valueOf().push([+_x, +_y]); + this._drawInstance.array().valueOf().push([+_x, +_y]); + + this._drawInstance.remember('_paintHandler').drawCircles(); + this._drawInstance.plot(this._drawInstance.array().valueOf()); + this._rescaleDrawPoints(); + } else if (this._accounter.clicks.length === 2) { + circle.classList.add('shape-creator-border-point-direction'); + } else { + if (this._accounter.clicks[1] === this._accounter.clicks[2]) { this._drawInstance.array().valueOf().pop(); - const borderPoint = points[pointID]; + const borderPoint = points[this._accounter.clicks[2]]; const [_x, _y] = borderPoint.split(','); this._drawInstance.array().valueOf().push([+_x, +_y]); this._drawInstance.array().valueOf().push([+_x, +_y]); - this._drawInstance.remember('_paintHandler').drawCircles(); this._drawInstance.plot(this._drawInstance.array().valueOf()); this._rescaleDrawPoints(); - } else if (accounter.clicks.length === 2) { - circle.classList.add('shape-creator-border-point-direction'); - } else { - // const s = (accounter.clicks[1] > accounter.clicks[0] - // && accounter.clicks[1] > accounter.clicks[2]) - // || (accounter.clicks[1] < accounter.clicks[0] - // && accounter.clicks[1] < accounter.clicks[2]) - // ? -1 : Math.sign(accounter.clicks[2] - accounter.clicks[0]); - - // TODO: ADD TWO POINTS TO A BOX - - const s = Math.sign(accounter.clicks[2] - accounter.clicks[0]) - * Math.sign(accounter.clicks[1] - accounter.clicks[0]) - * Math.sign(accounter.clicks[2] - accounter.clicks[1]); - - const fakeEvent = {}; - // eslint-disable-next-line - for (const prop in e) { - fakeEvent[prop] = e[prop]; + + while (this._accounter.clicks.length > 0) { + const resetID = this._accounter.clicks.pop(); + group.children[resetID].classList.remove('shape-creator-border-point-direction'); } - const border = []; - for (let i = accounter.clicks[0]; ; i += s) { - if (i < 0) { - i = points.length - 1; - } else if (i === points.length) { - i = 0; - } + this._accounter.shapeID = null; - border.push(points[i]); + return; + } - if (i === accounter.clicks[accounter.clicks.length - 1]) { - // put the last element twice - // specific of svg.draw.js - border.push(points[i]); - break; - } - } + const s = Math.sign(this._accounter.clicks[2] - this._accounter.clicks[0]) + * Math.sign(this._accounter.clicks[1] - this._accounter.clicks[0]) + * Math.sign(this._accounter.clicks[2] - this._accounter.clicks[1]); - if (!this._drawInstance.remember('_paintHandler').startPoint) { - this._drawInstance.draw('point', e); - this._drawInstance.array().valueOf().pop(); + const border = []; + for (let i = this._accounter.clicks[0]; ; i += s) { + if (i < 0) { + i = points.length - 1; + } else if (i === points.length) { + i = 0; } - // remove the latest cursor position from drawing array - this._drawInstance.array().valueOf().pop(); - for (const borderPoint of border) { - const [_x, _y] = borderPoint.split(','); - this._drawInstance.array().valueOf().push([+_x, +_y]); + border.push(points[i]); + + if (i === this._accounter.clicks[this._accounter.clicks.length - 1]) { + // put the last element twice + // specific of svg.draw.js + border.push(points[i]); + break; } + } - this._drawInstance.remember('_paintHandler').drawCircles(); - this._drawInstance.plot(this._drawInstance.array().valueOf()); - this._rescaleDrawPoints(); + // remove the latest cursor position from drawing array + this._drawInstance.array().valueOf().pop(); + for (const borderPoint of border) { + const [_x, _y] = borderPoint.split(','); + this._drawInstance.array().valueOf().push([+_x, +_y]); + } - while (accounter.clicks.length > 0) { - const resetID = accounter.clicks.pop(); - group.children[resetID].classList.remove('shape-creator-border-point-direction'); - } + this._drawInstance.remember('_paintHandler').drawCircles(); + this._drawInstance.plot(this._drawInstance.array().valueOf()); + this._rescaleDrawPoints(); - accounter.shapeID = null; + while (this._accounter.clicks.length > 0) { + const resetID = this._accounter.clicks.pop(); + group.children[resetID].classList.remove('shape-creator-border-point-direction'); } + + this._accounter.shapeID = null; } }; circle.addEventListener('click', circle.clickListener); + circle.addEventListener('dblclick', circle.doubleClickListener); return circle; }).forEach((circle) => group.appendChild(circle)); diff --git a/cvat/apps/engine/static/engine/js/polyshapeEditor.js b/cvat/apps/engine/static/engine/js/polyshapeEditor.js index e38cf399c22..cabe72a72b9 100644 --- a/cvat/apps/engine/static/engine/js/polyshapeEditor.js +++ b/cvat/apps/engine/static/engine/js/polyshapeEditor.js @@ -93,6 +93,7 @@ class PolyshapeEditorModel extends Listener { } get currentShapes() { + this._shapeCollection.update(); return this._shapeCollection.currentShapes; } } @@ -198,6 +199,7 @@ class PolyshapeEditorView { _addRawPoint(x, y) { this._correctLine.array().valueOf().pop(); this._correctLine.array().valueOf().push([x, y]); + // not error, specific of the library this._correctLine.array().valueOf().push([x, y]); this._correctLine.remember('_paintHandler').drawCircles(); this._correctLine.plot(this._correctLine.array().valueOf()); diff --git a/cvat/apps/engine/static/engine/js/shapeCreator.js b/cvat/apps/engine/static/engine/js/shapeCreator.js index 478955cf8cc..59036fddcfd 100644 --- a/cvat/apps/engine/static/engine/js/shapeCreator.js +++ b/cvat/apps/engine/static/engine/js/shapeCreator.js @@ -111,6 +111,7 @@ class ShapeCreatorModel extends Listener { } get currentShapes() { + this._shapeCollection.update(); return this._shapeCollection.currentShapes; } @@ -390,14 +391,13 @@ class ShapeCreatorView { }); this._drawInstance.on('drawstop', () => { + this._frameContent.off('mousedown.shapeCreator'); + this._frameContent.off('mousemove.shapeCreator'); $('body').off('keydown.shapeCreator'); if (this._borderSticker) { this._borderSticker.disable(); this._borderSticker = null; } - - this._frameContent.off('mousedown.shapeCreator'); - this._frameContent.off('mousemove.shapeCreator'); }); // Also we need callback on drawdone event for get points this._drawInstance.on('drawdone', function(e) { From 63c3af132e91a631ef184cfbff11c7d4387a45ad Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Mon, 23 Dec 2019 17:18:24 +0300 Subject: [PATCH 7/8] Added UI elements --- .../engine/static/engine/js/borderSticker.js | 17 ++++++++- .../static/engine/js/polyshapeEditor.js | 36 +++++++++++++------ .../engine/static/engine/js/shapeCreator.js | 28 +++++++++++---- .../engine/templates/engine/annotation.html | 4 +++ 4 files changed, 66 insertions(+), 19 deletions(-) diff --git a/cvat/apps/engine/static/engine/js/borderSticker.js b/cvat/apps/engine/static/engine/js/borderSticker.js index f6881ca5ad2..dd88e5c7cea 100644 --- a/cvat/apps/engine/static/engine/js/borderSticker.js +++ b/cvat/apps/engine/static/engine/js/borderSticker.js @@ -175,7 +175,7 @@ class BorderSticker { // remove the latest cursor position from drawing array this._drawInstance.array().valueOf().pop(); - for (const borderPoint of border) { + for (const borderPoint of border.slice(1)) { const [_x, _y] = borderPoint.split(','); this._drawInstance.array().valueOf().push([+_x, +_y]); } @@ -207,6 +207,21 @@ class BorderSticker { .forEach((group) => this._frameContent.node.appendChild(group)); } + reset() { + if (this._accounter.shapeID !== null) { + while (this._accounter.clicks.length > 0) { + const resetID = this._accounter.clicks.pop(); + this._groups[this._accounter.shapeID] + .children[resetID].classList.remove('shape-creator-border-point-direction'); + } + } + + this._accounter = { + clicks: [], + shapeID: null, + }; + } + disable() { this._removeBorderMarkers(); } diff --git a/cvat/apps/engine/static/engine/js/polyshapeEditor.js b/cvat/apps/engine/static/engine/js/polyshapeEditor.js index cabe72a72b9..d878ba5d8da 100644 --- a/cvat/apps/engine/static/engine/js/polyshapeEditor.js +++ b/cvat/apps/engine/static/engine/js/polyshapeEditor.js @@ -124,14 +124,30 @@ class PolyshapeEditorView { this._data = null; this._frameContent = SVG.adopt($('#frameContent')[0]); + this._autoBorderingCheckbox = $('#autoBorderingCheckbox'); this._originalShapePointsGroup = null; this._originalShapePoints = []; this._originalShape = null; this._correctLine = null; + this._borderSticker = null; this._scale = window.cvat.player.geometry.scale; this._frame = window.cvat.player.frames.current; + this._autoBorderingCheckbox.on('change.shapeEditor', () => { + if (this._correctLine) { + if (this._borderSticker) { + this._borderSticker.disable(); + this._borderSticker = null; + } else { + this._borderSticker = new BorderSticker(this._correctLine, this._frameContent, + this._controller.currentShapes + .filter((shape) => shape.model.id !== this._data.id), + this._scale); + } + } + }); + model.subscribe(this); } @@ -278,8 +294,10 @@ class PolyshapeEditorView { this._frameContent.on('contextmenu.polyshapeEditor', (e) => { if (PolyShapeModel.convertStringToNumberArray(this._correctLine.attr('points')).length > 2) { this._correctLine.draw('undo'); - } - else { + if (this._borderSticker) { + this._borderSticker.reset(); + } + } else { // Finish without points argument is just cancel this._controller.finish(); } @@ -379,17 +397,11 @@ class PolyshapeEditorView { }); } + this._autoBorderingCheckbox[0].disabled = false; $('body').on('keydown.shapeEditor', (e) => { if (e.ctrlKey && e.keyCode === 17) { - if (this._borderSticker) { - this._borderSticker.disable(); - this._borderSticker = null; - } else { - this._borderSticker = new BorderSticker(this._correctLine, this._frameContent, - this._controller.currentShapes - .filter((shape) => shape.model.id !== this._data.id), - this._scale); - } + this._autoBorderingCheckbox[0].checked = !this._autoBorderingCheckbox[0].checked; + this._autoBorderingCheckbox.trigger('change.shapeEditor'); } }); } @@ -417,6 +429,8 @@ class PolyshapeEditorView { this._frameContent.off('contextmenu.polyshapeEditor'); $('body').off('keydown.shapeEditor'); + this._autoBorderingCheckbox[0].checked = false; + this._autoBorderingCheckbox[0].disabled = true; if (this._borderSticker) { this._borderSticker.disable(); this._borderSticker = null; diff --git a/cvat/apps/engine/static/engine/js/shapeCreator.js b/cvat/apps/engine/static/engine/js/shapeCreator.js index 59036fddcfd..f20c028ef81 100644 --- a/cvat/apps/engine/static/engine/js/shapeCreator.js +++ b/cvat/apps/engine/static/engine/js/shapeCreator.js @@ -201,6 +201,7 @@ class ShapeCreatorView { this._modeSelector = $('#shapeModeSelector'); this._typeSelector = $('#shapeTypeSelector'); this._polyShapeSizeInput = $('#polyShapeSize'); + this._autoBorderingCheckbox = $('#autoBorderingCheckbox'); this._frameContent = SVG.adopt($('#frameContent')[0]); this._frameText = SVG.adopt($("#frameText")[0]); this._playerFrame = $('#playerFrame'); @@ -297,6 +298,18 @@ class ShapeCreatorView { } } }); + + this._autoBorderingCheckbox.on('change.shapeCreator', () => { + if (this._drawInstance) { + if (this._borderSticker) { + this._borderSticker.disable(); + this._borderSticker = null; + } else { + this._borderSticker = new BorderSticker(this._drawInstance, this._frameContent, + this._controller.currentShapes, this._scale); + } + } + }); } _createPolyEvents() { @@ -348,15 +361,11 @@ class ShapeCreatorView { numberOfPoints ++; }); + this._autoBorderingCheckbox[0].disabled = false; $('body').on('keydown.shapeCreator', (e) => { if (e.ctrlKey && e.keyCode === 17) { - if (this._borderSticker) { - this._borderSticker.disable(); - this._borderSticker = null; - } else { - this._borderSticker = new BorderSticker(this._drawInstance, this._frameContent, - this._controller.currentShapes, this._scale); - } + this._autoBorderingCheckbox[0].checked = !this._autoBorderingCheckbox[0].checked; + this._autoBorderingCheckbox.trigger('change.shapeCreator'); } }); @@ -364,6 +373,9 @@ class ShapeCreatorView { if (e.which === 3) { let lenBefore = this._drawInstance.array().value.length; this._drawInstance.draw('undo'); + if (this._borderSticker) { + this._borderSticker.reset(); + } let lenAfter = this._drawInstance.array().value.length; if (lenBefore != lenAfter) { numberOfPoints --; @@ -393,6 +405,8 @@ class ShapeCreatorView { this._drawInstance.on('drawstop', () => { this._frameContent.off('mousedown.shapeCreator'); this._frameContent.off('mousemove.shapeCreator'); + this._autoBorderingCheckbox[0].disabled = true; + this._autoBorderingCheckbox[0].checked = false; $('body').off('keydown.shapeCreator'); if (this._borderSticker) { this._borderSticker.disable(); diff --git a/cvat/apps/engine/templates/engine/annotation.html b/cvat/apps/engine/templates/engine/annotation.html index 86ffc8c051c..cfef81d83e1 100644 --- a/cvat/apps/engine/templates/engine/annotation.html +++ b/cvat/apps/engine/templates/engine/annotation.html @@ -192,6 +192,10 @@ +
+ + +
From a8b768dffaa5fb2b7b9f4dfc5610423898fcac45 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Mon, 23 Dec 2019 18:39:30 +0300 Subject: [PATCH 8/8] Refactoring --- .../engine/static/engine/js/borderSticker.js | 275 ++++++++---------- .../static/engine/js/polyshapeEditor.js | 29 +- .../engine/static/engine/js/shapeCreator.js | 25 +- 3 files changed, 149 insertions(+), 180 deletions(-) diff --git a/cvat/apps/engine/static/engine/js/borderSticker.js b/cvat/apps/engine/static/engine/js/borderSticker.js index dd88e5c7cea..c3ab90fedb9 100644 --- a/cvat/apps/engine/static/engine/js/borderSticker.js +++ b/cvat/apps/engine/static/engine/js/borderSticker.js @@ -7,22 +7,22 @@ /* exported BorderSticker */ class BorderSticker { - constructor(drawInstance, frameContent, shapes, scale) { - this._drawInstance = drawInstance; + constructor(currentShape, frameContent, shapes, scale) { + this._currentShape = currentShape; this._frameContent = frameContent; - this._shapes = this._formatShapes(shapes); this._enabled = false; this._groups = null; this._scale = scale; + this._accounter = { + clicks: [], + shapeID: null, + }; - this._drawBorderMarkers(); - } - - _formatShapes(shapes) { - return shapes + const transformedShapes = shapes .filter((shape) => !shape.model.removed) .map((shape) => { const pos = shape.interpolation.position; + // convert boxes to point sets if (!('points' in pos)) { return { points: window.cvat.translate.points @@ -38,171 +38,133 @@ class BorderSticker { color: shape.model.color.shape, }; }); - } - _rescaleDrawPoints() { - if (this._drawInstance) { - this._drawInstance.remember('_paintHandler').set.members.forEach((el) => { - el.attr('stroke-width', 1 / this._scale).attr('r', 2.5 / this._scale); - }); - } + this._drawBorderMarkers(transformedShapes); } - _removeBorderMarkers() { - if (this._groups) { - this._groups.forEach((group) => { - Array.from(group.children).forEach((circle) => { - circle.removeEventListener('click', circle.clickListener); - circle.removeEventListener('dblclick', circle.doubleClickListener); - }); - - group.remove(); - }); - - this._groups = null; - } + _addRawPoint(x, y) { + this._currentShape.array().valueOf().pop(); + this._currentShape.array().valueOf().push([x, y]); + // not error, specific of the library + this._currentShape.array().valueOf().push([x, y]); + const paintHandler = this._currentShape.remember('_paintHandler'); + paintHandler.drawCircles(); + paintHandler.set.members.forEach((el) => { + el.attr('stroke-width', 1 / this._scale).attr('r', 2.5 / this._scale); + }); + this._currentShape.plot(this._currentShape.array().valueOf()); } - _drawBorderMarkers() { - this._accounter = { - clicks: [], - shapeID: null, - }; - + _drawBorderMarkers(shapes) { const namespace = 'http://www.w3.org/2000/svg'; - const groups = this._shapes.reduce((acc, shape, shapeID, shapes) => { - const group = window.document.createElementNS(namespace, 'g'); - shape.points.split(/\s/) - .map((point, pointID, points) => { - const [x, y] = point.split(','); - const circle = window.document.createElementNS(namespace, 'circle'); - circle.classList.add('shape-creator-border-point'); - circle.setAttribute('fill', shape.color); - circle.setAttribute('stroke', 'black'); - circle.setAttribute('stroke-width', 1 / this._scale); - circle.setAttribute('cx', +x); - circle.setAttribute('cy', +y); - circle.setAttribute('r', 5 / this._scale); - circle.doubleClickListener = (e) => { - e.stopPropagation(); - }; - circle.clickListener = (e) => { - e.stopPropagation(); - if (this._accounter.shapeID !== null && this._accounter.shapeID !== shapeID) { - if (this._accounter.clicks.length === 2) { - const pointToAdd = shapes[this._accounter.shapeID].points.split(/\s/)[this._accounter.clicks[1]]; - const [_x, _y] = pointToAdd.split(','); - this._drawInstance.array().valueOf().pop(); - this._drawInstance.array().valueOf().push([+_x, +_y]); - this._drawInstance.array().valueOf().push([+_x, +_y]); - this._drawInstance.remember('_paintHandler').drawCircles(); - this._drawInstance.plot(this._drawInstance.array().valueOf()); - this._rescaleDrawPoints(); - } - - while (this._accounter.clicks.length > 0) { - const resetID = this._accounter.clicks.pop(); - groups[this._accounter.shapeID] - .children[resetID].classList.remove('shape-creator-border-point-direction'); - } - } - this._accounter.shapeID = shapeID; + this._groups = shapes.reduce((acc, shape, shapeID) => { + // Group all points by inside svg groups + const group = window.document.createElementNS(namespace, 'g'); + shape.points.split(/\s/).map((point, pointID, points) => { + const [x, y] = point.split(',').map((coordinate) => +coordinate); + const circle = window.document.createElementNS(namespace, 'circle'); + circle.classList.add('shape-creator-border-point'); + circle.setAttribute('fill', shape.color); + circle.setAttribute('stroke', 'black'); + circle.setAttribute('stroke-width', 1 / this._scale); + circle.setAttribute('cx', +x); + circle.setAttribute('cy', +y); + circle.setAttribute('r', 5 / this._scale); + + circle.doubleClickListener = (e) => { + // Just for convenience (prevent screen fit feature) + e.stopPropagation(); + }; + circle.clickListener = (e) => { + e.stopPropagation(); + // another shape was clicked + if (this._accounter.shapeID !== null && this._accounter.shapeID !== shapeID) { + this.reset(); + } + + this._accounter.shapeID = shapeID; + + if (this._accounter.clicks[1] === pointID) { + // the same point repeated two times + const [_x, _y] = point.split(',').map((coordinate) => +coordinate); + this._addRawPoint(_x, _y); + this.reset(); + return; + } + + // the first point can not be clicked twice + if (this._accounter.clicks[0] !== pointID) { this._accounter.clicks.push(pointID); + } else { + return; + } + + // up clicked group for convenience + this._frameContent.node.appendChild(group); + + // the first click + if (this._accounter.clicks.length === 1) { + // draw and remove initial point just to initialize data structures + if (!this._currentShape.remember('_paintHandler').startPoint) { + this._currentShape.draw('point', e); + this._currentShape.draw('undo'); + } - // up current group to work with its points easy - this._frameContent.node.appendChild(group); - if (this._accounter.clicks.length === 1) { - if (!this._drawInstance.remember('_paintHandler').startPoint) { - this._drawInstance.draw('point', e); - this._drawInstance.array().valueOf().pop(); - } - - this._drawInstance.array().valueOf().pop(); - const borderPoint = points[pointID]; - const [_x, _y] = borderPoint.split(','); - this._drawInstance.array().valueOf().push([+_x, +_y]); - this._drawInstance.array().valueOf().push([+_x, +_y]); - - this._drawInstance.remember('_paintHandler').drawCircles(); - this._drawInstance.plot(this._drawInstance.array().valueOf()); - this._rescaleDrawPoints(); - } else if (this._accounter.clicks.length === 2) { - circle.classList.add('shape-creator-border-point-direction'); - } else { - if (this._accounter.clicks[1] === this._accounter.clicks[2]) { - this._drawInstance.array().valueOf().pop(); - const borderPoint = points[this._accounter.clicks[2]]; - const [_x, _y] = borderPoint.split(','); - this._drawInstance.array().valueOf().push([+_x, +_y]); - this._drawInstance.array().valueOf().push([+_x, +_y]); - this._drawInstance.remember('_paintHandler').drawCircles(); - this._drawInstance.plot(this._drawInstance.array().valueOf()); - this._rescaleDrawPoints(); - - while (this._accounter.clicks.length > 0) { - const resetID = this._accounter.clicks.pop(); - group.children[resetID].classList.remove('shape-creator-border-point-direction'); - } - - this._accounter.shapeID = null; - - return; + const [_x, _y] = point.split(',').map((coordinate) => +coordinate); + this._addRawPoint(_x, _y); + // the second click + } else if (this._accounter.clicks.length === 2) { + circle.classList.add('shape-creator-border-point-direction'); + // the third click + } else { + // sign defines bypass direction + const landmarks = this._accounter.clicks; + const sign = Math.sign(landmarks[2] - landmarks[0]) + * Math.sign(landmarks[1] - landmarks[0]) + * Math.sign(landmarks[2] - landmarks[1]); + + // go via a polygon and get vertexes + // the first vertex has been already drawn + const way = []; + for (let i = landmarks[0] + sign; ; i += sign) { + if (i < 0) { + i = points.length - 1; + } else if (i === points.length) { + i = 0; } - const s = Math.sign(this._accounter.clicks[2] - this._accounter.clicks[0]) - * Math.sign(this._accounter.clicks[1] - this._accounter.clicks[0]) - * Math.sign(this._accounter.clicks[2] - this._accounter.clicks[1]); - - const border = []; - for (let i = this._accounter.clicks[0]; ; i += s) { - if (i < 0) { - i = points.length - 1; - } else if (i === points.length) { - i = 0; - } - - border.push(points[i]); - - if (i === this._accounter.clicks[this._accounter.clicks.length - 1]) { - // put the last element twice - // specific of svg.draw.js - border.push(points[i]); - break; - } - } + way.push(points[i]); - // remove the latest cursor position from drawing array - this._drawInstance.array().valueOf().pop(); - for (const borderPoint of border.slice(1)) { - const [_x, _y] = borderPoint.split(','); - this._drawInstance.array().valueOf().push([+_x, +_y]); + if (i === this._accounter.clicks[this._accounter.clicks.length - 1]) { + // put the last element twice + // specific of svg.draw.js + // way.push(points[i]); + break; } + } - this._drawInstance.remember('_paintHandler').drawCircles(); - this._drawInstance.plot(this._drawInstance.array().valueOf()); - this._rescaleDrawPoints(); + // remove the latest cursor position from drawing array + for (const wayPoint of way) { + const [_x, _y] = wayPoint.split(',').map((coordinate) => +coordinate); + this._addRawPoint(_x, _y); + } - while (this._accounter.clicks.length > 0) { - const resetID = this._accounter.clicks.pop(); - group.children[resetID].classList.remove('shape-creator-border-point-direction'); - } + this.reset(); + } + }; - this._accounter.shapeID = null; - } - }; + circle.addEventListener('click', circle.clickListener); + circle.addEventListener('dblclick', circle.doubleClickListener); - circle.addEventListener('click', circle.clickListener); - circle.addEventListener('dblclick', circle.doubleClickListener); + return circle; + }).forEach((circle) => group.appendChild(circle)); - return circle; - }).forEach((circle) => group.appendChild(circle)); acc.push(group); return acc; }, []); - this._groups = groups; this._groups .forEach((group) => this._frameContent.node.appendChild(group)); } @@ -223,7 +185,18 @@ class BorderSticker { } disable() { - this._removeBorderMarkers(); + if (this._groups) { + this._groups.forEach((group) => { + Array.from(group.children).forEach((circle) => { + circle.removeEventListener('click', circle.clickListener); + circle.removeEventListener('dblclick', circle.doubleClickListener); + }); + + group.remove(); + }); + + this._groups = null; + } } scale(scale) { diff --git a/cvat/apps/engine/static/engine/js/polyshapeEditor.js b/cvat/apps/engine/static/engine/js/polyshapeEditor.js index d878ba5d8da..7a30a7c1988 100644 --- a/cvat/apps/engine/static/engine/js/polyshapeEditor.js +++ b/cvat/apps/engine/static/engine/js/polyshapeEditor.js @@ -134,9 +134,9 @@ class PolyshapeEditorView { this._scale = window.cvat.player.geometry.scale; this._frame = window.cvat.player.frames.current; - this._autoBorderingCheckbox.on('change.shapeEditor', () => { + this._autoBorderingCheckbox.on('change.shapeEditor', (e) => { if (this._correctLine) { - if (this._borderSticker) { + if (!e.target.checked) { this._borderSticker.disable(); this._borderSticker = null; } else { @@ -264,23 +264,24 @@ class PolyshapeEditorView { } - const [prevPointX, prevPointY] = this._data.startPoint + const [x, y] = this._data.startPoint .split(',').map((el) => +el); let prevPoint = { - x: prevPointX, - y: prevPointY, + x, + y, }; - // draw/remove initial point just to initialize data structures + // draw and remove initial point just to initialize data structures this._correctLine.draw('point', this._data.event); - this._correctLine.array().valueOf().pop(); + this._correctLine.draw('undo'); - this._addRawPoint(prevPointX, prevPointY); + this._addRawPoint(x, y); this._frameContent.on('mousemove.polyshapeEditor', (e) => { - if (e.shiftKey && this._data.type != 'points') { - let delta = Math.sqrt(Math.pow(e.clientX - prevPoint.x, 2) + Math.pow(e.clientY - prevPoint.y, 2)); - let deltaTreshold = 15; + if (e.shiftKey && this._data.type !== 'points') { + const delta = Math.sqrt(Math.pow(e.clientX - prevPoint.x, 2) + + Math.pow(e.clientY - prevPoint.y, 2)); + const deltaTreshold = 15; if (delta > deltaTreshold) { this._correctLine.draw('point', e); prevPoint = { @@ -322,7 +323,9 @@ class PolyshapeEditorView { }).on('mouseout', () => { instance.attr('stroke-width', STROKE_WIDTH / this._scale); }).on('mousedown', (e) => { - if (e.which != 1) return; + if (e.which !== 1) { + return; + } let currentPoints = PolyShapeModel.convertStringToNumberArray(this._data.points); // replace the latest point from the event // (which has not precise coordinates, to precise coordinates) @@ -400,7 +403,7 @@ class PolyshapeEditorView { this._autoBorderingCheckbox[0].disabled = false; $('body').on('keydown.shapeEditor', (e) => { if (e.ctrlKey && e.keyCode === 17) { - this._autoBorderingCheckbox[0].checked = !this._autoBorderingCheckbox[0].checked; + this._autoBorderingCheckbox[0].checked = !this._borderSticker; this._autoBorderingCheckbox.trigger('change.shapeEditor'); } }); diff --git a/cvat/apps/engine/static/engine/js/shapeCreator.js b/cvat/apps/engine/static/engine/js/shapeCreator.js index f20c028ef81..8325be8d7aa 100644 --- a/cvat/apps/engine/static/engine/js/shapeCreator.js +++ b/cvat/apps/engine/static/engine/js/shapeCreator.js @@ -19,8 +19,6 @@ BorderSticker: false */ -"use strict"; - class ShapeCreatorModel extends Listener { constructor(shapeCollection) { super('onShapeCreatorUpdate', () => this); @@ -31,13 +29,12 @@ class ShapeCreatorModel extends Listener { this._defaultLabel = null; this._currentFrame = null; this._createEvent = null; - this._positionsCache = null; this._shapeCollection = shapeCollection; } finish(result) { - let data = {}; - let frame = window.cvat.player.frames.current; + const data = {}; + const frame = window.cvat.player.frames.current; data.label_id = this._defaultLabel; data.group = 0; @@ -52,7 +49,7 @@ class ShapeCreatorModel extends Listener { mode: this._defaultMode, type: this._defaultType, label: this._defaultLabel, - frame: frame, + frame, }); } @@ -66,7 +63,7 @@ class ShapeCreatorModel extends Listener { this._shapeCollection.add(data, `annotation_${this._defaultType}`); } - let model = this._shapeCollection.shapes.slice(-1)[0]; + const model = this._shapeCollection.shapes.slice(-1)[0]; // Undo/redo code window.cvat.addAction('Draw Object', () => { @@ -89,17 +86,13 @@ class ShapeCreatorModel extends Listener { if (this._createMode) { this._createEvent = Logger.addContinuedEvent(Logger.EventType.drawObject); window.cvat.mode = 'creation'; - } - else if (window.cvat.mode === 'creation') { + } else if (window.cvat.mode === 'creation') { window.cvat.mode = null; - this._positionsCache = null; } - } - else { + } else { this._createMode = false; if (window.cvat.mode === 'creation') { window.cvat.mode = null; - this._positionsCache = null; if (this._createEvent) { this._createEvent.close(); this._createEvent = null; @@ -299,9 +292,9 @@ class ShapeCreatorView { } }); - this._autoBorderingCheckbox.on('change.shapeCreator', () => { + this._autoBorderingCheckbox.on('change.shapeCreator', (e) => { if (this._drawInstance) { - if (this._borderSticker) { + if (!e.target.checked) { this._borderSticker.disable(); this._borderSticker = null; } else { @@ -364,7 +357,7 @@ class ShapeCreatorView { this._autoBorderingCheckbox[0].disabled = false; $('body').on('keydown.shapeCreator', (e) => { if (e.ctrlKey && e.keyCode === 17) { - this._autoBorderingCheckbox[0].checked = !this._autoBorderingCheckbox[0].checked; + this._autoBorderingCheckbox[0].checked = !this._borderSticker; this._autoBorderingCheckbox.trigger('change.shapeCreator'); } });