Added rotated bounding boxes (#3832)

Co-authored-by: Maxim Zhiltsov <maxim.zhiltsov@intel.com>
main
Boris Sekachev 4 years ago committed by GitHub
parent 14262fa951
commit 7bab58e1a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -193,7 +193,7 @@
"type": "node",
"request": "launch",
"name": "jest debug",
"program": "${workspaceFolder}/cvat-core/node_modules/.bin/jest",
"program": "${workspaceFolder}/node_modules/.bin/jest",
"args": [
"--config",
"${workspaceFolder}/cvat-core/jest.config.js"

@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add LFW format (<https://github.com/openvinotoolkit/cvat/pull/3770>)
- Add Cityscapes format (<https://github.com/openvinotoolkit/cvat/pull/3758>)
- Add Open Images V6 format (<https://github.com/openvinotoolkit/cvat/pull/3679>)
- Rotated bounding boxes (<https://github.com/openvinotoolkit/cvat/pull/3832>)
### Changed
- TDB

@ -137,6 +137,10 @@ polyline.cvat_canvas_shape_splitting {
stroke-dasharray: 5;
}
.svg_select_points_rot {
fill: white;
}
.cvat_canvas_shape .svg_select_points,
.cvat_canvas_shape .cvat_canvas_cuboid_projections {
stroke-dasharray: none;
@ -166,8 +170,9 @@ polyline.cvat_canvas_shape_splitting {
.cvat_canvas_removable_interaction_point {
cursor:
url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAxMCAxMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEgMUw5IDlNMSA5TDkgMSIgc3Ryb2tlPSJibGFjayIvPgo8L3N2Zz4K')
10 10,
url(
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAxMCAxMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEgMUw5IDlNMSA5TDkgMSIgc3Ryb2tlPSJibGFjayIvPgo8L3N2Zz4K'
) 10 10,
auto;
}

@ -31,6 +31,7 @@ import {
vectorLength,
ShapeSizeElement,
DrawnState,
rotate2DPoints,
} from './shared';
import {
CanvasModel,
@ -85,6 +86,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
private interactionHandler: InteractionHandler;
private activeElement: ActiveElement;
private configuration: Configuration;
private snapToAngleResize: number;
private serviceFlags: {
drawHidden: Record<number, boolean>;
};
@ -112,6 +114,39 @@ export class CanvasViewImpl implements CanvasView, Listener {
return points.map((coord: number): number => coord - offset);
}
private translatePointsFromRotatedShape(shape: SVG.Shape, points: number[]): number[] {
const { rotation } = shape.transform();
// currently shape is rotated and shifted somehow additionally (css transform property)
// let's remove rotation to get correct transformation matrix (element -> screen)
// correct means that we do not consider points to be rotated
// because rotation property is stored separately and already saved
shape.rotate(0);
const result = [];
try {
// get each point and apply a couple of matrix transformation to it
const point = this.content.createSVGPoint();
// matrix to convert from ELEMENT file system to CLIENT coordinate system
const ctm = ((shape.node as any) as SVGRectElement | SVGPolygonElement | SVGPolylineElement).getScreenCTM();
// matrix to convert from CLIENT coordinate system to CANVAS coordinate system
const ctm1 = this.content.getScreenCTM().inverse();
// NOTE: I tried to use element.getCTM(), but this way does not work on firefox
for (let i = 0; i < points.length; i += 2) {
point.x = points[i];
point.y = points[i + 1];
let transformedPoint = point.matrixTransform(ctm);
transformedPoint = transformedPoint.matrixTransform(ctm1);
result.push(transformedPoint.x, transformedPoint.y);
}
} finally {
shape.rotate(rotation);
}
return result;
}
private stringifyToCanvas(points: number[]): string {
return points.reduce((acc: string, val: number, idx: number): string => {
if (idx % 2) {
@ -199,7 +234,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
}
}
private onDrawDone(data: object | null, duration: number, continueDraw?: boolean): void {
private onDrawDone(data: any | null, duration: number, continueDraw?: boolean): void {
const hiddenBecauseOfDraw = Object.keys(this.serviceFlags.drawHidden).map((_clientID): number => +_clientID);
if (hiddenBecauseOfDraw.length) {
for (const hidden of hiddenBecauseOfDraw) {
@ -256,7 +291,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
}
}
private onEditDone(state: any, points: number[]): void {
private onEditDone(state: any, points: number[], rotation?: number): void {
if (state && points) {
const event: CustomEvent = new CustomEvent('canvas.edited', {
bubbles: false,
@ -264,6 +299,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
detail: {
state,
points,
rotation: typeof rotation === 'number' ? rotation : state.rotation,
},
});
@ -388,7 +424,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
}
private onFindObject(e: MouseEvent): void {
if (e.which === 1 || e.which === 0) {
if (e.button === 0) {
const { offset } = this.controller.geometry;
const [x, y] = translateToSVG(this.content, [e.clientX, e.clientY]);
const event: CustomEvent = new CustomEvent('canvas.find', {
@ -483,7 +519,10 @@ export class CanvasViewImpl implements CanvasView, Listener {
this.gridPath.setAttribute('stroke-width', `${consts.BASE_GRID_WIDTH / this.geometry.scale}px`);
// Transform all shape points
for (const element of window.document.getElementsByClassName('svg_select_points')) {
for (const element of [
...window.document.getElementsByClassName('svg_select_points'),
...window.document.getElementsByClassName('svg_select_points_rot'),
]) {
element.setAttribute('stroke-width', `${consts.POINTS_STROKE_WIDTH / this.geometry.scale}`);
element.setAttribute('r', `${consts.BASE_POINT_SIZE / this.geometry.scale}`);
}
@ -744,12 +783,11 @@ export class CanvasViewImpl implements CanvasView, Listener {
if (e.button !== 0) return;
e.preventDefault();
const pointID = Array.prototype.indexOf.call(
((e.target as HTMLElement).parentElement as HTMLElement).children,
e.target,
);
if (this.activeElement.clientID !== null) {
const pointID = Array.prototype.indexOf.call(
((e.target as HTMLElement).parentElement as HTMLElement).children,
e.target,
);
const [state] = this.controller.objects.filter(
(_state: any): boolean => _state.clientID === this.activeElement.clientID,
);
@ -821,13 +859,12 @@ export class CanvasViewImpl implements CanvasView, Listener {
e.preventDefault();
};
const getGeometry = (): Geometry => this.geometry;
if (value) {
const getGeometry = (): Geometry => this.geometry;
(shape as any).selectize(value, {
deepSelect: true,
pointSize: (2 * consts.BASE_POINT_SIZE) / this.geometry.scale,
rotationPoint: false,
rotationPoint: shape.type === 'rect',
pointType(cx: number, cy: number): SVG.Circle {
const circle: SVG.Circle = this.nested
.circle(this.options.pointSize)
@ -874,8 +911,45 @@ export class CanvasViewImpl implements CanvasView, Listener {
if (handler && handler.nested) {
handler.nested.fill(shape.attr('fill'));
}
const [rotationPoint] = window.document.getElementsByClassName('svg_select_points_rot');
if (rotationPoint && !rotationPoint.children.length) {
const title = document.createElementNS('http://www.w3.org/2000/svg', 'title');
title.textContent = 'Hold Shift to snap angle';
rotationPoint.appendChild(title);
}
}
private onShiftKeyDown = (e: KeyboardEvent): void => {
if (!e.repeat && e.code.toLowerCase().includes('shift')) {
this.snapToAngleResize = consts.SNAP_TO_ANGLE_RESIZE_SHIFT;
if (this.activeElement) {
const shape = this.svgShapes[this.activeElement.clientID];
if (shape && shape.hasClass('cvat_canvas_shape_activated')) {
(shape as any).resize({ snapToAngle: this.snapToAngleResize });
}
}
}
};
private onShiftKeyUp = (e: KeyboardEvent): void => {
if (e.code.toLowerCase().includes('shift') && this.activeElement) {
this.snapToAngleResize = consts.SNAP_TO_ANGLE_RESIZE_DEFAULT;
if (this.activeElement) {
const shape = this.svgShapes[this.activeElement.clientID];
if (shape && shape.hasClass('cvat_canvas_shape_activated')) {
(shape as any).resize({ snapToAngle: this.snapToAngleResize });
}
}
}
};
private onMouseUp = (event: MouseEvent): void => {
if (event.button === 0 || event.button === 1) {
this.controller.disableDrag();
}
};
public constructor(model: CanvasModel & Master, controller: CanvasController) {
this.controller = controller;
this.geometry = controller.geometry;
@ -889,6 +963,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
};
this.configuration = model.configuration;
this.mode = Mode.IDLE;
this.snapToAngleResize = consts.SNAP_TO_ANGLE_RESIZE_DEFAULT;
this.serviceFlags = {
drawHidden: {},
};
@ -1046,11 +1121,9 @@ export class CanvasViewImpl implements CanvasView, Listener {
}
});
window.document.addEventListener('mouseup', (event): void => {
if (event.which === 1 || event.which === 2) {
this.controller.disableDrag();
}
});
window.document.addEventListener('mouseup', this.onMouseUp);
window.document.addEventListener('keydown', this.onShiftKeyDown);
window.document.addEventListener('keyup', this.onShiftKeyUp);
this.content.addEventListener('wheel', (event): void => {
if (event.ctrlKey) return;
@ -1365,9 +1438,11 @@ export class CanvasViewImpl implements CanvasView, Listener {
cancelable: true,
}),
);
// We can't call namespaced svgjs event
// see - https://svgjs.dev/docs/2.7/events/
this.adoptedContent.fire('destroy');
window.document.removeEventListener('keydown', this.onShiftKeyDown);
window.document.removeEventListener('keyup', this.onShiftKeyUp);
window.document.removeEventListener('mouseup', this.onMouseUp);
this.interactionHandler.destroy();
}
if (model.imageBitmap && [UpdateReasons.IMAGE_CHANGED, UpdateReasons.OBJECTS_UPDATED].includes(reason)) {
@ -1387,6 +1462,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
const states = this.controller.objects;
const ctx = this.bitmap.getContext('2d');
ctx.imageSmoothingEnabled = false;
if (ctx) {
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, width, height);
@ -1394,31 +1470,34 @@ export class CanvasViewImpl implements CanvasView, Listener {
if (state.hidden || state.outside) continue;
ctx.fillStyle = 'white';
if (['rectangle', 'polygon', 'cuboid'].includes(state.shapeType)) {
let points = [];
let points = [...state.points];
if (state.shapeType === 'rectangle') {
points = [
state.points[0], // xtl
state.points[1], // ytl
state.points[2], // xbr
state.points[1], // ytl
state.points[2], // xbr
state.points[3], // ybr
state.points[0], // xtl
state.points[3], // ybr
];
points = rotate2DPoints(
points[0] + (points[2] - points[0]) / 2,
points[1] + (points[3] - points[1]) / 2,
state.rotation,
[
points[0], // xtl
points[1], // ytl
points[2], // xbr
points[1], // ytl
points[2], // xbr
points[3], // ybr
points[0], // xtl
points[3], // ybr
],
);
} else if (state.shapeType === 'cuboid') {
points = [
state.points[0],
state.points[1],
state.points[4],
state.points[5],
state.points[8],
state.points[9],
state.points[12],
state.points[13],
points[0],
points[1],
points[4],
points[5],
points[8],
points[9],
points[12],
points[13],
];
} else {
points = [...state.points];
}
ctx.beginPath();
ctx.moveTo(points[0], points[1]);
@ -1464,6 +1543,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
lock: state.lock,
shapeType: state.shapeType,
points: [...state.points],
rotation: state.rotation,
attributes: { ...state.attributes },
descriptions: [...state.descriptions],
zOrder: state.zOrder,
@ -1523,6 +1603,11 @@ export class CanvasViewImpl implements CanvasView, Listener {
this.activate(activeElement);
}
if (drawnState.rotation) {
// need to rotate it back before changing points
shape.untransform();
}
if (
state.points.length !== drawnState.points.length ||
state.points.some((p: number, id: number): boolean => p !== drawnState.points[id])
@ -1552,6 +1637,11 @@ export class CanvasViewImpl implements CanvasView, Listener {
}
}
if (state.rotation) {
// now, when points changed, need to rotate it to new angle
shape.rotate(state.rotation);
}
const stateDescriptions = state.descriptions;
const drawnStateDescriptions = drawnState.descriptions;
@ -1802,17 +1892,25 @@ export class CanvasViewImpl implements CanvasView, Listener {
const p1 = e.detail.handler.startPoints.point;
const p2 = e.detail.p;
const delta = 1;
const { offset } = this.controller.geometry;
const dx2 = (p1.x - p2.x) ** 2;
const dy2 = (p1.y - p2.y) ** 2;
if (Math.sqrt(dx2 + dy2) >= delta) {
const points = pointsToNumberArray(
// these points does not take into account possible transformations, applied on the element
// so, if any (like rotation) we need to map them to canvas coordinate space
let points = pointsToNumberArray(
shape.attr('points') || `${shape.attr('x')},${shape.attr('y')} ` +
`${shape.attr('x') + shape.attr('width')},` +
`${shape.attr('y') + shape.attr('height')}`,
).map((x: number): number => x - offset);
`${shape.attr('x') + shape.attr('width')},${shape.attr('y') + shape.attr('height')}`,
);
// let's keep current points, but they could be rewritten in updateObjects
this.drawnStates[clientID].points = this.translateFromCanvas(points);
const { rotation } = shape.transform();
if (rotation) {
points = this.translatePointsFromRotatedShape(shape, points);
}
this.drawnStates[state.clientID].points = points;
points = this.translateFromCanvas(points);
this.canvas.dispatchEvent(
new CustomEvent('canvas.dragshape', {
bubbles: false,
@ -1850,6 +1948,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
(shape as any)
.resize({
snapToGrid: 0.1,
snapToAngle: this.snapToAngleResize,
})
.on('resizestart', (): void => {
this.mode = Mode.RESIZE;
@ -1869,6 +1968,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
.on('resizedone', (): void => {
if (shapeSizeElement) {
shapeSizeElement.rm();
shapeSizeElement = null;
}
showDirection();
@ -1877,15 +1977,27 @@ export class CanvasViewImpl implements CanvasView, Listener {
this.mode = Mode.IDLE;
if (resized) {
const { offset } = this.controller.geometry;
let rotation = shape.transform().rotation || 0;
// be sure, that rotation in range [0; 360]
while (rotation < 0) rotation += 360;
rotation %= 360;
const points = pointsToNumberArray(
// these points does not take into account possible transformations, applied on the element
// so, if any (like rotation) we need to map them to canvas coordinate space
let points = pointsToNumberArray(
shape.attr('points') || `${shape.attr('x')},${shape.attr('y')} ` +
`${shape.attr('x') + shape.attr('width')},` +
`${shape.attr('y') + shape.attr('height')}`,
).map((x: number): number => x - offset);
`${shape.attr('x') + shape.attr('width')},${shape.attr('y') + shape.attr('height')}`,
);
this.drawnStates[state.clientID].points = points;
// let's keep current points, but they could be rewritten in updateObjects
this.drawnStates[clientID].points = this.translateFromCanvas(points);
this.drawnStates[clientID].rotation = rotation;
if (rotation) {
points = this.translatePointsFromRotatedShape(shape, points);
}
// points = this.translateFromCanvas(points);
this.canvas.dispatchEvent(
new CustomEvent('canvas.resizeshape', {
bubbles: false,
@ -1895,7 +2007,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
},
}),
);
this.onEditDone(state, points);
this.onEditDone(state, this.translateFromCanvas(points), rotation);
}
});
@ -1938,6 +2050,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
// Update text position after corresponding box has been moved, resized, etc.
private updateTextPosition(text: SVG.Text, shape: SVG.Shape): void {
if (text.node.style.display === 'none') return; // wrong transformation matrix
const { rotation } = shape.transform();
let box = (shape.node as any).getBBox();
// Translate the whole box to the client coordinate system
@ -1965,13 +2078,19 @@ export class CanvasViewImpl implements CanvasView, Listener {
}
// Translate back to text SVG
const [x, y]: number[] = translateToSVG(this.text, [
const [x, y, cx, cy]: number[] = translateToSVG(this.text, [
clientX + consts.TEXT_MARGIN,
clientY + consts.TEXT_MARGIN,
x1 + (x2 - x1) / 2,
y1 + (y2 - y1) / 2,
]);
// Finally draw a text
text.move(x, y);
if (rotation) {
text.rotate(rotation, cx, cy);
}
for (const tspan of (text.lines() as any).members) {
tspan.attr('x', text.attr('x'));
}
@ -2033,6 +2152,10 @@ export class CanvasViewImpl implements CanvasView, Listener {
.move(xtl, ytl)
.addClass('cvat_canvas_shape');
if (state.rotation) {
rect.rotate(state.rotation);
}
if (state.occluded) {
rect.addClass('cvat_canvas_shape_occluded');
}

@ -17,6 +17,8 @@ const UNDEFINED_ATTRIBUTE_VALUE = '__undefined__';
const ARROW_PATH = 'M13.162 6.284L.682.524a.483.483 0 0 0-.574.134.477.477 0 ' +
'0 0-.012.59L4.2 6.72.096 12.192a.479.479 0 0 0 .585.724l12.48-5.76a.48.48 0 0 0 0-.872z';
const BASE_PATTERN_SIZE = 5;
const SNAP_TO_ANGLE_RESIZE_DEFAULT = 0.1;
const SNAP_TO_ANGLE_RESIZE_SHIFT = 15;
export default {
BASE_STROKE_WIDTH,
@ -33,4 +35,6 @@ export default {
UNDEFINED_ATTRIBUTE_VALUE,
ARROW_PATH,
BASE_PATTERN_SIZE,
SNAP_TO_ANGLE_RESIZE_DEFAULT,
SNAP_TO_ANGLE_RESIZE_SHIFT,
};

@ -17,38 +17,25 @@ export interface InteractionHandler {
transform(geometry: Geometry): void;
interact(interactData: InteractionData): void;
configurate(config: Configuration): void;
destroy(): void;
cancel(): void;
}
export class InteractionHandlerImpl implements InteractionHandler {
private onInteraction: (shapes: InteractionResult[] | null, shapesUpdated?: boolean, isDone?: boolean) => void;
private configuration: Configuration;
private geometry: Geometry;
private canvas: SVG.Container;
private interactionData: InteractionData;
private cursorPosition: { x: number; y: number };
private shapesWereUpdated: boolean;
private interactionShapes: SVG.Shape[];
private currentInteractionShape: SVG.Shape | null;
private crosshair: Crosshair;
private threshold: SVG.Rect | null;
private thresholdRectSize: number;
private intermediateShape: PropType<InteractionData, 'intermediateShape'>;
private drawnIntermediateShape: SVG.Shape;
private thresholdWasModified: boolean;
private prepareResult(): InteractionResult[] {
@ -473,13 +460,8 @@ export class InteractionHandlerImpl implements InteractionHandler {
}
});
window.addEventListener('keyup', this.onKeyUp);
window.addEventListener('keydown', this.onKeyDown);
this.canvas.on('destroy.canvas', ():void => {
window.removeEventListener('keyup', this.onKeyUp);
window.removeEventListener('keydown', this.onKeyDown);
});
window.document.addEventListener('keyup', this.onKeyUp);
window.document.addEventListener('keydown', this.onKeyDown);
}
public transform(geometry: Geometry): void {
@ -560,4 +542,9 @@ export class InteractionHandlerImpl implements InteractionHandler {
this.release();
this.onInteraction(null);
}
public destroy(): void {
window.document.removeEventListener('keyup', this.onKeyUp);
window.document.removeEventListener('keydown', this.onKeyDown);
}
}

@ -44,6 +44,7 @@ export interface DrawnState {
source: 'AUTO' | 'MANUAL';
shapeType: string;
points?: number[];
rotation: number;
attributes: Record<number, string>;
descriptions: string[];
zOrder?: number;
@ -95,16 +96,30 @@ export function displayShapeSize(shapesContainer: SVG.Container, textContainer:
.fill('white')
.addClass('cvat_canvas_text'),
update(shape: SVG.Shape): void {
const bbox = shape.bbox();
const text = `${bbox.width.toFixed(1)}x${bbox.height.toFixed(1)}`;
const [x, y]: number[] = translateToSVG(
let text = `${Math.round(shape.width())}x${Math.round(shape.height())}px`;
if (shape.type === 'rect') {
let rotation = shape.transform().rotation || 0;
// be sure, that rotation in range [0; 360]
while (rotation < 0) rotation += 360;
rotation %= 360;
if (rotation) {
text = `${text} ${rotation.toFixed(1)}\u00B0`;
}
}
const [x, y, cx, cy]: number[] = translateToSVG(
(textContainer.node as any) as SVGSVGElement,
translateFromSVG((shapesContainer.node as any) as SVGSVGElement, [bbox.x, bbox.y]),
);
translateFromSVG((shapesContainer.node as any) as SVGSVGElement, [
shape.x(),
shape.y(),
shape.cx(),
shape.cy(),
]),
).map((coord: number): number => Math.round(coord));
this.sizeElement
.clear()
.plain(text)
.move(x + consts.TEXT_MARGIN, y + consts.TEXT_MARGIN);
.move(x + consts.TEXT_MARGIN, y + consts.TEXT_MARGIN)
.rotate(shape.transform().rotation, cx, cy);
},
rm(): void {
if (this.sizeElement) {
@ -117,6 +132,23 @@ export function displayShapeSize(shapesContainer: SVG.Container, textContainer:
return shapeSize;
}
export function rotate2DPoints(cx: number, cy: number, angle: number, points: number[]): number[] {
const rad = (Math.PI / 180) * angle;
const cos = Math.cos(rad);
const sin = Math.sin(rad);
const result = [];
for (let i = 0; i < points.length; i += 2) {
const x = points[i];
const y = points[i + 1];
result.push(
(x - cx) * cos - (y - cy) * sin + cx,
(y - cy) * cos + (x - cx) * sin + cy,
);
}
return result;
}
export function pointsToNumberArray(points: string | Point[]): number[] {
if (Array.isArray(points)) {
return points.reduce((acc: number[], point: Point): number[] => {

@ -167,18 +167,23 @@ SVG.Element.prototype.resize = function constructor(...args: any): any {
handler = this.remember('_resizeHandler');
handler.resize = function (e: any) {
const { event } = e.detail;
this.rotationPointPressed = e.type === 'rot';
if (
event.button === 0 &&
// ignore shift key for cuboid change perspective
(!event.shiftKey || this.el.parent().hasClass('cvat_canvas_shape_cuboid')) &&
!event.altKey
// ignore shift key for cuboids (change perspective) and rectangles (precise rotation)
(!event.shiftKey || (
this.el.parent().hasClass('cvat_canvas_shape_cuboid')
|| this.el.type === 'rect')
) && !event.altKey
) {
return handler.constructor.prototype.resize.call(this, e);
}
};
handler.update = function (e: any) {
this.m = this.el.node.getScreenCTM().inverse();
return handler.constructor.prototype.update.call(this, e);
if (!this.rotationPointPressed) {
this.m = this.el.node.getScreenCTM().inverse();
}
handler.constructor.prototype.update.call(this, e);
};
} else {
originalResize.call(this, ...args);

@ -1,12 +1,12 @@
{
"name": "cvat-core",
"version": "3.17.0",
"version": "3.19.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "cvat-core",
"version": "3.17.0",
"version": "3.19.0",
"license": "MIT",
"dependencies": {
"axios": "^0.21.4",

@ -1,6 +1,6 @@
{
"name": "cvat-core",
"version": "3.17.0",
"version": "3.19.0",
"description": "Part of Computer Vision Tool which presents an interface for client-side integration",
"main": "babel.config.js",
"scripts": {

@ -235,7 +235,7 @@
const object = this.objects[state.clientID];
if (typeof object === 'undefined') {
throw new ArgumentError(
'The object has not been saved yet. Call ObjectState.put([state]) before you can merge it',
'The object is not in collection yet. Call ObjectState.put([state]) before you can merge it',
);
}
return object;
@ -282,6 +282,7 @@
frame: object.frame,
points: [...object.points],
occluded: object.occluded,
rotation: object.rotation,
zOrder: object.zOrder,
outside: false,
attributes: Object.keys(object.attributes).reduce((accumulator, attrID) => {
@ -333,6 +334,7 @@
type: shapeType,
frame: +keyframe,
points: [...shape.points],
rotation: shape.rotation,
occluded: shape.occluded,
outside: shape.outside,
zOrder: shape.zOrder,
@ -442,6 +444,7 @@
const position = {
type: objectState.shapeType,
points: [...objectState.points],
rotation: objectState.rotation,
occluded: objectState.occluded,
outside: objectState.outside,
zOrder: objectState.zOrder,
@ -481,6 +484,12 @@
return shape;
});
prev.shapes.push(position);
// add extra keyframe if no other keyframes before outside
if (!prev.shapes.some((shape) => shape.frame === frame - 1)) {
prev.shapes.push(JSON.parse(JSON.stringify(position)));
prev.shapes[prev.shapes.length - 2].frame -= 1;
}
prev.shapes[prev.shapes.length - 1].outside = true;
let clientID = ++this.count;
@ -844,7 +853,7 @@
if (typeof object === 'undefined') {
throw new ArgumentError('The object has not been saved yet. Call annotations.put([state]) before');
}
const distance = object.constructor.distance(state.points, x, y);
const distance = object.constructor.distance(state.points, x, y, state.rotation);
if (distance !== null && (minimumDistance === null || distance < minimumDistance)) {
minimumDistance = distance;
minimumState = state;

@ -84,30 +84,34 @@
return area >= MIN_SHAPE_AREA;
}
function fitPoints(shapeType, points, maxX, maxY) {
const fittedPoints = [];
for (let i = 0; i < points.length - 1; i += 2) {
const x = points[i];
const y = points[i + 1];
function rotatePoint(x, y, angle, cx = 0, cy = 0) {
const sin = Math.sin((angle * Math.PI) / 180);
const cos = Math.cos((angle * Math.PI) / 180);
const rotX = (x - cx) * cos - (y - cy) * sin + cx;
const rotY = (y - cy) * cos + (x - cx) * sin + cy;
return [rotX, rotY];
}
checkObjectType('coordinate', x, 'number', null);
checkObjectType('coordinate', y, 'number', null);
function fitPoints(shapeType, points, rotation, maxX, maxY) {
checkObjectType('rotation', rotation, 'number', null);
points.forEach((coordinate) => checkObjectType('coordinate', coordinate, 'number', null));
fittedPoints.push(Math.clamp(x, 0, maxX), Math.clamp(y, 0, maxY));
if (shapeType === ObjectShape.CUBOID || !!rotation) {
// cuboids and rotated bounding boxes cannot be fitted
return points;
}
return shapeType === ObjectShape.CUBOID ? points : fittedPoints;
}
const fittedPoints = [];
function checkOutside(points, width, height) {
let inside = false;
for (let i = 0; i < points.length - 1; i += 2) {
const [x, y] = points.slice(i);
inside = inside || (x >= 0 && x <= width && y >= 0 && y <= height);
const x = points[i];
const y = points[i + 1];
const clampedX = Math.clamp(x, 0, maxX);
const clampedY = Math.clamp(y, 0, maxY);
fittedPoints.push(clampedX, clampedY);
}
return !inside;
return fittedPoints;
}
function validateAttributeValue(value, attr) {
@ -345,13 +349,13 @@
checkNumberOfPoints(this.shapeType, data.points);
// cut points
const { width, height, filename } = this.frameMeta[frame];
fittedPoints = fitPoints(this.shapeType, data.points, width, height);
fittedPoints = fitPoints(this.shapeType, data.points, data.rotation, width, height);
let check = true;
if (filename && filename.slice(filename.length - 3) === 'pcd') {
check = false;
}
if (check) {
if (!checkShapeArea(this.shapeType, fittedPoints) || checkOutside(fittedPoints, width, height)) {
if (!checkShapeArea(this.shapeType, fittedPoints)) {
fittedPoints = [];
}
}
@ -492,6 +496,7 @@
constructor(data, clientID, color, injection) {
super(data, clientID, color, injection);
this.points = data.points;
this.rotation = data.rotation || 0;
this.occluded = data.occluded;
this.zOrder = data.z_order;
}
@ -504,6 +509,7 @@
occluded: this.occluded,
z_order: this.zOrder,
points: [...this.points],
rotation: this.rotation,
attributes: Object.keys(this.attributes).reduce((attributeAccumulator, attrId) => {
attributeAccumulator.push({
spec_id: attrId,
@ -535,6 +541,7 @@
lock: this.lock,
zOrder: this.zOrder,
points: [...this.points],
rotation: this.rotation,
attributes: { ...this.attributes },
descriptions: [...this.descriptions],
label: this.label,
@ -548,9 +555,11 @@
};
}
_savePoints(points, frame) {
_savePoints(points, rotation, frame) {
const undoPoints = this.points;
const undoRotation = this.rotation;
const redoPoints = points;
const redoRotation = rotation;
const undoSource = this.source;
const redoSource = Source.MANUAL;
@ -559,11 +568,13 @@
() => {
this.points = undoPoints;
this.source = undoSource;
this.rotation = undoRotation;
this.updated = Date.now();
},
() => {
this.points = redoPoints;
this.source = redoSource;
this.rotation = redoRotation;
this.updated = Date.now();
},
[this.clientID],
@ -572,6 +583,7 @@
this.source = Source.MANUAL;
this.points = points;
this.rotation = rotation;
}
_saveOccluded(occluded, frame) {
@ -637,6 +649,7 @@
const updated = data.updateFlags;
const fittedPoints = this._validateStateBeforeSave(frame, data, updated);
const { rotation } = data;
// Now when all fields are validated, we can apply them
if (updated.label) {
@ -652,7 +665,7 @@
}
if (updated.points && fittedPoints.length) {
this._savePoints(fittedPoints, frame);
this._savePoints(fittedPoints, rotation, frame);
}
if (updated.occluded) {
@ -696,6 +709,7 @@
zOrder: value.z_order,
points: value.points,
outside: value.outside,
rotation: value.rotation || 0,
attributes: value.attributes.reduce((attributeAccumulator, attr) => {
attributeAccumulator[attr.spec_id] = attr.value;
return attributeAccumulator;
@ -736,6 +750,7 @@
occluded: this.shapes[frame].occluded,
z_order: this.shapes[frame].zOrder,
points: [...this.shapes[frame].points],
rotation: this.shapes[frame].rotation,
outside: this.shapes[frame].outside,
attributes: Object.keys(this.shapes[frame].attributes).reduce(
(attributeAccumulator, attrId) => {
@ -1009,22 +1024,21 @@
);
}
_savePoints(points, frame) {
_savePoints(points, rotation, frame) {
const current = this.get(frame);
const wasKeyframe = frame in this.shapes;
const undoSource = this.source;
const redoSource = Source.MANUAL;
const undoShape = wasKeyframe ? this.shapes[frame] : undefined;
const redoShape = wasKeyframe ?
{ ...this.shapes[frame], points } :
{
frame,
points,
zOrder: current.zOrder,
outside: current.outside,
occluded: current.occluded,
attributes: {},
};
const redoShape = wasKeyframe ? { ...this.shapes[frame], points, rotation } : {
frame,
points,
rotation,
zOrder: current.zOrder,
outside: current.outside,
occluded: current.occluded,
attributes: {},
};
this.shapes[frame] = redoShape;
this.source = Source.MANUAL;
@ -1049,6 +1063,7 @@
{
frame,
outside,
rotation: current.rotation,
zOrder: current.zOrder,
points: current.points,
occluded: current.occluded,
@ -1078,6 +1093,7 @@
{
frame,
occluded,
rotation: current.rotation,
zOrder: current.zOrder,
points: current.points,
outside: current.outside,
@ -1107,6 +1123,7 @@
{
frame,
zOrder,
rotation: current.rotation,
occluded: current.occluded,
points: current.points,
outside: current.outside,
@ -1139,6 +1156,7 @@
const redoShape = keyframe ?
{
frame,
rotation: current.rotation,
zOrder: current.zOrder,
points: current.points,
outside: current.outside,
@ -1172,6 +1190,7 @@
const updated = data.updateFlags;
const fittedPoints = this._validateStateBeforeSave(frame, data, updated);
const { rotation } = data;
if (updated.label) {
this._saveLabel(data.label, frame);
@ -1194,7 +1213,7 @@
}
if (updated.points && fittedPoints.length) {
this._savePoints(fittedPoints, frame);
this._savePoints(fittedPoints, rotation, frame);
}
if (updated.outside) {
@ -1246,6 +1265,7 @@
if (leftPosition) {
return {
points: [...leftPosition.points],
rotation: leftPosition.rotation,
occluded: leftPosition.occluded,
outside: leftPosition.outside,
zOrder: leftPosition.zOrder,
@ -1256,10 +1276,11 @@
if (rightPosition) {
return {
points: [...rightPosition.points],
rotation: rightPosition.rotation,
occluded: rightPosition.occluded,
outside: true,
zOrder: rightPosition.zOrder,
keyframe: targetFrame in this.shapes,
outside: true,
};
}
@ -1356,20 +1377,28 @@
checkNumberOfPoints(this.shapeType, this.points);
}
static distance(points, x, y) {
static distance(points, x, y, angle) {
const [xtl, ytl, xbr, ybr] = points;
const cx = xtl + (xbr - xtl) / 2;
const cy = ytl + (ybr - ytl) / 2;
const [rotX, rotY] = rotatePoint(x, y, -angle, cx, cy);
if (!(x >= xtl && x <= xbr && y >= ytl && y <= ybr)) {
if (!(rotX >= xtl && rotX <= xbr && rotY >= ytl && rotY <= ybr)) {
// Cursor is outside of a box
return null;
}
// The shortest distance from point to an edge
return Math.min.apply(null, [x - xtl, y - ytl, xbr - x, ybr - y]);
return Math.min.apply(null, [rotX - xtl, rotY - ytl, xbr - rotX, ybr - rotY]);
}
}
class PolyShape extends Shape {}
class PolyShape extends Shape {
constructor(data, clientID, color, injection) {
super(data, clientID, color, injection);
this.rotation = 0; // is not supported
}
}
class PolygonShape extends PolyShape {
constructor(data, clientID, color, injection) {
@ -1509,6 +1538,7 @@
class CuboidShape extends Shape {
constructor(data, clientID, color, injection) {
super(data, clientID, color, injection);
this.rotation = 0;
this.shapeType = ObjectShape.CUBOID;
this.pinned = false;
checkNumberOfPoints(this.shapeType, this.points);
@ -1638,10 +1668,25 @@
}
interpolatePosition(leftPosition, rightPosition, offset) {
function findAngleDiff(rightAngle, leftAngle) {
let angleDiff = rightAngle - leftAngle;
angleDiff = ((angleDiff + 180) % 360) - 180;
if (Math.abs(angleDiff) >= 180) {
// if the main arc is bigger than 180, go another arc
// to find it, just substract absolute value from 360 and inverse sign
angleDiff = 360 - Math.abs(angleDiff) * Math.sign(angleDiff) * -1;
}
return angleDiff;
}
const positionOffset = leftPosition.points.map((point, index) => rightPosition.points[index] - point);
return {
points: leftPosition.points.map((point, index) => point + positionOffset[index] * offset),
rotation:
(leftPosition.rotation + findAngleDiff(
rightPosition.rotation, leftPosition.rotation,
) * offset + 360) % 360,
occluded: leftPosition.occluded,
outside: leftPosition.outside,
zOrder: leftPosition.zOrder,
@ -1650,10 +1695,18 @@
}
class PolyTrack extends Track {
constructor(data, clientID, color, injection) {
super(data, clientID, color, injection);
for (const shape of Object.values(this.shapes)) {
shape.rotation = 0; // is not supported
}
}
interpolatePosition(leftPosition, rightPosition, offset) {
if (offset === 0) {
return {
points: [...leftPosition.points],
rotation: leftPosition.rotation,
occluded: leftPosition.occluded,
outside: leftPosition.outside,
zOrder: leftPosition.zOrder,
@ -1900,6 +1953,7 @@
return {
points: toArray(reducedPoints),
rotation: leftPosition.rotation,
occluded: leftPosition.occluded,
outside: leftPosition.outside,
zOrder: leftPosition.zOrder,
@ -1962,6 +2016,7 @@
points: leftPosition.points.map(
(value, index) => value + (rightPosition.points[index] - value) * offset,
),
rotation: leftPosition.rotation,
occluded: leftPosition.occluded,
outside: leftPosition.outside,
zOrder: leftPosition.zOrder,
@ -1970,6 +2025,7 @@
return {
points: [...leftPosition.points],
rotation: leftPosition.rotation,
occluded: leftPosition.occluded,
outside: leftPosition.outside,
zOrder: leftPosition.zOrder,
@ -1984,6 +2040,7 @@
this.pinned = false;
for (const shape of Object.values(this.shapes)) {
checkNumberOfPoints(this.shapeType, shape.points);
shape.rotation = 0; // is not supported
}
}
@ -1992,6 +2049,7 @@
return {
points: leftPosition.points.map((point, index) => point + positionOffset[index] * offset),
rotation: leftPosition.rotation,
occluded: leftPosition.occluded,
outside: leftPosition.outside,
zOrder: leftPosition.zOrder,

@ -100,6 +100,7 @@
'occluded',
'z_order',
'points',
'rotation',
'type',
'shapes',
'attributes',

@ -28,6 +28,7 @@ const { Source } = require('./enums');
descriptions: [],
points: null,
rotation: null,
outside: null,
occluded: null,
keyframe: null,
@ -204,6 +205,28 @@ const { Source } = require('./enums');
}
},
},
rotation: {
/**
* @name rotation
* @type {number} angle measured by degrees
* @memberof module:API.cvat.classes.ObjectState
* @throws {module:API.cvat.exceptions.ArgumentError}
* @instance
*/
get: () => data.rotation,
set: (rotation) => {
if (typeof rotation === 'number') {
data.updateFlags.points = true;
data.rotation = rotation;
} else {
throw new ArgumentError(
`Rotation is expected to be a number, but got ${
typeof rotation === 'object' ? rotation.constructor.name : typeof points
}`,
);
}
},
},
group: {
/**
* Object with short group info { color, id }
@ -410,6 +433,9 @@ const { Source } = require('./enums');
if (typeof serialized.color === 'string') {
this.color = serialized.color;
}
if (typeof serialized.rotation === 'number') {
this.rotation = serialized.rotation;
}
if (Array.isArray(serialized.points)) {
this.points = serialized.points;
}

@ -60,6 +60,37 @@ describe('Feature: get annotations', () => {
// TODO: Test filter (hasn't been implemented yet)
});
describe('Feature: get interpolated annotations', () => {
test('get interpolated box', async () => {
const task = (await window.cvat.tasks.get({ id: 101 }))[0];
let annotations = await task.annotations.get(5);
expect(Array.isArray(annotations)).toBeTruthy();
expect(annotations).toHaveLength(1);
const [xtl, ytl, xbr, ybr] = annotations[0].points;
const { rotation } = annotations[0];
expect(rotation).toBe(50);
expect(Math.round(xtl)).toBe(332);
expect(Math.round(ytl)).toBe(519);
expect(Math.round(xbr)).toBe(651);
expect(Math.round(ybr)).toBe(703);
annotations = await task.annotations.get(15);
expect(Array.isArray(annotations)).toBeTruthy();
expect(annotations).toHaveLength(2); // there is also a polygon on these frames (up to frame 22)
expect(annotations[1].rotation).toBe(40);
expect(annotations[1].shapeType).toBe('rectangle');
annotations = await task.annotations.get(30);
annotations[0].rotation = 20;
await annotations[0].save();
annotations = await task.annotations.get(25);
expect(annotations[0].rotation).toBe(0);
expect(annotations[0].shapeType).toBe('rectangle');
});
});
describe('Feature: put annotations', () => {
test('put a shape to a task', async () => {
const task = (await window.cvat.tasks.get({ id: 101 }))[0];

@ -1740,6 +1740,7 @@ const taskAnnotationsDummyData = {
occluded: false,
z_order: 1,
points: [425.58984375, 540.298828125, 755.9765625, 745.6328125],
rotation: 0,
id: 379,
frame: 0,
outside: false,
@ -1759,6 +1760,7 @@ const taskAnnotationsDummyData = {
occluded: false,
z_order: 1,
points: [238.8000000000011, 498.6000000000022, 546.01171875, 660.720703125],
rotation: 100,
id: 380,
frame: 10,
outside: false,
@ -1769,6 +1771,7 @@ const taskAnnotationsDummyData = {
occluded: false,
z_order: 1,
points: [13.3955078125, 447.650390625, 320.6072265624989, 609.7710937499978],
rotation: 340,
id: 381,
frame: 20,
outside: false,

@ -1,12 +1,12 @@
{
"name": "cvat-ui",
"version": "1.25.1",
"version": "1.26.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "cvat-ui",
"version": "1.25.1",
"version": "1.26.0",
"license": "MIT",
"dependencies": {
"@ant-design/icons": "^4.6.3",

@ -1,6 +1,6 @@
{
"name": "cvat-ui",
"version": "1.25.1",
"version": "1.26.0",
"description": "CVAT single-page application",
"main": "src/index.tsx",
"scripts": {

@ -525,8 +525,9 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
onEditShape(false);
const { state, points } = event.detail;
const { state, points, rotation } = event.detail;
state.points = points;
state.rotation = rotation;
onUpdateAnnotations([state]);
};

@ -38,7 +38,7 @@ const defaultState: AnnotationState = {
pointID: null,
clientID: null,
},
instance: new Canvas(),
instance: null,
ready: false,
activeControl: ActiveControl.CURSOR,
},
@ -166,7 +166,9 @@ export default (state = defaultState, action: AnyAction): AnnotationState => {
activeShapeType = ShapeType.CUBOID;
}
state.canvas.instance.destroy();
if (state.canvas.instance) {
state.canvas.instance.destroy();
}
return {
...state,
@ -705,7 +707,7 @@ export default (state = defaultState, action: AnyAction): AnnotationState => {
canvas: { activeControl, instance },
} = state;
if (activeControl !== ActiveControl.CURSOR || instance.mode() !== CanvasMode.IDLE) {
if (activeControl !== ActiveControl.CURSOR || (instance as Canvas | Canvas3d).mode() !== CanvasMode.IDLE) {
return state;
}
@ -929,7 +931,6 @@ export default (state = defaultState, action: AnyAction): AnnotationState => {
...state.annotations,
history,
states,
selectedStatesID: [],
activatedStateID: null,
collapsed: {},
},
@ -1223,6 +1224,9 @@ export default (state = defaultState, action: AnyAction): AnnotationState => {
}
case AnnotationActionTypes.CLOSE_JOB:
case AuthActionTypes.LOGOUT_SUCCESS: {
if (state.canvas.instance) {
state.canvas.instance.destroy();
}
return { ...defaultState };
}
default: {

@ -496,7 +496,7 @@ export interface AnnotationState {
pointID: number | null;
clientID: number | null;
};
instance: Canvas | Canvas3d;
instance: Canvas | Canvas3d | null;
ready: boolean;
activeControl: ActiveControl;
};

@ -429,14 +429,26 @@ class TrackManager(ObjectManager):
@staticmethod
def get_interpolated_shapes(track, start_frame, end_frame):
def copy_shape(source, frame, points=None):
def copy_shape(source, frame, points=None, rotation=None):
copied = deepcopy(source)
copied["keyframe"] = False
copied["frame"] = frame
if rotation is not None:
copied["rotation"] = rotation
if points is not None:
copied["points"] = points
return copied
def find_angle_diff(right_angle, left_angle):
angle_diff = right_angle - left_angle
angle_diff = ((angle_diff + 180) % 360) - 180
if abs(angle_diff) >= 180:
# if the main arc is bigger than 180, go another arc
# to find it, just substract absolute value from 360 and inverse sign
angle_diff = 360 - abs(angle_diff) * -1 if angle_diff > 0 else 1
return angle_diff
def simple_interpolation(shape0, shape1):
shapes = []
distance = shape1["frame"] - shape0["frame"]
@ -444,9 +456,12 @@ class TrackManager(ObjectManager):
for frame in range(shape0["frame"] + 1, shape1["frame"]):
offset = (frame - shape0["frame"]) / distance
rotation = (shape0["rotation"] + find_angle_diff(
shape1["rotation"], shape0["rotation"],
) * offset + 360) % 360
points = shape0["points"] + diff * offset
shapes.append(copy_shape(shape0, frame, points.tolist()))
shapes.append(copy_shape(shape0, frame, points.tolist(), rotation))
return shapes

@ -124,11 +124,11 @@ class InstanceLabelData:
class TaskData(InstanceLabelData):
Shape = namedtuple("Shape", 'id, label_id') # 3d
LabeledShape = namedtuple(
'LabeledShape', 'type, frame, label, points, occluded, attributes, source, group, z_order')
LabeledShape.__new__.__defaults__ = (0, 0)
'LabeledShape', 'type, frame, label, points, occluded, attributes, source, rotation, group, z_order')
LabeledShape.__new__.__defaults__ = (0, 0, 0)
TrackedShape = namedtuple(
'TrackedShape', 'type, frame, points, occluded, outside, keyframe, attributes, source, group, z_order, label, track_id')
TrackedShape.__new__.__defaults__ = ('manual', 0, 0, None, 0)
'TrackedShape', 'type, frame, points, occluded, outside, keyframe, attributes, rotation, source, group, z_order, label, track_id')
TrackedShape.__new__.__defaults__ = (0, 'manual', 0, 0, None, 0)
Track = namedtuple('Track', 'label, group, source, shapes')
Tag = namedtuple('Tag', 'frame, label, attributes, source, group')
Tag.__new__.__defaults__ = (0, )
@ -263,6 +263,7 @@ class TaskData(InstanceLabelData):
frame=self.abs_frame_id(shape["frame"]),
label=self._get_label_name(shape["label_id"]),
points=shape["points"],
rotation=shape["rotation"],
occluded=shape["occluded"],
z_order=shape.get("z_order", 0),
group=shape.get("group", 0),
@ -279,6 +280,7 @@ class TaskData(InstanceLabelData):
label=self._get_label_name(shape["label_id"]),
frame=self.abs_frame_id(shape["frame"]),
points=shape["points"],
rotation=shape["rotation"],
occluded=shape["occluded"],
z_order=shape.get("z_order", 0),
group=shape.get("group", 0),
@ -508,12 +510,12 @@ class TaskData(InstanceLabelData):
return None
class ProjectData(InstanceLabelData):
LabeledShape = NamedTuple('LabledShape', [('type', str), ('frame', int), ('label', str), ('points', List[float]), ('occluded', bool), ('attributes', List[InstanceLabelData.Attribute]), ('source', str), ('group', int), ('z_order', int), ('task_id', int)])
LabeledShape.__new__.__defaults__ = (0,0)
LabeledShape = NamedTuple('LabledShape', [('type', str), ('frame', int), ('label', str), ('points', List[float]), ('occluded', bool), ('attributes', List[InstanceLabelData.Attribute]), ('source', str), ('group', int), ('rotation', float), ('z_order', int), ('task_id', int)])
LabeledShape.__new__.__defaults__ = (0, 0, 0)
TrackedShape = NamedTuple('TrackedShape',
[('type', str), ('frame', int), ('points', List[float]), ('occluded', bool), ('outside', bool), ('keyframe', bool), ('attributes', List[InstanceLabelData.Attribute]), ('source', str), ('group', int), ('z_order', int), ('label', str), ('track_id', int)],
[('type', str), ('frame', int), ('points', List[float]), ('occluded', bool), ('outside', bool), ('keyframe', bool), ('attributes', List[InstanceLabelData.Attribute]), ('rotation', float), ('source', str), ('group', int), ('z_order', int), ('label', str), ('track_id', int)],
)
TrackedShape.__new__.__defaults__ = ('manual', 0, 0, None, 0)
TrackedShape.__new__.__defaults__ = (0, 'manual', 0, 0, None, 0)
Track = NamedTuple('Track', [('label', str), ('group', int), ('source', str), ('shapes', List[TrackedShape]), ('task_id', int)])
Tag = NamedTuple('Tag', [('frame', int), ('label', str), ('attributes', List[InstanceLabelData.Attribute]), ('source', str), ('group', int), ('task_id', int)])
Tag.__new__.__defaults__ = (0, )
@ -644,6 +646,7 @@ class ProjectData(InstanceLabelData):
frame=self.abs_frame_id(task_id, shape["frame"]),
label=self._get_label_name(shape["label_id"]),
points=shape["points"],
rotation=shape["rotation"],
occluded=shape["occluded"],
z_order=shape.get("z_order", 0),
group=shape.get("group", 0),
@ -660,6 +663,7 @@ class ProjectData(InstanceLabelData):
label=self._get_label_name(shape["label_id"]),
frame=self.abs_frame_id(task_id, shape["frame"]),
points=shape["points"],
rotation=shape["rotation"],
occluded=shape["occluded"],
z_order=shape.get("z_order", 0),
group=shape.get("group", 0),
@ -1125,6 +1129,8 @@ def convert_cvat_anno_to_dm(cvat_frame_anno, label_attrs, map_label, format_name
anno_label = map_label(shape_obj.label)
anno_attr = convert_attrs(shape_obj.label, shape_obj.attributes)
anno_attr['occluded'] = shape_obj.occluded
if shape_obj.type == ShapeType.RECTANGLE:
anno_attr['rotation'] = shape_obj.rotation
if hasattr(shape_obj, 'track_id'):
anno_attr['track_id'] = shape_obj.track_id

@ -11,6 +11,7 @@ from cvat.apps.dataset_manager.bindings import (GetCVATDataExtractor,
import_dm_annotations)
from cvat.apps.dataset_manager.util import make_zip_archive
from .transformations import RotatedBoxesToPolygons
from .registry import dm_env, exporter, importer
from .utils import make_colormap
@ -19,6 +20,7 @@ from .utils import make_colormap
def _export(dst_file, instance_data, save_images=False):
dataset = Dataset.from_extractors(GetCVATDataExtractor(
instance_data, include_images=save_images), env=dm_env)
dataset.transform(RotatedBoxesToPolygons)
dataset.transform('polygons_to_masks')
dataset.transform('boxes_to_masks')
dataset.transform('merge_instance_segments')

@ -13,6 +13,7 @@ from cvat.apps.dataset_manager.bindings import (GetCVATDataExtractor,
import_dm_annotations)
from cvat.apps.dataset_manager.util import make_zip_archive
from .transformations import RotatedBoxesToPolygons
from .registry import dm_env, exporter, importer
from .utils import make_colormap
@ -21,6 +22,7 @@ from .utils import make_colormap
def _export(dst_file, instance_data, save_images=False):
dataset = Dataset.from_extractors(GetCVATDataExtractor(
instance_data, include_images=save_images), env=dm_env)
dataset.transform(RotatedBoxesToPolygons)
dataset.transform('polygons_to_masks')
dataset.transform('boxes_to_masks')
dataset.transform('merge_instance_segments')

@ -215,6 +215,11 @@ def dump_as_cvat_annotation(dumper, annotations):
("xbr", "{:.2f}".format(shape.points[2])),
("ybr", "{:.2f}".format(shape.points[3]))
]))
if shape.rotation:
dump_data.update(OrderedDict([
("rotation", "{:.2f}".format(shape.rotation))
]))
elif shape.type == "cuboid":
dump_data.update(OrderedDict([
("xtl1", "{:.2f}".format(shape.points[0])),
@ -338,6 +343,11 @@ def dump_as_cvat_interpolation(dumper, annotations):
("xbr", "{:.2f}".format(shape.points[2])),
("ybr", "{:.2f}".format(shape.points[3])),
]))
if shape.rotation:
dump_data.update(OrderedDict([
("rotation", "{:.2f}".format(shape.rotation))
]))
elif shape.type == "cuboid":
dump_data.update(OrderedDict([
("xtl1", "{:.2f}".format(shape.points[0])),
@ -417,6 +427,7 @@ def dump_as_cvat_interpolation(dumper, annotations):
'shapes': [annotations.TrackedShape(
type=shape.type,
points=shape.points,
rotation=shape.rotation,
occluded=shape.occluded,
outside=False,
keyframe=True,
@ -428,6 +439,7 @@ def dump_as_cvat_interpolation(dumper, annotations):
[annotations.TrackedShape(
type=shape.type,
points=shape.points,
rotation=shape.rotation,
occluded=shape.occluded,
outside=True,
keyframe=True,
@ -511,6 +523,7 @@ def load(file_object, annotations):
shape['type'] = 'rectangle' if el.tag == 'box' else el.tag
shape['occluded'] = el.attrib['occluded'] == '1'
shape['z_order'] = int(el.attrib.get('z_order', 0))
shape['rotation'] = float(el.attrib.get('rotation', 0))
if el.tag == 'box':
shape['points'].append(el.attrib['xtl'])

@ -14,6 +14,7 @@ from cvat.apps.dataset_manager.bindings import (GetCVATDataExtractor,
import_dm_annotations)
from cvat.apps.dataset_manager.util import make_zip_archive
from .transformations import RotatedBoxesToPolygons
from .registry import dm_env, exporter, importer
@ -116,6 +117,7 @@ def _export_segmentation(dst_file, instance_data, save_images=False):
dataset = Dataset.from_extractors(GetCVATDataExtractor(
instance_data, include_images=save_images), env=dm_env)
with TemporaryDirectory() as temp_dir:
dataset.transform(RotatedBoxesToPolygons)
dataset.transform('polygons_to_masks')
dataset.transform('boxes_to_masks')
dataset.transform('merge_instance_segments')

@ -13,6 +13,7 @@ from cvat.apps.dataset_manager.bindings import (GetCVATDataExtractor,
ProjectData, import_dm_annotations)
from cvat.apps.dataset_manager.util import make_zip_archive
from .transformations import RotatedBoxesToPolygons
from .registry import dm_env, exporter, importer
from .utils import make_colormap
@ -23,6 +24,7 @@ def _export(dst_file, instance_data, save_images=False):
include_images=save_images), env=dm_env)
with TemporaryDirectory() as tmp_dir:
dataset.transform(RotatedBoxesToPolygons)
dataset.transform('polygons_to_masks')
dataset.transform('merge_instance_segments')
dataset.export(tmp_dir, format='kitti',

@ -11,14 +11,15 @@ from cvat.apps.dataset_manager.bindings import (GetCVATDataExtractor,
import_dm_annotations)
from cvat.apps.dataset_manager.util import make_zip_archive
from .transformations import RotatedBoxesToPolygons
from .registry import dm_env, exporter, importer
from .utils import make_colormap
@exporter(name='Segmentation mask', ext='ZIP', version='1.1')
def _export(dst_file, instance_data, save_images=False):
dataset = Dataset.from_extractors(GetCVATDataExtractor(
instance_data, include_images=save_images), env=dm_env)
dataset.transform(RotatedBoxesToPolygons)
dataset.transform('polygons_to_masks')
dataset.transform('boxes_to_masks')
dataset.transform('merge_instance_segments')

@ -13,6 +13,7 @@ from cvat.apps.dataset_manager.bindings import (GetCVATDataExtractor,
find_dataset_root, match_dm_item)
from cvat.apps.dataset_manager.util import make_zip_archive
from .transformations import RotatedBoxesToPolygons
from .registry import dm_env, exporter, importer
@ -26,6 +27,7 @@ def _export(dst_file, instance_data, save_images=False):
dataset = Dataset.from_extractors(GetCVATDataExtractor(
instance_data, include_images=save_images), env=dm_env)
dataset.transform(KeepTracks) # can only export tracks
dataset.transform(RotatedBoxesToPolygons)
dataset.transform('polygons_to_masks')
dataset.transform('boxes_to_masks')
dataset.transform('merge_instance_segments')

@ -15,6 +15,7 @@ from cvat.apps.dataset_manager.bindings import (GetCVATDataExtractor,
find_dataset_root, import_dm_annotations, match_dm_item)
from cvat.apps.dataset_manager.util import make_zip_archive
from .transformations import RotatedBoxesToPolygons
from .registry import dm_env, exporter, importer
@ -40,6 +41,7 @@ def find_item_ids(path):
def _export(dst_file, task_data, save_images=False):
dataset = Dataset.from_extractors(GetCVATDataExtractor(
task_data, include_images=save_images), env=dm_env)
dataset.transform(RotatedBoxesToPolygons)
dataset.transform('polygons_to_masks')
dataset.transform('merge_instance_segments')

@ -0,0 +1,34 @@
# Copyright (C) 2021 Intel Corporation
#
# SPDX-License-Identifier: MIT
import math
from itertools import chain
from datumaro.components.extractor import ItemTransform
import datumaro.components.annotation as datum_annotation
class RotatedBoxesToPolygons(ItemTransform):
def _rotate_point(self, p, angle, cx, cy):
[x, y] = p
rx = cx + math.cos(angle) * (x - cx) - math.sin(angle) * (y - cy)
ry = cy + math.sin(angle) * (x - cx) + math.cos(angle) * (y - cy)
return rx, ry
def transform_item(self, item):
annotations = item.annotations[:]
anns = [p for p in annotations if p.type == datum_annotation.AnnotationType.bbox and p.attributes['rotation']]
for ann in anns:
rotation = math.radians(ann.attributes['rotation'])
x0, y0, x1, y1 = ann.points
[cx, cy] = [(x0 + (x1 - x0) / 2), (y0 + (y1 - y0) / 2)]
anno_points = list(chain.from_iterable(
map(lambda p: self._rotate_point(p, rotation, cx, cy), [(x0, y0), (x1, y0), (x1, y1), (x0, y1)])
))
annotations.remove(ann)
annotations.append(datum_annotation.Polygon(anno_points,
label=ann.label, attributes=ann.attributes, group=ann.group,
z_order=ann.z_order))
return item.wrap(annotations=annotations)

@ -420,6 +420,7 @@ class JobAnnotation:
'source',
'occluded',
'z_order',
'rotation',
'points',
'labeledshapeattributeval__spec_id',
'labeledshapeattributeval__value',
@ -461,6 +462,7 @@ class JobAnnotation:
"trackedshape__type",
"trackedshape__occluded",
"trackedshape__z_order",
"trackedshape__rotation",
"trackedshape__points",
"trackedshape__id",
"trackedshape__frame",
@ -483,6 +485,7 @@ class JobAnnotation:
"trackedshape__occluded",
"trackedshape__z_order",
"trackedshape__points",
"trackedshape__rotation",
"trackedshape__id",
"trackedshape__frame",
"trackedshape__outside",

@ -104,6 +104,7 @@ class TrackManagerTest(TestCase):
{
"frame": 0,
"points": [1.0, 2.0, 3.0, 4.0],
"rotation": 0,
"type": "rectangle",
"occluded": False,
"outside": False,
@ -113,6 +114,7 @@ class TrackManagerTest(TestCase):
"frame": 2,
"attributes": [],
"points": [3.0, 4.0, 5.0, 6.0],
"rotation": 0,
"type": "rectangle",
"occluded": False,
"outside": True
@ -121,6 +123,7 @@ class TrackManagerTest(TestCase):
"frame": 4,
"attributes": [],
"points": [3.0, 4.0, 5.0, 6.0],
"rotation": 0,
"type": "rectangle",
"occluded": False,
"outside": False
@ -141,6 +144,7 @@ class TrackManagerTest(TestCase):
{
"frame": 0,
"points": [1.0, 2.0, 3.0, 4.0, 5.0, 6.0],
"rotation": 0,
"type": "polyline",
"occluded": False,
"outside": False,
@ -150,6 +154,7 @@ class TrackManagerTest(TestCase):
"frame": 2,
"attributes": [],
"points": [3.0, 4.0, 5.0, 6.0],
"rotation": 0,
"type": "polyline",
"occluded": False,
"outside": True
@ -158,6 +163,7 @@ class TrackManagerTest(TestCase):
"frame": 4,
"attributes": [],
"points": [3.0, 4.0, 5.0, 6.0],
"rotation": 0,
"type": "polyline",
"occluded": False,
"outside": False
@ -178,6 +184,7 @@ class TrackManagerTest(TestCase):
{
"frame": 0,
"points": [1.0, 2.0, 3.0, 4.0],
"rotation": 0,
"type": "rectangle",
"occluded": False,
"outside": False,
@ -186,6 +193,7 @@ class TrackManagerTest(TestCase):
{
"frame": 2,
"points": [3.0, 4.0, 5.0, 6.0],
"rotation": 0,
"type": "rectangle",
"occluded": False,
"outside": True,
@ -194,6 +202,7 @@ class TrackManagerTest(TestCase):
{
"frame": 4,
"points": [5.0, 6.0, 7.0, 8.0],
"rotation": 0,
"type": "rectangle",
"occluded": False,
"outside": True,
@ -206,6 +215,7 @@ class TrackManagerTest(TestCase):
{
"frame": 0,
"points": [1.0, 2.0, 3.0, 4.0],
"rotation": 0,
"type": "rectangle",
"occluded": False,
"outside": False,
@ -215,6 +225,7 @@ class TrackManagerTest(TestCase):
{
"frame": 1,
"points": [2.0, 3.0, 4.0, 5.0],
"rotation": 0,
"type": "rectangle",
"occluded": False,
"outside": False,
@ -224,6 +235,7 @@ class TrackManagerTest(TestCase):
{
"frame": 2,
"points": [3.0, 4.0, 5.0, 6.0],
"rotation": 0,
"type": "rectangle",
"occluded": False,
"outside": True,
@ -233,6 +245,7 @@ class TrackManagerTest(TestCase):
{
"frame": 4,
"points": [5.0, 6.0, 7.0, 8.0],
"rotation": 0,
"type": "rectangle",
"occluded": False,
"outside": True,

@ -0,0 +1,23 @@
# Generated by Django 3.1.13 on 2021-11-15 08:58
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('engine', '0043_auto_20211027_0718'),
]
operations = [
migrations.AddField(
model_name='labeledshape',
name='rotation',
field=models.FloatField(default=0),
),
migrations.AddField(
model_name='trackedshape',
name='rotation',
field=models.FloatField(default=0),
),
]

@ -10,6 +10,7 @@ from django.conf import settings
from django.contrib.auth.models import User
from django.core.files.storage import FileSystemStorage
from django.db import models
from django.db.models.fields import FloatField
from django.utils.translation import gettext_lazy as _
from cvat.apps.engine.utils import parse_specific_attributes
@ -479,6 +480,7 @@ class Shape(models.Model):
occluded = models.BooleanField(default=False)
z_order = models.IntegerField(default=0)
points = FloatArrayField()
rotation = FloatField(default=0)
class Meta:
abstract = True

@ -670,6 +670,7 @@ class ShapeSerializer(serializers.Serializer):
type = serializers.ChoiceField(choices=models.ShapeType.choices())
occluded = serializers.BooleanField()
z_order = serializers.IntegerField(default=0)
rotation = serializers.FloatField(default=0, min_value=0, max_value=360)
points = serializers.ListField(
child=serializers.FloatField(),
allow_empty=False,

@ -349,6 +349,7 @@ class Task3DTest(_DbTestBase):
"occluded": False,
"z_order": 0,
"points": [0.16, 0.20, -0.26, 0, -0.14, 0, 4.84, 4.48, 4.12, 0, 0, 0, 0, 0, 0, 0],
"rotation": 0,
"frame": 0,
"label_id": None,
"group": 0,

@ -11,7 +11,7 @@ context('Object make a copy.', () => {
const rectangleShape2Points = {
points: 'By 2 Points',
type: 'Shape',
labelName: labelName,
labelName,
firstX: 100,
firstY: 100,
secondX: 150,
@ -20,7 +20,7 @@ context('Object make a copy.', () => {
const createCuboidShape2Points = {
points: 'From rectangle',
type: 'Shape',
labelName: labelName,
labelName,
firstX: 200,
firstY: 100,
secondX: 250,
@ -29,7 +29,7 @@ context('Object make a copy.', () => {
const createPolygonShape = {
reDraw: false,
type: 'Shape',
labelName: labelName,
labelName,
pointsMap: [
{ x: 300, y: 100 },
{ x: 350, y: 100 },
@ -40,7 +40,7 @@ context('Object make a copy.', () => {
};
const createPolylinesShape = {
type: 'Shape',
labelName: labelName,
labelName,
pointsMap: [
{ x: 400, y: 100 },
{ x: 450, y: 100 },
@ -51,7 +51,7 @@ context('Object make a copy.', () => {
};
const createPointsShape = {
type: 'Shape',
labelName: labelName,
labelName,
pointsMap: [{ x: 500, y: 100 }],
complete: true,
numberOfPoints: null,
@ -97,7 +97,7 @@ context('Object make a copy.', () => {
describe(`Testing case "${caseId}"`, () => {
it('Make a copy via sidebar.', () => {
let coordX = 100;
let coordY = 300;
const coordY = 300;
for (let id = 1; id < countObject + 2; id++) {
cy.get(`#cvat-objects-sidebar-state-item-${id}`).within(() => {
cy.get('[aria-label="more"]').trigger('mouseover').wait(300); // Wait dropdown menu transition
@ -112,7 +112,8 @@ context('Object make a copy.', () => {
it('After copying via sidebar, the attributes of the objects are the same.', () => {
checkObjectArrSize(10, 12);
for (let id = 1; id < countObject; id++) {
compareObjectsAttr(`#cvat_canvas_shape_${id}`, `#cvat_canvas_shape_${id + countObject + 1}`); // Parameters id 1 equal patameters id 7, 2 to 8, etc.
// Parameters id 1 equal patameters id 7, 2 to 8, etc.
compareObjectsAttr(`#cvat_canvas_shape_${id}`, `#cvat_canvas_shape_${id + countObject + 1}`);
}
for (let idSidebar = 1; idSidebar < 7; idSidebar++) {
compareObjectsSidebarAttr(
@ -122,23 +123,17 @@ context('Object make a copy.', () => {
}
});
// Disabled part of the test for the Firefox browser due to possible problems positioning the element and completing the trigger() construct for moving the mouse cursor over the element.
// Disabled part of the test for the Firefox browser due to possible problems
// positioning the element and completing the trigger() construct for moving the mouse cursor over the element.
it('Make a copy via object context menu.', { browser: '!firefox' }, () => {
let coordX = 100;
let coordY = 400;
const coordY = 400;
for (let id = 1; id < countObject; id++) {
// Point doesn't have a context menu
if (id === 4) {
cy.get(`#cvat_canvas_shape_${id}`)
.trigger('mousemove', 'right')
.should('have.class', 'cvat_canvas_shape_activated')
.rightclick('right'); // When click in the center of polyline: is being covered by another element: <svg xmlns="http://www.w3.org/2000/svg" ...
} else {
cy.get(`#cvat_canvas_shape_${id}`)
.trigger('mousemove', 'right')
.should('have.class', 'cvat_canvas_shape_activated')
.rightclick();
}
cy.get(`#cvat_canvas_shape_${id}`)
.trigger('mousemove', 'right')
.should('have.class', 'cvat_canvas_shape_activated')
.rightclick({ force: true });
cy.get('.cvat-canvas-context-menu')
.last()
.should('be.visible')
@ -158,7 +153,8 @@ context('Object make a copy.', () => {
() => {
checkObjectArrSize(14, 16); // The point and tag was not copied via the object's context menu
for (let id = 1; id < countObject; id++) {
compareObjectsAttr(`#cvat_canvas_shape_${id}`, `#cvat_canvas_shape_${id + countObject + 7}`); // Parameters id 1 equal patameters id 13, 2 to 14, etc.
// Parameters id 1 equal patameters id 13, 2 to 14, etc.
compareObjectsAttr(`#cvat_canvas_shape_${id}`, `#cvat_canvas_shape_${id + countObject + 7}`);
}
for (let idSidebar = 1; idSidebar < 6; idSidebar++) {
compareObjectsSidebarAttr(
@ -198,10 +194,10 @@ context('Object make a copy.', () => {
.should('have.class', 'cvat_canvas_shape_activated');
cy.get('body').type('{ctrl}', { release: false }); // Hold
cy.get('body')
.trigger('keydown', { keyCode: keyCodeC, ctrlKey: true })
.trigger('keyup')
.trigger('keydown', { keyCode: keyCodeV, ctrlKey: true })
.trigger('keyup');
.trigger('keydown', { keyCode: keyCodeC, code: 'KeyC', ctrlKey: true })
.trigger('keyup', { keyCode: keyCodeC, code: 'KeyC', ctrlKey: true })
.trigger('keydown', { keyCode: keyCodeV, code: 'KeyV', ctrlKey: true })
.trigger('keyup', { keyCode: keyCodeC, code: 'KeyC', ctrlKey: true });
cy.get('.cvat-canvas-container').click(400, 300);
cy.get('.cvat-canvas-container').click(500, 300);
cy.get('body').type('{ctrl}'); // Unhold

@ -11,7 +11,7 @@ context('Redraw feature.', () => {
const createRectangleShape2Points = {
points: 'By 2 Points',
type: 'Shape',
labelName: labelName,
labelName,
firstX: 150,
firstY: 350,
secondX: 250,
@ -20,7 +20,7 @@ context('Redraw feature.', () => {
const createCuboidShape2Points = {
points: 'From rectangle',
type: 'Shape',
labelName: labelName,
labelName,
firstX: 300,
firstY: 350,
secondX: 400,
@ -29,7 +29,7 @@ context('Redraw feature.', () => {
const createPolygonShape = {
reDraw: false,
type: 'Shape',
labelName: labelName,
labelName,
pointsMap: [
{ x: 450, y: 350 },
{ x: 550, y: 350 },
@ -40,7 +40,7 @@ context('Redraw feature.', () => {
};
const createPolylinesShape = {
type: 'Shape',
labelName: labelName,
labelName,
pointsMap: [
{ x: 600, y: 350 },
{ x: 700, y: 350 },
@ -51,7 +51,7 @@ context('Redraw feature.', () => {
};
const createPointsShape = {
type: 'Shape',
labelName: labelName,
labelName,
pointsMap: [{ x: 750, y: 400 }],
complete: true,
numberOfPoints: null,
@ -67,7 +67,7 @@ context('Redraw feature.', () => {
cy.createRectangle(createRectangleShape2Points);
cy.get('.cvat-canvas-container').trigger('mousemove', 200, 400);
cy.get('#cvat_canvas_shape_1').should('have.class', 'cvat_canvas_shape_activated');
cy.get('body').trigger('keydown', { keyCode: keyCodeN, shiftKey: true }); // Start redraw the rectangle
cy.get('body').trigger('keydown', { keyCode: keyCodeN, code: 'KeyN', shiftKey: true }); // Start redraw the rectangle
cy.get('.cvat-canvas-container')
.click(createRectangleShape2Points.firstX, createRectangleShape2Points.firstY - 50)
.click(createRectangleShape2Points.secondX, createRectangleShape2Points.secondY - 50);
@ -83,11 +83,12 @@ context('Redraw feature.', () => {
cy.createPolygon(createPolygonShape);
cy.get('.cvat-canvas-container').trigger('mousemove', 520, 400);
cy.get('#cvat_canvas_shape_2').should('have.class', 'cvat_canvas_shape_activated');
cy.get('body').trigger('keydown', { keyCode: keyCodeN, shiftKey: true }); // Start redraw the polygon
cy.get('body').trigger('keydown', { keyCode: keyCodeN, code: 'KeyN', shiftKey: true }); // Start redraw the polygon
createPolygonShape.pointsMap.forEach((element) => {
cy.get('.cvat-canvas-container').click(element.x, element.y - 50);
});
cy.get('.cvat-canvas-container').trigger('keydown', { keyCode: keyCodeN }).trigger('keyup');
cy.get('.cvat-canvas-container').trigger('keydown', { keyCode: keyCodeN, code: 'KeyN' })
.trigger('keyup', { keyCode: keyCodeN, code: 'KeyN' });
cy.get('.cvat_canvas_shape').then(($shape) => {
expect($shape.length).to.be.equal(2);
});
@ -100,11 +101,12 @@ context('Redraw feature.', () => {
cy.createPolyline(createPolylinesShape);
cy.get('.cvat-canvas-container').trigger('mousemove', 700, 400);
cy.get('#cvat_canvas_shape_3').should('have.class', 'cvat_canvas_shape_activated');
cy.get('body').trigger('keydown', { keyCode: keyCodeN, shiftKey: true }); // Start redraw the polyline
cy.get('body').trigger('keydown', { keyCode: keyCodeN, code: 'KeyN', shiftKey: true }); // Start redraw the polyline
createPolylinesShape.pointsMap.forEach((element) => {
cy.get('.cvat-canvas-container').click(element.x, element.y - 50);
});
cy.get('.cvat-canvas-container').trigger('keydown', { keyCode: keyCodeN }).trigger('keyup');
cy.get('.cvat-canvas-container').trigger('keydown', { keyCode: keyCodeN, code: 'KeyN' })
.trigger('keyup', { keyCode: keyCodeN, code: 'KeyN' });
cy.get('.cvat_canvas_shape').then(($shape) => {
expect($shape.length).to.be.equal(3);
});
@ -116,11 +118,12 @@ context('Redraw feature.', () => {
it('Draw and redraw a point.', () => {
cy.createPoint(createPointsShape);
cy.get('.cvat-canvas-container').trigger('mousemove', 750, 400);
cy.get('body').trigger('keydown', { keyCode: keyCodeN, shiftKey: true }); // Start redraw the point
cy.get('body').trigger('keydown', { keyCode: keyCodeN, code: 'KeyN', shiftKey: true }); // Start redraw the point
createPointsShape.pointsMap.forEach((element) => {
cy.get('.cvat-canvas-container').click(element.x, element.y - 50);
});
cy.get('.cvat-canvas-container').trigger('keydown', { keyCode: keyCodeN }).trigger('keyup');
cy.get('.cvat-canvas-container').trigger('keydown', { keyCode: keyCodeN, code: 'KeyN' })
.trigger('keyup', { keyCode: keyCodeN, code: 'KeyN' });
cy.get('.cvat_canvas_shape').then(($shape) => {
expect($shape.length).to.be.equal(4);
});
@ -133,18 +136,18 @@ context('Redraw feature.', () => {
cy.createCuboid(createCuboidShape2Points);
cy.get('.cvat-canvas-container').trigger('mousemove', 350, 400);
cy.get('#cvat_canvas_shape_5').should('have.class', 'cvat_canvas_shape_activated');
cy.get('body').trigger('keydown', { keyCode: keyCodeN, shiftKey: true }); // Start redraw the cuboid
cy.get('body').trigger('keydown', { keyCode: keyCodeN, code: 'KeyN', shiftKey: true }); // Start redraw the cuboid
cy.get('.cvat-canvas-container')
.click(createCuboidShape2Points.firstX, createCuboidShape2Points.firstY - 50)
.click(createCuboidShape2Points.secondX, createCuboidShape2Points.secondY - 50);
// Check issue 3219. Press "N" during the redrawing of the cuboid
cy.get('.cvat-canvas-container').trigger('mousemove', 350, 300);
cy.get('#cvat_canvas_shape_5').should('have.class', 'cvat_canvas_shape_activated');
cy.get('body').trigger('keydown', { keyCode: keyCodeN, shiftKey: true }); // Start redraw the cuboid
cy.get('body').trigger('keydown', { keyCode: keyCodeN, code: 'KeyN', shiftKey: true }); // Start redraw the cuboid
cy.get('.cvat-canvas-container')
.click(createCuboidShape2Points.firstX, createCuboidShape2Points.firstY - 100)
.trigger('mousemove', createCuboidShape2Points.secondX, createCuboidShape2Points.secondY - 100);
cy.get('body').trigger('keydown', { keyCode: keyCodeN });
cy.get('body').trigger('keydown', { keyCode: keyCodeN, code: 'KeyN' });
cy.get('.cvat_canvas_shape_drawing').should('not.exist');
cy.get('.cvat_canvas_shape').then(($shape) => {
expect($shape.length).to.be.equal(5);

@ -11,7 +11,7 @@ context('Repeat draw feature.', () => {
const createRectangleShape2Points = {
points: 'By 2 Points',
type: 'Shape',
labelName: labelName,
labelName,
firstX: 150,
firstY: 350,
secondX: 250,
@ -20,7 +20,7 @@ context('Repeat draw feature.', () => {
const createCuboidShape2Points = {
points: 'From rectangle',
type: 'Shape',
labelName: labelName,
labelName,
firstX: 300,
firstY: 350,
secondX: 400,
@ -29,7 +29,7 @@ context('Repeat draw feature.', () => {
const createPolygonShape = {
redraw: false,
type: 'Shape',
labelName: labelName,
labelName,
pointsMap: [
{ x: 450, y: 350 },
{ x: 550, y: 350 },
@ -40,7 +40,7 @@ context('Repeat draw feature.', () => {
};
const createPolylinesShape = {
type: 'Shape',
labelName: labelName,
labelName,
pointsMap: [
{ x: 600, y: 350 },
{ x: 700, y: 350 },
@ -51,7 +51,7 @@ context('Repeat draw feature.', () => {
};
const createPointsShape = {
type: 'Shape',
labelName: labelName,
labelName,
pointsMap: [{ x: 750, y: 400 }],
complete: true,
numberOfPoints: null,
@ -69,11 +69,12 @@ context('Repeat draw feature.', () => {
}
function repeatDrawningStart() {
cy.get('body').trigger('keydown', { keyCode: keyCodeN });
cy.get('body').trigger('keydown', { keyCode: keyCodeN, code: 'KeyN' });
}
function repeatDrawningFinish() {
cy.get('.cvat-canvas-container').trigger('keydown', { keyCode: keyCodeN }).trigger('keyup');
cy.get('.cvat-canvas-container').trigger('keydown', { keyCode: keyCodeN, code: 'KeyN' })
.trigger('keyup', { keyCode: keyCodeN, code: 'KeyN' });
}
before(() => {

@ -11,7 +11,7 @@ context('Autoborder feature.', () => {
const createRectangleShape2Points = {
points: 'By 2 Points',
type: 'Shape',
labelName: labelName,
labelName,
firstX: 400,
firstY: 350,
secondX: 500,
@ -21,7 +21,7 @@ context('Autoborder feature.', () => {
const createRectangleShape2PointsSec = {
points: 'By 2 Points',
type: 'Shape',
labelName: labelName,
labelName,
firstX: 600,
firstY: 350,
secondX: 700,
@ -29,10 +29,10 @@ context('Autoborder feature.', () => {
};
const keyCodeN = 78;
let rectangleSvgJsCircleId = [];
let rectangleSvgJsCircleIdSecond = [];
let polygonSvgJsCircleId = [];
let polylineSvgJsCircleId = [];
const rectangleSvgJsCircleId = [];
const rectangleSvgJsCircleIdSecond = [];
const polygonSvgJsCircleId = [];
const polylineSvgJsCircleId = [];
function testCollectCxCircleCoord(arrToPush) {
cy.get('circle').then((circle) => {
@ -81,7 +81,8 @@ context('Autoborder feature.', () => {
cy.get('body').type('{Ctrl}'); // Autoborder activation
testAutoborderPointsCount(8); // 8 points at the rectangles
cy.get('.cvat-canvas-container').click(400, 350).click(450, 250).click(500, 350).click(500, 450);
cy.get('.cvat-canvas-container').trigger('keydown', { keyCode: keyCodeN }).trigger('keyup');
cy.get('.cvat-canvas-container').trigger('keydown', { keyCode: keyCodeN, code: 'KeyN' })
.trigger('keyup', { keyCode: keyCodeN, code: 'KeyN' });
cy.get('.cvat_canvas_autoborder_point').should('not.exist');
// Collect the polygon points coordinates
@ -99,7 +100,8 @@ context('Autoborder feature.', () => {
.click(550, 500)
.click(600, 450)
.click(600, 350);
cy.get('.cvat-canvas-container').trigger('keydown', { keyCode: keyCodeN }).trigger('keyup');
cy.get('.cvat-canvas-container').trigger('keydown', { keyCode: keyCodeN, code: 'KeyN' })
.trigger('keyup', { keyCode: keyCodeN, code: 'KeyN' });
cy.get('.cvat_canvas_autoborder_point').should('not.exist');
// Collect the polygon points coordinates
@ -108,9 +110,12 @@ context('Autoborder feature.', () => {
});
it('Checking whether the coordinates of the contact points of the shapes match.', () => {
expect(polygonSvgJsCircleId[0]).to.be.equal(rectangleSvgJsCircleId[0]); // The 1st point of the rect and the 1st polygon point
expect(polygonSvgJsCircleId[2]).to.be.equal(rectangleSvgJsCircleId[1]); // The 2nd point of the rect and the 3rd polygon point
expect(polylineSvgJsCircleId[1]).to.be.equal(rectangleSvgJsCircleId[3]); // The 2nd point of the polyline and the 4th point rect
expect(polygonSvgJsCircleId[0]).to
.be.equal(rectangleSvgJsCircleId[0]); // The 1st point of the rect and the 1st polygon point
expect(polygonSvgJsCircleId[2]).to
.be.equal(rectangleSvgJsCircleId[1]); // The 2nd point of the rect and the 3rd polygon point
expect(polylineSvgJsCircleId[1]).to
.be.equal(rectangleSvgJsCircleId[3]); // The 2nd point of the polyline and the 4th point rect
});
});
});

@ -17,7 +17,7 @@ context('Shortcuts window.', () => {
describe(`Testing case "${caseId}"`, () => {
it('Press "F1" from a task. Shortcuts window be visible. Closing the modal window by button "OK".', () => {
cy.get('body').trigger('keydown', { keyCode: keyCodeF1 });
cy.get('body').trigger('keydown', { keyCode: keyCodeF1, code: 'F1' });
cy.get('.cvat-shortcuts-modal-window')
.should('exist')
.and('be.visible')
@ -36,7 +36,7 @@ context('Shortcuts window.', () => {
it('Open a job. Press "F1". Shortcuts window be visible. Closing the modal window by F1.', () => {
cy.openJob();
cy.get('body').trigger('keydown', { keyCode: keyCodeF1 });
cy.get('body').trigger('keydown', { keyCode: keyCodeF1, code: 'F1' });
cy.get('.cvat-shortcuts-modal-window')
.should('exist')
.and('be.visible')
@ -49,7 +49,7 @@ context('Shortcuts window.', () => {
});
});
});
cy.get('body').trigger('keydown', { keyCode: keyCodeF1 });
cy.get('body').trigger('keydown', { keyCode: keyCodeF1, code: 'F1' });
cy.get('.cvat-shortcuts-modal-window').should('not.be.visible');
});
});

@ -11,7 +11,7 @@ context('OpenCV. Intelligent scissors. Histogram Equalization.', () => {
const caseId = '101';
const newLabel = `Case ${caseId}`;
const createOpencvShape = {
labelName: labelName,
labelName,
pointsMap: [
{ x: 200, y: 200 },
{ x: 250, y: 200 },
@ -74,9 +74,9 @@ context('OpenCV. Intelligent scissors. Histogram Equalization.', () => {
cy.get('.cvat-approx-poly-threshold-wrapper')
.find('[role="slider"]')
.type(generateString(4, 'rightarrow'));
cy.get('.cvat_canvas_interact_intermediate_shape').then((intermediateShape) => {
cy.get('.cvat_canvas_interact_intermediate_shape').then((_intermediateShape) => {
// Get count of points againe
const intermediateShapeNumberPointsAfterChange = intermediateShape.attr('points').split(' ').length;
const intermediateShapeNumberPointsAfterChange = _intermediateShape.attr('points').split(' ').length;
// expected 7 to be below 10
expect(intermediateShapeNumberPointsBeforeChange).to.be.lt(
intermediateShapeNumberPointsAfterChange,
@ -152,12 +152,12 @@ context('OpenCV. Intelligent scissors. Histogram Equalization.', () => {
.trigger('mousemove')
.trigger('mouseover')
.should('have.class', 'cvat_canvas_shape_activated');
cy.get('body').trigger('keydown', { keyCode: keyCodeN, shiftKey: true }).trigger('keyup');
cy.get('body').trigger('keydown', { keyCode: keyCodeN, code: 'KeyN', shiftKey: true }).trigger('keyup');
cy.get('.cvat-tools-control').should('have.attr', 'tabindex');
createOpencvShape.pointsMap.forEach((el) => {
cy.get('.cvat-canvas-container').click(el.x + 150, el.y + 50);
});
cy.get('body').trigger('keydown', { keyCode: keyCodeN }).trigger('keyup');
cy.get('body').trigger('keydown', { keyCode: keyCodeN, code: 'KeyN' }).trigger('keyup');
});
});
});

@ -6,12 +6,12 @@
import { taskName, labelName } from '../../support/const';
context("The points of the previous polygon mustn't appear while polygon's interpolation.", () => {
context('The points of the previous polygon mustn\'t appear while polygon\'s interpolation.', () => {
const issueId = '1882';
const createPolygonTrack = {
reDraw: false,
type: 'Track',
labelName: labelName,
labelName,
pointsMap: [
{ x: 309, y: 431 },
{ x: 360, y: 500 },
@ -23,7 +23,7 @@ context("The points of the previous polygon mustn't appear while polygon's inter
const reDrawPolygonTrack = {
reDraw: true,
type: 'Track',
labelName: labelName,
labelName,
pointsMap: [
{ x: 359, y: 431 },
{ x: 410, y: 500 },
@ -46,8 +46,10 @@ context("The points of the previous polygon mustn't appear while polygon's inter
const keyCodeN = 78;
cy.get('#cvat_canvas_shape_1')
.trigger('mousemove', { force: true })
.trigger('keydown', { keyCode: keyCodeN, shiftKey: true })
.trigger('keyup', { force: true }, { keyCode: keyCodeN, shiftKey: true });
.trigger('keydown', { keyCode: keyCodeN, code: 'KeyN', shiftKey: true })
.trigger('keyup', {
force: true, keyCode: keyCodeN, code: 'KeyN', shiftKey: true
});
cy.createPolygon(reDrawPolygonTrack);
});
it('Activate auto bordering mode', () => {

@ -11,7 +11,7 @@ context('Check hide functionality (H)', () => {
const createRectangleShape2Points = {
points: 'By 2 Points',
type: 'Shape',
labelName: labelName,
labelName,
firstX: 250,
firstY: 350,
secondX: 350,
@ -29,7 +29,7 @@ context('Check hide functionality (H)', () => {
cy.get('#cvat_canvas_shape_1')
.trigger('mousemove')
.trigger('mouseover')
.trigger('keydown', { keyCode: keyCodeH })
.trigger('keydown', { keyCode: keyCodeH, code: 'KeyH' })
.should('be.hidden');
});
});

@ -27,7 +27,7 @@ context('Check if the UI not to crash after remove a tag', () => {
.should('contain', '1')
.and('contain', 'TAG')
.trigger('mouseover')
.trigger('keydown', { keyCode: keyCodeDel });
.trigger('keydown', { keyCode: keyCodeDel, code: 'Delete' });
});
it('Page with the error is missing', () => {
cy.contains('Oops, something went wrong').should('not.exist');

@ -11,7 +11,7 @@ context('First part of a split track is visible', () => {
const createRectangleTrack2Points = {
points: 'By 2 Points',
type: 'Track',
labelName: labelName,
labelName,
firstX: 250,
firstY: 350,
secondX: 350,
@ -34,7 +34,7 @@ context('First part of a split track is visible', () => {
});
it('Split track', () => {
cy.get('body').type('{alt}m');
cy.get('#cvat_canvas_shape_1').trigger('mousemove', { which: 1 }).trigger('click', { which: 1 });
cy.get('#cvat_canvas_shape_1').trigger('mousemove', { button: 0 }).trigger('click', { button: 0 });
});
it('Go to previous frame', () => {
cy.get('.cvat-player-previous-button').click();

@ -19,7 +19,9 @@ Cypress.Commands.add('login', (username = Cypress.env('user'), password = Cypres
cy.url().should('match', /\/tasks$/);
cy.document().then((doc) => {
const loadSettingFailNotice = Array.from(doc.querySelectorAll('.cvat-notification-notice-load-settings-fail'));
loadSettingFailNotice.length > 0 ? cy.closeNotification('.cvat-notification-notice-load-settings-fail') : null;
if (loadSettingFailNotice.length > 0) {
cy.closeNotification('.cvat-notification-notice-load-settings-fail');
}
});
});
@ -56,17 +58,17 @@ Cypress.Commands.add('deletingRegisteredUsers', (accountToDelete) => {
password: Cypress.env('password'),
},
}).then((response) => {
const authKey = response['body']['key'];
const authKey = response.body.key;
cy.request({
url: '/api/v1/users?page_size=all',
headers: {
Authorization: `Token ${authKey}`,
},
}).then((response) => {
const responceResult = response['body']['results'];
}).then((_response) => {
const responceResult = _response.body.results;
for (const user of responceResult) {
const userId = user['id'];
const userName = user['username'];
const userId = user.id;
const userName = user.username;
for (const account of accountToDelete) {
if (userName === account) {
cy.request({
@ -90,10 +92,10 @@ Cypress.Commands.add('changeUserActiveStatus', (authKey, accountsToChangeActiveS
Authorization: `Token ${authKey}`,
},
}).then((response) => {
const responceResult = response['body']['results'];
const responceResult = response.body.results;
responceResult.forEach((user) => {
const userId = user['id'];
const userName = user['username'];
const userId = user.id;
const userName = user.username;
if (userName.includes(accountsToChangeActiveStatus)) {
cy.request({
method: 'PATCH',
@ -117,12 +119,12 @@ Cypress.Commands.add('checkUserStatuses', (authKey, userName, staffStatus, super
Authorization: `Token ${authKey}`,
},
}).then((response) => {
const responceResult = response['body']['results'];
const responceResult = response.body.results;
responceResult.forEach((user) => {
if (user['username'].includes(userName)) {
expect(staffStatus).to.be.equal(user['is_staff']);
expect(superuserStatus).to.be.equal(user['is_superuser']);
expect(activeStatus).to.be.equal(user['is_active']);
if (user.username.includes(userName)) {
expect(staffStatus).to.be.equal(user.is_staff);
expect(superuserStatus).to.be.equal(user.is_superuser);
expect(activeStatus).to.be.equal(user.is_active);
}
});
});
@ -211,9 +213,7 @@ Cypress.Commands.add('getJobNum', (jobID) => {
.find('td')
.eq(0)
.invoke('text')
.then(($tdText) => {
return Number($tdText.match(/\d+/g)) + jobID;
});
.then(($tdText) => (Number($tdText.match(/\d+/g)) + jobID));
});
Cypress.Commands.add('openJob', (jobID = 0, removeAnnotations = true, expectedFail = false) => {
@ -221,9 +221,12 @@ Cypress.Commands.add('openJob', (jobID = 0, removeAnnotations = true, expectedFa
cy.get('.cvat-task-jobs-table-row').contains('a', `Job #${$job}`).click();
});
cy.url().should('include', '/jobs');
expectedFail
? cy.get('.cvat-canvas-container').should('not.exist')
: cy.get('.cvat-canvas-container').should('exist');
if (expectedFail) {
cy.get('.cvat-canvas-container').should('not.exist');
} else {
cy.get('.cvat-canvas-container').should('exist');
}
if (removeAnnotations) {
cy.document().then((doc) => {
const objects = Array.from(doc.querySelectorAll('.cvat_canvas_shape'));
@ -284,7 +287,7 @@ Cypress.Commands.add('checkPopoverHidden', (objectType) => {
});
Cypress.Commands.add('checkObjectParameters', (objectParameters, objectType) => {
let listCanvasShapeId = [];
const listCanvasShapeId = [];
cy.document().then((doc) => {
const listCanvasShape = Array.from(doc.querySelectorAll('.cvat_canvas_shape'));
for (let i = 0; i < listCanvasShape.length; i++) {
@ -318,13 +321,11 @@ Cypress.Commands.add('createPoint', (createPointParams) => {
});
if (createPointParams.finishWithButton) {
cy.contains('span', 'Done').click();
} else {
if (!createPointParams.numberOfPoints) {
const keyCodeN = 78;
cy.get('.cvat-canvas-container')
.trigger('keydown', { keyCode: keyCodeN })
.trigger('keyup', { keyCode: keyCodeN });
}
} else if (!createPointParams.numberOfPoints) {
const keyCodeN = 78;
cy.get('.cvat-canvas-container')
.trigger('keydown', { keyCode: keyCodeN, code: 'KeyN' })
.trigger('keyup', { keyCode: keyCodeN, code: 'KeyN' });
}
cy.checkPopoverHidden('draw-points');
cy.checkObjectParameters(createPointParams, 'POINTS');
@ -339,13 +340,13 @@ Cypress.Commands.add('changeAppearance', (colorBy) => {
Cypress.Commands.add('shapeGrouping', (firstX, firstY, lastX, lastY) => {
const keyCodeG = 71;
cy.get('.cvat-canvas-container')
.trigger('keydown', { keyCode: keyCodeG })
.trigger('keyup', { keyCode: keyCodeG })
.trigger('keydown', { keyCode: keyCodeG, code: 'KeyG' })
.trigger('keyup', { keyCode: keyCodeG, code: 'KeyG' })
.trigger('mousedown', firstX, firstY, { which: 1 })
.trigger('mousemove', lastX, lastY)
.trigger('mouseup', lastX, lastY)
.trigger('keydown', { keyCode: keyCodeG })
.trigger('keyup', { keyCode: keyCodeG });
.trigger('keydown', { keyCode: keyCodeG, code: 'KeyG' })
.trigger('keyup', { keyCode: keyCodeG, code: 'KeyG' });
});
Cypress.Commands.add('createPolygon', (createPolygonParams) => {
@ -367,13 +368,11 @@ Cypress.Commands.add('createPolygon', (createPolygonParams) => {
});
if (createPolygonParams.finishWithButton) {
cy.contains('span', 'Done').click();
} else {
if (!createPolygonParams.numberOfPoints) {
const keyCodeN = 78;
cy.get('.cvat-canvas-container')
.trigger('keydown', { keyCode: keyCodeN })
.trigger('keyup', { keyCode: keyCodeN });
}
} else if (!createPolygonParams.numberOfPoints) {
const keyCodeN = 78;
cy.get('.cvat-canvas-container')
.trigger('keydown', { keyCode: keyCodeN, code: 'KeyN' })
.trigger('keyup', { keyCode: keyCodeN, code: 'KeyN' });
}
cy.checkPopoverHidden('draw-polygon');
cy.checkObjectParameters(createPolygonParams, 'POLYGON');
@ -444,7 +443,7 @@ Cypress.Commands.add('createCuboid', (createCuboidParams) => {
});
Cypress.Commands.add('updateAttributes', (multiAttrParams) => {
let cvatAttributeInputsWrapperId = [];
const cvatAttributeInputsWrapperId = [];
cy.get('.cvat-new-attribute-button').click();
cy.document().then((doc) => {
const cvatAttributeInputsWrapperList = Array.from(doc.querySelectorAll('.cvat-attribute-inputs-wrapper'));
@ -513,13 +512,11 @@ Cypress.Commands.add('createPolyline', (createPolylineParams) => {
});
if (createPolylineParams.finishWithButton) {
cy.contains('span', 'Done').click();
} else {
if (!createPolylineParams.numberOfPoints) {
const keyCodeN = 78;
cy.get('.cvat-canvas-container')
.trigger('keydown', { keyCode: keyCodeN })
.trigger('keyup', { keyCode: keyCodeN });
}
} else if (!createPolylineParams.numberOfPoints) {
const keyCodeN = 78;
cy.get('.cvat-canvas-container')
.trigger('keydown', { keyCode: keyCodeN, code: 'KeyN' })
.trigger('keyup', { keyCode: keyCodeN, code: 'KeyN' });
}
cy.checkPopoverHidden('draw-polyline');
cy.checkObjectParameters(createPolylineParams, 'POLYLINE');
@ -591,7 +588,7 @@ Cypress.Commands.add('changeColorViaBadge', (labelColor) => {
});
Cypress.Commands.add('collectLabelsName', () => {
let listCvatConstructorViewerItemText = [];
const listCvatConstructorViewerItemText = [];
cy.get('.cvat-constructor-viewer').should('exist');
cy.document().then((doc) => {
const labels = Array.from(doc.querySelectorAll('.cvat-constructor-viewer-item'));
@ -661,9 +658,7 @@ Cypress.Commands.add('goToRegisterPage', () => {
Cypress.Commands.add('getScaleValue', () => {
cy.get('#cvat_canvas_background')
.should('have.attr', 'style')
.then(($styles) => {
return Number($styles.match(/scale\((\d\.\d+)\)/m)[1]);
});
.then(($styles) => (Number($styles.match(/scale\((\d\.\d+)\)/m)[1])));
});
Cypress.Commands.add('goCheckFrameNumber', (frameNum) => {
@ -713,9 +708,7 @@ Cypress.Commands.add('getObjectIdNumberByLabelName', (labelName) => {
cy.get(stateItemLabelSelectorList[i])
.parents('.cvat-objects-sidebar-state-item')
.should('have.attr', 'id')
.then((id) => {
return Number(id.match(/\d+$/));
});
.then((id) => (Number(id.match(/\d+$/))));
}
}
});
@ -729,7 +722,9 @@ Cypress.Commands.add('closeModalUnsupportedPlatform', () => {
}
});
Cypress.Commands.add('exportTask', ({ as, type, format, archiveCustomeName }) => {
Cypress.Commands.add('exportTask', ({
as, type, format, archiveCustomeName,
}) => {
cy.interactMenu('Export task dataset');
cy.intercept('GET', `/api/v1/tasks/**/${type}**`).as(as);
cy.get('.cvat-modal-export-task').should('be.visible').find('.cvat-modal-export-select').click();

@ -35,15 +35,15 @@ Cypress.Commands.add('opencvCreateShape', (opencvShapeParams) => {
} else {
const keyCodeN = 78;
cy.get('.cvat-canvas-container')
.trigger('keydown', { keyCode: keyCodeN })
.trigger('keyup', { keyCode: keyCodeN });
.trigger('keydown', { keyCode: keyCodeN, code: 'KeyN' })
.trigger('keyup', { keyCode: keyCodeN, code: 'KeyN' });
}
cy.checkPopoverHidden('opencv-control');
cy.opencvCheckObjectParameters('POLYGON');
});
Cypress.Commands.add('opencvCheckObjectParameters', (objectType) => {
let listCanvasShapeId = [];
const listCanvasShapeId = [];
cy.document().then((doc) => {
const listCanvasShape = Array.from(doc.querySelectorAll('.cvat_canvas_shape'));
for (let i = 0; i < listCanvasShape.length; i++) {

2082
tests/package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -4,15 +4,13 @@
"cypress:run:firefox": "cypress run --env coverage=false --browser firefox --config-file cypress_cron_type.json",
"cypress:run:chrome:canvas3d": "cypress run --headed --browser chrome --env coverage=false --config-file cypress_canvas3d.json"
},
"devDependencies": {
"dependencies": {
"archiver": "^5.3.0",
"jimp": "^0.16.1",
"@cypress/code-coverage": "^3.9.10",
"cypress": "^8.3.1",
"cypress-file-upload": "^5.0.8",
"cypress-localstorage-commands": "^1.5.0",
"cypress-plugin-tab": "^1.0.5"
},
"dependencies": {
"archiver": "^5.3.0",
"jimp": "^0.16.1"
}
}

Loading…
Cancel
Save