diff --git a/cvat/apps/engine/annotation.py b/cvat/apps/engine/annotation.py
index 12540742..e62493b8 100644
--- a/cvat/apps/engine/annotation.py
+++ b/cvat/apps/engine/annotation.py
@@ -822,7 +822,7 @@ class _AnnotationForJob(_Annotation):
start_frame=db_path.frame,
stop_frame= self.stop_frame,
group_id=db_path.group_id,
- client_id=db_path.id,
+ client_id=db_path.client_id,
)
for db_attr in db_path.attributes:
spec = self.db_attributes[db_attr.spec_id]
diff --git a/cvat/apps/engine/static/engine/js/annotationUI.js b/cvat/apps/engine/static/engine/js/annotationUI.js
index 1cbd9079..041bbd00 100644
--- a/cvat/apps/engine/static/engine/js/annotationUI.js
+++ b/cvat/apps/engine/static/engine/js/annotationUI.js
@@ -4,7 +4,7 @@
* SPDX-License-Identifier: MIT
*/
-/* exported callAnnotationUI translateSVGPos blurAllElements drawBoxSize copyToClipboard */
+/* exported callAnnotationUI blurAllElements drawBoxSize copyToClipboard */
"use strict";
function callAnnotationUI(jid) {
@@ -40,6 +40,7 @@ function buildAnnotationUI(job, shapeData, loadJobEvent) {
// Setup some API
window.cvat = {
labelsInfo: new LabelsInfo(job),
+ translate: new CoordinateTranslator(),
player: {
geometry: {
scale: 1,
@@ -53,7 +54,8 @@ function buildAnnotationUI(job, shapeData, loadJobEvent) {
mode: null,
job: {
z_order: job.z_order,
- id: job.jobid
+ id: job.jobid,
+ images: job.image_meta_data,
},
search: {
value: window.location.search,
@@ -140,6 +142,8 @@ function buildAnnotationUI(job, shapeData, loadJobEvent) {
let aamModel = new AAMModel(shapeCollectionModel, (xtl, xbr, ytl, ybr) => {
playerModel.focus(xtl, xbr, ytl, ybr);
+ }, () => {
+ playerModel.fit();
});
let aamController = new AAMController(aamModel);
new AAMView(aamModel, aamController);
@@ -730,25 +734,6 @@ function saveAnnotation(shapeCollectionModel, job) {
});
}
-function translateSVGPos(svgCanvas, clientX, clientY) {
- let pt = svgCanvas.createSVGPoint();
- pt.x = clientX;
- pt.y = clientY;
- pt = pt.matrixTransform(svgCanvas.getScreenCTM().inverse());
-
- let pos = {
- x: pt.x,
- y: pt.y
- };
-
- if (platform.name.toLowerCase() == 'firefox') {
- pos.x /= window.cvat.player.geometry.scale;
- pos.y /= window.cvat.player.geometry.scale;
- }
-
- return pos;
-}
-
function blurAllElements() {
document.activeElement.blur();
diff --git a/cvat/apps/engine/static/engine/js/attributeAnnotationMode.js b/cvat/apps/engine/static/engine/js/attributeAnnotationMode.js
index e0e8d6cc..f0ae3cdb 100644
--- a/cvat/apps/engine/static/engine/js/attributeAnnotationMode.js
+++ b/cvat/apps/engine/static/engine/js/attributeAnnotationMode.js
@@ -10,10 +10,11 @@
const AAMUndefinedKeyword = '__undefined__';
class AAMModel extends Listener {
- constructor(shapeCollection, focus) {
+ constructor(shapeCollection, focus, fit) {
super('onAAMUpdate', () => this);
this._shapeCollection = shapeCollection;
this._focus = focus;
+ this._fit = fit;
this._activeAAM = false;
this._activeIdx = null;
this._active = null;
@@ -167,6 +168,7 @@ class AAMModel extends Listener {
// Notify for remove aam UI
this.notify();
+ this._fit();
}
}
diff --git a/cvat/apps/engine/static/engine/js/coordinateTranslator.js b/cvat/apps/engine/static/engine/js/coordinateTranslator.js
new file mode 100644
index 00000000..08797401
--- /dev/null
+++ b/cvat/apps/engine/static/engine/js/coordinateTranslator.js
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2018 Intel Corporation
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+/* exported CoordinateTranslator */
+"use strict";
+
+class CoordinateTranslator {
+ constructor() {
+ this._boxTranslator = {
+ _playerOffset: 0,
+ _convert: function(box, sign) {
+ for (let prop of ["xtl", "ytl", "xbr", "ybr", "x", "y"]) {
+ if (prop in box) {
+ box[prop] += this._playerOffset * sign;
+ }
+ }
+
+ return box;
+ },
+ actualToCanvas: function(actualBox) {
+ let canvasBox = {};
+ for (let key in actualBox) {
+ canvasBox[key] = actualBox[key];
+ }
+
+ return this._convert(canvasBox, 1);
+ },
+
+ canvasToActual: function(canvasBox) {
+ let actualBox = {};
+ for (let key in canvasBox) {
+ actualBox[key] = canvasBox[key];
+ }
+
+ return this._convert(actualBox, -1);
+ },
+ };
+
+ this._pointsTranslator = {
+ _playerOffset: 0,
+ _convert: function(points, sign) {
+ if (typeof(points) === 'string') {
+ return points.split(' ').map((coord) => coord.split(',')
+ .map((x) => +x + this._playerOffset * sign).join(',')).join(' ');
+ }
+ else if (typeof(points) === 'object') {
+ let result = [];
+ for (let point of points) {
+ result.push({
+ x: point.x + this._playerOffset * sign,
+ y: point.y + this._playerOffset * sign,
+ });
+ }
+ return result;
+ }
+ else {
+ throw Error('Unknown points type was found');
+ }
+ },
+ actualToCanvas: function(actualPoints) {
+ return this._convert(actualPoints, 1);
+ },
+
+ canvasToActual: function(canvasPoints) {
+ return this._convert(canvasPoints, -1);
+ }
+ },
+
+ this._pointTranslator = {
+ clientToCanvas: function(targetCanvas, clientX, clientY) {
+ let pt = targetCanvas.createSVGPoint();
+ pt.x = clientX;
+ pt.y = clientY;
+ pt = pt.matrixTransform(targetCanvas.getScreenCTM().inverse());
+ return pt;
+ },
+ canvasToClient: function(sourceCanvas, canvasX, canvasY) {
+ let pt = sourceCanvas.createSVGPoint();
+ pt.x = canvasX;
+ pt.y = canvasY;
+ pt = pt.matrixTransform(sourceCanvas.getScreenCTM());
+ return pt;
+ }
+ };
+ }
+
+ get box() {
+ return this._boxTranslator;
+ }
+
+ get points() {
+ return this._pointsTranslator;
+ }
+
+ get point() {
+ return this._pointTranslator;
+ }
+
+ set playerOffset(value) {
+ this._boxTranslator._playerOffset = value;
+ this._pointsTranslator._playerOffset = value;
+ }
+}
diff --git a/cvat/apps/engine/static/engine/js/player.js b/cvat/apps/engine/static/engine/js/player.js
index bc298d51..a3b57c3e 100644
--- a/cvat/apps/engine/static/engine/js/player.js
+++ b/cvat/apps/engine/static/engine/js/player.js
@@ -152,8 +152,15 @@ class PlayerModel extends Listener {
top: 0,
width: playerSize.width,
height: playerSize.height,
+ frameOffset: 0,
};
+ this._geometry.frameOffset = Math.floor(Math.max(
+ (playerSize.height - MIN_PLAYER_SCALE) / MIN_PLAYER_SCALE,
+ (playerSize.width - MIN_PLAYER_SCALE) / MIN_PLAYER_SCALE
+ ));
+ window.cvat.translate.playerOffset = this._geometry.frameOffset;
+
this._frameProvider.subscribe(this);
}
@@ -167,11 +174,7 @@ class PlayerModel extends Listener {
}
get geometry() {
- return {
- scale: this._geometry.scale,
- top: this._geometry.top,
- left: this._geometry.left
- };
+ return Object.assign({}, this._geometry);
}
get playing() {
@@ -498,7 +501,6 @@ class PlayerController {
this._moving = true;
this._lastClickX = e.clientX;
this._lastClickY = e.clientY;
- e.preventDefault();
}
}
@@ -520,6 +522,7 @@ class PlayerController {
let leftOffset = e.clientX - this._lastClickX;
this._lastClickX = e.clientX;
this._lastClickY = e.clientY;
+
this._model.move(topOffset, leftOffset);
}
}
@@ -649,10 +652,19 @@ class PlayerView {
this._playerGridPath = $('#playerGridPath');
this._contextMenuUI = $('#playerContextMenu');
- $('*').on('mouseup', () => this._controller.frameMouseUp());
+ $('*').on('mouseup.player', () => this._controller.frameMouseUp());
+ this._playerContentUI.on('mousedown', (e) => {
+ let pos = window.cvat.translate.point.clientToCanvas(this._playerBackgroundUI[0], e.clientX, e.clientY);
+ let frameWidth = window.cvat.player.geometry.frameWidth;
+ let frameHeight = window.cvat.player.geometry.frameHeight;
+ if (pos.x >= 0 && pos.y >= 0 && pos.x <= frameWidth && pos.y <= frameHeight) {
+ this._controller.frameMouseDown(e);
+ }
+ e.preventDefault();
+ });
+
this._playerUI.on('wheel', (e) => this._controller.zoom(e));
this._playerUI.on('dblclick', () => this._controller.fit());
- this._playerContentUI.on('mousedown', (e) => this._controller.frameMouseDown(e));
this._playerUI.on('mousemove', (e) => this._controller.frameMouseMove(e));
this._progressUI.on('mousedown', (e) => this._controller.progressMouseDown(e));
this._progressUI.on('mouseup', () => this._controller.progressMouseUp());
@@ -852,7 +864,7 @@ class PlayerView {
this._progressUI['0'].value = frames.current - frames.start;
- for (let obj of [this._playerBackgroundUI, this._playerContentUI, this._playerGridUI]) {
+ for (let obj of [this._playerBackgroundUI, this._playerGridUI]) {
obj.css('width', image.width);
obj.css('height', image.height);
obj.css('top', geometry.top);
@@ -860,6 +872,12 @@ class PlayerView {
obj.css('transform', 'scale(' + geometry.scale + ')');
}
+ this._playerContentUI.css('width', image.width + geometry.frameOffset * 2);
+ this._playerContentUI.css('height', image.height + geometry.frameOffset * 2);
+ this._playerContentUI.css('top', geometry.top - geometry.frameOffset * geometry.scale);
+ this._playerContentUI.css('left', geometry.left - geometry.frameOffset * geometry.scale);
+ this._playerContentUI.css('transform', 'scale(' + geometry.scale + ')');
+
this._playerGridPath.attr('stroke-width', 2 / geometry.scale);
this._frameNumber.prop('value', frames.current);
}
diff --git a/cvat/apps/engine/static/engine/js/shapeBuffer.js b/cvat/apps/engine/static/engine/js/shapeBuffer.js
index 42ef6bf5..449456ea 100644
--- a/cvat/apps/engine/static/engine/js/shapeBuffer.js
+++ b/cvat/apps/engine/static/engine/js/shapeBuffer.js
@@ -50,7 +50,7 @@ class ShapeBufferModel extends Listener {
}
}
- _makeObject(bbRect, polyPoints, trackedObj) {
+ _makeObject(box, points, trackedObj) {
if (!this._shape.type) {
return null;
}
@@ -71,17 +71,10 @@ class ShapeBufferModel extends Listener {
object.attributes = attributes;
if (this._shape.type === 'box') {
- let box = {};
-
- box.xtl = Math.max(bbRect.x, 0);
- box.ytl = Math.max(bbRect.y, 0);
- box.xbr = Math.min(bbRect.x + bbRect.width, window.cvat.player.geometry.frameWidth);
- box.ybr = Math.min(bbRect.y + bbRect.height, window.cvat.player.geometry.frameHeight);
box.occluded = this._shape.position.occluded;
box.frame = window.cvat.player.frames.current;
box.z_order = this._collection.zOrder(box.frame).max;
-
if (trackedObj) {
object.shapes = [];
object.shapes.push(Object.assign(box, {
@@ -95,8 +88,7 @@ class ShapeBufferModel extends Listener {
}
else {
let position = {};
-
- position.points = polyPoints;
+ position.points = points;
position.occluded = this._shape.position.occluded;
position.frame = window.cvat.player.frames.current;
position.z_order = this._collection.zOrder(position.frame).max;
@@ -135,78 +127,91 @@ class ShapeBufferModel extends Listener {
return false;
}
- pasteToFrame(bbRect, polyPoints) {
- if (!this._shape.type) {
- return;
- }
+ pasteToFrame(box, polyPoints) {
+ let object = this._makeObject(box, polyPoints, this._shape.mode === 'interpolation');
- Logger.addEvent(Logger.EventType.pasteObject);
- let object = this._makeObject(bbRect, polyPoints, this._shape.mode === 'interpolation');
- if (this._shape.type === 'box') {
- this._collection.add(object, `${this._shape.mode}_${this._shape.type}`);
- }
- else {
- this._collection.add(object, `annotation_${this._shape.type}`);
- }
+ if (object) {
+ Logger.addEvent(Logger.EventType.pasteObject);
+ if (this._shape.type === 'box') {
+ this._collection.add(object, `${this._shape.mode}_${this._shape.type}`);
+ }
+ else {
+ this._collection.add(object, `annotation_${this._shape.type}`);
+ }
- // Undo/redo code
- let model = this._collection.shapes.slice(-1)[0];
- window.cvat.addAction('Paste Object', () => {
- model.removed = true;
- model.unsubscribe(this._collection);
- }, () => {
- model.subscribe(this._collection);
- model.removed = false;
- }, window.cvat.player.frames.current);
- // End of undo/redo code
-
- this._collection.update();
+ // Undo/redo code
+ let model = this._collection.shapes.slice(-1)[0];
+ window.cvat.addAction('Paste Object', () => {
+ model.removed = true;
+ model.unsubscribe(this._collection);
+ }, () => {
+ model.subscribe(this._collection);
+ model.removed = false;
+ }, window.cvat.player.frames.current);
+ // End of undo/redo code
+
+ this._collection.update();
+ }
}
propagateToFrames() {
let numOfFrames = this._propagateFrames;
if (this._shape.type && Number.isInteger(numOfFrames)) {
- let bbRect = null;
- let polyPoints = null;
+ let object = null;
if (this._shape.type === 'box') {
- bbRect = {
- x: this._shape.position.xtl,
- y: this._shape.position.ytl,
- height: this._shape.position.ybr - this._shape.position.ytl,
- width: this._shape.position.xbr - this._shape.position.xtl,
+ let box = {
+ xtl: this._shape.position.xtl,
+ ytl: this._shape.position.ytl,
+ xbr: this._shape.position.xbr,
+ ybr: this._shape.position.ybr,
};
+ object = this._makeObject(box, null, false);
}
else {
- polyPoints = this._shape.position.points;
+ object = this._makeObject(null, this._shape.position.points, false);
}
- let object = this._makeObject(bbRect, polyPoints, false);
- Logger.addEvent(Logger.EventType.propagateObject, {
- count: numOfFrames,
- });
+ if (object) {
+ Logger.addEvent(Logger.EventType.propagateObject, {
+ count: numOfFrames,
+ });
- let addedObjects = [];
- while (numOfFrames > 0 && (object.frame + 1 <= window.cvat.player.frames.stop)) {
- object.frame ++;
- object.z_order = this._collection.zOrder(object.frame).max;
- this._collection.add(object, `annotation_${this._shape.type}`);
- addedObjects.push(this._collection.shapes.slice(-1)[0]);
- numOfFrames --;
- }
+ let imageSizes = window.cvat.job.images.original_size;
+ let startFrame = window.cvat.player.frames.start;
+ let originalImageSize = imageSizes[object.frame - startFrame] || imageSizes[0];
- // Undo/redo code
- window.cvat.addAction('Propagate Object', () => {
- for (let object of addedObjects) {
- object.removed = true;
- object.unsubscribe(this._collection);
+ let addedObjects = [];
+ while (numOfFrames > 0 && (object.frame + 1 <= window.cvat.player.frames.stop)) {
+ object.frame ++;
+ numOfFrames --;
+
+ // Propagate only for frames with same size
+ let imageSize = imageSizes[object.frame - startFrame] || imageSizes[0];
+ if ((imageSize.width != originalImageSize.width) || (imageSize.height != originalImageSize.height)) {
+ continue;
+ }
+
+ object.z_order = this._collection.zOrder(object.frame).max;
+ this._collection.add(object, `annotation_${this._shape.type}`);
+ addedObjects.push(this._collection.shapes.slice(-1)[0]);
}
- }, () => {
- for (let object of addedObjects) {
- object.removed = false;
- object.subscribe(this._collection);
+
+ if (addedObjects.length) {
+ // Undo/redo code
+ window.cvat.addAction('Propagate Object', () => {
+ for (let object of addedObjects) {
+ object.removed = true;
+ object.unsubscribe(this._collection);
+ }
+ }, () => {
+ for (let object of addedObjects) {
+ object.removed = false;
+ object.subscribe(this._collection);
+ }
+ }, window.cvat.player.frames.current);
+ // End of undo/redo code
}
- }, window.cvat.player.frames.current);
- // End of undo/redo code
+ }
}
}
@@ -264,7 +269,10 @@ class ShapeBufferController {
pasteToFrame(e, bbRect, polyPoints) {
if (this._model.pasteMode) {
- this._model.pasteToFrame(bbRect, polyPoints);
+ if (bbRect || polyPoints) {
+ this._model.pasteToFrame(bbRect, polyPoints);
+ }
+
if (!e.ctrlKey) {
this._model.switchPaste();
}
@@ -298,35 +306,37 @@ class ShapeBufferView {
_drawShapeView() {
let scale = window.cvat.player.geometry.scale;
+ let points = this._shape.position.points ?
+ window.cvat.translate.points.actualToCanvas(this._shape.position.points) : null;
switch (this._shape.type) {
case 'box': {
let width = this._shape.position.xbr - this._shape.position.xtl;
let height = this._shape.position.ybr - this._shape.position.ytl;
+ this._shape.position = window.cvat.translate.box.actualToCanvas(this._shape.position);
this._shapeView = this._frameContent.rect(width, height)
- .move(this._shape.position.xtl, this._shape.position.ytl)
- .addClass('shapeCreation').attr({
+ .move(this._shape.position.xtl, this._shape.position.ytl).addClass('shapeCreation').attr({
'stroke-width': STROKE_WIDTH / scale,
});
break;
}
case 'polygon':
- this._shapeView = this._frameContent.polygon(this._shape.position.points).addClass('shapeCreation').attr({
+ this._shapeView = this._frameContent.polygon(points).addClass('shapeCreation').attr({
'stroke-width': STROKE_WIDTH / scale,
});
break;
case 'polyline':
- this._shapeView = this._frameContent.polyline(this._shape.position.points).addClass('shapeCreation').attr({
+ this._shapeView = this._frameContent.polyline(points).addClass('shapeCreation').attr({
'stroke-width': STROKE_WIDTH / scale,
});
break;
case 'points':
- this._shapeView = this._frameContent.polyline(this._shape.position.points).addClass('shapeCreation').attr({
+ this._shapeView = this._frameContent.polyline(points).addClass('shapeCreation').attr({
'stroke-width': 0,
});
this._shapeViewGroup = this._frameContent.group();
- for (let point of PolyShapeModel.convertStringToNumberArray(this._shape.position.points)) {
+ for (let point of PolyShapeModel.convertStringToNumberArray(points)) {
let radius = POINT_RADIUS * 2 / window.cvat.player.geometry.scale;
let scaledStroke = STROKE_WIDTH / window.cvat.player.geometry.scale;
this._shapeViewGroup.circle(radius).move(point.x - radius / 2, point.y - radius / 2)
@@ -344,6 +354,7 @@ class ShapeBufferView {
_moveShapeView(pos) {
let rect = this._shapeView.node.getBBox();
+
this._shapeView.move(pos.x - rect.width / 2, pos.y - rect.height / 2);
if (this._shapeViewGroup) {
let rect = this._shapeViewGroup.node.getBBox();
@@ -362,25 +373,58 @@ class ShapeBufferView {
_enableEvents() {
this._frameContent.on('mousemove.buffer', (e) => {
- let pos = translateSVGPos(this._frameContent.node, e.clientX, e.clientY);
+ let pos = window.cvat.translate.point.clientToCanvas(this._frameContent.node, e.clientX, e.clientY);
this._shapeView.style('visibility', '');
this._moveShapeView(pos);
});
this._frameContent.on('mousedown.buffer', (e) => {
if (e.which != 1) return;
- let rect = this._shapeView.node.getBBox();
if (this._shape.type != 'box') {
- let points = PolyShapeModel.convertStringToNumberArray(this._shapeView.attr('points'));
- for (let point of points) {
- point.x = Math.clamp(point.x, 0, window.cvat.player.geometry.frameWidth);
- point.y = Math.clamp(point.y, 0, window.cvat.player.geometry.frameHeight);
+ let actualPoints = window.cvat.translate.points.canvasToActual(this._shapeView.attr('points'));
+ let frameWidth = window.cvat.player.geometry.frameWidth;
+ let frameHeight = window.cvat.player.geometry.frameHeight;
+
+ actualPoints = PolyShapeModel.convertStringToNumberArray(actualPoints);
+ for (let point of actualPoints) {
+ point.x = Math.clamp(point.x, 0, frameWidth);
+ point.y = Math.clamp(point.y, 0, frameHeight);
+ }
+ actualPoints = PolyShapeModel.convertNumberArrayToString(actualPoints);
+
+ // Set clamped points to a view in order to get an updated bounding box for a poly shape
+ this._shapeView.attr('points', window.cvat.translate.points.actualToCanvas(actualPoints));
+
+ // Get an updated bounding box for check it area
+ let polybox = this._shapeView.node.getBBox();
+ let w = polybox.width;
+ let h = polybox.height;
+ let area = w * h;
+ let type = this._shape.type;
+
+ if (area > AREA_TRESHOLD || type === 'points' || type === 'polyline' && (w >= AREA_TRESHOLD || h >= AREA_TRESHOLD)) {
+ this._controller.pasteToFrame(e, null, actualPoints);
+ }
+ else {
+ this._controller.pasteToFrame(e, null, null);
}
- points = PolyShapeModel.convertNumberArrayToString(points);
- this._controller.pasteToFrame(e, rect, points);
}
else {
- this._controller.pasteToFrame(e, rect);
+ let frameWidth = window.cvat.player.geometry.frameWidth;
+ let frameHeight = window.cvat.player.geometry.frameHeight;
+ let rect = window.cvat.translate.box.canvasToActual(this._shapeView.node.getBBox());
+ let box = {};
+ box.xtl = Math.clamp(rect.x, 0, frameWidth);
+ box.ytl = Math.clamp(rect.y, 0, frameHeight);
+ box.xbr = Math.clamp(rect.x + rect.width, 0, frameWidth);
+ box.ybr = Math.clamp(rect.y + rect.height, 0, frameHeight);
+
+ if ((box.xbr - box.xtl) * (box.ybr - box.ytl) >= AREA_TRESHOLD) {
+ this._controller.pasteToFrame(e, box, null);
+ }
+ else {
+ this._controller.pasteToFrame(e, null, null);
+ }
}
});
diff --git a/cvat/apps/engine/static/engine/js/shapeCollection.js b/cvat/apps/engine/static/engine/js/shapeCollection.js
index e8efe268..299fe3b8 100644
--- a/cvat/apps/engine/static/engine/js/shapeCollection.js
+++ b/cvat/apps/engine/static/engine/js/shapeCollection.js
@@ -1116,6 +1116,7 @@ class ShapeCollectionView {
constructor(collectionModel, collectionController) {
collectionModel.subscribe(this);
this._controller = collectionController;
+ this._frameBackground = $('#frameBackground');
this._frameContent = SVG.adopt($('#frameContent')[0]);
this._UIContent = $('#uiContent');
this._labelsContent = $('#labelsContent');
@@ -1229,12 +1230,16 @@ class ShapeCollectionView {
return;
}
- let pos = translateSVGPos(this._frameContent.node, e.clientX, e.clientY);
- if (!window.cvat.mode) {
- this._controller.selectShape(pos, false);
- }
+ let frameHeight = window.cvat.player.geometry.frameHeight;
+ let frameWidth = window.cvat.player.geometry.frameWidth;
+ let pos = window.cvat.translate.point.clientToCanvas(this._frameBackground[0], e.clientX, e.clientY);
+ if (pos.x >= 0 && pos.y >= 0 && pos.x <= frameWidth && pos.y <= frameHeight) {
+ if (!window.cvat.mode) {
+ this._controller.selectShape(pos, false);
+ }
- this._controller.setLastPosition(pos);
+ this._controller.setLastPosition(pos);
+ }
}.bind(this));
$('#shapeContextMenu li').click((e) => {
diff --git a/cvat/apps/engine/static/engine/js/shapeCreator.js b/cvat/apps/engine/static/engine/js/shapeCreator.js
index 7cd91213..53408080 100644
--- a/cvat/apps/engine/static/engine/js/shapeCreator.js
+++ b/cvat/apps/engine/static/engine/js/shapeCreator.js
@@ -234,22 +234,6 @@ class ShapeCreatorView {
this._polyShapeSizeInput.on('keydown', function(e) {
e.stopPropagation();
});
-
- this._playerFrame.on('mousemove', function(e) {
- // Save last coordinates in order to draw aim
- this._aimCoord = translateSVGPos(this._frameContent.node, e.clientX, e.clientY);
- if (this._aim) {
- this._aim.x.attr({
- y1: this._aimCoord.y,
- y2: this._aimCoord.y,
- });
-
- this._aim.y.attr({
- x1: this._aimCoord.x,
- x2: this._aimCoord.x,
- });
- }
- }.bind(this));
}
@@ -329,29 +313,36 @@ class ShapeCreatorView {
});
// Also we need callback on drawdone event for get points
this._drawInstance.on('drawdone', function(e) {
- let points = PolyShapeModel.convertStringToNumberArray(e.target.getAttribute('points'));
- for (let point of points) {
- point.x = Math.clamp(point.x, 0, window.cvat.player.geometry.frameWidth);
- point.y = Math.clamp(point.y, 0, window.cvat.player.geometry.frameHeight);
- }
+ let actualPoints = window.cvat.translate.points.canvasToActual(e.target.getAttribute('points'));
+ actualPoints = PolyShapeModel.convertStringToNumberArray(actualPoints);
// Min 2 points for polyline and 3 points for polygon
- if (points.length) {
- if (this._type === 'polyline' && points.length < 2) {
+ if (actualPoints.length) {
+ if (this._type === 'polyline' && actualPoints.length < 2) {
showMessage("Min 2 points must be for polyline drawing.");
}
- else if (this._type === 'polygon' && points.length < 3) {
+ else if (this._type === 'polygon' && actualPoints.length < 3) {
showMessage("Min 3 points must be for polygon drawing.");
}
else {
- points = PolyShapeModel.convertNumberArrayToString(points);
-
- // Update points in view in order to get updated box
- e.target.setAttribute('points', points);
- let box = e.target.getBBox();
- if (box.width * box.height >= AREA_TRESHOLD || this._type === 'points' ||
- this._type === 'polyline' && (box.width >= AREA_TRESHOLD || box.height >= AREA_TRESHOLD)) {
- this._controller.finish({points: e.target.getAttribute('points')}, this._type);
+ let frameWidth = window.cvat.player.geometry.frameWidth;
+ let frameHeight = window.cvat.player.geometry.frameHeight;
+ for (let point of actualPoints) {
+ point.x = Math.clamp(point.x, 0, frameWidth);
+ point.y = Math.clamp(point.y, 0, frameHeight);
+ }
+ actualPoints = PolyShapeModel.convertNumberArrayToString(actualPoints);
+
+ // Update points in a view in order to get an updated box
+ e.target.setAttribute('points', window.cvat.translate.points.actualToCanvas(actualPoints));
+ let polybox = e.target.getBBox();
+ let w = polybox.width;
+ let h = polybox.height;
+ let area = w * h;
+ let type = this.type;
+
+ if (area >= AREA_TRESHOLD || type === 'points' || type === 'polyline' && (w >= AREA_TRESHOLD || h >= AREA_TRESHOLD)) {
+ this._controller.finish({points: actualPoints}, type);
}
}
}
@@ -373,19 +364,22 @@ class ShapeCreatorView {
sizeUI.rm();
sizeUI = null;
}
- let result = {
- xtl: Math.max(0, +e.target.getAttribute('x')),
- ytl: Math.max(0, +e.target.getAttribute('y')),
- xbr: Math.min(window.cvat.player.geometry.frameWidth, +e.target.getAttribute('x') + +e.target.getAttribute('width')),
- ybr: Math.min(window.cvat.player.geometry.frameHeight, +e.target.getAttribute('y') + +e.target.getAttribute('height')),
- };
+
+ let frameWidth = window.cvat.player.geometry.frameWidth;
+ let frameHeight = window.cvat.player.geometry.frameHeight;
+ let rect = window.cvat.translate.box.canvasToActual(e.target.getBBox());
+ let box = {};
+ box.xtl = Math.clamp(rect.x, 0, frameWidth);
+ box.ytl = Math.clamp(rect.y, 0, frameHeight);
+ box.xbr = Math.clamp(rect.x + rect.width, 0, frameWidth);
+ box.ybr = Math.clamp(rect.y + rect.height, 0, frameHeight);
if (this._mode === 'interpolation') {
- result.outside = false;
+ box.outside = false;
}
- if ((result.ybr - result.ytl) * (result.xbr - result.xtl) >= AREA_TRESHOLD) {
- this._controller.finish(result, this._type);
+ if ((box.ybr - box.ytl) * (box.xbr - box.xtl) >= AREA_TRESHOLD) {
+ this._controller.finish(box, this._type);
}
this._controller.switchCreateMode(true);
@@ -434,37 +428,6 @@ class ShapeCreatorView {
throw Error(`Bad type found ${this._type}`);
}
- this._playerFrame.on('click.shapeCreation', (e) => {
- if (e.target === this._playerFrame[0]) {
- let original = e.originalEvent;
- Object.defineProperty(original, 'clientX', {
- value: original.clientX,
- writable: true,
- });
-
- Object.defineProperty(original, 'clientY', {
- value: original.clientY,
- writable: true,
- });
-
- let svgNodePos = this._frameContent.node.getBoundingClientRect();
-
- original.clientX = Math.clamp(original.clientX, svgNodePos.left, svgNodePos.right);
- original.clientY = Math.clamp(original.clientY, svgNodePos.top, svgNodePos.bottom);
-
- if (this._type === 'box') {
- this._drawInstance.draw(original);
- }
- else {
- for (let point of this._drawInstance.array().value) {
- point[0] = Math.clamp(point[0], 0, window.cvat.player.geometry.frameWidth);
- point[1] = Math.clamp(point[1], 0, window.cvat.player.geometry.frameHeight);
- }
- this._drawInstance.draw('point', original);
- }
- }
- });
-
this._drawInstance.attr({
'z_order': Number.MAX_SAFE_INTEGER,
});
@@ -480,13 +443,13 @@ class ShapeCreatorView {
_drawAim() {
if (!(this._aim)) {
this._aim = {
- x: this._frameContent.line(0, this._aimCoord.y, window.cvat.player.geometry.frameWidth, this._aimCoord.y)
+ x: this._frameContent.line(0, this._aimCoord.y, this._frameContent.node.clientWidth, this._aimCoord.y)
.attr({
'stroke-width': STROKE_WIDTH / this._scale,
'stroke': 'red',
'z_order': Number.MAX_SAFE_INTEGER,
}).addClass('aim'),
- y: this._frameContent.line(this._aimCoord.x, 0, this._aimCoord.x, window.cvat.player.geometry.frameHeight)
+ y: this._frameContent.line(this._aimCoord.x, 0, this._aimCoord.x, this._frameContent.node.clientHeight)
.attr({
'stroke-width': STROKE_WIDTH / this._scale,
'stroke': 'red',
@@ -512,6 +475,20 @@ class ShapeCreatorView {
if (!['polygon', 'polyline', 'points'].includes(this._type)) {
this._drawAim();
+ this._playerFrame.on('mousemove.shapeCreatorAIM', (e) => {
+ this._aimCoord = window.cvat.translate.point.clientToCanvas(this._frameContent.node, e.clientX, e.clientY);
+ if (this._aim) {
+ this._aim.x.attr({
+ y1: this._aimCoord.y,
+ y2: this._aimCoord.y,
+ });
+
+ this._aim.y.attr({
+ x1: this._aimCoord.x,
+ x2: this._aimCoord.x,
+ });
+ }
+ });
}
this._createButton.text("Stop Creation");
@@ -519,18 +496,19 @@ class ShapeCreatorView {
this._create();
}
else {
+ this._playerFrame.off('mousemove.shapeCreatorAIM');
this._removeAim();
+ this._aimCoord = {
+ x: 0,
+ y: 0
+ };
this._cancel = true;
this._createButton.text("Create Shape");
document.oncontextmenu = null;
- this._playerFrame.off('click.shapeCreation');
if (this._drawInstance) {
- // if need save current result for poly shape, do it.
- // drawInstance and env will clean in the future when
- // drawdone handler will call switchCreateMode with force argument
- // also need draw min one point. Otherwise errors occur in SVG.draw.js on done event
+ // We save current result for poly shape if it's need
+ // drawInstance will be removed after save when drawdone handler calls switchCreateMode with force argument
if (model.saveCurrent && this._type != 'box') {
- // FIXME: Error occured in svg.draw.js if no points was drawed and done, cancel or stop action applied
this._drawInstance.draw('done');
}
else {
diff --git a/cvat/apps/engine/static/engine/js/shapeGrouper.js b/cvat/apps/engine/static/engine/js/shapeGrouper.js
index 7a0dccbf..a7cbf9ee 100644
--- a/cvat/apps/engine/static/engine/js/shapeGrouper.js
+++ b/cvat/apps/engine/static/engine/js/shapeGrouper.js
@@ -186,11 +186,12 @@ class ShapeGrouperView {
_enableEvents() {
this._frameContent.on('mousedown.grouper', (e) => {
- this._initPoint = translateSVGPos(this._frameContent[0], e.clientX, e.clientY);
+ this._initPoint = window.cvat.translate.point.clientToCanvas(this._frameContent[0], e.clientX, e.clientY);
});
this._frameContent.on('mousemove.grouper', (e) => {
- let currentPoint = translateSVGPos(this._frameContent[0], e.clientX, e.clientY);
+ let currentPoint = window.cvat.translate.point.clientToCanvas(this._frameContent[0], e.clientX, e.clientY);
+
if (this._initPoint) {
if (!this._rectSelector) {
this._rectSelector = $(document.createElementNS('http://www.w3.org/2000/svg', 'rect'));
diff --git a/cvat/apps/engine/static/engine/js/shapes.js b/cvat/apps/engine/static/engine/js/shapes.js
index 4dc4567a..4d6c79d7 100644
--- a/cvat/apps/engine/static/engine/js/shapes.js
+++ b/cvat/apps/engine/static/engine/js/shapes.js
@@ -609,7 +609,7 @@ class BoxModel extends ShapeModel {
return Object.assign({},
this._positions[this._frame],
{
- outside: false
+ outside: this._frame != frame
}
);
}
@@ -868,16 +868,42 @@ class PolyShapeModel extends ShapeModel {
}
_interpolatePosition(frame) {
+ if (this._type.startsWith('annotation')) {
+ return Object.assign({},
+ this._positions[this._frame],
+ {
+ outside: this._frame != frame
+ }
+ );
+ }
+
+ let [leftFrame, rightFrame] = this._neighboringFrames(frame);
if (frame in this._positions) {
- return Object.assign({}, this._positions[frame], {
- outside: false
- });
+ leftFrame = frame;
}
- else {
- return {
- outside: true
- };
+
+ let leftPos = null;
+ let rightPos = null;
+
+ if (leftFrame != null) leftPos = this._positions[leftFrame];
+ if (rightFrame != null) rightPos = this._positions[rightFrame];
+
+ if (!leftPos) {
+ if (rightPos) {
+ return Object.assign({}, rightPos, {
+ outside: true,
+ });
+ }
+ else {
+ return {
+ outside: true
+ };
+ }
}
+
+ return Object.assign({}, leftPos, {
+ outside: leftFrame != frame,
+ });
}
updatePosition(frame, position, silent) {
@@ -1405,6 +1431,8 @@ class ShapeView extends Listener {
this._shapeContextMenu = $('#shapeContextMenu');
this._pointContextMenu = $('#pointContextMenu');
+ this._rightBorderFrame = $('#playerFrame')[0].offsetWidth;
+
shapeModel.subscribe(this);
}
@@ -2415,24 +2443,18 @@ class ShapeView extends Listener {
this._uis.shape.attr('stroke-width', STROKE_WIDTH / scale);
}
-
if (this._uis.text && this._uis.text.node.parentElement) {
let revscale = 1 / scale;
let shapeBBox = this._uis.shape.node.getBBox();
- let textBBox = this._uis.text.node.getBBox();
+ let textBBox = this._uis.text.node.getBBox()
- let x = shapeBBox.x + shapeBBox.width + TEXT_MARGIN;
+ let x = shapeBBox.x + shapeBBox.width + TEXT_MARGIN * revscale;
let y = shapeBBox.y;
- if (x + textBBox.width * revscale > window.cvat.player.geometry.frameWidth) {
- x = shapeBBox.x - TEXT_MARGIN - textBBox.width * revscale;
- if (x < 0) {
- x = shapeBBox.x + TEXT_MARGIN;
- }
- }
-
- if (y + textBBox.height * revscale > window.cvat.player.geometry.frameHeight) {
- y = Math.max(0, window.cvat.player.geometry.frameHeight - textBBox.height * revscale);
+ let transl = window.cvat.translate.point;
+ let canvas = this._scenes.svg.node;
+ if (transl.canvasToClient(canvas, x + textBBox.width * revscale, 0).x > this._rightBorderFrame) {
+ x = shapeBBox.x + TEXT_MARGIN * revscale;
}
this._uis.text.move(x / revscale, y / revscale);
@@ -2752,7 +2774,7 @@ class BoxView extends ShapeView {
_buildPosition() {
let shape = this._uis.shape.node;
- return {
+ return window.cvat.translate.box.canvasToActual({
xtl: +shape.getAttribute('x'),
ytl: +shape.getAttribute('y'),
xbr: +shape.getAttribute('x') + +shape.getAttribute('width'),
@@ -2760,13 +2782,12 @@ class BoxView extends ShapeView {
occluded: this._uis.shape.hasClass('occludedShape'),
outside: false, // if drag or resize possible, track is not outside
z_order: +shape.getAttribute('z_order'),
- };
+ });
}
_drawShapeUI(position) {
- let xtl = position.xtl;
- let ytl = position.ytl;
+ position = window.cvat.translate.box.actualToCanvas(position);
let width = position.xbr - position.xtl;
let height = position.ybr - position.ytl;
@@ -2776,7 +2797,7 @@ class BoxView extends ShapeView {
'stroke-width': STROKE_WIDTH / window.cvat.player.geometry.scale,
'z_order': position.z_order,
'fill-opacity': this._appearance.fillOpacity
- }).move(xtl, ytl).addClass('shape');
+ }).move(position.xtl, position.ytl).addClass('shape');
ShapeView.prototype._drawShapeUI.call(this);
}
@@ -2791,12 +2812,14 @@ class BoxView extends ShapeView {
oldMask.remove();
}
- let size = {
+ let size = window.cvat.translate.box.actualToCanvas({
x: 0,
y: 0,
width: window.cvat.player.geometry.frameWidth,
height: window.cvat.player.geometry.frameHeight
- };
+ });
+
+ pos = window.cvat.translate.box.actualToCanvas(pos);
let excludeField = this._scenes.svg.rect(size.width, size.height).move(size.x, size.y).fill('#666');
let includeField = this._scenes.svg.rect(pos.xbr - pos.xtl, pos.ybr - pos.ytl).move(pos.xtl, pos.ytl);
@@ -2822,7 +2845,7 @@ class PolyShapeView extends ShapeView {
_buildPosition() {
return {
- points: this._uis.shape.node.getAttribute('points'),
+ points: window.cvat.translate.points.canvasToActual(this._uis.shape.node.getAttribute('points')),
occluded: this._uis.shape.hasClass('occludedShape'),
outside: false,
z_order: +this._uis.shape.node.getAttribute('z_order'),
@@ -2840,15 +2863,17 @@ class PolyShapeView extends ShapeView {
oldMask.remove();
}
- let size = {
+ let size = window.cvat.translate.box.actualToCanvas({
x: 0,
y: 0,
width: window.cvat.player.geometry.frameWidth,
height: window.cvat.player.geometry.frameHeight
- };
+ });
+
+ let points = window.cvat.translate.points.actualToCanvas(pos.points);
let excludeField = this._scenes.svg.rect(size.width, size.height).move(size.x, size.y).fill('#666');
- let includeField = this._scenes.svg.polygon(pos.points);
+ let includeField = this._scenes.svg.polygon(points);
this._scenes.svg.mask().add(excludeField).add(includeField).fill('black').attr('id', 'outsideMask');
this._scenes.svg.rect(size.width, size.height).move(size.x, size.y).attr({
mask: 'url(#outsideMask)',
@@ -2879,6 +2904,7 @@ class PolyShapeView extends ShapeView {
});
e.preventDefault();
+ e.stopPropagation();
});
point.on('dblclick.polyshapeEditor', (e) => {
@@ -2940,7 +2966,8 @@ class PolygonView extends PolyShapeView {
}
_drawShapeUI(position) {
- this._uis.shape = this._scenes.svg.polygon(position.points).fill(this._appearance.colors.shape).attr({
+ let points = window.cvat.translate.points.actualToCanvas(position.points);
+ this._uis.shape = this._scenes.svg.polygon(points).fill(this._appearance.colors.shape).attr({
'fill': this._appearance.fill || this._appearance.colors.shape,
'stroke': this._appearance.stroke || this._appearance.colors.shape,
'stroke-width': STROKE_WIDTH / window.cvat.player.geometry.scale,
@@ -2980,7 +3007,8 @@ class PolylineView extends PolyShapeView {
_drawShapeUI(position) {
- this._uis.shape = this._scenes.svg.polyline(position.points).fill(this._appearance.colors.shape).attr({
+ let points = window.cvat.translate.points.actualToCanvas(position.points);
+ this._uis.shape = this._scenes.svg.polyline(points).fill(this._appearance.colors.shape).attr({
'stroke': this._appearance.stroke || this._appearance.colors.shape,
'stroke-width': STROKE_WIDTH / window.cvat.player.geometry.scale,
'z_order': position.z_order,
@@ -3122,17 +3150,19 @@ class PointsView extends PolyShapeView {
if (!this._controller.hiddenShape) {
let interpolation = this._controller.interpolate(window.cvat.player.frames.current);
if (interpolation.position.points) {
- this._drawPointMarkers(interpolation.position);
+ let points = window.cvat.translate.points.actualToCanvas(interpolation.position.points);
+ this._drawPointMarkers(Object.assign(interpolation.position.points, {points: points}));
}
}
}
_drawShapeUI(position) {
- this._uis.shape = this._scenes.svg.polyline(position.points).addClass('shape points').attr({
+ let points = window.cvat.translate.points.actualToCanvas(position.points);
+ this._uis.shape = this._scenes.svg.polyline(points).addClass('shape points').attr({
'z_order': position.z_order,
});
- this._drawPointMarkers(position);
+ this._drawPointMarkers(Object.assign(position, {points: points}));
ShapeView.prototype._drawShapeUI.call(this);
}
diff --git a/cvat/apps/engine/static/engine/stylesheet.css b/cvat/apps/engine/static/engine/stylesheet.css
index 6b89d57b..1a5484a8 100644
--- a/cvat/apps/engine/static/engine/stylesheet.css
+++ b/cvat/apps/engine/static/engine/stylesheet.css
@@ -263,6 +263,7 @@
fill: white;
text-shadow: 0px 0px 3px black;
cursor: default;
+ pointer-events: none;
}
.highlightedShape {
@@ -460,6 +461,7 @@
#frameContent {
position: absolute;
z-index: 1;
+ outline: 10px solid black;
-moz-transform-origin: top left;
-webkit-transform-origin: top left;
}
diff --git a/cvat/apps/engine/templates/engine/annotation.html b/cvat/apps/engine/templates/engine/annotation.html
index a72bd4db..b5d612ff 100644
--- a/cvat/apps/engine/templates/engine/annotation.html
+++ b/cvat/apps/engine/templates/engine/annotation.html
@@ -35,6 +35,7 @@
+
diff --git a/tests/eslintrc.conf.js b/tests/eslintrc.conf.js
index 487df365..9e27f60c 100644
--- a/tests/eslintrc.conf.js
+++ b/tests/eslintrc.conf.js
@@ -41,7 +41,6 @@ module.exports = {
'AnnotationParser': true,
// from annotationUI.js
'callAnnotationUI': true,
- 'translateSVGPos': true,
'blurAllElements': true,
'drawBoxSize': true,
'copyToClipboard': true,
@@ -123,5 +122,7 @@ module.exports = {
'PolyshapeEditorModel': true,
'PolyshapeEditorController': true,
'PolyshapeEditorView': true,
+ // from coordinateTranslator
+ 'CoordinateTranslator': true,
},
};