Automatic bordering feature during drawing/editing (#997)

main
Boris Sekachev 6 years ago committed by Nikita Manovich
parent c7052842a7
commit f7df747cdf

@ -583,7 +583,7 @@ function buildAnnotationUI(jobData, taskData, imageMetaData, annotationData, ann
const shapeCreatorController = new ShapeCreatorController(shapeCreatorModel); const shapeCreatorController = new ShapeCreatorController(shapeCreatorModel);
const shapeCreatorView = new ShapeCreatorView(shapeCreatorModel, shapeCreatorController); const shapeCreatorView = new ShapeCreatorView(shapeCreatorModel, shapeCreatorController);
const polyshapeEditorModel = new PolyshapeEditorModel(); const polyshapeEditorModel = new PolyshapeEditorModel(shapeCollectionModel);
const polyshapeEditorController = new PolyshapeEditorController(polyshapeEditorModel); const polyshapeEditorController = new PolyshapeEditorController(polyshapeEditorModel);
const polyshapeEditorView = new PolyshapeEditorView(polyshapeEditorModel, const polyshapeEditorView = new PolyshapeEditorView(polyshapeEditorModel,
polyshapeEditorController); polyshapeEditorController);

@ -0,0 +1,213 @@
/*
* Copyright (C) 2019 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
/* exported BorderSticker */
class BorderSticker {
constructor(currentShape, frameContent, shapes, scale) {
this._currentShape = currentShape;
this._frameContent = frameContent;
this._enabled = false;
this._groups = null;
this._scale = scale;
this._accounter = {
clicks: [],
shapeID: null,
};
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
.actualToCanvas(`${pos.xtl},${pos.ytl} ${pos.xbr},${pos.ytl}`
+ ` ${pos.xbr},${pos.ybr} ${pos.xtl},${pos.ybr}`),
color: shape.model.color.shape,
};
}
return {
points: window.cvat.translate.points
.actualToCanvas(pos.points),
color: shape.model.color.shape,
};
});
this._drawBorderMarkers(transformedShapes);
}
_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(shapes) {
const namespace = 'http://www.w3.org/2000/svg';
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');
}
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;
}
way.push(points[i]);
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;
}
}
// 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);
}
this.reset();
}
};
circle.addEventListener('click', circle.clickListener);
circle.addEventListener('dblclick', circle.doubleClickListener);
return circle;
}).forEach((circle) => group.appendChild(circle));
acc.push(group);
return acc;
}, []);
this._groups
.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() {
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) {
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);
});
});
}
}
}

@ -12,16 +12,18 @@
PolyShapeModel:false PolyShapeModel:false
STROKE_WIDTH:false STROKE_WIDTH:false
SVG:false SVG:false
BorderSticker:false
*/ */
"use strict"; "use strict";
class PolyshapeEditorModel extends Listener { class PolyshapeEditorModel extends Listener {
constructor() { constructor(shapeCollection) {
super("onPolyshapeEditorUpdate", () => this); super("onPolyshapeEditorUpdate", () => this);
this._modeName = 'poly_editing'; this._modeName = 'poly_editing';
this._active = false; this._active = false;
this._shapeCollection = shapeCollection;
this._data = { this._data = {
points: null, points: null,
color: null, color: null,
@ -29,10 +31,12 @@ class PolyshapeEditorModel extends Listener {
oncomplete: null, oncomplete: null,
type: null, type: null,
event: null, event: null,
startPoint: null,
id: null,
}; };
} }
edit(type, points, color, start, event, oncomplete) { edit(type, points, color, start, startPoint, e, oncomplete, id) {
if (!this._active && !window.cvat.mode) { if (!this._active && !window.cvat.mode) {
window.cvat.mode = this._modeName; window.cvat.mode = this._modeName;
this._active = true; this._active = true;
@ -41,7 +45,9 @@ class PolyshapeEditorModel extends Listener {
this._data.start = start; this._data.start = start;
this._data.oncomplete = oncomplete; this._data.oncomplete = oncomplete;
this._data.type = type; this._data.type = type;
this._data.event = event; this._data.event = e;
this._data.startPoint = startPoint;
this._data.id = id;
this.notify(); this.notify();
} }
else if (this._active) { else if (this._active) {
@ -73,6 +79,7 @@ class PolyshapeEditorModel extends Listener {
this._data.oncomplete = null; this._data.oncomplete = null;
this._data.type = null; this._data.type = null;
this._data.event = null; this._data.event = null;
this._data.startPoint = null;
this.notify(); this.notify();
} }
} }
@ -84,6 +91,11 @@ class PolyshapeEditorModel extends Listener {
get data() { get data() {
return this._data; return this._data;
} }
get currentShapes() {
this._shapeCollection.update();
return this._shapeCollection.currentShapes;
}
} }
@ -99,6 +111,10 @@ class PolyshapeEditorController {
cancel() { cancel() {
this._model.cancel(); this._model.cancel();
} }
get currentShapes() {
return this._model.currentShapes;
}
} }
@ -108,14 +124,30 @@ class PolyshapeEditorView {
this._data = null; this._data = null;
this._frameContent = SVG.adopt($('#frameContent')[0]); this._frameContent = SVG.adopt($('#frameContent')[0]);
this._autoBorderingCheckbox = $('#autoBorderingCheckbox');
this._originalShapePointsGroup = null; this._originalShapePointsGroup = null;
this._originalShapePoints = []; this._originalShapePoints = [];
this._originalShape = null; this._originalShape = null;
this._correctLine = null; this._correctLine = null;
this._borderSticker = null;
this._scale = window.cvat.player.geometry.scale; this._scale = window.cvat.player.geometry.scale;
this._frame = window.cvat.player.frames.current; this._frame = window.cvat.player.frames.current;
this._autoBorderingCheckbox.on('change.shapeEditor', (e) => {
if (this._correctLine) {
if (!e.target.checked) {
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); model.subscribe(this);
} }
@ -180,6 +212,16 @@ class PolyshapeEditorView {
return offset; return offset;
} }
_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());
this._rescaleDrawPoints();
}
_startEdit() { _startEdit() {
this._frame = window.cvat.player.frames.current; this._frame = window.cvat.player.frames.current;
let strokeWidth = this._data.type === 'points' ? 0 : STROKE_WIDTH / this._scale; let strokeWidth = this._data.type === 'points' ? 0 : STROKE_WIDTH / this._scale;
@ -222,17 +264,24 @@ class PolyshapeEditorView {
} }
const [x, y] = this._data.startPoint
.split(',').map((el) => +el);
let prevPoint = { let prevPoint = {
x: this._data.event.clientX, x,
y: this._data.event.clientY y,
}; };
// draw and remove initial point just to initialize data structures
this._correctLine.draw('point', this._data.event); this._correctLine.draw('point', this._data.event);
this._rescaleDrawPoints(); this._correctLine.draw('undo');
this._addRawPoint(x, y);
this._frameContent.on('mousemove.polyshapeEditor', (e) => { this._frameContent.on('mousemove.polyshapeEditor', (e) => {
if (e.shiftKey && this._data.type != 'points') { 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)); const delta = Math.sqrt(Math.pow(e.clientX - prevPoint.x, 2)
let deltaTreshold = 15; + Math.pow(e.clientY - prevPoint.y, 2));
const deltaTreshold = 15;
if (delta > deltaTreshold) { if (delta > deltaTreshold) {
this._correctLine.draw('point', e); this._correctLine.draw('point', e);
prevPoint = { prevPoint = {
@ -246,8 +295,10 @@ class PolyshapeEditorView {
this._frameContent.on('contextmenu.polyshapeEditor', (e) => { this._frameContent.on('contextmenu.polyshapeEditor', (e) => {
if (PolyShapeModel.convertStringToNumberArray(this._correctLine.attr('points')).length > 2) { if (PolyShapeModel.convertStringToNumberArray(this._correctLine.attr('points')).length > 2) {
this._correctLine.draw('undo'); this._correctLine.draw('undo');
} if (this._borderSticker) {
else { this._borderSticker.reset();
}
} else {
// Finish without points argument is just cancel // Finish without points argument is just cancel
this._controller.finish(); this._controller.finish();
} }
@ -272,9 +323,19 @@ class PolyshapeEditorView {
}).on('mouseout', () => { }).on('mouseout', () => {
instance.attr('stroke-width', STROKE_WIDTH / this._scale); instance.attr('stroke-width', STROKE_WIDTH / this._scale);
}).on('mousedown', (e) => { }).on('mousedown', (e) => {
if (e.which != 1) return; if (e.which !== 1) {
return;
}
let currentPoints = PolyShapeModel.convertStringToNumberArray(this._data.points); 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 = []; let resultPoints = [];
if (this._data.type === 'polygon') { if (this._data.type === 'polygon') {
@ -338,6 +399,14 @@ class PolyshapeEditorView {
this._controller.finish(PolyShapeModel.convertNumberArrayToString(resultPoints)); this._controller.finish(PolyShapeModel.convertNumberArrayToString(resultPoints));
}); });
} }
this._autoBorderingCheckbox[0].disabled = false;
$('body').on('keydown.shapeEditor', (e) => {
if (e.ctrlKey && e.keyCode === 17) {
this._autoBorderingCheckbox[0].checked = !this._borderSticker;
this._autoBorderingCheckbox.trigger('change.shapeEditor');
}
});
} }
_endEdit() { _endEdit() {
@ -361,6 +430,14 @@ class PolyshapeEditorView {
this._frameContent.off('mousemove.polyshapeEditor'); this._frameContent.off('mousemove.polyshapeEditor');
this._frameContent.off('mousedown.polyshapeEditor'); this._frameContent.off('mousedown.polyshapeEditor');
this._frameContent.off('contextmenu.polyshapeEditor'); 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;
}
} }
@ -379,6 +456,10 @@ class PolyshapeEditorView {
if (this._scale != scale) { if (this._scale != scale) {
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 strokeWidth = this._data && this._data.type === 'points' ? 0 : STROKE_WIDTH / this._scale;
let pointRadius = POINT_RADIUS / this._scale; let pointRadius = POINT_RADIUS / this._scale;

@ -16,10 +16,9 @@
showMessage:false showMessage:false
STROKE_WIDTH:false STROKE_WIDTH:false
SVG:false SVG:false
BorderSticker: false
*/ */
"use strict";
class ShapeCreatorModel extends Listener { class ShapeCreatorModel extends Listener {
constructor(shapeCollection) { constructor(shapeCollection) {
super('onShapeCreatorUpdate', () => this); super('onShapeCreatorUpdate', () => this);
@ -34,8 +33,8 @@ class ShapeCreatorModel extends Listener {
} }
finish(result) { finish(result) {
let data = {}; const data = {};
let frame = window.cvat.player.frames.current; const frame = window.cvat.player.frames.current;
data.label_id = this._defaultLabel; data.label_id = this._defaultLabel;
data.group = 0; data.group = 0;
@ -50,7 +49,7 @@ class ShapeCreatorModel extends Listener {
mode: this._defaultMode, mode: this._defaultMode,
type: this._defaultType, type: this._defaultType,
label: this._defaultLabel, label: this._defaultLabel,
frame: frame, frame,
}); });
} }
@ -64,7 +63,7 @@ class ShapeCreatorModel extends Listener {
this._shapeCollection.add(data, `annotation_${this._defaultType}`); 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 // Undo/redo code
window.cvat.addAction('Draw Object', () => { window.cvat.addAction('Draw Object', () => {
@ -87,12 +86,10 @@ class ShapeCreatorModel extends Listener {
if (this._createMode) { if (this._createMode) {
this._createEvent = Logger.addContinuedEvent(Logger.EventType.drawObject); this._createEvent = Logger.addContinuedEvent(Logger.EventType.drawObject);
window.cvat.mode = 'creation'; window.cvat.mode = 'creation';
} } else if (window.cvat.mode === 'creation') {
else if (window.cvat.mode === 'creation') {
window.cvat.mode = null; window.cvat.mode = null;
} }
} } else {
else {
this._createMode = false; this._createMode = false;
if (window.cvat.mode === 'creation') { if (window.cvat.mode === 'creation') {
window.cvat.mode = null; window.cvat.mode = null;
@ -106,6 +103,11 @@ class ShapeCreatorModel extends Listener {
this.notify(); this.notify();
} }
get currentShapes() {
this._shapeCollection.update();
return this._shapeCollection.currentShapes;
}
get saveCurrent() { get saveCurrent() {
return this._saveCurrent; return this._saveCurrent;
} }
@ -177,6 +179,10 @@ class ShapeCreatorController {
finish(result) { finish(result) {
this._model.finish(result); this._model.finish(result);
} }
get currentShapes() {
return this._model.currentShapes;
}
} }
class ShapeCreatorView { class ShapeCreatorView {
@ -188,6 +194,7 @@ class ShapeCreatorView {
this._modeSelector = $('#shapeModeSelector'); this._modeSelector = $('#shapeModeSelector');
this._typeSelector = $('#shapeTypeSelector'); this._typeSelector = $('#shapeTypeSelector');
this._polyShapeSizeInput = $('#polyShapeSize'); this._polyShapeSizeInput = $('#polyShapeSize');
this._autoBorderingCheckbox = $('#autoBorderingCheckbox');
this._frameContent = SVG.adopt($('#frameContent')[0]); this._frameContent = SVG.adopt($('#frameContent')[0]);
this._frameText = SVG.adopt($("#frameText")[0]); this._frameText = SVG.adopt($("#frameText")[0]);
this._playerFrame = $('#playerFrame'); this._playerFrame = $('#playerFrame');
@ -203,6 +210,7 @@ class ShapeCreatorView {
this._mode = null; this._mode = null;
this._cancel = false; this._cancel = false;
this._scale = 1; this._scale = 1;
this._borderSticker = null;
let shortkeys = window.cvat.config.shortkeys; let shortkeys = window.cvat.config.shortkeys;
this._createButton.attr('title', ` this._createButton.attr('title', `
@ -288,8 +296,19 @@ class ShapeCreatorView {
} }
} }
}); });
}
this._autoBorderingCheckbox.on('change.shapeCreator', (e) => {
if (this._drawInstance) {
if (!e.target.checked) {
this._borderSticker.disable();
this._borderSticker = null;
} else {
this._borderSticker = new BorderSticker(this._drawInstance, this._frameContent,
this._controller.currentShapes, this._scale);
}
}
});
}
_createPolyEvents() { _createPolyEvents() {
// If number of points for poly shape specified, use it. // If number of points for poly shape specified, use it.
@ -340,10 +359,21 @@ class ShapeCreatorView {
numberOfPoints ++; numberOfPoints ++;
}); });
this._autoBorderingCheckbox[0].disabled = false;
$('body').on('keydown.shapeCreator', (e) => {
if (e.ctrlKey && e.keyCode === 17) {
this._autoBorderingCheckbox[0].checked = !this._borderSticker;
this._autoBorderingCheckbox.trigger('change.shapeCreator');
}
});
this._frameContent.on('mousedown.shapeCreator', (e) => { this._frameContent.on('mousedown.shapeCreator', (e) => {
if (e.which === 3) { if (e.which === 3) {
let lenBefore = this._drawInstance.array().value.length; let lenBefore = this._drawInstance.array().value.length;
this._drawInstance.draw('undo'); this._drawInstance.draw('undo');
if (this._borderSticker) {
this._borderSticker.reset();
}
let lenAfter = this._drawInstance.array().value.length; let lenAfter = this._drawInstance.array().value.length;
if (lenBefore != lenAfter) { if (lenBefore != lenAfter) {
numberOfPoints --; numberOfPoints --;
@ -373,6 +403,13 @@ class ShapeCreatorView {
this._drawInstance.on('drawstop', () => { this._drawInstance.on('drawstop', () => {
this._frameContent.off('mousedown.shapeCreator'); this._frameContent.off('mousedown.shapeCreator');
this._frameContent.off('mousemove.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();
this._borderSticker = null;
}
}); });
// Also we need callback on drawdone event for get points // Also we need callback on drawdone event for get points
this._drawInstance.on('drawdone', function(e) { this._drawInstance.on('drawdone', function(e) {
@ -418,7 +455,7 @@ class ShapeCreatorView {
let sizeUI = null; let sizeUI = null;
switch(this._type) { switch(this._type) {
case 'box': 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, 'stroke-width': STROKE_WIDTH / this._scale,
}).on('drawstop', function(e) { }).on('drawstop', function(e) {
if (this._cancel) return; if (this._cancel) return;
@ -461,9 +498,10 @@ class ShapeCreatorView {
}); });
break; break;
case 'points': case 'points':
this._drawInstance = this._frameContent.polyline().draw({snapToGrid: 0.1}).addClass('shapeCreation').attr({ this._drawInstance = this._frameContent.polyline().draw({ snapToGrid: 0.1 })
'stroke-width': 0, .addClass('shapeCreation').attr({
}); 'stroke-width': 0,
});
this._createPolyEvents(); this._createPolyEvents();
break; break;
case 'polygon': case 'polygon':
@ -474,9 +512,10 @@ class ShapeCreatorView {
this._controller.switchCreateMode(true); this._controller.switchCreateMode(true);
return; return;
} }
this._drawInstance = this._frameContent.polygon().draw({snapToGrid: 0.1}).addClass('shapeCreation').attr({ this._drawInstance = this._frameContent.polygon().draw({ snapToGrid: 0.1 })
'stroke-width': STROKE_WIDTH / this._scale, .addClass('shapeCreation').attr({
}); 'stroke-width': STROKE_WIDTH / this._scale,
});
this._createPolyEvents(); this._createPolyEvents();
break; break;
case 'polyline': case 'polyline':
@ -487,9 +526,10 @@ class ShapeCreatorView {
this._controller.switchCreateMode(true); this._controller.switchCreateMode(true);
return; return;
} }
this._drawInstance = this._frameContent.polyline().draw({snapToGrid: 0.1}).addClass('shapeCreation').attr({ this._drawInstance = this._frameContent.polyline().draw({ snapToGrid: 0.1 })
'stroke-width': STROKE_WIDTH / this._scale, .addClass('shapeCreation').attr({
}); 'stroke-width': STROKE_WIDTH / this._scale,
});
this._createPolyEvents(); this._createPolyEvents();
break; break;
default: default:
@ -585,6 +625,9 @@ class ShapeCreatorView {
this._scale = player.geometry.scale; this._scale = player.geometry.scale;
if (this._drawInstance) { if (this._drawInstance) {
this._rescaleDrawPoints(); this._rescaleDrawPoints();
if (this._borderSticker) {
this._borderSticker.scale(this._scale);
}
if (this._aim) { if (this._aim) {
this._aim.x.attr('stroke-width', STROKE_WIDTH / this._scale); this._aim.x.attr('stroke-width', STROKE_WIDTH / this._scale);
this._aim.y.attr('stroke-width', STROKE_WIDTH / this._scale); this._aim.y.attr('stroke-width', STROKE_WIDTH / this._scale);

@ -3007,7 +3007,8 @@ class PolyShapeView extends ShapeView {
// Run edit mode // Run edit mode
PolyShapeView.editor.edit(this._controller.type.split('_')[1], 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) => { (points) => {
this._uis.shape.removeClass('hidden'); this._uis.shape.removeClass('hidden');
if (this._uis.points) { if (this._uis.points) {
@ -3017,7 +3018,8 @@ class PolyShapeView extends ShapeView {
this._uis.shape.attr('points', points); this._uis.shape.attr('points', points);
this._controller.updatePosition(window.cvat.player.frames.current, this._buildPosition()); this._controller.updatePosition(window.cvat.player.frames.current, this._buildPosition());
} }
} },
this._controller.id
); );
} }
} }

@ -224,6 +224,24 @@
cursor: w-resize; cursor: w-resize;
} }
.shape-creator-border-point {
opacity: 0.55;
}
.shape-creator-border-point:hover {
opacity: 1;
fill: red;
}
.shape-creator-border-point:active {
opacity: 0.55;
fill: red;
}
.shape-creator-border-point-direction {
fill: blueviolet;
}
.shapeText { .shapeText {
font-size: 0.12em; font-size: 0.12em;
fill: white; fill: white;

@ -41,6 +41,7 @@
<script type="text/javascript" src="{% static 'engine/js/server.js' %}"></script> <script type="text/javascript" src="{% static 'engine/js/server.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/listener.js' %}"></script> <script type="text/javascript" src="{% static 'engine/js/listener.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/history.js' %}"></script> <script type="text/javascript" src="{% static 'engine/js/history.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/borderSticker.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/coordinateTranslator.js' %}"></script> <script type="text/javascript" src="{% static 'engine/js/coordinateTranslator.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/labelsInfo.js' %}"></script> <script type="text/javascript" src="{% static 'engine/js/labelsInfo.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/annotationParser.js' %}"></script> <script type="text/javascript" src="{% static 'engine/js/annotationParser.js' %}"></script>
@ -191,6 +192,10 @@
<label style="margin-right: 10px;"> Label </label> <label style="margin-right: 10px;"> Label </label>
<input type="radio" name="colorByRadio" id="colorByLabelRadio" class="settingsBox"/> <input type="radio" name="colorByRadio" id="colorByLabelRadio" class="settingsBox"/>
</div> </div>
<div style="float: left; margin-left: 50px;" title="Press Ctrl to switch">
<label style="margin-right: 10px;" for="autoBorderingCheckbox"> Auto bordering </label>
<input type="checkbox" id="autoBorderingCheckbox" class="settingsBox" disabled/>
</div>
</td> </td>
</tr> </tr>
</table> </table>

Loading…
Cancel
Save