Image rotation in client part (#305)

* Rotation shortcuts Ctrl+R, Ctrl+Shift+R
* Changelog has been updated
main
Boris Sekachev 7 years ago committed by Nikita Manovich
parent 46d2120685
commit b4e6f22f58

@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- OpenVINO auto annotation: it is possible to upload a custom model and annotate images automatically.
- Ability to rotate images/video in the client part (Ctrl+R, Shift+Ctrl+R shortcuts) (#305)
### Changed
- Propagation setup has been moved from settings to bottom player panel

@ -653,13 +653,11 @@ function setupMenu(job, shapeCollectionModel, annotationParser, aamModel, player
}
function drawBoxSize(scene, box) {
let scale = window.cvat.player.geometry.scale;
let width = +box.getAttribute('width');
let height = +box.getAttribute('height');
let text = `${width.toFixed(1)}x${height.toFixed(1)}`;
function drawBoxSize(boxScene, textScene, box) {
let clientBox = window.cvat.translate.box.canvasToClient(boxScene.node, box);
let text = `${box.width.toFixed(1)}x${box.height.toFixed(1)}`;
let obj = this && this.textUI && this.rm ? this : {
textUI: scene.text('').font({
textUI: textScene.text('').font({
weight: 'bolder'
}).fill('white'),
@ -670,16 +668,11 @@ function drawBoxSize(scene, box) {
}
};
obj.textUI.clear().plain(text);
obj.textUI.font({
size: 20 / scale,
}).style({
stroke: 'black',
'stroke-width': 1 / scale
});
let textPoint = window.cvat.translate.point.clientToCanvas(textScene.node, clientBox.x, clientBox.y);
obj.textUI.move(+box.getAttribute('x'), +box.getAttribute('y'));
obj.textUI.clear().plain(text);
obj.textUI.addClass("shapeText");
obj.textUI.move(textPoint.x, textPoint.y);
return obj;
}

@ -37,6 +37,30 @@ class CoordinateTranslator {
return this._convert(actualBox, -1);
},
canvasToClient: function(sourceCanvas, canvasBox) {
let points = [
window.cvat.translate.point.canvasToClient(sourceCanvas, canvasBox.x, canvasBox.y),
window.cvat.translate.point.canvasToClient(sourceCanvas, canvasBox.x + canvasBox.width, canvasBox.y),
window.cvat.translate.point.canvasToClient(sourceCanvas, canvasBox.x, canvasBox.y + canvasBox.height),
window.cvat.translate.point.canvasToClient(sourceCanvas, canvasBox.x + canvasBox.width, canvasBox.y + canvasBox.height),
];
let xes = points.map((el) => el.x);
let yes = points.map((el) => el.y);
let xmin = Math.min(...xes);
let xmax = Math.max(...xes);
let ymin = Math.min(...yes);
let ymax = Math.max(...yes);
return {
x: xmin,
y: ymin,
width: xmax - xmin,
height: ymax - ymin
};
},
};
this._pointsTranslator = {
@ -70,6 +94,7 @@ class CoordinateTranslator {
},
this._pointTranslator = {
_rotation: 0,
clientToCanvas: function(targetCanvas, clientX, clientY) {
let pt = targetCanvas.createSVGPoint();
pt.x = clientX;
@ -83,6 +108,19 @@ class CoordinateTranslator {
pt.y = canvasY;
pt = pt.matrixTransform(sourceCanvas.getScreenCTM());
return pt;
},
rotate(x, y, cx, cy) {
cx = (typeof cx === "undefined" ? 0 : cx);
cy = (typeof cy === "undefined" ? 0 : cy);
let radians = (Math.PI / 180) * window.cvat.player.rotation;
let cos = Math.cos(radians);
let sin = Math.sin(radians);
return {
x: (cos * (x - cx)) + (sin * (y - cy)) + cx,
y: (cos * (y - cy)) - (sin * (x - cx)) + cy
}
}
};
}
@ -103,4 +141,8 @@ class CoordinateTranslator {
this._boxTranslator._playerOffset = value;
this._pointsTranslator._playerOffset = value;
}
set rotation(value) {
this._pointTranslator._rotation = value;
}
}

@ -363,6 +363,8 @@ var Logger = {
debugInfo: 24,
// dumped as "Fit image". There are no additional required fields.
fitImage: 25,
// dumped as "Rotate image". There are no additional required fields.
rotateImage: 26,
},
/**
@ -526,6 +528,7 @@ var Logger = {
case this.EventType.changeFrame: return 'Change frame';
case this.EventType.debugInfo: return 'Debug info';
case this.EventType.fitImage: return 'Fit image';
case this.EventType.rotateImage: return 'Rotate image';
default: return 'Unknown';
}
},

@ -146,6 +146,7 @@ class PlayerModel extends Listener {
this._settings = {
multipleStep: 10,
fps: 25,
rotateAll: job.mode === 'interpolation',
resetZoom: job.mode === 'annotation'
};
@ -162,13 +163,16 @@ class PlayerModel extends Listener {
width: playerSize.width,
height: playerSize.height,
frameOffset: 0,
rotation: 0,
};
this._framewiseRotation = {};
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;
window.cvat.player.rotation = this._geometry.rotation;
this._frameProvider.subscribe(this);
}
@ -183,7 +187,10 @@ class PlayerModel extends Listener {
}
get geometry() {
return Object.assign({}, this._geometry);
let copy = Object.assign({}, this._geometry);
copy.rotation = this._settings.rotateAll ? this._geometry.rotation :
this._framewiseRotation[this._frame.current] || 0;
return copy;
}
get playing() {
@ -202,6 +209,20 @@ class PlayerModel extends Listener {
return this._settings.multipleStep;
}
get rotateAll() {
return this._settings.rotateAll;
}
set rotateAll(value) {
this._settings.rotateAll = value;
if (!value) {
this._geometry.rotation = 0;
} else {
this._framewiseRotation = {};
}
}
set fps(value) {
this._settings.fps = value;
}
@ -304,7 +325,8 @@ class PlayerModel extends Listener {
});
let changed = this._frame.previous != this._frame.current;
if (this._settings.resetZoom || this._frame.previous === null) { // fit in annotation mode or once in interpolation mode
// fit if tool is in the annotation mode or frame loading is first in the interpolation mode
if (this._settings.resetZoom || !this._settings.rotateAll || this._frame.previous === null) {
this._frame.previous = this._frame.current;
this.fit(); // notify() inside the fit()
}
@ -319,10 +341,22 @@ class PlayerModel extends Listener {
fit() {
let img = this._frameProvider.require(this._frame.current);
if (!img) return;
this._geometry.scale = Math.min(this._geometry.width / img.width, this._geometry.height / img.height);
let rotation = this.geometry.rotation;
if ((rotation / 90) % 2) {
// 90, 270, ..
this._geometry.scale = Math.min(this._geometry.width / img.height, this._geometry.height / img.width);
}
else {
// 0, 180, ..
this._geometry.scale = Math.min(this._geometry.width / img.width, this._geometry.height / img.height);
}
this._geometry.top = (this._geometry.height - img.height * this._geometry.scale) / 2;
this._geometry.left = (this._geometry.width - img.width * this._geometry.scale ) / 2;
window.cvat.player.rotation = rotation;
window.cvat.player.geometry.scale = this._geometry.scale;
this.notify();
}
@ -352,28 +386,19 @@ class PlayerModel extends Listener {
window.cvat.player.geometry.scale = this._geometry.scale;
this._frame.previous = this._frame.current; // fix infinite loop via playerUpdate->collectionUpdate*->AAMUpdate->playerUpdate->...
this.notify();
}
scale(x, y, value) {
scale(point, value) {
if (!this._frameProvider.require(this._frame.current)) return;
let currentCenter = {
x: (x - this._geometry.left) / this._geometry.scale,
y: (y - this._geometry.top) / this._geometry.scale
};
this._geometry.scale = value > 0 ? this._geometry.scale * 6/5 : this._geometry.scale * 5/6;
this._geometry.scale = Math.min(this._geometry.scale, MAX_PLAYER_SCALE);
this._geometry.scale = Math.max(this._geometry.scale, MIN_PLAYER_SCALE);
let newCenter = {
x: (x - this._geometry.left) / this._geometry.scale,
y: (y - this._geometry.top) / this._geometry.scale
};
let oldScale = this._geometry.scale;
this._geometry.scale = Math.clamp(
value > 0 ? this._geometry.scale * 6/5 : this._geometry.scale * 5/6,
MIN_PLAYER_SCALE, MAX_PLAYER_SCALE
);
this._geometry.left += (newCenter.x - currentCenter.x) * this._geometry.scale;
this._geometry.top += (newCenter.y - currentCenter.y) * this._geometry.scale;
this._geometry.left += (point.x * (oldScale / this._geometry.scale - 1)) * this._geometry.scale;
this._geometry.top += (point.y * (oldScale / this._geometry.scale - 1)) * this._geometry.scale;
window.cvat.player.geometry.scale = this._geometry.scale;
this.notify();
@ -384,6 +409,26 @@ class PlayerModel extends Listener {
this._geometry.left += leftOffset;
this.notify();
}
rotate(angle) {
if (['resize', 'drag'].indexOf(window.cvat.mode) != -1) {
return false;
}
if (this._settings.rotateAll) {
this._geometry.rotation += angle;
this._geometry.rotation %= 360;
} else {
if (typeof(this._framewiseRotation[this._frame.current]) === 'undefined') {
this._framewiseRotation[this._frame.current] = angle;
} else {
this._framewiseRotation[this._frame.current] += angle;
this._framewiseRotation[this._frame.current] %= 360;
}
}
this.fit();
}
}
@ -483,19 +528,27 @@ class PlayerController {
Mousetrap.bind(shortkeys["forward_frame"].value, forwardHandler, 'keydown');
Mousetrap.bind(shortkeys["backward_frame"].value, backwardHandler, 'keydown');
Mousetrap.bind(shortkeys["play_pause"].value, playPauseHandler, 'keydown');
Mousetrap.bind(shortkeys['clockwise_rotation'].value, (e) => {
e.preventDefault();
this.rotate(90);
}, 'keydown');
Mousetrap.bind(shortkeys['counter_clockwise_rotation'].value, (e) => {
e.preventDefault();
this.rotate(-90);
}, 'keydown');
}
}
zoom(e) {
let x = e.originalEvent.pageX - this._leftOffset;
let y = e.originalEvent.pageY - this._topOffset;
zoom(e, canvas) {
let point = window.cvat.translate.point.clientToCanvas(canvas, e.clientX, e.clientY);
let zoomImageEvent = Logger.addContinuedEvent(Logger.EventType.zoomImage);
if (e.originalEvent.deltaY < 0) {
this._model.scale(x, y, 1);
this._model.scale(point, 1);
}
else {
this._model.scale(x, y, -1);
this._model.scale(point, -1);
}
zoomImageEvent.close();
e.preventDefault();
@ -509,8 +562,11 @@ class PlayerController {
frameMouseDown(e) {
if ((e.which === 1 && !window.cvat.mode) || (e.which === 2)) {
this._moving = true;
this._lastClickX = e.clientX;
this._lastClickY = e.clientY;
let p = window.cvat.translate.point.rotate(e.clientX, e.clientY);
this._lastClickX = p.x;
this._lastClickY = p.y;
}
}
@ -528,11 +584,11 @@ class PlayerController {
this._events.move = Logger.addContinuedEvent(Logger.EventType.moveImage);
}
let topOffset = e.clientY - this._lastClickY;
let leftOffset = e.clientX - this._lastClickX;
this._lastClickX = e.clientX;
this._lastClickY = e.clientY;
let p = window.cvat.translate.point.rotate(e.clientX, e.clientY);
let topOffset = p.y - this._lastClickY;
let leftOffset = p.x - this._lastClickX;
this._lastClickX = p.x;
this._lastClickY = p.y;
this._model.move(topOffset, leftOffset);
}
}
@ -634,6 +690,19 @@ class PlayerController {
seek(frame) {
this._model.shift(frame, true);
}
rotate(angle) {
Logger.addEvent(Logger.EventType.rotateImage);
this._model.rotate(angle);
}
get rotateAll() {
return this._model.rotateAll;
}
set rotateAll(value) {
this._model.rotateAll = value;
}
}
@ -644,6 +713,7 @@ class PlayerView {
this._playerBackgroundUI = $('#frameBackground');
this._playerContentUI = $('#frameContent');
this._playerGridUI = $('#frameGrid');
this._playerTextUI = $('#frameText');
this._progressUI = $('#playerProgress');
this._loadingUI = $('#frameLoadingAnim');
this._playButtonUI = $('#playButton');
@ -661,6 +731,23 @@ class PlayerView {
this._playerGridPattern = $('#playerGridPattern');
this._playerGridPath = $('#playerGridPath');
this._contextMenuUI = $('#playerContextMenu');
this._clockwiseRotationButtonUI = $('#clockwiseRotation');
this._counterClockwiseRotationButtonUI = $('#counterClockwiseRotation');
this._rotationWrapperUI = $('#rotationWrapper');
this._rotatateAllImagesUI = $('#rotateAllImages');
this._clockwiseRotationButtonUI.on('click', () => {
this._controller.rotate(90);
});
this._counterClockwiseRotationButtonUI.on('click', () => {
this._controller.rotate(-90);
});
this._rotatateAllImagesUI.prop("checked", this._controller.rotateAll);
this._rotatateAllImagesUI.on("change", (e) => {
this._controller.rotateAll = e.target.checked;
});
$('*').on('mouseup.player', () => this._controller.frameMouseUp());
this._playerContentUI.on('mousedown', (e) => {
@ -673,9 +760,9 @@ class PlayerView {
e.preventDefault();
});
this._playerUI.on('wheel', (e) => this._controller.zoom(e));
this._playerUI.on('dblclick', () => this._controller.fit());
this._playerUI.on('mousemove', (e) => this._controller.frameMouseMove(e));
this._playerContentUI.on('wheel', (e) => this._controller.zoom(e, this._playerBackgroundUI[0]));
this._playerContentUI.on('dblclick', () => this._controller.fit());
this._playerContentUI.on('mousemove', (e) => this._controller.frameMouseMove(e));
this._progressUI.on('mousedown', (e) => this._controller.progressMouseDown(e));
this._progressUI.on('mouseup', () => this._controller.progressMouseUp());
this._progressUI.on('mousemove', (e) => this._controller.progressMouseMove(e));
@ -699,6 +786,12 @@ class PlayerView {
});
let shortkeys = window.cvat.config.shortkeys;
this._clockwiseRotationButtonUI.attr('title', `
${shortkeys['clockwise_rotation'].view_value} - ${shortkeys['clockwise_rotation'].description}`);
this._counterClockwiseRotationButtonUI.attr('title', `
${shortkeys['counter_clockwise_rotation'].view_value} - ${shortkeys['counter_clockwise_rotation'].description}`);
let playerGridOpacityInput = $('#playerGridOpacityInput');
playerGridOpacityInput.on('input', (e) => {
let value = Math.clamp(+e.target.value, +e.target.min, +e.target.max);
@ -879,6 +972,8 @@ class PlayerView {
this._progressUI['0'].value = frames.current - frames.start;
this._rotationWrapperUI.css("transform", `rotate(${geometry.rotation}deg)`);
for (let obj of [this._playerBackgroundUI, this._playerGridUI]) {
obj.css('width', image.width);
obj.css('height', image.height);
@ -887,12 +982,15 @@ 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 + ')');
for (let obj of [this._playerContentUI, this._playerTextUI]) {
obj.css('width', image.width + geometry.frameOffset * 2);
obj.css('height', image.height + geometry.frameOffset * 2);
obj.css('top', geometry.top - geometry.frameOffset * geometry.scale);
obj.css('left', geometry.left - geometry.frameOffset * geometry.scale);
}
this._playerContentUI.css('transform', 'scale(' + geometry.scale + ')');
this._playerTextUI.css('transform', `scale(10) rotate(${-geometry.rotation}deg)`);
this._playerGridPath.attr('stroke-width', 2 / geometry.scale);
this._frameNumber.prop('value', frames.current);
}

@ -8,7 +8,8 @@
let qUnitTests = [];
window.cvat = {
translate: {}
translate: {},
player: {},
};
// Run all tests

@ -1123,6 +1123,7 @@ class ShapeCollectionView {
this._controller = collectionController;
this._frameBackground = $('#frameBackground');
this._frameContent = SVG.adopt($('#frameContent')[0]);
this._textContent = SVG.adopt($('#frameText')[0]);
this._UIContent = $('#uiContent');
this._labelsContent = $('#labelsContent');
this._showAllInterpolationBox = $('#showAllInterBox');
@ -1141,6 +1142,7 @@ class ShapeCollectionView {
this._activeShapeUI = null;
this._scale = 1;
this._rotation = 0;
this._colorSettings = {
"fill-opacity": 0
};
@ -1495,7 +1497,7 @@ class ShapeCollectionView {
this._updateLabelUIs();
function drawView(shape, model) {
let view = buildShapeView(model, buildShapeController(model), this._frameContent, this._UIContent);
let view = buildShapeView(model, buildShapeController(model), this._frameContent, this._UIContent, this._textContent);
view.draw(shape.interpolation);
view.updateColorSettings(this._colorSettings);
model.subscribe(view);
@ -1509,7 +1511,13 @@ class ShapeCollectionView {
if (!player.ready()) this._frameContent.addClass('hidden');
else this._frameContent.removeClass('hidden');
if (this._scale === player.geometry.scale) return;
let geometry = player.geometry;
if (this._rotation != geometry.rotation) {
this._rotation = geometry.rotation;
this._controller.resetActive();
}
if (this._scale === geometry.scale) return;
this._scale = player.geometry.scale;
let scaledR = POINT_RADIUS / this._scale;

@ -184,6 +184,7 @@ class ShapeCreatorView {
this._typeSelector = $('#shapeTypeSelector');
this._polyShapeSizeInput = $('#polyShapeSize');
this._frameContent = SVG.adopt($('#frameContent')[0]);
this._frameText = SVG.adopt($("#frameText")[0]);
this._playerFrame = $('#playerFrame');
this._createButton.on('click', () => this._controller.switchCreateMode(false));
this._drawInstance = null;
@ -406,7 +407,7 @@ class ShapeCreatorView {
this._controller.switchCreateMode(true);
}.bind(this)).on('drawupdate', (e) => {
sizeUI = drawBoxSize.call(sizeUI, this._frameContent, e.target);
sizeUI = drawBoxSize.call(sizeUI, this._frameContent, this._frameText, e.target.getBBox());
}).on('drawcancel', () => {
if (sizeUI) {
sizeUI.rm();

@ -1399,7 +1399,7 @@ class PolygonController extends PolyShapeController {
/******************************** SHAPE VIEWS ********************************/
class ShapeView extends Listener {
constructor(shapeModel, shapeController, svgScene, menusScene) {
constructor(shapeModel, shapeController, svgScene, menusScene, textsScene) {
super('onShapeViewUpdate', () => this);
this._uis = {
menu: null,
@ -1412,7 +1412,8 @@ class ShapeView extends Listener {
this._scenes = {
svg: svgScene,
menus: menusScene
menus: menusScene,
texts: textsScene
};
this._appearance = {
@ -1512,6 +1513,23 @@ class ShapeView extends Listener {
this.notify('resize');
});
let centers = ['t', 'r', 'b', 'l'];
let corners = ['lt', 'rt', 'rb', 'lb'];
let elements = {};
for (let i = 0; i < 4; ++i) {
elements[centers[i]] = $(`.svg_select_points_${centers[i]}`);
elements[corners[i]] = $(`.svg_select_points_${corners[i]}`);
}
let angle = window.cvat.player.rotation;
let offset = angle / 90 < 0 ? angle / 90 + centers.length : angle / 90;
for (let i = 0; i < 4; ++i) {
elements[centers[i]].removeClass(`svg_select_points_${centers[i]}`)
.addClass(`svg_select_points_${centers[(i+offset) % centers.length]}`);
elements[corners[i]].removeClass(`svg_select_points_${corners[i]}`)
.addClass(`svg_select_points_${corners[(i+offset) % centers.length]}`);
}
this._updateColorForDots();
let self = this;
@ -1678,7 +1696,7 @@ class ShapeView extends Listener {
_hideShapeText() {
if (this._uis.text && this._uis.text.node.parentElement) {
this._scenes.svg.node.removeChild(this._uis.text.node);
this._scenes.texts.node.removeChild(this._uis.text.node);
}
}
@ -1689,7 +1707,7 @@ class ShapeView extends Listener {
this._drawShapeText(this._controller.interpolate(frame).attributes);
}
else if (!this._uis.text.node.parentElement) {
this._scenes.svg.node.appendChild(this._uis.text.node);
this._scenes.texts.node.appendChild(this._uis.text.node);
}
this.updateShapeTextPosition();
@ -1701,18 +1719,16 @@ class ShapeView extends Listener {
if (this._uis.shape) {
let id = this._controller.id;
let label = ShapeView.labels()[this._controller.label];
let bbox = this._uis.shape.node.getBBox();
let x = bbox.x + bbox.width + TEXT_MARGIN;
this._uis.text = this._scenes.svg.text((add) => {
add.tspan(`${label.normalize()} ${id}`).addClass('bold');
this._uis.text = this._scenes.texts.text((add) => {
add.tspan(`${label.normalize()} ${id}`).style("text-transform", "uppercase");
for (let attrId in attributes) {
let value = attributes[attrId].value != AAMUndefinedKeyword ?
attributes[attrId].value : '';
let name = attributes[attrId].name;
add.tspan(`${name}: ${value}`).attr({ dy: '1em', x: x, attrId: attrId});
add.tspan(`${name}: ${value}`).attr({ dy: '1em', x: 0, attrId: attrId});
}
}).move(x, bbox.y).addClass('shapeText regular');
}).move(0, 0).addClass('shapeText bold');
}
}
@ -2461,21 +2477,29 @@ class ShapeView extends Listener {
}
if (this._uis.text && this._uis.text.node.parentElement) {
let revscale = 1 / scale;
let shapeBBox = this._uis.shape.node.getBBox();
let shapeBBox = window.cvat.translate.box.canvasToClient(this._scenes.svg.node, this._uis.shape.node.getBBox());
let textBBox = this._uis.text.node.getBBox();
let x = shapeBBox.x + shapeBBox.width + TEXT_MARGIN * revscale;
let y = shapeBBox.y;
let drawPoint = {
x: shapeBBox.x + shapeBBox.width + TEXT_MARGIN,
y: shapeBBox.y
};
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;
const textContentScale = 10;
if ((drawPoint.x + textBBox.width * textContentScale) > this._rightBorderFrame) {
drawPoint = {
x: shapeBBox.x + TEXT_MARGIN,
y: shapeBBox.y
};
}
this._uis.text.move(x / revscale, y / revscale);
this._uis.text.attr('transform', `scale(${revscale})`);
let textPoint = window.cvat.translate.point.clientToCanvas(
this._scenes.texts.node,
drawPoint.x,
drawPoint.y
);
this._uis.text.move(textPoint.x, textPoint.y);
for (let tspan of this._uis.text.lines().members) {
tspan.attr('x', this._uis.text.attr('x'));
@ -2758,8 +2782,8 @@ ShapeView.labels = function() {
class BoxView extends ShapeView {
constructor(boxModel, boxController, svgScene, menusScene) {
super(boxModel, boxController, svgScene, menusScene);
constructor(boxModel, boxController, svgScene, menusScene, textsScene) {
super(boxModel, boxController, svgScene, menusScene, textsScene);
this._uis.boxSize = null;
}
@ -2774,9 +2798,9 @@ class BoxView extends ShapeView {
this._uis.boxSize = null;
}
this._uis.boxSize = drawBoxSize(this._scenes.svg, e.target);
this._uis.boxSize = drawBoxSize(this._scenes.svg, this._scenes.texts, e.target.getBBox());
}).on('resizing', (e) => {
this._uis.boxSize = drawBoxSize.call(this._uis.boxSize, this._scenes.svg, e.target);
this._uis.boxSize = drawBoxSize.call(this._uis.boxSize, this._scenes.svg, this._scenes.texts, e.target.getBBox());
}).on('resizedone', () => {
this._uis.boxSize.rm();
});
@ -2827,8 +2851,8 @@ class BoxView extends ShapeView {
class PolyShapeView extends ShapeView {
constructor(polyShapeModel, polyShapeController, svgScene, menusScene) {
super(polyShapeModel, polyShapeController, svgScene, menusScene);
constructor(polyShapeModel, polyShapeController, svgScene, menusScene, textsScene) {
super(polyShapeModel, polyShapeController, svgScene, menusScene, textsScene);
}
@ -2918,8 +2942,8 @@ class PolyShapeView extends ShapeView {
class PolygonView extends PolyShapeView {
constructor(polygonModel, polygonController, svgContent, UIContent) {
super(polygonModel, polygonController, svgContent, UIContent);
constructor(polygonModel, polygonController, svgContent, UIContent, textsScene) {
super(polygonModel, polygonController, svgContent, UIContent, textsScene);
}
_drawShapeUI(position) {
@ -2958,8 +2982,8 @@ class PolygonView extends PolyShapeView {
class PolylineView extends PolyShapeView {
constructor(polylineModel, polylineController, svgScene, menusScene) {
super(polylineModel, polylineController, svgScene, menusScene);
constructor(polylineModel, polylineController, svgScene, menusScene, textsScene) {
super(polylineModel, polylineController, svgScene, menusScene, textsScene);
}
@ -3031,8 +3055,8 @@ class PolylineView extends PolyShapeView {
class PointsView extends PolyShapeView {
constructor(pointsModel, pointsController, svgScene, menusScene) {
super(pointsModel, pointsController, svgScene, menusScene);
constructor(pointsModel, pointsController, svgScene, menusScene, textsScene) {
super(pointsModel, pointsController, svgScene, menusScene, textsScene);
this._uis.points = null;
}
@ -3220,20 +3244,20 @@ function buildShapeController(shapeModel) {
}
function buildShapeView(shapeModel, shapeController, svgContent, UIContent) {
function buildShapeView(shapeModel, shapeController, svgContent, UIContent, textsContent) {
switch (shapeModel.type) {
case 'interpolation_box':
case 'annotation_box':
return new BoxView(shapeModel, shapeController, svgContent, UIContent);
return new BoxView(shapeModel, shapeController, svgContent, UIContent, textsContent);
case 'interpolation_points':
case 'annotation_points':
return new PointsView(shapeModel, shapeController, svgContent, UIContent);
return new PointsView(shapeModel, shapeController, svgContent, UIContent, textsContent);
case 'interpolation_polyline':
case 'annotation_polyline':
return new PolylineView(shapeModel, shapeController, svgContent, UIContent);
return new PolylineView(shapeModel, shapeController, svgContent, UIContent, textsContent);
case 'interpolation_polygon':
case 'annotation_polygon':
return new PolygonView(shapeModel, shapeController, svgContent, UIContent);
return new PolygonView(shapeModel, shapeController, svgContent, UIContent, textsContent);
}
throw Error('Unreacheable code was reached.');
}

@ -286,7 +286,19 @@ class Config {
value: 'esc',
view_value: "Esc",
description: "cancel active mode"
}
},
clockwise_rotation: {
value: 'ctrl+r',
view_value: 'Ctrl + R',
description: 'clockwise image rotation'
},
counter_clockwise_rotation: {
value: 'ctrl+shift+r',
view_value: 'Ctrl + Shift + R',
description: 'counter clockwise image rotation'
},
};
if (window.cvat && window.cvat.job && window.cvat.job.z_order) {

@ -225,11 +225,11 @@
}
.shapeText {
font-size: 1.2em;
font-size: 0.12em;
fill: white;
text-shadow: 0px 0px 3px black;
stroke:black;
stroke-width: 0.05;
cursor: default;
pointer-events: none;
}
.highlightedShape {
@ -424,19 +424,30 @@
position: relative;
}
#rotationWrapper {
width: 100%;
height: 100%;
transform-origin: center center;
}
#frameContent {
position: absolute;
z-index: 1;
z-index: 2;
outline: 10px solid black;
-moz-transform-origin: top left;
-webkit-transform-origin: top left;
transform-origin: top left;
}
#frameText {
position: absolute;
z-index: 3;
transform-origin: center center;
pointer-events: none;
}
#frameGrid {
position: absolute;
z-index: 2;
-moz-transform-origin: top left;
-webkit-transform-origin: top left;
transform-origin: top left;
pointer-events: none;
}
@ -444,8 +455,7 @@
position: absolute;
z-index: 0;
background-repeat: no-repeat;
-moz-transform-origin: top left;
-webkit-transform-origin: top left;
transform-origin: top left;
}
#frameLoadingAnimation {

@ -66,39 +66,42 @@
<div id="taskAnnotationCenterPanel">
<div id="player">
<div id="playerFrame">
<svg id="frameLoadingAnim" style="width: 100%; height: 100%;" class="hidden">
<circle r="30" cx="50%" cy="50%" id="frameLoadingAnimation"/>
</svg>
<svg id="frameContent"> </svg>
<svg id="frameBackground"> </svg>
<svg id="frameGrid" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="playerGridPattern" width="100" height="100" patternUnits="userSpaceOnUse">
<!-- Max size value for grid is 1000. Path size should be >= such value in order to it displayed correct -->
<path id="playerGridPath" d="M 1000 0 L 0 0 0 1000" fill="none" stroke="white" opacity="0" stroke-width="2"/>
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#playerGridPattern)" />
</svg>
<div id="rotationWrapper">
<svg id="frameLoadingAnim" style="width: 100%; height: 100%;" class="hidden">
<circle r="30" cx="50%" cy="50%" id="frameLoadingAnimation"/>
</svg>
<svg id="frameContent"> </svg>
<svg id="frameText"> </svg>
<svg id="frameBackground"> </svg>
<svg id="frameGrid" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="playerGridPattern" width="100" height="100" patternUnits="userSpaceOnUse">
<!-- Max size value for grid is 1000. Path size should be >= such value in order to it displayed correct -->
<path id="playerGridPath" d="M 1000 0 L 0 0 0 1000" fill="none" stroke="white" opacity="0" stroke-width="2"/>
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#playerGridPattern)" />
</svg>
<ul id="shapeContextMenu" class='custom-menu' oncontextmenu="return false;">
<li action="object_url"> Copy Object URL </li>
<li action="change_color"> Change Color </li>
<li action="remove_shape"> Remove Shape </li>
<li action="switch_occluded"> Switch Occluded </li>
<li action="switch_lock"> Switch Lock </li>
<li class="interpolationItem" action="split_track"> Split </li>
<li class="polygonItem" action="drag_polygon"> Enable Dragging </li>
</ul>
<ul id="shapeContextMenu" class='custom-menu' oncontextmenu="return false;">
<li action="object_url"> Copy Object URL </li>
<li action="change_color"> Change Color </li>
<li action="remove_shape"> Remove Shape </li>
<li action="switch_occluded"> Switch Occluded </li>
<li action="switch_lock"> Switch Lock </li>
<li class="interpolationItem" action="split_track"> Split </li>
<li class="polygonItem" action="drag_polygon"> Enable Dragging </li>
</ul>
<ul id="playerContextMenu" class='custom-menu' oncontextmenu="return false;">
<li action="job_url"> Copy Job URL </li>
<li action="frame_url"> Copy Frame URL </li>
</ul>
<ul id="playerContextMenu" class='custom-menu' oncontextmenu="return false;">
<li action="job_url"> Copy Job URL </li>
<li action="frame_url"> Copy Frame URL </li>
</ul>
<ul id="pointContextMenu" class='custom-menu' oncontextmenu="return false;">
<li action="remove_point"> Remove </li>
</ul>
<ul id="pointContextMenu" class='custom-menu' oncontextmenu="return false;">
<li action="remove_point"> Remove </li>
</ul>
</div>
</div>
<div id="playerPanel">
<svg id="firstButton" class="playerButton">
@ -146,8 +149,11 @@
<option id="lastRedoText" title="Redo Action"> None </option>
</select>
<button class="regular h2" id="redoButton" disabled> &#10227; </button>
<label class="regular h2" style="margin-left: 15px;"> Propagation: </label>
<label class="regular h2" style="margin-left: 15px;"> Propagation </label>
<input type ="number" id="propagateFramesInput" style="width: 3em" min="1" max="10000" value="50" class="regular h2"/>
<label class="regular h2" style="margin-left: 15px;"> Rotation </label>
<button class="regular h2" id="clockwiseRotation" title="Clockwise rotation"> &#10227; </button>
<button class="regular h2" id="counterClockwiseRotation" title="Counter clockwise rotation"> &#10226; </button>
<div style="float: right;">
<label class="regular h2"> Frame </label>
<input class="regular h2" style="width: 3.5em;" type="number" id="frameNumber">
@ -306,6 +312,10 @@
<td> <label> Auto Saving Interval (Min) </label> </td>
<td> <input type = "number" id="autoSaveTime" style="width: 3em" min="5" max="60" value="15" class="regular h2"/> </td>
</tr>
<tr>
<td> <label> Rotate All Images </label> </td>
<td> <input type = "checkbox" id="rotateAllImages" class="settingsBox"/> </td>
</tr>
</table>
</div>

Loading…
Cancel
Save