Added support of skeletons (#1)

main
Boris Sekachev 4 years ago committed by GitHub
parent 5f58a0f7be
commit 7e20b279af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -24,10 +24,10 @@ module.exports = {
'plugin:import/typescript', 'plugin:@typescript-eslint/recommended', 'airbnb-typescript/base',
],
rules: {
'header/header': [2, 'line', [{
pattern: ' {1}Copyright \\(C\\) (?:20\\d{2}-)?2022 Intel Corporation',
template: ' Copyright (C) 2022 Intel Corporation'
}, '', ' SPDX-License-Identifier: MIT']],
// 'header/header': [2, 'line', [{
// pattern: ' {1}Copyright \\(C\\) (?:20\\d{2}-)?2022 Intel Corporation',
// template: ' Copyright (C) 2022 Intel Corporation'
// }, '', ' SPDX-License-Identifier: MIT']],
'no-plusplus': 0,
'no-continue': 0,
'no-console': 0,
@ -52,6 +52,7 @@ module.exports = {
'import/order': ['error', {'groups': ['builtin', 'external', 'internal']}],
'import/prefer-default-export': 0, // works incorrect with interfaces
'@typescript-eslint/ban-ts-comment': 0,
'@typescript-eslint/no-explicit-any': 0,
'@typescript-eslint/indent': ['error', 4],
'@typescript-eslint/lines-between-class-members': 0,

@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## \[2.2.0] - Unreleased
### Added
- Added ability to delete frames from a job based on (<https://github.com/openvinotoolkit/cvat/pull/4194>)
- Support of attributes returned by serverless functions based on (<https://github.com/openvinotoolkit/cvat/pull/4506>)
- Project/task backups uploading via chunk uploads
- Fixed UX bug when jobs pagination is reset after changing a job
@ -21,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Documentation for LDAP authentication (<https://github.com/cvat-ai/cvat/pull/39>)
- OpenCV.js caching and autoload (<https://github.com/cvat-ai/cvat/pull/30>)
- Publishing dev version of CVAT docker images (<https://github.com/cvat-ai/cvat/pull/53>)
- Support of Human Pose Estimation, Facial Landmarks (and similar) use-cases, new shape type: Skeleton (<https://github.com/cvat-ai/cvat/pull/1>)
### Changed
- Bumped nuclio version to 1.8.14
@ -57,7 +59,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Advanced filtration and sorting for a list of tasks/projects/cloudstorages (<https://github.com/openvinotoolkit/cvat/pull/4403>)
- Project dataset importing via chunk uploads (<https://github.com/openvinotoolkit/cvat/pull/4485>)
- Support paginated list for job commits (<https://github.com/openvinotoolkit/cvat/pull/4482>)
- Added ability to delete frames from a job (<https://github.com/openvinotoolkit/cvat/pull/4194>)
### Changed
- Added missing geos dependency into Dockerfile (<https://github.com/openvinotoolkit/cvat/pull/4451>)

@ -1,6 +1,6 @@
{
"name": "cvat-canvas",
"version": "2.14.0",
"version": "2.15.0",
"description": "Part of Computer Vision Annotation Tool which presents its canvas library",
"main": "src/canvas.ts",
"scripts": {

@ -10,6 +10,12 @@
stroke-opacity: 1;
}
g.cvat_canvas_shape {
> circle {
fill-opacity: 1;
}
}
polyline.cvat_canvas_shape {
fill-opacity: 0;
}
@ -120,7 +126,6 @@ polyline.cvat_canvas_shape_splitting {
@extend .cvat_shape_drawing_opacity;
fill: white;
stroke: black;
}
.cvat_canvas_zoom_selection {
@ -134,6 +139,12 @@ polyline.cvat_canvas_shape_splitting {
stroke-dasharray: 5;
}
g.cvat_canvas_shape_occluded {
> rect {
stroke-dasharray: 5;
}
}
.svg_select_points_rot {
fill: white;
}
@ -226,6 +237,12 @@ polyline.cvat_canvas_shape_splitting {
}
}
.cvat_canvas_skeleton_wrapping_rect {
// wrapping rect must not apply transform attribute from selectize.js
// otherwise it rotated twice, because we apply the same rotation value to parent element (skeleton itself)
transform: none !important;
}
.cvat_canvas_pixelized {
image-rendering: optimizeSpeed; /* Legal fallback */
image-rendering: -moz-crisp-edges; /* Firefox */

@ -5,7 +5,7 @@
import * as SVG from 'svg.js';
import consts from './consts';
import { Geometry } from './canvasModel';
import { Configuration, Geometry } from './canvasModel';
interface TransformedShape {
points: string;
@ -14,6 +14,7 @@ interface TransformedShape {
export interface AutoborderHandler {
autoborder(enabled: boolean, currentShape?: SVG.Shape, currentID?: number): void;
configurate(configuration: Configuration): void;
transform(geometry: Geometry): void;
updateObjects(): void;
}
@ -24,19 +25,14 @@ export class AutoborderHandlerImpl implements AutoborderHandler {
private frameContent: SVGSVGElement;
private enabled: boolean;
private scale: number;
private controlPointsSize: number;
private groups: SVGGElement[];
private auxiliaryGroupID: number | null;
private auxiliaryClicks: number[];
private listeners: Record<
number,
Record<
number,
{
private listeners: Record<number, Record<number, {
click: (event: MouseEvent) => void;
dblclick: (event: MouseEvent) => void;
}
>
>;
}>>;
public constructor(frameContent: SVGSVGElement) {
this.frameContent = frameContent;
@ -45,6 +41,7 @@ export class AutoborderHandlerImpl implements AutoborderHandler {
this.enabled = false;
this.scale = 1;
this.groups = [];
this.controlPointsSize = consts.BASE_POINT_SIZE;
this.auxiliaryGroupID = null;
this.auxiliaryClicks = [];
this.listeners = {};
@ -126,7 +123,7 @@ export class AutoborderHandlerImpl implements AutoborderHandler {
circle.setAttribute('stroke-width', `${consts.POINTS_STROKE_WIDTH / this.scale}`);
circle.setAttribute('cx', x);
circle.setAttribute('cy', y);
circle.setAttribute('r', `${consts.BASE_POINT_SIZE / this.scale}`);
circle.setAttribute('r', `${this.controlPointsSize / this.scale}`);
const click = (event: MouseEvent): void => {
event.stopPropagation();
@ -303,9 +300,13 @@ export class AutoborderHandlerImpl implements AutoborderHandler {
this.scale = geometry.scale;
this.groups.forEach((group: SVGGElement): void => {
Array.from(group.children).forEach((circle: SVGCircleElement): void => {
circle.setAttribute('r', `${consts.BASE_POINT_SIZE / this.scale}`);
circle.setAttribute('r', `${this.controlPointsSize / this.scale}`);
circle.setAttribute('stroke-width', `${consts.BASE_STROKE_WIDTH / this.scale}`);
});
});
}
public configurate(configuration: Configuration): void {
this.controlPointsSize = configuration.controlPointsSize || consts.BASE_POINT_SIZE;
}
}

@ -64,8 +64,12 @@ export interface Configuration {
forceDisableEditing?: boolean;
intelligentPolygonCrop?: boolean;
forceFrameUpdate?: boolean;
creationOpacity?: number;
CSSImageFilter?: string;
colorBy?: string;
selectedShapeOpacity?: number;
shapeOpacity?: number;
controlPointsSize?: number;
outlinedBorders?: string | false;
}
export interface DrawData {
@ -73,6 +77,7 @@ export interface DrawData {
shapeType?: string;
rectDrawingMethod?: RectDrawingMethod;
cuboidDrawingMethod?: CuboidDrawingMethod;
skeletonSVG?: string;
numberOfPoints?: number;
initialState?: any;
crosshair?: boolean;
@ -265,12 +270,23 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
width: 0,
},
configuration: {
displayAllText: false,
smoothImage: true,
autoborders: false,
undefinedAttrValue: '',
textContent: 'id,label,attributes,source,descriptions',
textPosition: 'auto',
displayAllText: false,
showProjections: false,
forceDisableEditing: false,
intelligentPolygonCrop: false,
forceFrameUpdate: false,
CSSImageFilter: '',
colorBy: 'Label',
selectedShapeOpacity: 0.5,
shapeOpacity: 0.2,
outlinedBorders: false,
textFontSize: consts.DEFAULT_SHAPE_TEXT_SIZE,
controlPointsSize: consts.BASE_POINT_SIZE,
textPosition: consts.DEFAULT_SHAPE_TEXT_POSITION,
textContent: consts.DEFAULT_SHAPE_TEXT_CONTENT,
undefinedAttrValue: consts.DEFAULT_UNDEFINED_ATTR_VALUE,
},
imageBitmap: false,
image: null,
@ -541,6 +557,10 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
}
if (drawData.enabled) {
if (drawData.shapeType === 'skeleton' && !drawData.skeletonSVG) {
throw new Error('Skeleton template must be specified when drawing a skeleton');
}
if (this.data.drawData.enabled) {
throw new Error('Drawing has been already started');
} else if (!drawData.shapeType && !drawData.initialState) {
@ -670,6 +690,10 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
this.data.configuration.textFontSize = configuration.textFontSize;
}
if (typeof configuration.controlPointsSize === 'number') {
this.data.configuration.controlPointsSize = configuration.controlPointsSize;
}
if (['auto', 'center'].includes(configuration.textPosition)) {
this.data.configuration.textPosition = configuration.textPosition;
}
@ -702,8 +726,17 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
if (typeof configuration.forceFrameUpdate === 'boolean') {
this.data.configuration.forceFrameUpdate = configuration.forceFrameUpdate;
}
if (typeof configuration.creationOpacity === 'number') {
this.data.configuration.creationOpacity = configuration.creationOpacity;
if (typeof configuration.selectedShapeOpacity === 'number') {
this.data.configuration.selectedShapeOpacity = configuration.selectedShapeOpacity;
}
if (typeof configuration.shapeOpacity === 'number') {
this.data.configuration.shapeOpacity = configuration.shapeOpacity;
}
if (['string', 'boolean'].includes(typeof configuration.outlinedBorders)) {
this.data.configuration.outlinedBorders = configuration.outlinedBorders;
}
if (['Instance', 'Group', 'Label'].includes(configuration.colorBy)) {
this.data.configuration.colorBy = configuration.colorBy;
}
if (typeof configuration.CSSImageFilter === 'string') {

File diff suppressed because it is too large Load Diff

@ -4,7 +4,7 @@
const BASE_STROKE_WIDTH = 1.25;
const BASE_GRID_WIDTH = 2;
const BASE_POINT_SIZE = 5;
const BASE_POINT_SIZE = 4;
const TEXT_MARGIN = 10;
const AREA_THRESHOLD = 9;
const SIZE_THRESHOLD = 3;
@ -19,8 +19,13 @@ const ARROW_PATH = 'M13.162 6.284L.682.524a.483.483 0 0 0-.574.134.477.477 0 ' +
const BASE_PATTERN_SIZE = 5;
const SNAP_TO_ANGLE_RESIZE_DEFAULT = 0.1;
const SNAP_TO_ANGLE_RESIZE_SHIFT = 15;
const DEFAULT_SHAPE_TEXT_SIZE = 12;
const MINIMUM_TEXT_FONT_SIZE = 8;
const SKELETON_RECT_MARGIN = 20;
const DEFAULT_SHAPE_TEXT_SIZE = 12;
const DEFAULT_SHAPE_TEXT_CONTENT = 'id,label,attributes,source,descriptions';
const DEFAULT_SHAPE_TEXT_POSITION: 'auto' | 'center' = 'auto';
const DEFAULT_UNDEFINED_ATTR_VALUE = '__undefined__';
export default {
BASE_STROKE_WIDTH,
@ -40,5 +45,9 @@ export default {
SNAP_TO_ANGLE_RESIZE_DEFAULT,
SNAP_TO_ANGLE_RESIZE_SHIFT,
DEFAULT_SHAPE_TEXT_SIZE,
DEFAULT_SHAPE_TEXT_CONTENT,
DEFAULT_SHAPE_TEXT_POSITION,
DEFAULT_UNDEFINED_ATTR_VALUE,
MINIMUM_TEXT_FONT_SIZE,
SKELETON_RECT_MARGIN,
};

@ -17,6 +17,11 @@ import {
Point,
readPointsFromShape,
clamp,
translateToCanvas,
computeWrappingBox,
makeSVGFromTemplate,
setupSkeletonEdges,
translateFromCanvas,
} from './shared';
import Crosshair from './crosshair';
import consts from './consts';
@ -83,9 +88,11 @@ export class DrawHandlerImpl implements DrawHandler {
private crosshair: Crosshair;
private drawData: DrawData;
private geometry: Geometry;
private configuration: Configuration;
private autoborderHandler: AutoborderHandler;
private autobordersEnabled: boolean;
private controlPointsSize: number;
private selectedShapeOpacity: number;
private outlinedBorders: string;
// we should use any instead of SVG.Shape because svg plugins cannot change declared interface
// so, methods like draw() just undefined for SVG.Shape, but nevertheless they exist
@ -348,26 +355,26 @@ export class DrawHandlerImpl implements DrawHandler {
this.canvas.off('mousedown.draw');
this.canvas.off('mousemove.draw');
if (this.pointsGroup) {
this.pointsGroup.remove();
this.pointsGroup = null;
}
// Draw plugin in some cases isn't activated
// For example when draw from initialState
// Or when no drawn points, but we call cancel() drawing
// We check if it is activated with remember function
if (this.drawInstance.remember('_paintHandler')) {
if (
['polygon', 'polyline', 'points'].includes(this.drawData.shapeType) ||
if (['polygon', 'polyline', 'points'].includes(this.drawData.shapeType) ||
(this.drawData.shapeType === 'cuboid' &&
this.drawData.cuboidDrawingMethod === CuboidDrawingMethod.CORNER_POINTS)
) {
this.drawData.cuboidDrawingMethod === CuboidDrawingMethod.CORNER_POINTS)) {
// Check for unsaved drawn shapes
this.drawInstance.draw('done');
}
// Clear drawing
this.drawInstance.draw('stop');
} else if (this.drawInstance && this.drawData.shapeType === 'ellipse' && !this.drawData.initialState) {
this.drawInstance.fire('drawstop');
}
if (this.pointsGroup) {
this.pointsGroup.remove();
this.pointsGroup = null;
}
this.drawInstance.off();
@ -417,7 +424,8 @@ export class DrawHandlerImpl implements DrawHandler {
.addClass('cvat_canvas_shape_drawing')
.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
'fill-opacity': this.configuration.creationOpacity,
'fill-opacity': this.selectedShapeOpacity,
stroke: this.outlinedBorders,
});
}
@ -426,7 +434,8 @@ export class DrawHandlerImpl implements DrawHandler {
.addClass('cvat_canvas_shape_drawing')
.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
'fill-opacity': this.configuration.creationOpacity,
'fill-opacity': this.selectedShapeOpacity,
stroke: this.outlinedBorders,
});
const initialPoint: {
@ -442,21 +451,7 @@ export class DrawHandlerImpl implements DrawHandler {
const translated = translateToSVG(this.canvas.node as any as SVGSVGElement, [e.clientX, e.clientY]);
[initialPoint.x, initialPoint.y] = translated;
} else {
const points = this.getFinalEllipseCoordinates(readPointsFromShape(this.drawInstance), false);
const { shapeType, redraw: clientID } = this.drawData;
this.release();
if (this.canceled) return;
if (checkConstraint('ellipse', points)) {
this.onDrawDone(
{
clientID,
shapeType,
points,
},
Date.now() - this.startTimestamp,
);
}
this.drawInstance.fire('drawstop');
}
});
@ -472,6 +467,25 @@ export class DrawHandlerImpl implements DrawHandler {
this.shapeSizeElement.update(this.drawInstance);
}
});
this.drawInstance.on('drawstop', () => {
this.drawInstance.off('drawstop');
const points = this.getFinalEllipseCoordinates(readPointsFromShape(this.drawInstance), false);
const { shapeType, redraw: clientID } = this.drawData;
this.release();
if (this.canceled) return;
if (checkConstraint('ellipse', points)) {
this.onDrawDone(
{
clientID,
shapeType,
points,
},
Date.now() - this.startTimestamp,
);
}
});
}
private drawBoxBy4Points(): void {
@ -612,7 +626,8 @@ export class DrawHandlerImpl implements DrawHandler {
.addClass('cvat_canvas_shape_drawing')
.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
'fill-opacity': this.configuration.creationOpacity,
'fill-opacity': this.selectedShapeOpacity,
stroke: this.outlinedBorders,
});
this.drawPolyshape();
@ -628,6 +643,7 @@ export class DrawHandlerImpl implements DrawHandler {
.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
'fill-opacity': 0,
stroke: this.outlinedBorders,
});
this.drawPolyshape();
@ -651,6 +667,7 @@ export class DrawHandlerImpl implements DrawHandler {
.addClass('cvat_canvas_shape_drawing')
.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
stroke: this.outlinedBorders,
});
this.drawPolyshape();
}
@ -681,7 +698,131 @@ export class DrawHandlerImpl implements DrawHandler {
.addClass('cvat_canvas_shape_drawing')
.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
'fill-opacity': this.configuration.creationOpacity,
'fill-opacity': this.selectedShapeOpacity,
stroke: this.outlinedBorders,
});
}
private drawSkeleton(): void {
this.drawInstance = this.canvas.rect().attr({
stroke: this.outlinedBorders,
});
this.pointsGroup = makeSVGFromTemplate(this.drawData.skeletonSVG);
this.canvas.add(this.pointsGroup);
this.pointsGroup.attr('stroke-width', consts.BASE_STROKE_WIDTH / this.geometry.scale);
this.pointsGroup.attr('stroke', this.outlinedBorders);
let minX = Number.MAX_SAFE_INTEGER;
let minY = Number.MAX_SAFE_INTEGER;
let maxX = 0;
let maxY = 0;
this.pointsGroup.children().forEach((child: SVG.Element): void => {
const cx = child.cx();
const cy = child.cy();
minX = Math.min(cx, minX);
minY = Math.min(cy, minY);
maxX = Math.max(cx, maxX);
maxY = Math.max(cy, maxY);
});
this.drawInstance
.on('drawstop', (e: Event): void => {
const points = readPointsFromShape((e.target as any as { instance: SVG.Rect }).instance);
const [xtl, ytl, xbr, ybr] = this.getFinalRectCoordinates(points, true);
const elements: any[] = [];
Array.from(this.pointsGroup.node.children).forEach((child: Element) => {
if (child.tagName === 'circle') {
const cx = +(child.getAttribute('cx') as string) + xtl;
const cy = +(child.getAttribute('cy') as string) + ytl;
const label = +child.getAttribute('data-label-id');
elements.push({
shapeType: 'points',
points: [cx, cy],
labelID: label,
});
}
});
const { shapeType, redraw: clientID } = this.drawData;
this.release();
if (this.canceled) return;
if (checkConstraint('rectangle', [xtl, ytl, xbr, ybr])) {
this.onDrawDone({
clientID,
shapeType,
elements,
},
Date.now() - this.startTimestamp);
}
})
.on('drawupdate', (): void => {
const x = this.drawInstance.x();
const y = this.drawInstance.y();
const width = this.drawInstance.width();
const height = this.drawInstance.height();
this.pointsGroup.style({
transform: `translate(${x}px, ${y}px)`,
});
/* eslint-disable-next-line no-unsanitized/property */
this.pointsGroup.node.innerHTML = this.drawData.skeletonSVG;
Array.from(this.pointsGroup.node.children).forEach((child: Element) => {
const dataType = child.getAttribute('data-type');
if (child.tagName === 'circle' && dataType && dataType.includes('element')) {
child.setAttribute('r', `${this.controlPointsSize / this.geometry.scale}`);
let cx = +(child.getAttribute('cx') as string);
let cy = +(child.getAttribute('cy') as string);
const cxOffset = (cx - minX) / (maxX - minX);
const cyOffset = (cy - minY) / (maxY - minY);
cx = Number.isNaN(cxOffset) ? 0.5 * width : cxOffset * width;
cy = Number.isNaN(cyOffset) ? 0.5 * height : cyOffset * height;
child.setAttribute('cx', `${cx}`);
child.setAttribute('cy', `${cy}`);
}
});
Array.from(this.pointsGroup.node.children).forEach((child: Element) => {
const dataType = child.getAttribute('data-type');
if (child.tagName === 'line' && dataType && dataType.includes('edge')) {
child.setAttribute('stroke-width', 'inherit');
child.setAttribute('stroke', 'inherit');
const dataNodeFrom = child.getAttribute('data-node-from');
const dataNodeTo = child.getAttribute('data-node-to');
if (dataNodeFrom && dataNodeTo) {
const from = this.pointsGroup.node.querySelector(`[data-node-id="${dataNodeFrom}"]`);
const to = this.pointsGroup.node.querySelector(`[data-node-id="${dataNodeTo}"]`);
if (from && to) {
const x1 = from.getAttribute('cx');
const y1 = from.getAttribute('cy');
const x2 = to.getAttribute('cx');
const y2 = to.getAttribute('cy');
if (x1 && y1 && x2 && y2) {
child.setAttribute('x1', x1);
child.setAttribute('y1', y1);
child.setAttribute('x2', x2);
child.setAttribute('y2', y2);
}
}
}
let cx = +(child.getAttribute('cx') as string);
let cy = +(child.getAttribute('cy') as string);
const cxOffset = cx / 100;
const cyOffset = cy / 100;
cx = cxOffset * width;
cy = cyOffset * height;
child.setAttribute('cx', `${cx}`);
child.setAttribute('cy', `${cy}`);
}
});
})
.addClass('cvat_canvas_shape_drawing')
.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
'fill-opacity': this.selectedShapeOpacity,
});
}
@ -721,19 +862,18 @@ export class DrawHandlerImpl implements DrawHandler {
// Common settings for rectangle and polyshapes
private pasteShape(): void {
function moveShape(shape: SVG.Shape, x: number, y: number): void {
const bbox = shape.bbox();
const moveShape = (shape: SVG.Shape, x: number, y: number): void => {
const { rotation } = shape.transform();
shape.untransform();
shape.move(x - bbox.width / 2, y - bbox.height / 2);
shape.center(x, y);
shape.rotate(rotation);
}
};
const { x: initialX, y: initialY } = this.cursorPosition;
moveShape(this.drawInstance, initialX, initialY);
this.canvas.on('mousemove.draw', (): void => {
const { x, y } = this.cursorPosition; // was computer in another callback
const { x, y } = this.cursorPosition; // was computed in another callback
moveShape(this.drawInstance, x, y);
});
}
@ -741,11 +881,12 @@ export class DrawHandlerImpl implements DrawHandler {
private pasteBox(box: BBox, rotation: number): void {
this.drawInstance = (this.canvas as any)
.rect(box.width, box.height)
.move(box.x, box.y)
.center(box.x, box.y)
.addClass('cvat_canvas_shape_drawing')
.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
'fill-opacity': this.configuration.creationOpacity,
'fill-opacity': this.selectedShapeOpacity,
stroke: this.outlinedBorders,
}).rotate(rotation);
this.pasteShape();
@ -782,7 +923,8 @@ export class DrawHandlerImpl implements DrawHandler {
.addClass('cvat_canvas_shape_drawing')
.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
'fill-opacity': this.configuration.creationOpacity,
'fill-opacity': this.selectedShapeOpacity,
stroke: this.outlinedBorders,
}).rotate(rotation);
this.pasteShape();
@ -820,7 +962,8 @@ export class DrawHandlerImpl implements DrawHandler {
.addClass('cvat_canvas_shape_drawing')
.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
'fill-opacity': this.configuration.creationOpacity,
'fill-opacity': this.selectedShapeOpacity,
stroke: this.outlinedBorders,
});
this.pasteShape();
this.pastePolyshape();
@ -832,6 +975,7 @@ export class DrawHandlerImpl implements DrawHandler {
.addClass('cvat_canvas_shape_drawing')
.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
stroke: this.outlinedBorders,
});
this.pasteShape();
this.pastePolyshape();
@ -843,26 +987,107 @@ export class DrawHandlerImpl implements DrawHandler {
.addClass('cvat_canvas_shape_drawing')
.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
'face-stroke': 'black',
'fill-opacity': this.configuration.creationOpacity,
'face-stroke': this.outlinedBorders,
'fill-opacity': this.selectedShapeOpacity,
stroke: this.outlinedBorders,
});
this.pasteShape();
this.pastePolyshape();
}
private pasteSkeleton(box: BBox, elements: any[]): void {
const { offset } = this.geometry;
let [xtl, ytl] = [box.x, box.y];
this.pasteBox(box, 0);
this.pointsGroup = makeSVGFromTemplate(this.drawData.skeletonSVG);
this.pointsGroup.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
stroke: this.outlinedBorders,
});
this.canvas.add(this.pointsGroup);
this.pointsGroup.children().forEach((child: SVG.Element): void => {
const dataType = child.attr('data-type');
if (child.node.tagName === 'circle' && dataType && dataType.includes('element')) {
child.attr('r', `${this.controlPointsSize / this.geometry.scale}`);
const labelID = +child.attr('data-label-id');
const element = elements.find((_element: any): boolean => _element.label.id === labelID);
if (element) {
const points = translateToCanvas(offset, element.points);
child.center(points[0], points[1]);
}
}
});
this.drawInstance.off('done').on('done', (e: CustomEvent) => {
const result = {
shapeType: this.drawData.initialState.shapeType,
objectType: this.drawData.initialState.objectType,
elements: this.drawData.initialState.elements.map((element: any) => ({
shapeType: element.shapeType,
outside: element.outside,
occluded: element.occluded,
label: element.label,
attributes: element.attributes,
points: (() => {
const circle = this.pointsGroup.children()
.find((child: SVG.Element) => child.attr('data-label-id') === element.label.id);
const points = translateFromCanvas(this.geometry.offset, [circle.cx(), circle.cy()]);
return points;
})(),
})),
occluded: this.drawData.initialState.occluded,
attributes: { ...this.drawData.initialState.attributes },
label: this.drawData.initialState.label,
color: this.drawData.initialState.color,
rotation: this.drawData.initialState.rotation,
};
if (!e.detail.originalEvent.ctrlKey) {
this.release();
}
this.onDrawDone(
result,
Date.now() - this.startTimestamp,
e.detail.originalEvent.ctrlKey,
);
});
this.canvas.on('mousemove.draw', (): void => {
const [newXtl, newYtl] = [
this.drawInstance.x(), this.drawInstance.y(),
this.drawInstance.width(), this.drawInstance.height(),
];
const [xDiff, yDiff] = [newXtl - xtl, newYtl - ytl];
xtl = newXtl;
ytl = newYtl;
this.pointsGroup.children().forEach((child: SVG.Element): void => {
const dataType = child.attr('data-type');
if (child.node.tagName === 'circle' && dataType && dataType.includes('element')) {
const [cx, cy] = [child.cx(), child.cy()];
child.center(cx + xDiff, cy + yDiff);
}
});
this.pointsGroup.untransform();
setupSkeletonEdges(this.pointsGroup, this.pointsGroup);
});
}
private pastePoints(initialPoints: string): void {
function moveShape(shape: SVG.PolyLine, group: SVG.G, x: number, y: number, scale: number): void {
const moveShape = (shape: SVG.PolyLine, group: SVG.G, x: number, y: number, scale: number): void => {
const bbox = shape.bbox();
shape.move(x - bbox.width / 2, y - bbox.height / 2);
const points = shape.attr('points').split(' ');
const radius = consts.BASE_POINT_SIZE / scale;
const radius = this.controlPointsSize / scale;
group.children().forEach((child: SVG.Element, idx: number): void => {
const [px, py] = points[idx].split(',');
child.move(px - radius / 2, py - radius / 2);
});
}
};
const { x: initialX, y: initialY } = this.cursorPosition;
this.pointsGroup = this.canvas.group();
@ -873,7 +1098,7 @@ export class DrawHandlerImpl implements DrawHandler {
let numOfPoints = initialPoints.split(' ').length;
while (numOfPoints) {
numOfPoints--;
const radius = consts.BASE_POINT_SIZE / this.geometry.scale;
const radius = this.controlPointsSize / this.geometry.scale;
const stroke = consts.POINTS_STROKE_WIDTH / this.geometry.scale;
this.pointsGroup.circle().fill('white').stroke('black').attr({
r: radius,
@ -919,10 +1144,7 @@ export class DrawHandlerImpl implements DrawHandler {
if (this.drawData.initialState) {
const { offset } = this.geometry;
if (this.drawData.shapeType === 'rectangle') {
const [xtl, ytl, xbr, ybr] = this.drawData.initialState.points.map(
(coord: number): number => coord + offset,
);
const [xtl, ytl, xbr, ybr] = translateToCanvas(offset, this.drawData.initialState.points);
this.pasteBox({
x: xtl,
y: ytl,
@ -930,13 +1152,15 @@ export class DrawHandlerImpl implements DrawHandler {
height: ybr - ytl,
}, this.drawData.initialState.rotation);
} else if (this.drawData.shapeType === 'ellipse') {
const [cx, cy, rightX, topY] = this.drawData.initialState.points.map(
(coord: number): number => coord + offset,
);
const [cx, cy, rightX, topY] = translateToCanvas(offset, this.drawData.initialState.points);
this.pasteEllipse([cx, cy, rightX - cx, cy - topY], this.drawData.initialState.rotation);
} else if (this.drawData.shapeType === 'skeleton') {
const box = computeWrappingBox(
translateToCanvas(offset, this.drawData.initialState.points), consts.SKELETON_RECT_MARGIN,
);
this.pasteSkeleton(box, this.drawData.initialState.elements);
} else {
const points = this.drawData.initialState.points.map((coord: number): number => coord + offset);
const points = translateToCanvas(offset, this.drawData.initialState.points);
const stringifiedPoints = stringifyPoints(points);
if (this.drawData.shapeType === 'polygon') {
@ -975,6 +1199,8 @@ export class DrawHandlerImpl implements DrawHandler {
this.drawCuboid();
this.shapeSizeElement = displayShapeSize(this.canvas, this.text);
}
} else if (this.drawData.shapeType === 'skeleton') {
this.drawSkeleton();
}
if (this.drawData.shapeType !== 'ellipse') {
@ -995,6 +1221,9 @@ export class DrawHandlerImpl implements DrawHandler {
configuration: Configuration,
) {
this.autoborderHandler = autoborderHandler;
this.controlPointsSize = configuration.controlPointsSize;
this.selectedShapeOpacity = configuration.selectedShapeOpacity;
this.outlinedBorders = configuration.outlinedBorders || 'black';
this.autobordersEnabled = false;
this.startTimestamp = Date.now();
this.onDrawDone = onDrawDone;
@ -1004,7 +1233,6 @@ export class DrawHandlerImpl implements DrawHandler {
this.canceled = false;
this.drawData = null;
this.geometry = geometry;
this.configuration = configuration;
this.crosshair = new Crosshair();
this.drawInstance = null;
this.pointsGroup = null;
@ -1023,7 +1251,9 @@ export class DrawHandlerImpl implements DrawHandler {
}
public configurate(configuration: Configuration): void {
this.configuration = configuration;
this.controlPointsSize = configuration.controlPointsSize;
this.selectedShapeOpacity = configuration.selectedShapeOpacity;
this.outlinedBorders = configuration.outlinedBorders || 'black';
const isFillableRect = this.drawData &&
this.drawData.shapeType === 'rectangle' &&
@ -1034,17 +1264,23 @@ export class DrawHandlerImpl implements DrawHandler {
const isFilalblePolygon = this.drawData && this.drawData.shapeType === 'polygon';
if (this.drawInstance && (isFillableRect || isFillableCuboid || isFilalblePolygon)) {
this.drawInstance.fill({ opacity: configuration.creationOpacity });
this.drawInstance.fill({ opacity: configuration.selectedShapeOpacity });
}
if (typeof configuration.autoborders === 'boolean') {
this.autobordersEnabled = configuration.autoborders;
if (this.drawInstance && !this.drawData.initialState) {
if (this.autobordersEnabled) {
this.autoborderHandler.autoborder(true, this.drawInstance, this.drawData.redraw);
} else {
this.autoborderHandler.autoborder(false);
}
if (this.drawInstance && this.drawInstance.attr('stroke')) {
this.drawInstance.attr('stroke', this.outlinedBorders);
}
if (this.pointsGroup && this.pointsGroup.attr('stroke')) {
this.pointsGroup.attr('stroke', this.outlinedBorders);
}
this.autobordersEnabled = configuration.autoborders;
if (this.drawInstance && !this.drawData.initialState) {
if (this.autobordersEnabled) {
this.autoborderHandler.autoborder(true, this.drawInstance, this.drawData.redraw);
} else {
this.autoborderHandler.autoborder(false);
}
}
}
@ -1061,10 +1297,14 @@ export class DrawHandlerImpl implements DrawHandler {
}
if (this.pointsGroup) {
this.pointsGroup.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
});
for (const point of this.pointsGroup.children()) {
point.attr({
'stroke-width': consts.POINTS_STROKE_WIDTH / geometry.scale,
r: consts.BASE_POINT_SIZE / geometry.scale,
r: this.controlPointsSize / geometry.scale,
});
}
}
@ -1079,7 +1319,7 @@ export class DrawHandlerImpl implements DrawHandler {
for (const point of (paintHandler as any).set.members) {
point.attr('stroke-width', `${consts.POINTS_STROKE_WIDTH / geometry.scale}`);
point.attr('r', `${consts.BASE_POINT_SIZE / geometry.scale}`);
point.attr('r', `${this.controlPointsSize / geometry.scale}`);
}
}
}

@ -26,8 +26,10 @@ export class EditHandlerImpl implements EditHandler {
private editedShape: SVG.Shape;
private editLine: SVG.PolyLine;
private clones: SVG.Polygon[];
private controlPointsSize: number;
private autobordersEnabled: boolean;
private intelligentCutEnabled: boolean;
private outlinedBorders: string;
private setupTrailingPoint(circle: SVG.Circle): void {
const head = this.editedShape.attr('points').split(' ').slice(0, this.editData.pointID).join(' ');
@ -112,16 +114,15 @@ export class EditHandlerImpl implements EditHandler {
});
}
const strokeColor = this.editedShape.attr('stroke');
(this.editLine as any)
.addClass('cvat_canvas_shape_drawing')
.style({
'pointer-events': 'none',
'fill-opacity': 0,
stroke: strokeColor,
})
.attr({
'data-origin-client-id': this.editData.state.clientID,
stroke: this.editedShape.attr('stroke'),
})
.on('drawstart drawpoint', (e: CustomEvent): void => {
this.transform(this.geometry);
@ -299,7 +300,7 @@ export class EditHandlerImpl implements EditHandler {
if (enabled) {
(this.editedShape as any).selectize(true, {
deepSelect: true,
pointSize: (2 * consts.BASE_POINT_SIZE) / getGeometry().scale,
pointSize: (2 * this.controlPointsSize) / getGeometry().scale,
rotationPoint: false,
pointType(cx: number, cy: number): SVG.Circle {
const circle: SVG.Circle = this.nested
@ -365,7 +366,9 @@ export class EditHandlerImpl implements EditHandler {
}
private initEditing(): void {
this.editedShape = this.canvas.select(`#cvat_canvas_shape_${this.editData.state.clientID}`).first().clone();
this.editedShape = this.canvas
.select(`#cvat_canvas_shape_${this.editData.state.clientID}`).first()
.clone().attr('stroke', this.outlinedBorders);
this.setupPoints(true);
this.startEdit();
// draw points for this with selected and start editing till another point is clicked
@ -387,6 +390,8 @@ export class EditHandlerImpl implements EditHandler {
this.autoborderHandler = autoborderHandler;
this.autobordersEnabled = false;
this.intelligentCutEnabled = false;
this.controlPointsSize = consts.BASE_POINT_SIZE;
this.outlinedBorders = 'black';
this.onEditDone = onEditDone;
this.canvas = canvas;
this.editData = null;
@ -416,20 +421,23 @@ export class EditHandlerImpl implements EditHandler {
}
public configurate(configuration: Configuration): void {
if (typeof configuration.autoborders === 'boolean') {
this.autobordersEnabled = configuration.autoborders;
if (this.editLine) {
if (this.autobordersEnabled) {
this.autoborderHandler.autoborder(true, this.editLine, this.editData.state.clientID);
} else {
this.autoborderHandler.autoborder(false);
}
}
this.autobordersEnabled = configuration.autoborders;
this.outlinedBorders = configuration.outlinedBorders || 'black';
if (this.editedShape) {
this.editedShape.attr('stroke', this.outlinedBorders);
}
if (typeof configuration.intelligentPolygonCrop === 'boolean') {
this.intelligentCutEnabled = configuration.intelligentPolygonCrop;
if (this.editLine) {
this.editLine.attr('stroke', this.outlinedBorders);
if (this.autobordersEnabled) {
this.autoborderHandler.autoborder(true, this.editLine, this.editData.state.clientID);
} else {
this.autoborderHandler.autoborder(false);
}
}
this.controlPointsSize = configuration.controlPointsSize || consts.BASE_POINT_SIZE;
this.intelligentCutEnabled = configuration.intelligentPolygonCrop;
}
public transform(geometry: Geometry): void {
@ -453,7 +461,7 @@ export class EditHandlerImpl implements EditHandler {
for (const point of (paintHandler as any).set.members) {
point.attr('stroke-width', `${consts.POINTS_STROKE_WIDTH / geometry.scale}`);
point.attr('r', `${consts.BASE_POINT_SIZE / geometry.scale}`);
point.attr('r', `${this.controlPointsSize / geometry.scale}`);
}
}
}

@ -23,7 +23,6 @@ export interface InteractionHandler {
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;
@ -37,6 +36,8 @@ export class InteractionHandlerImpl implements InteractionHandler {
private intermediateShape: PropType<InteractionData, 'intermediateShape'>;
private drawnIntermediateShape: SVG.Shape;
private thresholdWasModified: boolean;
private controlPointsSize: number;
private selectedShapeOpacity: number;
private prepareResult(): InteractionResult[] {
return this.interactionShapes.map(
@ -111,7 +112,7 @@ export class InteractionHandlerImpl implements InteractionHandler {
if (!this.isWithinThreshold(cx, cy)) return;
this.currentInteractionShape = this.canvas
.circle((consts.BASE_POINT_SIZE * 2) / this.geometry.scale)
.circle((this.controlPointsSize * 2) / this.geometry.scale)
.center(cx, cy)
.fill('white')
.stroke(e.button === 0 ? 'green' : 'red')
@ -137,7 +138,7 @@ export class InteractionHandlerImpl implements InteractionHandler {
self.addClass('cvat_canvas_removable_interaction_point');
self.attr({
'stroke-width': consts.POINTS_SELECTED_STROKE_WIDTH / this.geometry.scale,
r: (consts.BASE_POINT_SIZE * 1.5) / this.geometry.scale,
r: (this.controlPointsSize * 1.5) / this.geometry.scale,
});
self.on('mousedown', (_e: MouseEvent): void => {
@ -162,7 +163,7 @@ export class InteractionHandlerImpl implements InteractionHandler {
self.removeClass('cvat_canvas_removable_interaction_point');
self.attr({
'stroke-width': consts.POINTS_STROKE_WIDTH / this.geometry.scale,
r: consts.BASE_POINT_SIZE / this.geometry.scale,
r: this.controlPointsSize / this.geometry.scale,
});
self.off('mousedown');
@ -205,7 +206,7 @@ export class InteractionHandlerImpl implements InteractionHandler {
.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
})
.fill({ opacity: this.configuration.creationOpacity, color: 'white' });
.fill({ opacity: this.selectedShapeOpacity, color: 'white' });
}
private initInteraction(): void {
@ -300,7 +301,7 @@ export class InteractionHandlerImpl implements InteractionHandler {
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
stroke: erroredShape ? 'red' : 'black',
})
.fill({ opacity: this.configuration.creationOpacity, color: 'white' })
.fill({ opacity: this.selectedShapeOpacity, color: 'white' })
.addClass('cvat_canvas_interact_intermediate_shape');
this.selectize(true, this.drawnIntermediateShape, erroredShape);
} else {
@ -317,7 +318,7 @@ export class InteractionHandlerImpl implements InteractionHandler {
if (value) {
(shape as any).selectize(value, {
deepSelect: true,
pointSize: consts.BASE_POINT_SIZE / self.geometry.scale,
pointSize: this.controlPointsSize / self.geometry.scale,
rotationPoint: false,
classPoints: 'cvat_canvas_interact_intermediate_shape_point',
pointType(cx: number, cy: number): SVG.Circle {
@ -399,7 +400,6 @@ export class InteractionHandlerImpl implements InteractionHandler {
onInteraction(shapes, shapesUpdated, isDone, this.threshold ? this.thresholdRectSize / 2 : null);
};
this.canvas = canvas;
this.configuration = configuration;
this.geometry = geometry;
this.shapesWereUpdated = false;
this.interactionShapes = [];
@ -410,6 +410,8 @@ export class InteractionHandlerImpl implements InteractionHandler {
this.thresholdRectSize = 300;
this.intermediateShape = null;
this.drawnIntermediateShape = null;
this.controlPointsSize = configuration.controlPointsSize;
this.selectedShapeOpacity = configuration.selectedShapeOpacity;
this.cursorPosition = {
x: 0,
y: 0,
@ -477,10 +479,10 @@ export class InteractionHandlerImpl implements InteractionHandler {
for (const shape of shapesToBeScaled) {
if (shape.type === 'circle') {
if (shape.hasClass('cvat_canvas_removable_interaction_point')) {
(shape as SVG.Circle).radius((consts.BASE_POINT_SIZE * 1.5) / this.geometry.scale);
(shape as SVG.Circle).radius((this.controlPointsSize * 1.5) / this.geometry.scale);
shape.attr('stroke-width', consts.POINTS_SELECTED_STROKE_WIDTH / this.geometry.scale);
} else {
(shape as SVG.Circle).radius(consts.BASE_POINT_SIZE / this.geometry.scale);
(shape as SVG.Circle).radius(this.controlPointsSize / this.geometry.scale);
shape.attr('stroke-width', consts.POINTS_STROKE_WIDTH / this.geometry.scale);
}
} else {
@ -490,7 +492,7 @@ export class InteractionHandlerImpl implements InteractionHandler {
for (const element of window.document.getElementsByClassName('cvat_canvas_interact_intermediate_shape_point')) {
element.setAttribute('stroke-width', `${consts.POINTS_STROKE_WIDTH / (2 * this.geometry.scale)}`);
element.setAttribute('r', `${consts.BASE_POINT_SIZE / this.geometry.scale}`);
element.setAttribute('r', `${this.controlPointsSize / this.geometry.scale}`);
}
if (this.drawnIntermediateShape) {
@ -520,21 +522,23 @@ export class InteractionHandlerImpl implements InteractionHandler {
}
public configurate(configuration: Configuration): void {
this.configuration = configuration;
this.controlPointsSize = configuration.controlPointsSize;
this.selectedShapeOpacity = configuration.selectedShapeOpacity;
if (this.drawnIntermediateShape) {
this.drawnIntermediateShape.fill({
opacity: configuration.creationOpacity,
opacity: configuration.selectedShapeOpacity,
});
}
// when interactRectangle
if (this.currentInteractionShape && this.currentInteractionShape.type === 'rect') {
this.currentInteractionShape.fill({ opacity: configuration.creationOpacity });
this.currentInteractionShape.fill({ opacity: configuration.selectedShapeOpacity });
}
// when interactPoints with startwithbbox
if (this.interactionShapes[0] && this.interactionShapes[0].type === 'rect') {
this.interactionShapes[0].fill({ opacity: configuration.creationOpacity });
this.interactionShapes[0].fill({ opacity: configuration.selectedShapeOpacity });
}
}

@ -52,6 +52,9 @@ export interface DrawnState {
updated: number;
frame: number;
label: any;
group: any;
color: string;
elements: DrawnState[] | null;
}
// Translate point array from the canvas coordinate system
@ -192,11 +195,13 @@ export function readPointsFromShape(shape: SVG.Shape): number[] {
let points = null;
if (shape.type === 'ellipse') {
const [rx, ry] = [+shape.attr('rx'), +shape.attr('ry')];
const [cx, cy] = [+shape.attr('cx'), +shape.attr('cy')];
const [cx, cy] = [shape.cx(), shape.cy()];
points = `${cx},${cy} ${cx + rx},${cy - ry}`;
} else if (shape.type === 'rect') {
points = `${shape.attr('x')},${shape.attr('y')} ` +
`${shape.attr('x') + shape.attr('width')},${shape.attr('y') + shape.attr('height')}`;
} else if (shape.type === 'circle') {
points = `${shape.cx()},${shape.cy()}`;
} else {
points = shape.attr('points');
}
@ -239,4 +244,121 @@ export function translateFromCanvas(offset: number, points: number[]): number[]
return points.map((coord: number): number => coord - offset);
}
export function computeWrappingBox(points: number[], margin = 0): Box & BBox {
let xtl = Number.MAX_SAFE_INTEGER;
let ytl = Number.MAX_SAFE_INTEGER;
let xbr = Number.MIN_SAFE_INTEGER;
let ybr = Number.MIN_SAFE_INTEGER;
for (let i = 0; i < points.length; i += 2) {
const [x, y] = [points[i], points[i + 1]];
xtl = Math.min(xtl, x);
ytl = Math.min(ytl, y);
xbr = Math.max(xbr, x);
ybr = Math.max(ybr, y);
}
const box = {
xtl: xtl - margin,
ytl: ytl - margin,
xbr: xbr + margin,
ybr: ybr + margin,
};
return {
...box,
x: box.xtl,
y: box.ytl,
width: box.xbr - box.xtl,
height: box.ybr - box.ytl,
};
}
export function getSkeletonEdgeCoordinates(edge: SVG.Line): {
x1: number, y1: number, x2: number, y2: number
} {
let x1 = 0;
let y1 = 0;
let x2 = 0;
let y2 = 0;
const parent = edge.parent() as any as SVG.G;
if (parent.type !== 'g') {
throw new Error('Edge parent must be a group');
}
const dataNodeFrom = edge.attr('data-node-from');
const dataNodeTo = edge.attr('data-node-to');
const nodeFrom = parent.children()
.find((element: SVG.Element): boolean => element.attr('data-node-id') === dataNodeFrom);
const nodeTo = parent.children()
.find((element: SVG.Element): boolean => element.attr('data-node-id') === dataNodeTo);
if (!nodeFrom || !nodeTo) {
throw new Error(`Edge's nodeFrom ${dataNodeFrom} or nodeTo ${dataNodeTo} do not to refer to any node`);
}
x1 = nodeFrom.cx();
y1 = nodeFrom.cy();
x2 = nodeTo.cx();
y2 = nodeTo.cy();
if (nodeFrom.hasClass('cvat_canvas_hidden') || nodeTo.hasClass('cvat_canvas_hidden')) {
edge.addClass('cvat_canvas_hidden');
} else {
edge.removeClass('cvat_canvas_hidden');
}
if (nodeFrom.hasClass('cvat_canvas_shape_occluded') || nodeTo.hasClass('cvat_canvas_shape_occluded')) {
edge.addClass('cvat_canvas_shape_occluded');
}
if ([x1, y1, x2, y2].some((coord: number): boolean => typeof coord !== 'number')) {
throw new Error(`Edge coordinates must be numbers, got [${x1}, ${y1}, ${x2}, ${y2}]`);
}
return {
x1, y1, x2, y2,
};
}
export function makeSVGFromTemplate(template: string): SVG.G {
const SVGElement = new SVG.G();
/* eslint-disable-next-line no-unsanitized/property */
SVGElement.node.innerHTML = template;
return SVGElement;
}
export function setupSkeletonEdges(skeleton: SVG.G, referenceSVG: SVG.G): void {
for (const child of referenceSVG.children()) {
// search for all edges on template
const dataType = child.attr('data-type');
if (child.type === 'line' && dataType === 'edge') {
const dataNodeFrom = child.attr('data-node-from');
const dataNodeTo = child.attr('data-node-to');
if (!Number.isInteger(dataNodeFrom) || !Number.isInteger(dataNodeTo)) {
throw new Error(`Edge nodeFrom and nodeTo must be numbers, got ${dataNodeFrom}, ${dataNodeTo}`);
}
// try to find the same edge on the skeleton
let edge = skeleton.children().find((_child: SVG.Element) => (
_child.attr('data-node-from') === dataNodeFrom && _child.attr('data-node-to') === dataNodeTo
)) as SVG.Line;
// if not found, lets create it
if (!edge) {
edge = skeleton.line(0, 0, 0, 0).attr({
'data-node-from': dataNodeFrom,
'data-node-to': dataNodeTo,
'stroke-width': 'inherit',
}).addClass('cvat_canvas_skeleton_edge') as SVG.Line;
}
skeleton.node.prepend(edge.node);
const points = getSkeletonEdgeCoordinates(edge);
edge.attr({ ...points, 'stroke-width': 'inherit' });
}
}
}
export type PropType<T, Prop extends keyof T> = T[Prop];

@ -287,7 +287,6 @@ export class Canvas3dViewImpl implements Canvas3dView, Listener {
(_state: any): boolean => _state.clientID === Number(intersects[0].object.name),
);
if (item.length !== 0) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.model.data.groupData.grouped = this.model.data.groupData.grouped.filter(
(_state: any): boolean => _state.clientID !== Number(intersects[0].object.name),

@ -5,6 +5,7 @@
const { defaults } = require('jest-config');
module.exports = {
preset: 'ts-jest',
coverageDirectory: 'reports/coverage',
coverageReporters: ['json', ['lcov', { projectRoot: '../' }]],
moduleFileExtensions: [...defaults.moduleFileExtensions, 'ts', 'tsx'],
@ -12,4 +13,9 @@ module.exports = {
testMatch: ['**/tests/**/*.js'],
testPathIgnorePatterns: ['/node_modules/', '/tests/mocks/*'],
automock: false,
globals: {
'ts-jest': {
diagnostics: false,
},
},
};

@ -1,6 +1,6 @@
{
"name": "cvat-core",
"version": "5.1.0",
"version": "6.0.0",
"description": "Part of Computer Vision Tool which presents an interface for client-side integration",
"main": "src/api.ts",
"scripts": {
@ -23,7 +23,8 @@
"coveralls": "^3.0.5",
"jest": "^26.6.3",
"jest-junit": "^6.4.0",
"jsdoc": "^3.6.6"
"jsdoc": "^3.6.6",
"ts-jest": "26"
},
"dependencies": {
"axios": "^0.27.2",

@ -2,188 +2,209 @@
//
// SPDX-License-Identifier: MIT
(() => {
/**
* Class representing an annotation loader
* @memberof module:API.cvat.classes
* @hideconstructor
*/
class Loader {
constructor(initialData) {
const data = {
name: initialData.name,
format: initialData.ext,
version: initialData.version,
enabled: initialData.enabled,
dimension: initialData.dimension,
};
interface RawLoaderData {
name: string;
ext: string;
version: string;
enabled: boolean;
dimension: '2d' | '3d';
}
Object.defineProperties(this, {
name: {
/**
* @name name
* @type {string}
* @memberof module:API.cvat.classes.Loader
* @readonly
* @instance
*/
get: () => data.name,
},
format: {
/**
* @name format
* @type {string}
* @memberof module:API.cvat.classes.Loader
* @readonly
* @instance
*/
get: () => data.format,
},
version: {
/**
* @name version
* @type {string}
* @memberof module:API.cvat.classes.Loader
* @readonly
* @instance
*/
get: () => data.version,
},
enabled: {
/**
* @name enabled
* @type {string}
* @memberof module:API.cvat.classes.Loader
* @readonly
* @instance
*/
get: () => data.enabled,
},
dimension: {
/**
* @name dimension
* @type {string}
* @memberof module:API.cvat.enums.DimensionType
* @readonly
* @instance
*/
get: () => data.dimension,
},
});
}
}
/**
* Class representing an annotation loader
* @memberof module:API.cvat.classes
* @hideconstructor
*/
export class Loader {
public name: string;
public format: string;
public version: string;
public enabled: boolean;
public dimension: '2d' | '3d';
/**
* Class representing an annotation dumper
* @memberof module:API.cvat.classes
* @hideconstructor
*/
class Dumper {
constructor(initialData) {
const data = {
name: initialData.name,
format: initialData.ext,
version: initialData.version,
enabled: initialData.enabled,
dimension: initialData.dimension,
};
constructor(initialData: RawLoaderData) {
const data = {
name: initialData.name,
format: initialData.ext,
version: initialData.version,
enabled: initialData.enabled,
dimension: initialData.dimension,
};
Object.defineProperties(this, {
name: {
/**
* @name name
* @type {string}
* @memberof module:API.cvat.classes.Dumper
* @readonly
* @instance
*/
get: () => data.name,
},
format: {
/**
* @name format
* @type {string}
* @memberof module:API.cvat.classes.Dumper
* @readonly
* @instance
*/
get: () => data.format,
},
version: {
/**
* @name version
* @type {string}
* @memberof module:API.cvat.classes.Dumper
* @readonly
* @instance
*/
get: () => data.version,
},
enabled: {
/**
* @name enabled
* @type {string}
* @memberof module:API.cvat.classes.Loader
* @readonly
* @instance
*/
get: () => data.enabled,
},
dimension: {
/**
* @name dimension
* @type {string}
* @memberof module:API.cvat.enums.DimensionType
* @readonly
* @instance
*/
get: () => data.dimension,
},
});
}
Object.defineProperties(this, {
name: {
/**
* @name name
* @type {string}
* @memberof module:API.cvat.classes.Loader
* @readonly
* @instance
*/
get: () => data.name,
},
format: {
/**
* @name format
* @type {string}
* @memberof module:API.cvat.classes.Loader
* @readonly
* @instance
*/
get: () => data.format,
},
version: {
/**
* @name version
* @type {string}
* @memberof module:API.cvat.classes.Loader
* @readonly
* @instance
*/
get: () => data.version,
},
enabled: {
/**
* @name enabled
* @type {boolean}
* @memberof module:API.cvat.classes.Loader
* @readonly
* @instance
*/
get: () => data.enabled,
},
dimension: {
/**
* @name dimension
* @type {module:API.cvat.enums.DimensionType}
* @memberof module:API.cvat.classes.Loader
* @readonly
* @instance
*/
get: () => data.dimension,
},
});
}
}
type RawDumperData = RawLoaderData;
/**
* Class representing an annotation format
* @memberof module:API.cvat.classes
* @hideconstructor
*/
class AnnotationFormats {
constructor(initialData) {
const data = {
exporters: initialData.exporters.map((el) => new Dumper(el)),
importers: initialData.importers.map((el) => new Loader(el)),
};
/**
* Class representing an annotation dumper
* @memberof module:API.cvat.classes
* @hideconstructor
*/
export class Dumper {
public name: string;
public format: string;
public version: string;
public enabled: boolean;
public dimension: '2d' | '3d';
// Now all fields are readonly
Object.defineProperties(this, {
loaders: {
/**
* @name loaders
* @type {module:API.cvat.classes.Loader[]}
* @memberof module:API.cvat.classes.AnnotationFormats
* @readonly
* @instance
*/
get: () => [...data.importers],
},
dumpers: {
/**
* @name dumpers
* @type {module:API.cvat.classes.Dumper[]}
* @memberof module:API.cvat.classes.AnnotationFormats
* @readonly
* @instance
*/
get: () => [...data.exporters],
},
});
}
constructor(initialData: RawDumperData) {
const data = {
name: initialData.name,
format: initialData.ext,
version: initialData.version,
enabled: initialData.enabled,
dimension: initialData.dimension,
};
Object.defineProperties(this, {
name: {
/**
* @name name
* @type {string}
* @memberof module:API.cvat.classes.Dumper
* @readonly
* @instance
*/
get: () => data.name,
},
format: {
/**
* @name format
* @type {string}
* @memberof module:API.cvat.classes.Dumper
* @readonly
* @instance
*/
get: () => data.format,
},
version: {
/**
* @name version
* @type {string}
* @memberof module:API.cvat.classes.Dumper
* @readonly
* @instance
*/
get: () => data.version,
},
enabled: {
/**
* @name enabled
* @type {boolean}
* @memberof module:API.cvat.classes.Dumper
* @readonly
* @instance
*/
get: () => data.enabled,
},
dimension: {
/**
* @name dimension
* @type {module:API.cvat.enums.DimensionType}
* @memberof module:API.cvat.classes.Dumper
* @readonly
* @instance
*/
get: () => data.dimension,
},
});
}
}
interface AnnotationFormatRawData {
importers: RawLoaderData[];
exporters: RawDumperData[];
}
module.exports = {
AnnotationFormats,
Loader,
Dumper,
};
})();
/**
* Class representing an annotation format
* @memberof module:API.cvat.classes
* @hideconstructor
*/
export class AnnotationFormats {
public loaders: Loader[];
public dumpers: Dumper[];
constructor(initialData: AnnotationFormatRawData) {
const data = {
exporters: initialData.exporters.map((el) => new Dumper(el)),
importers: initialData.importers.map((el) => new Loader(el)),
};
Object.defineProperties(this, {
loaders: {
/**
* @name loaders
* @type {module:API.cvat.classes.Loader[]}
* @memberof module:API.cvat.classes.AnnotationFormats
* @readonly
* @instance
*/
get: () => [...data.importers],
},
dumpers: {
/**
* @name dumpers
* @type {module:API.cvat.classes.Dumper[]}
* @memberof module:API.cvat.classes.AnnotationFormats
* @readonly
* @instance
*/
get: () => [...data.exporters],
},
});
}
}

@ -4,100 +4,22 @@
(() => {
const {
RectangleShape,
PolygonShape,
PolylineShape,
PointsShape,
EllipseShape,
CuboidShape,
RectangleTrack,
PolygonTrack,
PolylineTrack,
PointsTrack,
EllipseTrack,
CuboidTrack,
shapeFactory,
trackFactory,
Track,
Shape,
Tag,
objectStateFactory,
} = require('./annotations-objects');
const AnnotationsFilter = require('./annotations-filter');
const AnnotationsFilter = require('./annotations-filter').default;
const { checkObjectType } = require('./common');
const Statistics = require('./statistics');
const { Label } = require('./labels');
const { DataError, ArgumentError, ScriptingError } = require('./exceptions');
const { ArgumentError, ScriptingError } = require('./exceptions');
const ObjectState = require('./object-state').default;
const {
HistoryActions, ObjectShape, ObjectType, colors,
HistoryActions, ShapeType, ObjectType, colors, Source,
} = require('./enums');
const ObjectState = require('./object-state');
function shapeFactory(shapeData, clientID, injection) {
const { type } = shapeData;
const color = colors[clientID % colors.length];
let shapeModel = null;
switch (type) {
case 'rectangle':
shapeModel = new RectangleShape(shapeData, clientID, color, injection);
break;
case 'polygon':
shapeModel = new PolygonShape(shapeData, clientID, color, injection);
break;
case 'polyline':
shapeModel = new PolylineShape(shapeData, clientID, color, injection);
break;
case 'points':
shapeModel = new PointsShape(shapeData, clientID, color, injection);
break;
case 'ellipse':
shapeModel = new EllipseShape(shapeData, clientID, color, injection);
break;
case 'cuboid':
shapeModel = new CuboidShape(shapeData, clientID, color, injection);
break;
default:
throw new DataError(`An unexpected type of shape "${type}"`);
}
return shapeModel;
}
function trackFactory(trackData, clientID, injection) {
if (trackData.shapes.length) {
const { type } = trackData.shapes[0];
const color = colors[clientID % colors.length];
let trackModel = null;
switch (type) {
case 'rectangle':
trackModel = new RectangleTrack(trackData, clientID, color, injection);
break;
case 'polygon':
trackModel = new PolygonTrack(trackData, clientID, color, injection);
break;
case 'polyline':
trackModel = new PolylineTrack(trackData, clientID, color, injection);
break;
case 'points':
trackModel = new PointsTrack(trackData, clientID, color, injection);
break;
case 'ellipse':
trackModel = new EllipseTrack(trackData, clientID, color, injection);
break;
case 'cuboid':
trackModel = new CuboidTrack(trackData, clientID, color, injection);
break;
default:
throw new DataError(`An unexpected type of track "${type}"`);
}
return trackModel;
}
console.warn('The track without any shapes had been found. It was ignored.');
return null;
}
class Collection {
constructor(data) {
@ -107,6 +29,10 @@
this.labels = data.labels.reduce((labelAccumulator, label) => {
labelAccumulator[label.id] = label;
(label?.structure?.sublabels || []).forEach((sublabel) => {
labelAccumulator[sublabel.id] = sublabel;
});
return labelAccumulator;
}, {});
@ -126,6 +52,7 @@
groups: this.groups,
frameMeta: this.frameMeta,
history: this.history,
nextClientID: () => ++this.count,
groupColors: {},
};
}
@ -202,10 +129,7 @@
const tags = this.tags[frame] || [];
const objects = [].concat(tracks, shapes, tags);
const visible = {
models: [],
data: [],
};
const visible = [];
for (const object of objects) {
if (object.removed) {
@ -213,21 +137,19 @@
}
const stateData = object.get(frame);
if (!allTracks && stateData.outside && !stateData.keyframe) {
if (stateData.outside && !stateData.keyframe && !allTracks && object instanceof Track) {
continue;
}
visible.models.push(object);
visible.data.push(stateData);
visible.push(stateData);
}
const objectStates = [];
const filtered = this.annotationsFilter.filter(visible.data, filters);
const filtered = this.annotationsFilter.filter(visible, filters);
visible.data.forEach((stateData, idx) => {
visible.forEach((stateData, idx) => {
if (!filters.length || filtered.includes(stateData.clientID)) {
const model = visible.models[idx];
const objectState = objectStateFactory.call(model, frame, stateData);
const objectState = new ObjectState(stateData);
objectStates.push(objectState);
}
});
@ -255,7 +177,7 @@
throw new ArgumentError(`Unknown label for the task: ${label.id}`);
}
if (!Object.values(ObjectShape).includes(shapeType)) {
if (!Object.values(ShapeType).includes(shapeType)) {
throw new ArgumentError(`Got unknown shapeType "${shapeType}"`);
}
@ -288,10 +210,14 @@
keyframes[object.frame] = {
type: shapeType,
frame: object.frame,
points: [...object.points],
points: object.shapeType === ShapeType.SKELETON ? undefined : [...object.points],
elements: object.shapeType === ShapeType.SKELETON ? object.elements.map((el) => {
const { id, clientID, ...rest } = el.toJSON();
return rest;
}) : undefined,
occluded: object.occluded,
rotation: object.rotation,
zOrder: object.zOrder,
z_order: object.zOrder,
outside: false,
attributes: Object.keys(object.attributes).reduce((accumulator, attrID) => {
// We save only mutable attributes inside a keyframe
@ -311,13 +237,24 @@
keyframes[object.frame + 1] = JSON.parse(JSON.stringify(keyframes[object.frame]));
keyframes[object.frame + 1].outside = true;
keyframes[object.frame + 1].frame++;
keyframes[object.frame + 1].attributes = [];
(keyframes[object.frame + 1].elements || []).forEach((el) => {
el.outside = keyframes[object.frame + 1].outside;
el.frame = keyframes[object.frame + 1].frame;
});
}
} else if (object instanceof Track) {
// If this object is track, iterate through all its
// keyframes and push copies to new keyframes
const attributes = {}; // id:value
for (const keyframe of Object.keys(object.shapes)) {
const shape = object.shapes[keyframe];
const trackShapes = object.shapes;
const exportedShapes = object.shapeType === ShapeType.SKELETON ?
object.prepareShapesForServer().reduce((acc, val) => {
acc[val.frame] = val;
return acc;
}, {}) : {};
for (const keyframe of Object.keys(trackShapes)) {
const shape = trackShapes[keyframe];
// Frame already saved and it is not outside
if (keyframe in keyframes && !keyframes[keyframe].outside) {
// This shape is outside and non-outside shape already exists
@ -341,11 +278,16 @@
keyframes[keyframe] = {
type: shapeType,
frame: +keyframe,
points: [...shape.points],
points: object.shapeType === ShapeType.SKELETON ? undefined : [...shape.points],
elements: object.shapeType === ShapeType.SKELETON ?
exportedShapes[keyframe].elements.map((el) => {
const { id, ...rest } = el;
return rest;
}) : undefined,
rotation: shape.rotation,
occluded: shape.occluded,
outside: shape.outside,
zOrder: shape.zOrder,
z_order: shape.zOrder,
attributes: updatedAttributes ? Object.keys(attributes).reduce((accumulator, attrID) => {
accumulator.push({
spec_id: +attrID,
@ -451,13 +393,30 @@
const exported = object.toJSON();
const position = {
type: objectState.shapeType,
points: [...objectState.points],
points: objectState.shapeType === ShapeType.SKELETON ? undefined : [...objectState.points],
elements: objectState.shapeType === ShapeType.SKELETON ? objectState.elements.map((el: ObjectState) => {
const elementAttributes = el.attributes;
return {
attributes: Object.keys(elementAttributes).reduce((acc, attrID) => {
acc.push({
spec_id: +attrID,
value: elementAttributes[attrID],
});
return acc;
}, []),
label_id: el.label.id,
occluded: el.occluded,
outside: el.outside,
points: [...el.points],
type: el.shapeType,
};
}) : undefined,
rotation: objectState.rotation,
occluded: objectState.occluded,
outside: objectState.outside,
zOrder: objectState.zOrder,
z_order: objectState.zOrder,
attributes: Object.keys(objectState.attributes).reduce((accumulator, attrID) => {
if (!labelAttributes[attrID].mutable) {
if (labelAttributes[attrID].mutable) {
accumulator.push({
spec_id: +attrID,
value: objectState.attributes[attrID],
@ -475,14 +434,19 @@
label_id: exported.label_id,
attributes: exported.attributes,
shapes: [],
source: Source.MANUAL,
};
const next = JSON.parse(JSON.stringify(prev));
next.frame = frame;
next.shapes.push(JSON.parse(JSON.stringify(position)));
exported.shapes.map((shape) => {
delete shape.id;
(shape.elements || []).forEach((element) => {
delete element.id;
});
if (shape.frame < frame) {
prev.shapes.push(JSON.parse(JSON.stringify(shape)));
} else if (shape.frame > frame) {
@ -499,6 +463,9 @@
prev.shapes[prev.shapes.length - 2].frame -= 1;
}
prev.shapes[prev.shapes.length - 1].outside = true;
(prev.shapes[prev.shapes.length - 1].elements || []).forEach((el) => {
el.outside = true;
});
let clientID = ++this.count;
const prevTrack = trackFactory(prev, clientID, this.injection);
@ -546,6 +513,7 @@
const undoGroups = objectsForGroup.map((object) => object.group);
for (const object of objectsForGroup) {
object.group = groupIdx;
object.updated = Date.now();
}
const redoGroups = objectsForGroup.map((object) => object.group);
@ -554,11 +522,13 @@
() => {
objectsForGroup.forEach((object, idx) => {
object.group = undoGroups[idx];
object.updated = Date.now();
});
},
() => {
objectsForGroup.forEach((object, idx) => {
object.group = redoGroups[idx];
object.updated = Date.now();
});
},
objectsForGroup.map((object) => object.clientID),
@ -579,8 +549,17 @@
tracks.forEach((track) => {
if (track.frame <= endframe) {
if (delTrackKeyframesOnly) {
for (const keyframe in track.shapes) {
if (keyframe >= startframe && keyframe <= endframe) { delete track.shapes[keyframe]; }
for (const keyframe of Object.keys(track.shapes)) {
if (+keyframe >= startframe && +keyframe <= endframe) {
delete track.shapes[keyframe];
(track.elements || []).forEach((element) => {
if (keyframe in element.shapes) {
delete element.shapes[keyframe];
element.updated = Date.now();
}
});
track.updated = Date.now();
}
}
} else if (track.frame >= startframe) {
const index = tracks.indexOf(track);
@ -593,7 +572,7 @@
this.shapes = {};
this.tags = {};
this.tracks = [];
this.objects = {}; // by id
this.objects = {};
this.count = 0;
this.flush = true;
@ -606,42 +585,74 @@
statistics() {
const labels = {};
const skeleton = {
rectangle: {
shape: 0,
track: 0,
},
polygon: {
shape: 0,
track: 0,
},
polyline: {
shape: 0,
track: 0,
},
points: {
shape: 0,
track: 0,
},
ellipse: {
shape: 0,
track: 0,
},
cuboid: {
shape: 0,
track: 0,
},
tags: 0,
const shapes = ['rectangle', 'polygon', 'polyline', 'points', 'ellipse', 'cuboid', 'skeleton'];
const body = {
...(shapes.reduce((acc, val) => ({
...acc,
[val]: { shape: 0, track: 0 },
}), {})),
tag: 0,
manually: 0,
interpolated: 0,
total: 0,
};
const total = JSON.parse(JSON.stringify(skeleton));
for (const label of Object.values(this.labels)) {
const { name } = label;
labels[name] = JSON.parse(JSON.stringify(skeleton));
}
const sep = '{{cvat.skeleton.lbl.sep}}';
const fillBody = (spec, prefix = ''): void => {
const pref = prefix ? `${prefix}${sep}` : '';
for (const label of spec) {
const { name } = label;
labels[`${pref}${name}`] = JSON.parse(JSON.stringify(body));
if (label?.structure?.sublabels) {
fillBody(label.structure.sublabels, `${pref}${name}`);
}
}
};
const total = JSON.parse(JSON.stringify(body));
fillBody(Object.values(this.labels).filter((label) => !label.hasParent));
const scanTrack = (track, prefix = ''): void => {
const pref = prefix ? `${prefix}${sep}` : '';
const label = `${pref}${track.label.name}`;
labels[label][track.shapeType].track++;
const keyframes = Object.keys(track.shapes)
.sort((a, b) => +a - +b)
.map((el) => +el);
let prevKeyframe = keyframes[0];
let visible = false;
for (const keyframe of keyframes) {
if (visible) {
const interpolated = keyframe - prevKeyframe - 1;
labels[label].interpolated += interpolated;
labels[label].total += interpolated;
}
visible = !track.shapes[keyframe].outside;
prevKeyframe = keyframe;
if (visible) {
labels[label].manually++;
labels[label].total++;
}
}
let lastKey = keyframes[keyframes.length - 1];
if (track.shapeType === ShapeType.SKELETON) {
track.elements.forEach((element) => {
scanTrack(element, label);
lastKey = Math.max(lastKey, ...Object.keys(element.shapes).map((key) => +key));
});
}
if (lastKey !== this.stopFrame && !track.get(lastKey).outside) {
const interpolated = this.stopFrame - lastKey;
labels[label].interpolated += interpolated;
labels[label].total += interpolated;
}
};
for (const object of Object.values(this.objects)) {
if (object.removed) {
@ -659,59 +670,37 @@
throw new ScriptingError(`Unexpected object type: "${objectType}"`);
}
const label = object.label.name;
const { name: label } = object.label;
if (objectType === 'tag') {
labels[label].tags++;
labels[label].tag++;
labels[label].manually++;
labels[label].total++;
} else if (objectType === 'track') {
scanTrack(object);
} else {
const { shapeType } = object;
labels[label][shapeType][objectType]++;
if (objectType === 'track') {
const keyframes = Object.keys(object.shapes)
.sort((a, b) => +a - +b)
.map((el) => +el);
let prevKeyframe = keyframes[0];
let visible = false;
for (const keyframe of keyframes) {
if (visible) {
const interpolated = keyframe - prevKeyframe - 1;
labels[label].interpolated += interpolated;
labels[label].total += interpolated;
}
visible = !object.shapes[keyframe].outside;
prevKeyframe = keyframe;
if (visible) {
labels[label].manually++;
labels[label].total++;
}
}
const lastKey = keyframes[keyframes.length - 1];
if (lastKey !== this.stopFrame && !object.shapes[lastKey].outside) {
const interpolated = this.stopFrame - lastKey;
labels[label].interpolated += interpolated;
labels[label].total += interpolated;
}
} else {
labels[label].manually++;
labels[label].total++;
labels[label][shapeType].shape++;
labels[label].manually++;
labels[label].total++;
if (shapeType === ShapeType.SKELETON) {
object.elements.forEach((element) => {
const combinedName = [label, element.label.name].join(sep);
labels[combinedName][element.shapeType].shape++;
labels[combinedName].manually++;
labels[combinedName].total++;
});
}
}
}
for (const label of Object.keys(labels)) {
for (const key of Object.keys(labels[label])) {
if (typeof labels[label][key] === 'object') {
for (const objectType of Object.keys(labels[label][key])) {
total[key][objectType] += labels[label][key][objectType];
for (const shapeType of Object.keys(labels[label])) {
if (typeof labels[label][shapeType] === 'object') {
for (const objectType of Object.keys(labels[label][shapeType])) {
total[shapeType][objectType] += labels[label][shapeType][objectType];
}
} else {
total[key] += labels[label][key];
total[shapeType] += labels[label][shapeType];
}
}
}
@ -744,7 +733,7 @@
for (const state of objectStates) {
checkObjectType('object state', state, null, ObjectState);
checkObjectType('state client ID', state.clientID, 'undefined', null);
checkObjectType('state client ID', state.clientID, null, null);
checkObjectType('state frame', state.frame, 'integer', null);
checkObjectType('state rotation', state.rotation || 0, 'number', null);
checkObjectType('state attributes', state.attributes, null, Object);
@ -775,9 +764,9 @@
checkObjectType('point coordinate', coord, 'number', null);
}
if (!Object.values(ObjectShape).includes(state.shapeType)) {
if (!Object.values(ShapeType).includes(state.shapeType)) {
throw new ArgumentError(
`Object shape must be one of: ${JSON.stringify(Object.values(ObjectShape))}`,
`Object shape must be one of: ${JSON.stringify(Object.values(ShapeType))}`,
);
}
@ -794,6 +783,18 @@
type: state.shapeType,
z_order: state.zOrder,
source: state.source,
elements: state.shapeType === 'skeleton' ? state.elements.map((element) => ({
attributes: [],
frame: element.frame,
group: 0,
label_id: element.label.id,
points: [...element.points],
rotation: 0,
type: element.shapeType,
z_order: 0,
outside: element.outside || false,
occluded: element.occluded || false,
})) : undefined,
});
} else if (state.objectType === 'track') {
constructed.tracks.push({
@ -807,7 +808,7 @@
{
attributes: attributes.filter((attr) => labelAttributes[attr.spec_id].mutable),
frame: state.frame,
occluded: state.occluded || false,
occluded: false,
outside: false,
points: [...state.points],
rotation: state.rotation || 0,
@ -815,6 +816,33 @@
z_order: state.zOrder,
},
],
elements: state.shapeType === 'skeleton' ? state.elements.map((element) => {
const elementAttrValues = Object.keys(state.attributes)
.reduce(convertAttributes.bind(state), []);
const elementAttributes = element.label.attributes.reduce((accumulator, attribute) => {
accumulator[attribute.id] = attribute;
return accumulator;
}, {});
return ({
attributes: elementAttrValues
.filter((attr) => !elementAttributes[attr.spec_id].mutable),
frame: state.frame,
group: 0,
label_id: element.label.id,
shapes: [{
frame: state.frame,
type: element.shapeType,
points: [...element.points],
zOrder: state.zOrder,
outside: element.outside || false,
occluded: element.occluded || false,
rotation: element.rotation || 0,
attributes: elementAttrValues
.filter((attr) => !elementAttributes[attr.spec_id].mutable),
}],
});
}) : undefined,
});
} else {
throw new ArgumentError(

@ -2,14 +2,14 @@
//
// SPDX-License-Identifier: MIT
const jsonLogic = require('json-logic-js');
const { AttributeType, ObjectType } = require('./enums');
import jsonLogic from 'json-logic-js';
import { AttributeType, ObjectType } from './enums';
function adjustName(name) {
function adjustName(name): string {
return name.replace(/\./g, '\u2219');
}
class AnnotationsFilter {
export default class AnnotationsFilter {
_convertObjects(statesData) {
const objects = statesData.map((state) => {
const labelAttributes = state.label.attributes.reduce((acc, attr) => {
@ -24,7 +24,11 @@ class AnnotationsFilter {
let [width, height] = [null, null];
if (state.objectType !== ObjectType.TAG) {
state.points.forEach((coord, idx) => {
const points = state.points || state.elements.reduce((acc, val) => {
acc.push(val.points);
return acc;
}, []).flat();
points.forEach((coord, idx) => {
if (idx % 2) {
// y
ytl = Math.min(ytl, coord);
@ -75,5 +79,3 @@ class AnnotationsFilter {
.filter((_, index) => jsonLogic.apply(filters[0], converted[index]));
}
}
module.exports = AnnotationsFilter;

@ -2,26 +2,40 @@
//
// SPDX-License-Identifier: MIT
import { HistoryActions } from './enums';
const MAX_HISTORY_LENGTH = 128;
class AnnotationHistory {
interface ActionItem {
action: HistoryActions;
undo: Function;
redo: Function;
clientIDs: number[];
frame: number;
}
export default class AnnotationHistory {
private frozen: boolean;
private _undo: ActionItem[];
private _redo: ActionItem[];
constructor() {
this.frozen = false;
this.clear();
}
freeze(frozen) {
public freeze(frozen: boolean): void {
this.frozen = frozen;
}
get() {
public get(): { undo: [HistoryActions, number][], redo: [HistoryActions, number][] } {
return {
undo: this._undo.map((undo) => [undo.action, undo.frame]),
redo: this._redo.map((redo) => [redo.action, redo.frame]),
};
}
do(action, undo, redo, clientIDs, frame) {
public do(action: HistoryActions, undo: Function, redo: Function, clientIDs: number[], frame: number): void {
if (this.frozen) return;
const actionItem = {
clientIDs,
@ -36,7 +50,7 @@ class AnnotationHistory {
this._redo = [];
}
async undo(count) {
public async undo(count: number): Promise<number[]> {
const affectedObjects = [];
for (let i = 0; i < count; i++) {
const action = this._undo.pop();
@ -52,7 +66,7 @@ class AnnotationHistory {
return affectedObjects;
}
async redo(count) {
public async redo(count: number): Promise<number[]> {
const affectedObjects = [];
for (let i = 0; i < count; i++) {
const action = this._redo.pop();
@ -68,10 +82,8 @@ class AnnotationHistory {
return affectedObjects;
}
clear() {
public clear(): void {
this._undo = [];
this._redo = [];
}
}
module.exports = AnnotationHistory;

File diff suppressed because it is too large Load Diff

@ -103,6 +103,7 @@
'rotation',
'type',
'shapes',
'elements',
'attributes',
'value',
'spec_id',

@ -6,7 +6,7 @@
const serverProxy = require('./server-proxy');
const Collection = require('./annotations-collection');
const AnnotationsSaver = require('./annotations-saver');
const AnnotationsHistory = require('./annotations-history');
const AnnotationsHistory = require('./annotations-history').default;
const { checkObjectType } = require('./common');
const { Project } = require('./project');
const { Task, Job } = require('./session');

@ -5,7 +5,7 @@
const config = require('./config');
(() => {
const PluginRegistry = require('./plugins');
const PluginRegistry = require('./plugins').default;
const serverProxy = require('./server-proxy');
const lambdaManager = require('./lambda-manager');
const {

@ -8,10 +8,10 @@
*/
function build() {
const PluginRegistry = require('./plugins');
const PluginRegistry = require('./plugins').default;
const loggerStorage = require('./logger-storage');
const Log = require('./log');
const ObjectState = require('./object-state');
const ObjectState = require('./object-state').default;
const Statistics = require('./statistics');
const Comment = require('./comment');
const Issue = require('./issue');
@ -880,11 +880,6 @@ function build() {
cvat.organizations = Object.freeze(cvat.organizations);
const implementAPI = require('./api-implementation');
Math.clamp = function clamp(value, min, max) {
return Math.min(Math.max(value, min), max);
};
const implemented = Object.freeze(implementAPI(cvat));
return implemented;
}

@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT
(() => {
const PluginRegistry = require('./plugins');
const PluginRegistry = require('./plugins').default;
const serverProxy = require('./server-proxy');
const { isBrowser, isNode } = require('browser-or-node');
const { ArgumentError } = require('./exceptions');

@ -2,130 +2,121 @@
//
// SPDX-License-Identifier: MIT
(() => {
const { ArgumentError } = require('./exceptions');
import { ArgumentError } from './exceptions';
function isBoolean(value) {
return typeof value === 'boolean';
}
export function isBoolean(value): boolean {
return typeof value === 'boolean';
}
function isInteger(value) {
return typeof value === 'number' && Number.isInteger(value);
}
export function isInteger(value): boolean {
return typeof value === 'number' && Number.isInteger(value);
}
// Called with specific Enum context
function isEnum(value) {
for (const key in this) {
if (Object.prototype.hasOwnProperty.call(this, key)) {
if (this[key] === value) {
return true;
}
// Called with specific Enum context
export function isEnum(value): boolean {
for (const key in this) {
if (Object.prototype.hasOwnProperty.call(this, key)) {
if (this[key] === value) {
return true;
}
}
return false;
}
function isString(value) {
return typeof value === 'string';
}
return false;
}
function checkFilter(filter, fields) {
for (const prop in filter) {
if (Object.prototype.hasOwnProperty.call(filter, prop)) {
if (!(prop in fields)) {
throw new ArgumentError(`Unsupported filter property has been received: "${prop}"`);
} else if (!fields[prop](filter[prop])) {
throw new ArgumentError(`Received filter property "${prop}" does not satisfy API`);
}
export function isString(value): boolean {
return typeof value === 'string';
}
export function checkFilter(filter, fields): void {
for (const prop in filter) {
if (Object.prototype.hasOwnProperty.call(filter, prop)) {
if (!(prop in fields)) {
throw new ArgumentError(`Unsupported filter property has been received: "${prop}"`);
} else if (!fields[prop](filter[prop])) {
throw new ArgumentError(`Received filter property "${prop}" does not satisfy API`);
}
}
}
}
function checkExclusiveFields(obj, exclusive, ignore) {
const fields = {
exclusive: [],
other: [],
};
for (const field in Object.keys(obj)) {
if (!(field in ignore)) {
if (field in exclusive) {
if (fields.other.length) {
throw new ArgumentError(`Do not use the filter field "${field}" with others`);
}
fields.exclusive.push(field);
} else {
fields.other.push(field);
export function checkExclusiveFields(obj, exclusive, ignore): void {
const fields = {
exclusive: [],
other: [],
};
for (const field in Object.keys(obj)) {
if (!(field in ignore)) {
if (field in exclusive) {
if (fields.other.length) {
throw new ArgumentError(`Do not use the filter field "${field}" with others`);
}
fields.exclusive.push(field);
} else {
fields.other.push(field);
}
}
}
}
function checkObjectType(name, value, type, instance) {
if (type) {
if (typeof value !== type) {
// specific case for integers which aren't native type in JS
if (type === 'integer' && Number.isInteger(value)) {
return true;
}
throw new ArgumentError(`"${name}" is expected to be "${type}", but "${typeof value}" has been got.`);
export function checkObjectType(name, value, type, instance): boolean {
if (type) {
if (typeof value !== type) {
// specific case for integers which aren't native type in JS
if (type === 'integer' && Number.isInteger(value)) {
return true;
}
} else if (instance) {
if (!(value instanceof instance)) {
if (value !== undefined) {
throw new ArgumentError(
`"${name}" is expected to be ${instance.name}, but ` +
`"${value.constructor.name}" has been got`,
);
}
throw new ArgumentError(`"${name}" is expected to be ${instance.name}, but "undefined" has been got.`);
}
throw new ArgumentError(`"${name}" is expected to be "${type}", but "${typeof value}" has been got.`);
}
} else if (instance) {
if (!(value instanceof instance)) {
if (value !== undefined) {
throw new ArgumentError(
`"${name}" is expected to be ${instance.name}, but ` +
`"${value.constructor.name}" has been got`,
);
}
return true;
throw new ArgumentError(`"${name}" is expected to be ${instance.name}, but "undefined" has been got.`);
}
}
class FieldUpdateTrigger {
constructor() {
let updatedFlags = {};
return true;
}
export class FieldUpdateTrigger {
constructor() {
let updatedFlags = {};
Object.defineProperties(
this,
Object.freeze({
reset: {
value: () => {
updatedFlags = {};
},
Object.defineProperties(
this,
Object.freeze({
reset: {
value: () => {
updatedFlags = {};
},
update: {
value: (name) => {
updatedFlags[name] = true;
},
},
update: {
value: (name) => {
updatedFlags[name] = true;
},
getUpdated: {
value: (data, propMap = {}) => {
const result = {};
for (const updatedField of Object.keys(updatedFlags)) {
result[propMap[updatedField] || updatedField] = data[updatedField];
}
return result;
},
},
getUpdated: {
value: (data, propMap = {}) => {
const result = {};
for (const updatedField of Object.keys(updatedFlags)) {
result[propMap[updatedField] || updatedField] = data[updatedField];
}
return result;
},
}),
);
}
},
}),
);
}
}
module.exports = {
isBoolean,
isInteger,
isEnum,
isString,
checkFilter,
checkObjectType,
checkExclusiveFields,
FieldUpdateTrigger,
};
})();
export function clamp(value: number, min: number, max: number): number {
return Math.min(Math.max(value, min), max);
}

@ -2,10 +2,12 @@
//
// SPDX-License-Identifier: MIT
module.exports = {
const config = {
backendAPI: '/api',
proxy: false,
organizationID: null,
origin: '',
uploadChunkSize: 100,
};
export default config;

@ -1,450 +1,425 @@
// Copyright (C) 2019-2022 Intel Corporation
//
// SPDX-License-Identifier: MIT
// SPDX-License-Identifier = MIT
(() => {
/**
* Share files types
* @enum {string}
* @name ShareFileType
* @memberof module:API.cvat.enums
* @property {string} DIR 'DIR'
* @property {string} REG 'REG'
* @readonly
*/
const ShareFileType = Object.freeze({
DIR: 'DIR',
REG: 'REG',
});
/**
* Share files types
* @enum {string}
* @name ShareFileType
* @memberof module:API.cvat.enums
* @property {string} DIR 'DIR'
* @property {string} REG 'REG'
* @readonly
*/
export enum ShareFileType {
DIR = 'DIR',
REG = 'REG',
}
/**
* Task statuses
* @enum {string}
* @name TaskStatus
* @memberof module:API.cvat.enums
* @property {string} ANNOTATION 'annotation'
* @property {string} VALIDATION 'validation'
* @property {string} COMPLETED 'completed'
* @readonly
*/
const TaskStatus = Object.freeze({
ANNOTATION: 'annotation',
VALIDATION: 'validation',
COMPLETED: 'completed',
});
/**
* Task statuses
* @enum {string}
* @name TaskStatus
* @memberof module:API.cvat.enums
* @property {string} ANNOTATION 'annotation'
* @property {string} VALIDATION 'validation'
* @property {string} COMPLETED 'completed'
* @readonly
*/
export enum TaskStatus {
ANNOTATION = 'annotation',
VALIDATION = 'validation',
COMPLETED = 'completed',
}
/**
* Job stages
* @enum {string}
* @name JobStage
* @memberof module:API.cvat.enums
* @property {string} ANNOTATION 'annotation'
* @property {string} VALIDATION 'validation'
* @property {string} ACCEPTANCE 'acceptance'
* @readonly
*/
const JobStage = Object.freeze({
ANNOTATION: 'annotation',
VALIDATION: 'validation',
ACCEPTANCE: 'acceptance',
});
/**
* Job stages
* @enum {string}
* @name JobStage
* @memberof module:API.cvat.enums
* @property {string} ANNOTATION 'annotation'
* @property {string} VALIDATION 'validation'
* @property {string} ACCEPTANCE 'acceptance'
* @readonly
*/
export enum JobStage {
ANNOTATION = 'annotation',
VALIDATION = 'validation',
ACCEPTANCE = 'acceptance',
}
/**
* Job states
* @enum {string}
* @name JobState
* @memberof module:API.cvat.enums
* @property {string} NEW 'new'
* @property {string} IN_PROGRESS 'in progress'
* @property {string} COMPLETED 'completed'
* @property {string} REJECTED 'rejected'
* @readonly
*/
const JobState = Object.freeze({
NEW: 'new',
IN_PROGRESS: 'in progress',
COMPLETED: 'completed',
REJECTED: 'rejected',
});
/**
* Job states
* @enum {string}
* @name JobState
* @memberof module:API.cvat.enums
* @property {string} NEW 'new'
* @property {string} IN_PROGRESS 'in progress'
* @property {string} COMPLETED 'completed'
* @property {string} REJECTED 'rejected'
* @readonly
*/
export enum JobState {
NEW = 'new',
IN_PROGRESS = 'in progress',
COMPLETED = 'completed',
REJECTED = 'rejected',
}
/**
* Task dimension
* @enum
* @name DimensionType
* @memberof module:API.cvat.enums
* @property {string} DIMENSION_2D '2d'
* @property {string} DIMENSION_3D '3d'
* @readonly
*/
const DimensionType = Object.freeze({
DIMENSION_2D: '2d',
DIMENSION_3D: '3d',
});
/**
* Task dimension
* @enum
* @name DimensionType
* @memberof module:API.cvat.enums
* @property {string} DIMENSION_2D '2d'
* @property {string} DIMENSION_3D '3d'
* @readonly
*/
export enum DimensionType {
DIMENSION_2D = '2d',
DIMENSION_3D = '3d',
}
/**
* List of RQ statuses
* @enum {string}
* @name RQStatus
* @memberof module:API.cvat.enums
* @property {string} QUEUED 'queued'
* @property {string} STARTED 'started'
* @property {string} FINISHED 'finished'
* @property {string} FAILED 'failed'
* @property {string} UNKNOWN 'unknown'
* @readonly
*/
const RQStatus = Object.freeze({
QUEUED: 'queued',
STARTED: 'started',
FINISHED: 'finished',
FAILED: 'failed',
UNKNOWN: 'unknown',
});
/**
* List of RQ statuses
* @enum {string}
* @name RQStatus
* @memberof module:API.cvat.enums
* @property {string} QUEUED 'queued'
* @property {string} STARTED 'started'
* @property {string} FINISHED 'finished'
* @property {string} FAILED 'failed'
* @property {string} UNKNOWN 'unknown'
* @readonly
*/
export enum RQStatus {
QUEUED = 'queued',
STARTED = 'started',
FINISHED = 'finished',
FAILED = 'failed',
UNKNOWN = 'unknown',
}
/**
* Task modes
* @enum {string}
* @name TaskMode
* @memberof module:API.cvat.enums
* @property {string} ANNOTATION 'annotation'
* @property {string} INTERPOLATION 'interpolation'
* @readonly
*/
const TaskMode = Object.freeze({
ANNOTATION: 'annotation',
INTERPOLATION: 'interpolation',
});
/**
* Task modes
* @enum {string}
* @name TaskMode
* @memberof module:API.cvat.enums
* @property {string} ANNOTATION 'annotation'
* @property {string} INTERPOLATION 'interpolation'
* @readonly
*/
export enum TaskMode {
ANNOTATION = 'annotation',
INTERPOLATION = 'interpolation',
}
/**
* Attribute types
* @enum {string}
* @name AttributeType
* @memberof module:API.cvat.enums
* @property {string} CHECKBOX 'checkbox'
* @property {string} SELECT 'select'
* @property {string} RADIO 'radio'
* @property {string} NUMBER 'number'
* @property {string} TEXT 'text'
* @readonly
*/
const AttributeType = Object.freeze({
CHECKBOX: 'checkbox',
RADIO: 'radio',
SELECT: 'select',
NUMBER: 'number',
TEXT: 'text',
});
/**
* Attribute types
* @enum {string}
* @name AttributeType
* @memberof module:API.cvat.enums
* @property {string} CHECKBOX 'checkbox'
* @property {string} SELECT 'select'
* @property {string} RADIO 'radio'
* @property {string} NUMBER 'number'
* @property {string} TEXT 'text'
* @readonly
*/
export enum AttributeType {
CHECKBOX = 'checkbox',
RADIO = 'radio',
SELECT = 'select',
NUMBER = 'number',
TEXT = 'text',
}
/**
* Object types
* @enum {string}
* @name ObjectType
* @memberof module:API.cvat.enums
* @property {string} TAG 'tag'
* @property {string} SHAPE 'shape'
* @property {string} TRACK 'track'
* @readonly
*/
const ObjectType = Object.freeze({
TAG: 'tag',
SHAPE: 'shape',
TRACK: 'track',
});
/**
* Object types
* @enum {string}
* @name ObjectType
* @memberof module:API.cvat.enums
* @property {string} TAG 'tag'
* @property {string} SHAPE 'shape'
* @property {string} TRACK 'track'
* @readonly
*/
export enum ObjectType {
TAG = 'tag',
SHAPE = 'shape',
TRACK = 'track',
}
/**
* Object shapes
* @enum {string}
* @name ObjectShape
* @memberof module:API.cvat.enums
* @property {string} RECTANGLE 'rectangle'
* @property {string} POLYGON 'polygon'
* @property {string} POLYLINE 'polyline'
* @property {string} POINTS 'points'
* @property {string} CUBOID 'cuboid'
* @readonly
*/
const ObjectShape = Object.freeze({
RECTANGLE: 'rectangle',
POLYGON: 'polygon',
POLYLINE: 'polyline',
POINTS: 'points',
ELLIPSE: 'ellipse',
CUBOID: 'cuboid',
});
/**
* Object shapes
* @enum {string}
* @name ShapeType
* @memberof module:API.cvat.enums
* @property {string} RECTANGLE 'rectangle'
* @property {string} POLYGON 'polygon'
* @property {string} POLYLINE 'polyline'
* @property {string} POINTS 'points'
* @property {string} CUBOID 'cuboid'
* @property {string} SKELETON 'skeleton'
* @readonly
*/
export enum ShapeType {
RECTANGLE = 'rectangle',
POLYGON = 'polygon',
POLYLINE = 'polyline',
POINTS = 'points',
ELLIPSE = 'ellipse',
CUBOID = 'cuboid',
SKELETON = 'skeleton',
}
/**
* Annotation type
* @enum {string}
* @name Source
* @memberof module:API.cvat.enums
* @property {string} MANUAL 'manual'
* @property {string} AUTO 'auto'
* @readonly
*/
const Source = Object.freeze({
MANUAL: 'manual',
AUTO: 'auto',
});
/**
* Annotation type
* @enum {string}
* @name Source
* @memberof module:API.cvat.enums
* @property {string} MANUAL 'manual'
* @property {string} AUTO 'auto'
* @readonly
*/
export enum Source {
MANUAL = 'manual',
AUTO = 'auto',
}
/**
* Logger event types
* @enum {string}
* @name LogType
* @memberof module:API.cvat.enums
* @property {string} loadJob Load job
* @property {string} saveJob Save job
* @property {string} restoreJob Restore job
* @property {string} uploadAnnotations Upload annotations
* @property {string} sendUserActivity Send user activity
* @property {string} sendException Send exception
* @property {string} sendTaskInfo Send task info
/**
* Logger event types
* @enum {string}
* @name LogType
* @memberof module:API.cvat.enums
* @property {string} loadJob Load job
* @property {string} saveJob Save job
* @property {string} restoreJob Restore job
* @property {string} uploadAnnotations Upload annotations
* @property {string} sendUserActivity Send user activity
* @property {string} sendException Send exception
* @property {string} sendTaskInfo Send task info
* @property {string} drawObject Draw object
* @property {string} pasteObject Paste object
* @property {string} copyObject Copy object
* @property {string} propagateObject Propagate object
* @property {string} dragObject Drag object
* @property {string} resizeObject Resize object
* @property {string} deleteObject Delete object
* @property {string} lockObject Lock object
* @property {string} mergeObjects Merge objects
* @property {string} changeAttribute Change attribute
* @property {string} changeLabel Change label
* @property {string} changeFrame Change frame
* @property {string} moveImage Move image
* @property {string} zoomImage Zoom image
* @property {string} fitImage Fit image
* @property {string} rotateImage Rotate image
* @property {string} undoAction Undo action
* @property {string} redoAction Redo action
* @property {string} pressShortcut Press shortcut
* @property {string} debugInfo Debug info
* @readonly
*/
export enum LogType {
loadJob = 'Load job',
saveJob = 'Save job',
restoreJob = 'Restore job',
uploadAnnotations = 'Upload annotations',
sendUserActivity = 'Send user activity',
sendException = 'Send exception',
sendTaskInfo = 'Send task info',
* @property {string} drawObject Draw object
* @property {string} pasteObject Paste object
* @property {string} copyObject Copy object
* @property {string} propagateObject Propagate object
* @property {string} dragObject Drag object
* @property {string} resizeObject Resize object
* @property {string} deleteObject Delete object
* @property {string} lockObject Lock object
* @property {string} mergeObjects Merge objects
* @property {string} changeAttribute Change attribute
* @property {string} changeLabel Change label
drawObject = 'Draw object',
pasteObject = 'Paste object',
copyObject = 'Copy object',
propagateObject = 'Propagate object',
dragObject = 'Drag object',
resizeObject = 'Resize object',
deleteObject = 'Delete object',
lockObject = 'Lock object',
mergeObjects = 'Merge objects',
changeAttribute = 'Change attribute',
changeLabel = 'Change label',
* @property {string} changeFrame Change frame
* @property {string} moveImage Move image
* @property {string} zoomImage Zoom image
* @property {string} fitImage Fit image
* @property {string} rotateImage Rotate image
changeFrame = 'Change frame',
moveImage = 'Move image',
zoomImage = 'Zoom image',
fitImage = 'Fit image',
rotateImage = 'Rotate image',
* @property {string} undoAction Undo action
* @property {string} redoAction Redo action
undoAction = 'Undo action',
redoAction = 'Redo action',
* @property {string} pressShortcut Press shortcut
* @property {string} debugInfo Debug info
* @readonly
*/
const LogType = Object.freeze({
loadJob: 'Load job',
saveJob: 'Save job',
restoreJob: 'Restore job',
uploadAnnotations: 'Upload annotations',
sendUserActivity: 'Send user activity',
sendException: 'Send exception',
sendTaskInfo: 'Send task info',
pressShortcut = 'Press shortcut',
debugInfo = 'Debug info',
}
drawObject: 'Draw object',
pasteObject: 'Paste object',
copyObject: 'Copy object',
propagateObject: 'Propagate object',
dragObject: 'Drag object',
resizeObject: 'Resize object',
deleteObject: 'Delete object',
lockObject: 'Lock object',
mergeObjects: 'Merge objects',
changeAttribute: 'Change attribute',
changeLabel: 'Change label',
/**
* Types of actions with annotations
* @enum {string}
* @name HistoryActions
* @memberof module:API.cvat.enums
* @property {string} CHANGED_LABEL Changed label
* @property {string} CHANGED_ATTRIBUTES Changed attributes
* @property {string} CHANGED_POINTS Changed points
* @property {string} CHANGED_OUTSIDE Changed outside
* @property {string} CHANGED_OCCLUDED Changed occluded
* @property {string} CHANGED_ZORDER Changed z-order
* @property {string} CHANGED_LOCK Changed lock
* @property {string} CHANGED_COLOR Changed color
* @property {string} CHANGED_HIDDEN Changed hidden
* @property {string} CHANGED_SOURCE Changed source
* @property {string} MERGED_OBJECTS Merged objects
* @property {string} SPLITTED_TRACK Splitted track
* @property {string} GROUPED_OBJECTS Grouped objects
* @property {string} CREATED_OBJECTS Created objects
* @property {string} REMOVED_OBJECT Removed object
* @property {string} REMOVED_FRAME Removed frame
* @property {string} RESTORED_FRAME Restored frame
* @readonly
*/
export enum HistoryActions {
CHANGED_LABEL = 'Changed label',
CHANGED_ATTRIBUTES = 'Changed attributes',
CHANGED_POINTS = 'Changed points',
CHANGED_ROTATION = 'Object rotated',
CHANGED_OUTSIDE = 'Changed outside',
CHANGED_OCCLUDED = 'Changed occluded',
CHANGED_ZORDER = 'Changed z-order',
CHANGED_KEYFRAME = 'Changed keyframe',
CHANGED_LOCK = 'Changed lock',
CHANGED_PINNED = 'Changed pinned',
CHANGED_COLOR = 'Changed color',
CHANGED_HIDDEN = 'Changed hidden',
CHANGED_SOURCE = 'Changed source',
MERGED_OBJECTS = 'Merged objects',
SPLITTED_TRACK = 'Splitted track',
GROUPED_OBJECTS = 'Grouped objects',
CREATED_OBJECTS = 'Created objects',
REMOVED_OBJECT = 'Removed object',
REMOVED_FRAME = 'Removed frame',
RESTORED_FRAME = 'Restored frame',
}
changeFrame: 'Change frame',
moveImage: 'Move image',
zoomImage: 'Zoom image',
fitImage: 'Fit image',
rotateImage: 'Rotate image',
/**
* Enum string values.
* @name ModelType
* @memberof module:API.cvat.enums
* @enum {string}
*/
export enum ModelType {
DETECTOR = 'detector',
INTERACTOR = 'interactor',
TRACKER = 'tracker',
}
undoAction: 'Undo action',
redoAction: 'Redo action',
/**
* Array of hex colors
* @name colors
* @memberof module:API.cvat.enums
* @type {string[]}
* @readonly
*/
export const colors = [
'#33ddff',
'#fa3253',
'#34d1b7',
'#ff007c',
'#ff6037',
'#ddff33',
'#24b353',
'#b83df5',
'#66ff66',
'#32b7fa',
'#ffcc33',
'#83e070',
'#fafa37',
'#5986b3',
'#8c78f0',
'#ff6a4d',
'#f078f0',
'#2a7dd1',
'#b25050',
'#cc3366',
'#cc9933',
'#aaf0d1',
'#ff00cc',
'#3df53d',
'#fa32b7',
'#fa7dbb',
'#ff355e',
'#f59331',
'#3d3df5',
'#733380',
];
pressShortcut: 'Press shortcut',
debugInfo: 'Debug info',
});
/**
* Types of cloud storage providers
* @enum {string}
* @name CloudStorageProviderType
* @memberof module:API.cvat.enums
* @property {string} AWS_S3 'AWS_S3_BUCKET'
* @property {string} AZURE 'AZURE_CONTAINER'
* @property {string} GOOGLE_CLOUD_STORAGE 'GOOGLE_CLOUD_STORAGE'
* @readonly
*/
export enum CloudStorageProviderType {
AWS_S3_BUCKET = 'AWS_S3_BUCKET',
AZURE_CONTAINER = 'AZURE_CONTAINER',
GOOGLE_CLOUD_STORAGE = 'GOOGLE_CLOUD_STORAGE',
}
/**
* Types of actions with annotations
* @enum {string}
* @name HistoryActions
* @memberof module:API.cvat.enums
* @property {string} CHANGED_LABEL Changed label
* @property {string} CHANGED_ATTRIBUTES Changed attributes
* @property {string} CHANGED_POINTS Changed points
* @property {string} CHANGED_OUTSIDE Changed outside
* @property {string} CHANGED_OCCLUDED Changed occluded
* @property {string} CHANGED_ZORDER Changed z-order
* @property {string} CHANGED_LOCK Changed lock
* @property {string} CHANGED_COLOR Changed color
* @property {string} CHANGED_HIDDEN Changed hidden
* @property {string} CHANGED_SOURCE Changed source
* @property {string} MERGED_OBJECTS Merged objects
* @property {string} SPLITTED_TRACK Splitted track
* @property {string} GROUPED_OBJECTS Grouped objects
* @property {string} CREATED_OBJECTS Created objects
* @property {string} REMOVED_OBJECT Removed object
* @property {string} REMOVED_FRAME Removed frame
* @property {string} RESTORED_FRAME Restored frame
* @readonly
*/
const HistoryActions = Object.freeze({
CHANGED_LABEL: 'Changed label',
CHANGED_ATTRIBUTES: 'Changed attributes',
CHANGED_POINTS: 'Changed points',
CHANGED_OUTSIDE: 'Changed outside',
CHANGED_OCCLUDED: 'Changed occluded',
CHANGED_ZORDER: 'Changed z-order',
CHANGED_KEYFRAME: 'Changed keyframe',
CHANGED_LOCK: 'Changed lock',
CHANGED_PINNED: 'Changed pinned',
CHANGED_COLOR: 'Changed color',
CHANGED_HIDDEN: 'Changed hidden',
CHANGED_SOURCE: 'Changed source',
MERGED_OBJECTS: 'Merged objects',
SPLITTED_TRACK: 'Splitted track',
GROUPED_OBJECTS: 'Grouped objects',
CREATED_OBJECTS: 'Created objects',
REMOVED_OBJECT: 'Removed object',
REMOVED_FRAME: 'Removed frame',
RESTORED_FRAME: 'Restored frame',
});
/**
* Types of cloud storage credentials
* @enum {string}
* @name CloudStorageCredentialsType
* @memberof module:API.cvat.enums
* @property {string} KEY_SECRET_KEY_PAIR 'KEY_SECRET_KEY_PAIR'
* @property {string} ACCOUNT_NAME_TOKEN_PAIR 'ACCOUNT_NAME_TOKEN_PAIR'
* @property {string} ANONYMOUS_ACCESS 'ANONYMOUS_ACCESS'
* @property {string} KEY_FILE_PATH 'KEY_FILE_PATH'
* @readonly
*/
export enum CloudStorageCredentialsType {
KEY_SECRET_KEY_PAIR = 'KEY_SECRET_KEY_PAIR',
ACCOUNT_NAME_TOKEN_PAIR = 'ACCOUNT_NAME_TOKEN_PAIR',
ANONYMOUS_ACCESS = 'ANONYMOUS_ACCESS',
KEY_FILE_PATH = 'KEY_FILE_PATH',
}
/**
* Enum string values.
* @name ModelType
* @memberof module:API.cvat.enums
* @enum {string}
*/
const ModelType = {
DETECTOR: 'detector',
INTERACTOR: 'interactor',
TRACKER: 'tracker',
};
/**
* Task statuses
* @enum {string}
* @name MembershipRole
* @memberof module:API.cvat.enums
* @property {string} WORKER 'worker'
* @property {string} SUPERVISOR 'supervisor'
* @property {string} MAINTAINER 'maintainer'
* @property {string} OWNER 'owner'
* @readonly
*/
export enum MembershipRole {
WORKER = 'worker',
SUPERVISOR = 'supervisor',
MAINTAINER = 'maintainer',
OWNER = 'owner',
}
/**
* Array of hex colors
* @name colors
* @memberof module:API.cvat.enums
* @type {string[]}
* @readonly
*/
const colors = [
'#33ddff',
'#fa3253',
'#34d1b7',
'#ff007c',
'#ff6037',
'#ddff33',
'#24b353',
'#b83df5',
'#66ff66',
'#32b7fa',
'#ffcc33',
'#83e070',
'#fafa37',
'#5986b3',
'#8c78f0',
'#ff6a4d',
'#f078f0',
'#2a7dd1',
'#b25050',
'#cc3366',
'#cc9933',
'#aaf0d1',
'#ff00cc',
'#3df53d',
'#fa32b7',
'#fa7dbb',
'#ff355e',
'#f59331',
'#3d3df5',
'#733380',
];
/**
* Types of cloud storage providers
* @enum {string}
* @name CloudStorageProviderType
* @memberof module:API.cvat.enums
* @property {string} AWS_S3 'AWS_S3_BUCKET'
* @property {string} AZURE 'AZURE_CONTAINER'
* @property {string} GOOGLE_CLOUD_STORAGE 'GOOGLE_CLOUD_STORAGE'
* @readonly
*/
const CloudStorageProviderType = Object.freeze({
AWS_S3_BUCKET: 'AWS_S3_BUCKET',
AZURE_CONTAINER: 'AZURE_CONTAINER',
GOOGLE_CLOUD_STORAGE: 'GOOGLE_CLOUD_STORAGE',
});
/**
* Types of cloud storage credentials
* @enum {string}
* @name CloudStorageCredentialsType
* @memberof module:API.cvat.enums
* @property {string} KEY_SECRET_KEY_PAIR 'KEY_SECRET_KEY_PAIR'
* @property {string} ACCOUNT_NAME_TOKEN_PAIR 'ACCOUNT_NAME_TOKEN_PAIR'
* @property {string} ANONYMOUS_ACCESS 'ANONYMOUS_ACCESS'
* @property {string} KEY_FILE_PATH 'KEY_FILE_PATH'
* @readonly
*/
const CloudStorageCredentialsType = Object.freeze({
KEY_SECRET_KEY_PAIR: 'KEY_SECRET_KEY_PAIR',
ACCOUNT_NAME_TOKEN_PAIR: 'ACCOUNT_NAME_TOKEN_PAIR',
ANONYMOUS_ACCESS: 'ANONYMOUS_ACCESS',
KEY_FILE_PATH: 'KEY_FILE_PATH',
});
/**
* Task statuses
* @enum {string}
* @name MembershipRole
* @memberof module:API.cvat.enums
* @property {string} WORKER 'worker'
* @property {string} SUPERVISOR 'supervisor'
* @property {string} MAINTAINER 'maintainer'
* @property {string} OWNER 'owner'
* @readonly
*/
const MembershipRole = Object.freeze({
WORKER: 'worker',
SUPERVISOR: 'supervisor',
MAINTAINER: 'maintainer',
OWNER: 'owner',
});
/**
* Sorting methods
* @enum {string}
* @name SortingMethod
* @memberof module:API.cvat.enums
* @property {string} LEXICOGRAPHICAL 'lexicographical'
* @property {string} NATURAL 'natural'
* @property {string} PREDEFINED 'predefined'
* @property {string} RANDOM 'random'
* @readonly
*/
const SortingMethod = Object.freeze({
LEXICOGRAPHICAL: 'lexicographical',
NATURAL: 'natural',
PREDEFINED: 'predefined',
RANDOM: 'random',
});
module.exports = {
ShareFileType,
TaskStatus,
JobStage,
JobState,
TaskMode,
AttributeType,
ObjectType,
ObjectShape,
LogType,
ModelType,
HistoryActions,
RQStatus,
colors,
Source,
DimensionType,
CloudStorageProviderType,
CloudStorageCredentialsType,
MembershipRole,
SortingMethod,
};
})();
/**
* Sorting methods
* @enum {string}
* @name SortingMethod
* @memberof module:API.cvat.enums
* @property {string} LEXICOGRAPHICAL 'lexicographical'
* @property {string} NATURAL 'natural'
* @property {string} PREDEFINED 'predefined'
* @property {string} RANDOM 'random'
* @readonly
*/
export enum SortingMethod {
LEXICOGRAPHICAL = 'lexicographical',
NATURAL = 'natural',
PREDEFINED = 'predefined',
RANDOM = 'random',
}

@ -2,268 +2,254 @@
//
// SPDX-License-Identifier: MIT
(() => {
const Platform = require('platform');
const ErrorStackParser = require('error-stack-parser');
const config = require('./config');
import Platform from 'platform';
import ErrorStackParser from 'error-stack-parser';
// import config from './config';
/**
* Base exception class
* @memberof module:API.cvat.exceptions
* @extends Error
* @ignore
*/
export class Exception extends Error {
private readonly time: string;
private readonly system: string;
private readonly client: string;
private readonly info: string;
private readonly filename: string;
private readonly line: number;
private readonly column: number;
/**
* Base exception class
* @memberof module:API.cvat.exceptions
* @extends Error
* @ignore
* @param {string} message - Exception message
*/
class Exception extends Error {
/**
* @param {string} message - Exception message
*/
constructor(message) {
super(message);
const time = new Date().toISOString();
const system = Platform.os.toString();
const client = `${Platform.name} ${Platform.version}`;
const info = ErrorStackParser.parse(this)[0];
const filename = `${info.fileName}`;
const line = info.lineNumber;
const column = info.columnNumber;
const { jobID, taskID, clientID } = config;
constructor(message) {
super(message);
const time = new Date().toISOString();
const system = Platform.os.toString();
const client = `${Platform.name} ${Platform.version}`;
const info = ErrorStackParser.parse(this)[0];
const filename = `${info.fileName}`;
const line = info.lineNumber;
const column = info.columnNumber;
const projID = undefined; // wasn't implemented
// TODO: NOT IMPLEMENTED?
// const {
// jobID, taskID, clientID, projID,
// } = config;
Object.defineProperties(
this,
Object.freeze({
system: {
/**
* @name system
* @type {string}
* @memberof module:API.cvat.exceptions.Exception
* @readonly
* @instance
*/
get: () => system,
},
client: {
/**
* @name client
* @type {string}
* @memberof module:API.cvat.exceptions.Exception
* @readonly
* @instance
*/
get: () => client,
},
time: {
/**
* @name time
* @type {string}
* @memberof module:API.cvat.exceptions.Exception
* @readonly
* @instance
*/
get: () => time,
},
jobID: {
/**
* @name jobID
* @type {number}
* @memberof module:API.cvat.exceptions.Exception
* @readonly
* @instance
*/
get: () => jobID,
},
taskID: {
/**
* @name taskID
* @type {number}
* @memberof module:API.cvat.exceptions.Exception
* @readonly
* @instance
*/
get: () => taskID,
},
projID: {
/**
* @name projID
* @type {number}
* @memberof module:API.cvat.exceptions.Exception
* @readonly
* @instance
*/
get: () => projID,
},
clientID: {
/**
* @name clientID
* @type {number}
* @memberof module:API.cvat.exceptions.Exception
* @readonly
* @instance
*/
get: () => clientID,
},
filename: {
/**
* @name filename
* @type {string}
* @memberof module:API.cvat.exceptions.Exception
* @readonly
* @instance
*/
get: () => filename,
},
line: {
/**
* @name line
* @type {number}
* @memberof module:API.cvat.exceptions.Exception
* @readonly
* @instance
*/
get: () => line,
},
column: {
/**
* @name column
* @type {number}
* @memberof module:API.cvat.exceptions.Exception
* @readonly
* @instance
*/
get: () => column,
},
}),
);
}
Object.defineProperties(
this,
Object.freeze({
system: {
/**
* @name system
* @type {string}
* @memberof module:API.cvat.exceptions.Exception
* @readonly
* @instance
*/
get: () => system,
},
client: {
/**
* @name client
* @type {string}
* @memberof module:API.cvat.exceptions.Exception
* @readonly
* @instance
*/
get: () => client,
},
time: {
/**
* @name time
* @type {string}
* @memberof module:API.cvat.exceptions.Exception
* @readonly
* @instance
*/
get: () => time,
},
// jobID: {
// /**
// * @name jobID
// * @type {number}
// * @memberof module:API.cvat.exceptions.Exception
// * @readonly
// * @instance
// */
// get: () => jobID,
// },
// taskID: {
// /**
// * @name taskID
// * @type {number}
// * @memberof module:API.cvat.exceptions.Exception
// * @readonly
// * @instance
// */
// get: () => taskID,
// },
// projID: {
// /**
// * @name projID
// * @type {number}
// * @memberof module:API.cvat.exceptions.Exception
// * @readonly
// * @instance
// */
// get: () => projID,
// },
// clientID: {
// /**
// * @name clientID
// * @type {number}
// * @memberof module:API.cvat.exceptions.Exception
// * @readonly
// * @instance
// */
// get: () => clientID,
// },
filename: {
/**
* @name filename
* @type {string}
* @memberof module:API.cvat.exceptions.Exception
* @readonly
* @instance
*/
get: () => filename,
},
line: {
/**
* @name line
* @type {number}
* @memberof module:API.cvat.exceptions.Exception
* @readonly
* @instance
*/
get: () => line,
},
column: {
/**
* @name column
* @type {number}
* @memberof module:API.cvat.exceptions.Exception
* @readonly
* @instance
*/
get: () => column,
},
}),
);
}
/**
* Save an exception on a server
* @name save
* @method
* @memberof Exception
* @instance
* @async
*/
async save() {
const exceptionObject = {
system: this.system,
client: this.client,
time: this.time,
job_id: this.jobID,
task_id: this.taskID,
proj_id: this.projID,
client_id: this.clientID,
message: this.message,
filename: this.filename,
line: this.line,
column: this.column,
stack: this.stack,
};
/**
* Save an exception on a server
* @name save
* @method
* @memberof Exception
* @instance
* @async
*/
async save(): Promise<void> {
const exceptionObject = {
system: this.system,
client: this.client,
time: this.time,
// job_id: this.jobID,
// task_id: this.taskID,
// proj_id: this.projID,
// client_id: this.clientID,
message: this.message,
filename: this.filename,
line: this.line,
column: this.column,
stack: this.stack,
};
try {
const serverProxy = require('./server-proxy');
await serverProxy.server.exception(exceptionObject);
} catch (exception) {
// add event
}
try {
const serverProxy = require('./server-proxy');
await serverProxy.server.exception(exceptionObject);
} catch (exception) {
// add event
}
}
}
/**
* Exceptions are referred with arguments data
* @memberof module:API.cvat.exceptions
* @extends module:API.cvat.exceptions.Exception
*/
export class ArgumentError extends Exception {
/**
* Exceptions are referred with arguments data
* @memberof module:API.cvat.exceptions
* @extends module:API.cvat.exceptions.Exception
* @param {string} message - Exception message
*/
class ArgumentError extends Exception {
/**
* @param {string} message - Exception message
*/
constructor(message) {
super(message);
}
}
}
/**
* Unexpected problems with data which are not connected with a user input
* @memberof module:API.cvat.exceptions
* @extends module:API.cvat.exceptions.Exception
*/
export class DataError extends Exception {
/**
* Unexpected problems with data which are not connected with a user input
* @memberof module:API.cvat.exceptions
* @extends module:API.cvat.exceptions.Exception
* @param {string} message - Exception message
*/
class DataError extends Exception {
/**
* @param {string} message - Exception message
*/
constructor(message) {
super(message);
}
}
}
/**
* Unexpected situations in code
* @memberof module:API.cvat.exceptions
* @extends module:API.cvat.exceptions.Exception
*/
export class ScriptingError extends Exception {
/**
* Unexpected situations in code
* @memberof module:API.cvat.exceptions
* @extends module:API.cvat.exceptions.Exception
* @param {string} message - Exception message
*/
class ScriptingError extends Exception {
/**
* @param {string} message - Exception message
*/
constructor(message) {
super(message);
}
}
}
/**
* Plugin-referred exceptions
* @memberof module:API.cvat.exceptions
* @extends module:API.cvat.exceptions.Exception
*/
export class PluginError extends Exception {
/**
* Plugin-referred exceptions
* @memberof module:API.cvat.exceptions
* @extends module:API.cvat.exceptions.Exception
* @param {string} message - Exception message
*/
class PluginError extends Exception {
/**
* @param {string} message - Exception message
*/
constructor(message) {
super(message);
}
}
}
/**
* Exceptions in interaction with a server
* @memberof module:API.cvat.exceptions
* @extends module:API.cvat.exceptions.Exception
*/
export class ServerError extends Exception {
/**
* Exceptions in interaction with a server
* @memberof module:API.cvat.exceptions
* @extends module:API.cvat.exceptions.Exception
* @param {string} message - Exception message
* @param {(string|number)} code - Response code
*/
class ServerError extends Exception {
/**
* @param {string} message - Exception message
* @param {(string|number)} code - Response code
*/
constructor(message, code) {
super(message);
constructor(message, code) {
super(message);
Object.defineProperties(
this,
Object.freeze({
/**
* @name code
* @type {(string|number)}
* @memberof module:API.cvat.exceptions.ServerError
* @readonly
* @instance
*/
code: {
get: () => code,
},
}),
);
}
Object.defineProperties(
this,
Object.freeze({
/**
* @name code
* @type {(string|number)}
* @memberof module:API.cvat.exceptions.ServerError
* @readonly
* @instance
*/
code: {
get: () => code,
},
}),
);
}
module.exports = {
Exception,
ArgumentError,
DataError,
ScriptingError,
PluginError,
ServerError,
};
})();
}

@ -4,7 +4,7 @@
(() => {
const cvatData = require('cvat-data');
const PluginRegistry = require('./plugins');
const PluginRegistry = require('./plugins').default;
const serverProxy = require('./server-proxy');
const { isBrowser, isNode } = require('browser-or-node');
const { Exception, ArgumentError, DataError } = require('./exceptions');

@ -4,7 +4,7 @@
const quickhull = require('quickhull');
const PluginRegistry = require('./plugins');
const PluginRegistry = require('./plugins').default;
const Comment = require('./comment');
const User = require('./user');
const { ArgumentError } = require('./exceptions');

@ -2,244 +2,355 @@
//
// SPDX-License-Identifier: MIT
(() => {
const { AttributeType } = require('./enums');
const { ArgumentError } = require('./exceptions');
/**
* Class representing an attribute
* @memberof module:API.cvat.classes
* @hideconstructor
*/
class Attribute {
constructor(initialData) {
const data = {
id: undefined,
default_value: undefined,
input_type: undefined,
mutable: undefined,
name: undefined,
values: undefined,
};
for (const key in data) {
if (Object.prototype.hasOwnProperty.call(data, key)) {
if (Object.prototype.hasOwnProperty.call(initialData, key)) {
if (Array.isArray(initialData[key])) {
data[key] = [...initialData[key]];
} else {
data[key] = initialData[key];
}
import { ShapeType, AttributeType } from './enums';
import { ArgumentError } from './exceptions';
type AttrInputType = 'select' | 'radio' | 'checkbox' | 'number' | 'text';
export interface RawAttribute {
name: string;
mutable: boolean;
input_type: AttrInputType;
default_value: string;
values: string[];
id?: number;
}
/**
* Class representing an attribute
* @memberof module:API.cvat.classes
* @hideconstructor
*/
export class Attribute {
public id?: number;
public defaultValue: string;
public inputType: AttrInputType;
public mutable: boolean;
public name: string;
public values: string[];
constructor(initialData: RawAttribute) {
const data = {
id: undefined,
default_value: undefined,
input_type: undefined,
mutable: undefined,
name: undefined,
values: undefined,
};
for (const key in data) {
if (Object.prototype.hasOwnProperty.call(data, key)) {
if (Object.prototype.hasOwnProperty.call(initialData, key)) {
if (Array.isArray(initialData[key])) {
data[key] = [...initialData[key]];
} else {
data[key] = initialData[key];
}
}
}
}
if (!Object.values(AttributeType).includes(data.input_type)) {
throw new ArgumentError(`Got invalid attribute type ${data.input_type}`);
}
Object.defineProperties(
this,
Object.freeze({
/**
* @name id
* @type {number}
* @memberof module:API.cvat.classes.Attribute
* @readonly
* @instance
*/
id: {
get: () => data.id,
},
/**
* @name defaultValue
* @type {(string|integer|boolean)}
* @memberof module:API.cvat.classes.Attribute
* @readonly
* @instance
*/
defaultValue: {
get: () => data.default_value,
},
/**
* @name inputType
* @type {module:API.cvat.enums.AttributeType}
* @memberof module:API.cvat.classes.Attribute
* @readonly
* @instance
*/
inputType: {
get: () => data.input_type,
},
/**
* @name mutable
* @type {boolean}
* @memberof module:API.cvat.classes.Attribute
* @readonly
* @instance
*/
mutable: {
get: () => data.mutable,
},
/**
* @name name
* @type {string}
* @memberof module:API.cvat.classes.Attribute
* @readonly
* @instance
*/
name: {
get: () => data.name,
},
/**
* @name values
* @type {(string[]|integer[]|boolean[])}
* @memberof module:API.cvat.classes.Attribute
* @readonly
* @instance
*/
values: {
get: () => [...data.values],
},
}),
);
if (!Object.values(AttributeType).includes(data.input_type)) {
throw new ArgumentError(`Got invalid attribute type ${data.input_type}`);
}
toJSON() {
const object = {
name: this.name,
mutable: this.mutable,
input_type: this.inputType,
default_value: this.defaultValue,
values: this.values,
};
if (typeof this.id !== 'undefined') {
object.id = this.id;
}
Object.defineProperties(
this,
Object.freeze({
/**
* @name id
* @type {number}
* @memberof module:API.cvat.classes.Attribute
* @readonly
* @instance
*/
id: {
get: () => data.id,
},
/**
* @name defaultValue
* @type {string}
* @memberof module:API.cvat.classes.Attribute
* @readonly
* @instance
*/
defaultValue: {
get: () => data.default_value,
},
/**
* @name inputType
* @type {module:API.cvat.enums.AttributeType}
* @memberof module:API.cvat.classes.Attribute
* @readonly
* @instance
*/
inputType: {
get: () => data.input_type,
},
/**
* @name mutable
* @type {boolean}
* @memberof module:API.cvat.classes.Attribute
* @readonly
* @instance
*/
mutable: {
get: () => data.mutable,
},
/**
* @name name
* @type {string}
* @memberof module:API.cvat.classes.Attribute
* @readonly
* @instance
*/
name: {
get: () => data.name,
},
/**
* @name values
* @type {string[]}
* @memberof module:API.cvat.classes.Attribute
* @readonly
* @instance
*/
values: {
get: () => [...data.values],
},
}),
);
}
toJSON(): RawAttribute {
const object: RawAttribute = {
name: this.name,
mutable: this.mutable,
input_type: this.inputType,
default_value: this.defaultValue,
values: this.values,
};
return object;
if (typeof this.id !== 'undefined') {
object.id = this.id;
}
return object;
}
}
/**
* Class representing a label
* @memberof module:API.cvat.classes
* @hideconstructor
*/
class Label {
constructor(initialData) {
const data = {
id: undefined,
name: undefined,
color: undefined,
deleted: false,
};
for (const key in data) {
if (Object.prototype.hasOwnProperty.call(data, key)) {
if (Object.prototype.hasOwnProperty.call(initialData, key)) {
data[key] = initialData[key];
}
}
type LabelType = 'rectangle' | 'polygon' | 'polyline' | 'points' | 'ellipse' | 'cuboid' | 'skeleton' | 'any';
export interface RawLabel {
id?: number;
name: string;
color?: string;
type: LabelType;
svg?: string;
sublabels?: RawLabel[];
has_parent?: boolean;
deleted?: boolean;
attributes: RawAttribute[];
}
/**
* Class representing a label
* @memberof module:API.cvat.classes
* @hideconstructor
*/
export class Label {
public name: string;
public readonly id?: number;
public readonly color?: string;
public readonly attributes: Attribute[];
public readonly type: LabelType;
public structure: {
sublabels: Label[];
svg: string;
} | null;
public deleted: boolean;
public readonly hasParent?: boolean;
constructor(initialData: RawLabel) {
const data = {
id: undefined,
name: undefined,
color: undefined,
type: undefined,
structure: undefined,
has_parent: false,
deleted: false,
svg: undefined,
elements: undefined,
sublabels: undefined,
attributes: [],
};
for (const key of Object.keys(data)) {
if (Object.prototype.hasOwnProperty.call(initialData, key)) {
data[key] = initialData[key];
}
}
data.attributes = [];
data.attributes = [];
if (
Object.prototype.hasOwnProperty.call(initialData, 'attributes') &&
Array.isArray(initialData.attributes)
) {
for (const attrData of initialData.attributes) {
data.attributes.push(new Attribute(attrData));
}
if (
Object.prototype.hasOwnProperty.call(initialData, 'attributes') &&
Array.isArray(initialData.attributes)
) {
for (const attrData of initialData.attributes) {
data.attributes.push(new Attribute(attrData));
}
}
Object.defineProperties(
this,
Object.freeze({
/**
* @name id
* @type {number}
* @memberof module:API.cvat.classes.Label
* @readonly
* @instance
*/
id: {
get: () => data.id,
},
/**
* @name name
* @type {string}
* @memberof module:API.cvat.classes.Label
* @instance
*/
name: {
get: () => data.name,
set: (name) => {
if (typeof name !== 'string') {
throw new ArgumentError(`Name must be a string, but ${typeof name} was given`);
}
data.name = name;
},
if (data.type === 'skeleton') {
data.sublabels = data.sublabels.map((internalLabel) => new Label({ ...internalLabel, has_parent: true }));
}
Object.defineProperties(
this,
Object.freeze({
/**
* @name id
* @type {number}
* @memberof module:API.cvat.classes.Label
* @readonly
* @instance
*/
id: {
get: () => data.id,
},
/**
* @name name
* @type {string}
* @memberof module:API.cvat.classes.Label
* @instance
*/
name: {
get: () => data.name,
set: (name) => {
if (typeof name !== 'string') {
throw new ArgumentError(`Name must be a string, but ${typeof name} was given`);
}
data.name = name;
},
/**
* @name color
* @type {string}
* @memberof module:API.cvat.classes.Label
* @instance
*/
color: {
get: () => data.color,
set: (color) => {
if (typeof color === 'string' && color.match(/^#[0-9a-f]{6}$|^$/)) {
data.color = color;
} else {
throw new ArgumentError('Trying to set wrong color format');
}
},
},
/**
* @name color
* @type {string}
* @memberof module:API.cvat.classes.Label
* @instance
*/
color: {
get: () => data.color,
set: (color) => {
if (typeof color === 'string' && color.match(/^#[0-9a-f]{6}$|^$/)) {
data.color = color;
} else {
throw new ArgumentError('Trying to set wrong color format');
}
},
/**
* @name attributes
* @type {module:API.cvat.classes.Attribute[]}
* @memberof module:API.cvat.classes.Label
* @readonly
* @instance
*/
attributes: {
get: () => [...data.attributes],
},
/**
* @name attributes
* @type {module:API.cvat.classes.Attribute[]}
* @memberof module:API.cvat.classes.Label
* @readonly
* @instance
*/
attributes: {
get: () => [...data.attributes],
},
/**
* @typedef {Object} SkeletonStructure
* @property {module:API.cvat.classes.Label[]} sublabels A list of labels the skeleton includes
* @property {Object[]} svg An SVG representation of the skeleton
* A type of a file
* @global
*/
/**
* @name type
* @type {string | undefined}
* @memberof module:API.cvat.classes.Label
* @readonly
* @instance
*/
type: {
get: () => data.type,
},
/**
* @name type
* @type {SkeletonStructure | undefined}
* @memberof module:API.cvat.classes.Label
* @readonly
* @instance
*/
structure: {
get: () => {
if (data.type === ShapeType.SKELETON) {
return {
svg: data.svg,
sublabels: [...data.sublabels],
};
}
return null;
},
deleted: {
get: () => data.deleted,
set: (value) => {
data.deleted = value;
},
},
/**
* @name deleted
* @type {boolean}
* @memberof module:API.cvat.classes.Label
* @instance
*/
deleted: {
get: () => data.deleted,
set: (value) => {
data.deleted = value;
},
}),
);
},
/**
* @name hasParent
* @type {boolean}
* @memberof module:API.cvat.classes.Label
* @readonly
* @instance
*/
hasParent: {
get: () => data.has_parent,
},
}),
);
}
toJSON(): RawLabel {
const object: RawLabel = {
name: this.name,
attributes: [...this.attributes.map((el) => el.toJSON())],
type: this.type,
};
if (typeof this.color !== 'undefined') {
object.color = this.color;
}
toJSON() {
const object = {
name: this.name,
attributes: [...this.attributes.map((el) => el.toJSON())],
color: this.color,
};
if (typeof this.id !== 'undefined') {
object.id = this.id;
}
if (typeof this.id !== 'undefined') {
object.id = this.id;
}
if (this.deleted) {
object.deleted = this.deleted;
}
if (this.deleted) {
object.deleted = this.deleted;
}
if (this.type) {
object.type = this.type;
}
return object;
const { structure } = this;
if (structure) {
object.svg = structure.svg;
object.sublabels = structure.sublabels.map((internalLabel) => internalLabel.toJSON());
}
}
module.exports = {
Attribute,
Label,
};
})();
return object;
}
}

@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT
const { detect } = require('detect-browser');
const PluginRegistry = require('./plugins');
const PluginRegistry = require('./plugins').default;
const { ArgumentError } = require('./exceptions');
const { LogType } = require('./enums');

@ -2,7 +2,7 @@
//
// SPDX-License-Identifier: MIT
const PluginRegistry = require('./plugins');
const PluginRegistry = require('./plugins').default;
const serverProxy = require('./server-proxy');
const logFactory = require('./log');
const { ArgumentError } = require('./exceptions');
@ -10,7 +10,7 @@ const { LogType } = require('./enums');
const WORKING_TIME_THRESHOLD = 100000; // ms, 1.66 min
function sleep(ms) {
function sleep(ms): Promise<void> {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});

File diff suppressed because it is too large Load Diff

@ -6,7 +6,7 @@ const { checkObjectType, isEnum } = require('./common');
const config = require('./config');
const { MembershipRole } = require('./enums');
const { ArgumentError, ServerError } = require('./exceptions');
const PluginRegistry = require('./plugins');
const PluginRegistry = require('./plugins').default;
const serverProxy = require('./server-proxy');
const User = require('./user');

@ -2,107 +2,103 @@
//
// SPDX-License-Identifier: MIT
(() => {
const { PluginError } = require('./exceptions');
const plugins = [];
class PluginRegistry {
static async apiWrapper(wrappedFunc, ...args) {
// I have to optimize the wrapper
const pluginList = await PluginRegistry.list();
for (const plugin of pluginList) {
const pluginDecorators = plugin.functions.filter((obj) => obj.callback === wrappedFunc)[0];
if (pluginDecorators && pluginDecorators.enter) {
try {
await pluginDecorators.enter.call(this, plugin, ...args);
} catch (exception) {
if (exception instanceof PluginError) {
throw exception;
} else {
throw new PluginError(`Exception in plugin ${plugin.name}: ${exception.toString()}`);
}
import { PluginError } from './exceptions';
const plugins = [];
export default class PluginRegistry {
static async apiWrapper(wrappedFunc, ...args) {
// I have to optimize the wrapper
const pluginList = await PluginRegistry.list();
for (const plugin of pluginList) {
const pluginDecorators = plugin.functions.filter((obj) => obj.callback === wrappedFunc)[0];
if (pluginDecorators && pluginDecorators.enter) {
try {
await pluginDecorators.enter.call(this, plugin, ...args);
} catch (exception) {
if (exception instanceof PluginError) {
throw exception;
} else {
throw new PluginError(`Exception in plugin ${plugin.name}: ${exception.toString()}`);
}
}
}
}
let result = await wrappedFunc.implementation.call(this, ...args);
for (const plugin of pluginList) {
const pluginDecorators = plugin.functions.filter((obj) => obj.callback === wrappedFunc)[0];
if (pluginDecorators && pluginDecorators.leave) {
try {
result = await pluginDecorators.leave.call(this, plugin, result, ...args);
} catch (exception) {
if (exception instanceof PluginError) {
throw exception;
} else {
throw new PluginError(`Exception in plugin ${plugin.name}: ${exception.toString()}`);
}
let result = await wrappedFunc.implementation.call(this, ...args);
for (const plugin of pluginList) {
const pluginDecorators = plugin.functions.filter((obj) => obj.callback === wrappedFunc)[0];
if (pluginDecorators && pluginDecorators.leave) {
try {
result = await pluginDecorators.leave.call(this, plugin, result, ...args);
} catch (exception) {
if (exception instanceof PluginError) {
throw exception;
} else {
throw new PluginError(`Exception in plugin ${plugin.name}: ${exception.toString()}`);
}
}
}
return result;
}
// Called with cvat context
static async register(plug) {
const functions = [];
return result;
}
if (typeof plug !== 'object') {
throw new PluginError(`Plugin should be an object, but got "${typeof plug}"`);
}
// Called with cvat context
static async register(plug) {
const functions = [];
if (!('name' in plug) || typeof plug.name !== 'string') {
throw new PluginError('Plugin must contain a "name" field and it must be a string');
}
if (typeof plug !== 'object') {
throw new PluginError(`Plugin should be an object, but got "${typeof plug}"`);
}
if (!('description' in plug) || typeof plug.description !== 'string') {
throw new PluginError('Plugin must contain a "description" field and it must be a string');
}
if (!('name' in plug) || typeof plug.name !== 'string') {
throw new PluginError('Plugin must contain a "name" field and it must be a string');
}
if ('functions' in plug) {
throw new PluginError('Plugin must not contain a "functions" field');
}
if (!('description' in plug) || typeof plug.description !== 'string') {
throw new PluginError('Plugin must contain a "description" field and it must be a string');
}
if ('functions' in plug) {
throw new PluginError('Plugin must not contain a "functions" field');
}
function traverse(plugin, api) {
const decorator = {};
for (const key in plugin) {
if (Object.prototype.hasOwnProperty.call(plugin, key)) {
if (typeof plugin[key] === 'object') {
if (Object.prototype.hasOwnProperty.call(api, key)) {
traverse(plugin[key], api[key]);
}
} else if (
['enter', 'leave'].includes(key) &&
typeof api === 'function' &&
typeof (plugin[key] === 'function')
) {
decorator.callback = api;
decorator[key] = plugin[key];
function traverse(plugin, api) {
const decorator = {};
for (const key in plugin) {
if (Object.prototype.hasOwnProperty.call(plugin, key)) {
if (typeof plugin[key] === 'object') {
if (Object.prototype.hasOwnProperty.call(api, key)) {
traverse(plugin[key], api[key]);
}
} else if (
['enter', 'leave'].includes(key) &&
typeof api === 'function' &&
typeof (plugin[key] === 'function')
) {
decorator.callback = api;
decorator[key] = plugin[key];
}
}
if (Object.keys(decorator).length) {
functions.push(decorator);
}
}
traverse(plug, { cvat: this });
if (Object.keys(decorator).length) {
functions.push(decorator);
}
}
Object.defineProperty(plug, 'functions', {
value: functions,
writable: false,
});
traverse(plug, { cvat: this });
plugins.push(plug);
}
Object.defineProperty(plug, 'functions', {
value: functions,
writable: false,
});
static async list() {
return plugins;
}
plugins.push(plug);
}
module.exports = PluginRegistry;
})();
static async list() {
return plugins;
}
}

@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT
(() => {
const PluginRegistry = require('./plugins');
const PluginRegistry = require('./plugins').default;
const { ArgumentError } = require('./exceptions');
const { Label } = require('./labels');
const User = require('./user');
@ -48,10 +48,8 @@
data.labels = [];
if (Array.isArray(initialData.labels)) {
for (const label of initialData.labels) {
const classInstance = new Label(label);
data.labels.push(classInstance);
}
data.labels = initialData.labels
.map((labelData) => new Label(labelData)).filter((label) => !label.hasParent);
}
if (typeof initialData.training_project === 'object') {

@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT
(() => {
const PluginRegistry = require('./plugins');
const PluginRegistry = require('./plugins').default;
const loggerStorage = require('./logger-storage');
const serverProxy = require('./server-proxy');
const {
@ -830,7 +830,7 @@
}
return new Label(labelData);
});
}).filter((label) => !label.hasParent);
} else {
throw new Error('Job labels must be an array');
}
@ -1226,10 +1226,8 @@
});
if (Array.isArray(initialData.labels)) {
for (const label of initialData.labels) {
const classInstance = new Label(label);
data.labels.push(classInstance);
}
data.labels = initialData.labels
.map((labelData) => new Label(labelData)).filter((label) => !label.hasParent);
}
if (Array.isArray(initialData.segments)) {

@ -14,38 +14,42 @@
this,
Object.freeze({
/**
* Statistics by labels with a structure:
* Statistics collected by labels, has the following structure:
* @example
* {
* label: {
* boxes: {
* tracks: 10,
* shapes: 11,
* rectangle: {
* track: 10,
* shape: 11,
* },
* polygons: {
* tracks: 13,
* shapes: 14,
* polygon: {
* track: 13,
* shape: 14,
* },
* polylines: {
* tracks: 16,
* shapes: 17,
* polyline: {
* track: 16,
* shape: 17,
* },
* points: {
* tracks: 19,
* shapes: 20,
* track: 19,
* shape: 20,
* },
* ellipse: {
* tracks: 13,
* shapes: 15,
* track: 13,
* shape: 15,
* },
* cuboids: {
* tracks: 21,
* shapes: 22,
* cuboid: {
* track: 21,
* shape: 22,
* },
* tags: 66,
* manually: 186,
* skeleton: {
* track: 21,
* shape: 22,
* },
* tag: 66,
* manually: 207,
* interpolated: 500,
* total: 608,
* total: 630,
* }
* }
* @name label
@ -58,22 +62,22 @@
get: () => JSON.parse(JSON.stringify(label)),
},
/**
* Total statistics (covers all labels) with a structure:
* Total objects statistics (within all the labels), has the following structure:
* @example
* {
* boxes: {
* tracks: 10,
* shapes: 11,
* },
* polygons: {
* tracks: 13,
* shapes: 14,
* },
* polylines: {
* rectangle: {
* tracks: 10,
* shapes: 11,
* },
* polygon: {
* tracks: 13,
* shapes: 14,
* },
* polyline: {
* tracks: 16,
* shapes: 17,
* },
* points: {
* point: {
* tracks: 19,
* shapes: 20,
* },
@ -81,11 +85,15 @@
* tracks: 13,
* shapes: 15,
* },
* cuboids: {
* cuboid: {
* tracks: 21,
* shapes: 22,
* },
* skeleton: {
* tracks: 21,
* shapes: 22,
* },
* tags: 66,
* tag: 66,
* manually: 186,
* interpolated: 500,
* total: 608,

@ -119,7 +119,7 @@ describe('Feature: put annotations', () => {
const state = new window.cvat.classes.ObjectState({
frame: 1,
objectType: window.cvat.enums.ObjectType.SHAPE,
shapeType: window.cvat.enums.ObjectShape.POLYGON,
shapeType: window.cvat.enums.ShapeType.POLYGON,
points: [0, 0, 100, 0, 100, 50],
occluded: true,
label: task.labels[0],
@ -141,7 +141,7 @@ describe('Feature: put annotations', () => {
const state = new window.cvat.classes.ObjectState({
frame: 5,
objectType: window.cvat.enums.ObjectType.SHAPE,
shapeType: window.cvat.enums.ObjectShape.RECTANGLE,
shapeType: window.cvat.enums.ShapeType.RECTANGLE,
points: [0, 0, 100, 100],
occluded: false,
label: job.labels[0],
@ -163,7 +163,7 @@ describe('Feature: put annotations', () => {
const state = new window.cvat.classes.ObjectState({
frame: 5,
objectType: window.cvat.enums.ObjectType.SHAPE,
shapeType: window.cvat.enums.ObjectShape.ELLIPSE,
shapeType: window.cvat.enums.ShapeType.ELLIPSE,
points: [500, 500, 800, 100],
occluded: true,
label: job.labels[0],
@ -185,7 +185,7 @@ describe('Feature: put annotations', () => {
const state = new window.cvat.classes.ObjectState({
frame: 1,
objectType: window.cvat.enums.ObjectType.TRACK,
shapeType: window.cvat.enums.ObjectShape.POLYGON,
shapeType: window.cvat.enums.ShapeType.POLYGON,
points: [0, 0, 100, 0, 100, 50],
occluded: true,
label: task.labels[0],
@ -207,7 +207,7 @@ describe('Feature: put annotations', () => {
const state = new window.cvat.classes.ObjectState({
frame: 5,
objectType: window.cvat.enums.ObjectType.TRACK,
shapeType: window.cvat.enums.ObjectShape.RECTANGLE,
shapeType: window.cvat.enums.ShapeType.RECTANGLE,
points: [0, 0, 100, 100],
occluded: false,
label: job.labels[0],
@ -224,16 +224,14 @@ describe('Feature: put annotations', () => {
test('put object without objectType to a task', async () => {
const task = (await window.cvat.tasks.get({ id: 101 }))[0];
await task.annotations.clear(true);
const state = new window.cvat.classes.ObjectState({
expect(() => new window.cvat.classes.ObjectState({
frame: 1,
shapeType: window.cvat.enums.ObjectShape.POLYGON,
shapeType: window.cvat.enums.ShapeType.POLYGON,
points: [0, 0, 100, 0, 100, 50],
occluded: true,
label: task.labels[0],
zOrder: 0,
});
expect(task.annotations.put([state])).rejects.toThrow(window.cvat.exceptions.ArgumentError);
})).toThrow(window.cvat.exceptions.ArgumentError);
});
test('put shape with bad attributes to a task', async () => {
@ -242,7 +240,7 @@ describe('Feature: put annotations', () => {
const state = new window.cvat.classes.ObjectState({
frame: 1,
objectType: window.cvat.enums.ObjectType.SHAPE,
shapeType: window.cvat.enums.ObjectShape.POLYGON,
shapeType: window.cvat.enums.ShapeType.POLYGON,
points: [0, 0, 100, 0, 100, 50],
attributes: { 'bad key': 55 },
occluded: true,
@ -259,7 +257,7 @@ describe('Feature: put annotations', () => {
const state = new window.cvat.classes.ObjectState({
frame: 1,
objectType: window.cvat.enums.ObjectType.SHAPE,
shapeType: window.cvat.enums.ObjectShape.POLYGON,
shapeType: window.cvat.enums.ShapeType.POLYGON,
points: [0, 0, 100, 0, 100, 50],
attributes: { 'bad key': 55 },
occluded: true,
@ -272,7 +270,7 @@ describe('Feature: put annotations', () => {
const state1 = new window.cvat.classes.ObjectState({
frame: 1,
objectType: window.cvat.enums.ObjectType.SHAPE,
shapeType: window.cvat.enums.ObjectShape.POLYGON,
shapeType: window.cvat.enums.ShapeType.POLYGON,
points: [0, 0, 100, 0, 100, 50],
attributes: { 'bad key': 55 },
occluded: true,
@ -289,20 +287,19 @@ describe('Feature: put annotations', () => {
const state = new window.cvat.classes.ObjectState({
frame: 1,
objectType: window.cvat.enums.ObjectType.SHAPE,
shapeType: window.cvat.enums.ObjectShape.POLYGON,
shapeType: window.cvat.enums.ShapeType.POLYGON,
occluded: true,
points: [],
label: task.labels[0],
zOrder: 0,
});
await expect(task.annotations.put([state])).rejects.toThrow(window.cvat.exceptions.DataError);
expect(() => state.points = ['150,50 250,30']).toThrow(window.cvat.exceptions.ArgumentError);
delete state.points;
await expect(task.annotations.put([state])).rejects.toThrow(window.cvat.exceptions.DataError);
expect(task.annotations.put([state])).rejects.toThrow(window.cvat.exceptions.DataError);
state.points = ['150,50 250,30'];
expect(task.annotations.put([state])).rejects.toThrow(window.cvat.exceptions.ArgumentError);
state.points = [];
expect(task.annotations.put([state])).rejects.toThrow(window.cvat.exceptions.DataError);
});
test('put shape without type to a task', async () => {
@ -323,38 +320,35 @@ describe('Feature: put annotations', () => {
test('put shape without label and with bad label to a task', async () => {
const task = (await window.cvat.tasks.get({ id: 101 }))[0];
await task.annotations.clear(true);
const state = new window.cvat.classes.ObjectState({
const state = {
frame: 1,
objectType: window.cvat.enums.ObjectType.SHAPE,
shapeType: window.cvat.enums.ObjectShape.POLYGON,
shapeType: window.cvat.enums.ShapeType.POLYGON,
points: [0, 0, 100, 0, 100, 50],
occluded: true,
zOrder: 0,
});
await expect(task.annotations.put([state])).rejects.toThrow(window.cvat.exceptions.ArgumentError);
state.label = 'bad label';
await expect(task.annotations.put([state])).rejects.toThrow(window.cvat.exceptions.ArgumentError);
};
state.label = {};
await expect(task.annotations.put([state])).rejects.toThrow(window.cvat.exceptions.ArgumentError);
expect(() => new window.cvat.classes.ObjectState(state))
.toThrow(window.cvat.exceptions.ArgumentError);
expect(() => new window.cvat.classes.ObjectState({ ...state, label: 'bad label' }))
.toThrow(window.cvat.exceptions.ArgumentError);
expect(() => new window.cvat.classes.ObjectState({ ...state, label: {} }))
.toThrow(window.cvat.exceptions.ArgumentError);
});
test('put shape with bad frame to a task', async () => {
const task = (await window.cvat.tasks.get({ id: 101 }))[0];
await task.annotations.clear(true);
const state = new window.cvat.classes.ObjectState({
expect(() => new window.cvat.classes.ObjectState({
frame: '5',
objectType: window.cvat.enums.ObjectType.SHAPE,
shapeType: window.cvat.enums.ObjectShape.POLYGON,
shapeType: window.cvat.enums.ShapeType.POLYGON,
points: [0, 0, 100, 0, 100, 50],
occluded: true,
label: task.labels[0],
zOrder: 0,
});
expect(task.annotations.put([state])).rejects.toThrow(window.cvat.exceptions.ArgumentError);
})).toThrow(window.cvat.exceptions.ArgumentError);
});
});
@ -389,7 +383,7 @@ describe('Feature: save annotations', () => {
const state = new window.cvat.classes.ObjectState({
frame: 0,
objectType: window.cvat.enums.ObjectType.SHAPE,
shapeType: window.cvat.enums.ObjectShape.POLYGON,
shapeType: window.cvat.enums.ShapeType.POLYGON,
points: [0, 0, 100, 0, 100, 50],
occluded: true,
label: task.labels[0],
@ -411,7 +405,7 @@ describe('Feature: save annotations', () => {
const state = new window.cvat.classes.ObjectState({
frame: 0,
objectType: window.cvat.enums.ObjectType.SHAPE,
shapeType: window.cvat.enums.ObjectShape.POLYGON,
shapeType: window.cvat.enums.ShapeType.POLYGON,
points: [0, 0, 100, 0, 100, 50],
occluded: true,
label: task.labels[0],
@ -457,7 +451,7 @@ describe('Feature: save annotations', () => {
const state = new window.cvat.classes.ObjectState({
frame: 0,
objectType: window.cvat.enums.ObjectType.SHAPE,
shapeType: window.cvat.enums.ObjectShape.POLYGON,
shapeType: window.cvat.enums.ShapeType.POLYGON,
points: [0, 0, 100, 0, 100, 50],
occluded: true,
label: job.labels[0],
@ -574,7 +568,7 @@ describe('Feature: merge annotations', () => {
const state = new window.cvat.classes.ObjectState({
frame: 0,
objectType: window.cvat.enums.ObjectType.SHAPE,
shapeType: window.cvat.enums.ObjectShape.POLYGON,
shapeType: window.cvat.enums.ShapeType.POLYGON,
points: [0, 0, 100, 0, 100, 50],
occluded: true,
label: task.labels[0],
@ -602,7 +596,7 @@ describe('Feature: merge annotations', () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0];
const annotations0 = await task.annotations.get(0);
const annotations1 = (await task.annotations.get(1)).filter(
(state) => state.shapeType === window.cvat.enums.ObjectShape.POLYGON,
(state) => state.shapeType === window.cvat.enums.ShapeType.POLYGON,
);
const states = [annotations0[0], annotations1[0]];
@ -691,7 +685,7 @@ describe('Feature: group annotations', () => {
const state = new window.cvat.classes.ObjectState({
frame: 0,
objectType: window.cvat.enums.ObjectType.SHAPE,
shapeType: window.cvat.enums.ObjectShape.POLYGON,
shapeType: window.cvat.enums.ShapeType.POLYGON,
points: [0, 0, 100, 0, 100, 50],
occluded: true,
label: task.labels[0],
@ -783,18 +777,18 @@ describe('Feature: select object', () => {
const task = (await window.cvat.tasks.get({ id: 100 }))[0];
const annotations = await task.annotations.get(0);
let result = await task.annotations.select(annotations, 1430, 765);
expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.RECTANGLE);
expect(result.state.shapeType).toBe(window.cvat.enums.ShapeType.RECTANGLE);
result = await task.annotations.select(annotations, 1415, 765);
expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.POLYGON);
expect(result.state.shapeType).toBe(window.cvat.enums.ShapeType.POLYGON);
expect(result.state.points).toHaveLength(10);
result = await task.annotations.select(annotations, 1083, 543);
expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.POINTS);
expect(result.state.shapeType).toBe(window.cvat.enums.ShapeType.POINTS);
expect(result.state.points).toHaveLength(16);
result = await task.annotations.select(annotations, 613, 811);
expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.POLYGON);
expect(result.state.shapeType).toBe(window.cvat.enums.ShapeType.POLYGON);
expect(result.state.points).toHaveLength(94);
result = await task.annotations.select(annotations, 600, 900);
expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.CUBOID);
expect(result.state.shapeType).toBe(window.cvat.enums.ShapeType.CUBOID);
expect(result.state.points).toHaveLength(16);
});
@ -802,16 +796,16 @@ describe('Feature: select object', () => {
const job = (await window.cvat.jobs.get({ jobID: 100 }))[0];
const annotations = await job.annotations.get(0);
let result = await job.annotations.select(annotations, 490, 540);
expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.RECTANGLE);
expect(result.state.shapeType).toBe(window.cvat.enums.ShapeType.RECTANGLE);
result = await job.annotations.select(annotations, 430, 260);
expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.POLYLINE);
expect(result.state.shapeType).toBe(window.cvat.enums.ShapeType.POLYLINE);
result = await job.annotations.select(annotations, 1473, 250);
expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.RECTANGLE);
expect(result.state.shapeType).toBe(window.cvat.enums.ShapeType.RECTANGLE);
result = await job.annotations.select(annotations, 1490, 237);
expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.POLYGON);
expect(result.state.shapeType).toBe(window.cvat.enums.ShapeType.POLYGON);
expect(result.state.points).toHaveLength(94);
result = await job.annotations.select(annotations, 600, 900);
expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.CUBOID);
expect(result.state.shapeType).toBe(window.cvat.enums.ShapeType.CUBOID);
expect(result.state.points).toHaveLength(16);
});

@ -14,8 +14,9 @@ window.cvat = require('../../src/api');
describe('Feature: set attributes for an object state', () => {
test('set a valid value', () => {
const state = new window.cvat.classes.ObjectState({
label: new window.cvat.classes.Label({ name: 'test label', id: 1, color: '#000000', attributes: [] }),
objectType: window.cvat.enums.ObjectType.SHAPE,
shapeType: window.cvat.enums.ObjectShape.RECTANGLE,
shapeType: window.cvat.enums.ShapeType.RECTANGLE,
frame: 5,
});
@ -30,8 +31,9 @@ describe('Feature: set attributes for an object state', () => {
test('trying to set a bad value', () => {
const state = new window.cvat.classes.ObjectState({
label: new window.cvat.classes.Label({ name: 'test label', id: 1, color: '#000000', attributes: [] }),
objectType: window.cvat.enums.ObjectType.SHAPE,
shapeType: window.cvat.enums.ObjectShape.RECTANGLE,
shapeType: window.cvat.enums.ShapeType.RECTANGLE,
frame: 5,
});
@ -55,8 +57,9 @@ describe('Feature: set attributes for an object state', () => {
describe('Feature: set points for an object state', () => {
test('set a valid value', () => {
const state = new window.cvat.classes.ObjectState({
label: new window.cvat.classes.Label({ name: 'test label', id: 1, color: '#000000', attributes: [] }),
objectType: window.cvat.enums.ObjectType.SHAPE,
shapeType: window.cvat.enums.ObjectShape.RECTANGLE,
shapeType: window.cvat.enums.ShapeType.RECTANGLE,
frame: 5,
});
@ -67,8 +70,9 @@ describe('Feature: set points for an object state', () => {
test('trying to set a bad value', () => {
const state = new window.cvat.classes.ObjectState({
label: new window.cvat.classes.Label({ name: 'test label', id: 1, color: '#000000', attributes: [] }),
objectType: window.cvat.enums.ObjectType.SHAPE,
shapeType: window.cvat.enums.ObjectShape.RECTANGLE,
shapeType: window.cvat.enums.ShapeType.RECTANGLE,
frame: 5,
});
@ -100,7 +104,7 @@ describe('Feature: save object from its state', () => {
const annotations = await task.annotations.get(0);
let state = annotations[0];
expect(state.objectType).toBe(window.cvat.enums.ObjectType.SHAPE);
expect(state.shapeType).toBe(window.cvat.enums.ObjectShape.RECTANGLE);
expect(state.shapeType).toBe(window.cvat.enums.ShapeType.RECTANGLE);
state.points = [0, 0, 100, 100];
state.occluded = true;
[, state.label] = task.labels;
@ -118,7 +122,7 @@ describe('Feature: save object from its state', () => {
const annotations = await task.annotations.get(10);
let state = annotations[1];
expect(state.objectType).toBe(window.cvat.enums.ObjectType.TRACK);
expect(state.shapeType).toBe(window.cvat.enums.ObjectShape.RECTANGLE);
expect(state.shapeType).toBe(window.cvat.enums.ShapeType.RECTANGLE);
state.occluded = true;
state.lock = true;
@ -163,12 +167,9 @@ describe('Feature: save object from its state', () => {
state.occluded = 'false';
await expect(state.save()).rejects.toThrow(window.cvat.exceptions.ArgumentError);
const oldPoints = state.points;
state.occluded = false;
state.points = ['100', '50', '100', {}];
await expect(state.save()).rejects.toThrow(window.cvat.exceptions.ArgumentError);
expect(() => state.points = ['100', '50', '100', {}]).toThrow(window.cvat.exceptions.ArgumentError);
state.points = oldPoints;
state.lock = 'true';
await expect(state.save()).rejects.toThrow(window.cvat.exceptions.ArgumentError);
@ -190,12 +191,9 @@ describe('Feature: save object from its state', () => {
state.occluded = 'false';
await expect(state.save()).rejects.toThrow(window.cvat.exceptions.ArgumentError);
const oldPoints = state.points;
state.occluded = false;
state.points = ['100', '50', '100', {}];
await expect(state.save()).rejects.toThrow(window.cvat.exceptions.ArgumentError);
expect(() => state.points = ['100', '50', '100', {}]).toThrow(window.cvat.exceptions.ArgumentError);
state.points = oldPoints;
state.lock = 'true';
await expect(state.save()).rejects.toThrow(window.cvat.exceptions.ArgumentError);

@ -1,6 +1,6 @@
{
"name": "cvat-ui",
"version": "1.40.1",
"version": "1.41.0",
"description": "CVAT single-page application",
"main": "src/index.tsx",
"scripts": {
@ -33,7 +33,7 @@
"@types/react-share": "^3.0.3",
"@types/redux-logger": "^3.0.9",
"@types/resize-observer-browser": "^0.1.6",
"antd": "~4.21.6",
"antd": "~4.18.9",
"copy-to-clipboard": "^3.3.1",
"cvat-canvas": "file:../cvat-canvas",
"cvat-canvas3d": "file:../cvat-canvas3d",

@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT
import { ActionUnion, createAction, ThunkAction } from 'utils/redux';
import getCore from 'cvat-core-wrapper';
import { getCore } from 'cvat-core-wrapper';
const core = getCore();

@ -11,7 +11,7 @@ import { CanvasMode as Canvas3DMode } from 'cvat-canvas3d-wrapper';
import {
RectDrawingMethod, CuboidDrawingMethod, Canvas, CanvasMode as Canvas2DMode,
} from 'cvat-canvas-wrapper';
import getCore from 'cvat-core-wrapper';
import { getCore } from 'cvat-core-wrapper';
import logger, { LogType } from 'cvat-logger';
import { getCVATStore } from 'cvat-store';
@ -28,7 +28,7 @@ import {
ShapeType,
Task,
Workspace,
} from 'reducers/interfaces';
} from 'reducers';
import { updateJobAsync } from './tasks-actions';
import { switchToolsBlockerState } from './settings-actions';
@ -109,7 +109,7 @@ async function jobInfoGenerator(job: any): Promise<Record<string, number>> {
'polyline count': total.polyline.shape + total.polyline.track,
'points count': total.points.shape + total.points.track,
'cuboids count': total.cuboid.shape + total.cuboid.track,
'tag count': total.tags,
'tag count': total.tag,
};
}
@ -464,22 +464,26 @@ export function showFilters(visible: boolean): AnyAction {
export function propagateObjectAsync(sessionInstance: any, objectState: any, from: number, to: number): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try {
const copy = {
attributes: objectState.attributes,
points: objectState.points,
occluded: objectState.occluded,
objectType: objectState.objectType !== ObjectType.TRACK ? objectState.objectType : ObjectType.SHAPE,
shapeType: objectState.shapeType,
label: objectState.label,
zOrder: objectState.zOrder,
const getCopyFromState = (_objectState: any): any => ({
attributes: _objectState.attributes,
points: _objectState.shapeType === 'skeleton' ? null : _objectState.points,
occluded: _objectState.occluded,
objectType: _objectState.objectType !== ObjectType.TRACK ? _objectState.objectType : ObjectType.SHAPE,
shapeType: _objectState.shapeType,
label: _objectState.label,
zOrder: _objectState.zOrder,
frame: from,
source: objectState.source,
};
elements: _objectState.shapeType === 'skeleton' ? _objectState.elements
.map((element: any): any => getCopyFromState(element)) : [],
source: _objectState.source,
});
const copy = getCopyFromState(objectState);
await sessionInstance.logger.log(LogType.propagateObject, { count: to - from + 1 });
const states = [];
for (let frame = from; frame <= to; frame++) {
copy.frame = frame;
copy.elements.forEach((element: any) => { element.frame = frame; });
const newState = new cvat.classes.ObjectState(copy);
states.push(newState);
}
@ -585,11 +589,16 @@ export function copyShape(objectState: any): AnyAction {
};
}
export function activateObject(activatedStateID: number | null, activatedAttributeID: number | null): AnyAction {
export function activateObject(
activatedStateID: number | null,
activatedElementID: number | null,
activatedAttributeID: number | null,
): AnyAction {
return {
type: AnnotationActionTypes.ACTIVATE_OBJECT,
payload: {
activatedStateID,
activatedElementID,
activatedAttributeID,
},
};
@ -1263,11 +1272,18 @@ export function updateAnnotationsAsync(statesToUpdate: any[]): ThunkAction {
try {
if (statesToUpdate.some((state: any): boolean => state.updateFlags.zOrder)) {
// deactivate object to visualize changes immediately (UX)
dispatch(activateObject(null, null));
dispatch(activateObject(null, null, null));
}
const promises = statesToUpdate.map((objectState: any): Promise<any> => objectState.save());
const states = await Promise.all(promises);
const withSkeletonElements = states.some((state: any) => state.parentID !== null);
if (withSkeletonElements) {
dispatch(fetchAnnotationsAsync());
return;
}
const history = await jobInstance.actions.get();
const [minZ, maxZ] = computeZRange(states);
@ -1418,12 +1434,12 @@ export function changeGroupColorAsync(group: number, color: string): ThunkAction
const groupStates = state.annotation.annotations.states.filter(
(_state: any): boolean => _state.group.id === group,
);
if (groupStates.length) {
groupStates[0].group.color = color;
dispatch(updateAnnotationsAsync(groupStates));
} else {
dispatch(updateAnnotationsAsync([]));
for (const objectState of groupStates) {
objectState.group.color = color;
}
dispatch(updateAnnotationsAsync(groupStates));
};
}
@ -1511,7 +1527,7 @@ export function pasteShapeAsync(): ThunkAction {
drawing: { activeInitialState: initialState },
} = getStore().getState().annotation;
if (initialState) {
if (initialState && canvasInstance) {
let activeControl = ActiveControl.CURSOR;
if (initialState.shapeType === ShapeType.RECTANGLE) {
activeControl = ActiveControl.DRAW_RECTANGLE;
@ -1523,6 +1539,10 @@ export function pasteShapeAsync(): ThunkAction {
activeControl = ActiveControl.DRAW_POLYLINE;
} else if (initialState.shapeType === ShapeType.CUBOID) {
activeControl = ActiveControl.DRAW_CUBOID;
} else if (initialState.shapeType === ShapeType.ELLIPSE) {
activeControl = ActiveControl.DRAW_ELLIPSE;
} else if (initialState.shapeType === ShapeType.SKELETON) {
activeControl = ActiveControl.DRAW_SKELETON;
}
dispatch({
@ -1531,9 +1551,8 @@ export function pasteShapeAsync(): ThunkAction {
activeControl,
},
});
if (canvasInstance instanceof Canvas) {
canvasInstance.cancel();
}
canvasInstance.cancel();
if (initialState.objectType === ObjectType.TAG) {
const objectState = new cvat.classes.ObjectState({
objectType: ObjectType.TAG,
@ -1546,6 +1565,8 @@ export function pasteShapeAsync(): ThunkAction {
canvasInstance.draw({
enabled: true,
initialState,
...(initialState.shapeType === ShapeType.SKELETON ?
{ skeletonSVG: initialState.label.structure.svg } : {}),
});
}
}
@ -1614,6 +1635,8 @@ export function repeatDrawShapeAsync(): ThunkAction {
activeControl = ActiveControl.DRAW_CUBOID;
} else if (activeShapeType === ShapeType.ELLIPSE) {
activeControl = ActiveControl.DRAW_ELLIPSE;
} else if (activeShapeType === ShapeType.SKELETON) {
activeControl = ActiveControl.DRAW_SKELETON;
}
dispatch({
@ -1627,6 +1650,11 @@ export function repeatDrawShapeAsync(): ThunkAction {
canvasInstance.cancel();
}
const [activeLabel] = labels.filter((label: any) => label.id === activeLabelID);
if (!activeLabel) {
throw new Error(`Label with ID ${activeLabelID}, was not found`);
}
if (activeObjectType === ObjectType.TAG) {
const tags = states.filter((objectState: any): boolean => objectState.objectType === ObjectType.TAG);
if (tags.every((objectState: any): boolean => objectState.label.id !== activeLabelID)) {
@ -1645,6 +1673,7 @@ export function repeatDrawShapeAsync(): ThunkAction {
numberOfPoints: activeNumOfPoints,
shapeType: activeShapeType,
crosshair: [ShapeType.RECTANGLE, ShapeType.CUBOID, ShapeType.ELLIPSE].includes(activeShapeType),
skeletonSVG: activeShapeType === ShapeType.SKELETON ? activeLabel.structure.svg : undefined,
});
}
};
@ -1671,6 +1700,8 @@ export function redrawShapeAsync(): ThunkAction {
activeControl = ActiveControl.DRAW_POLYLINE;
} else if (state.shapeType === ShapeType.CUBOID) {
activeControl = ActiveControl.DRAW_CUBOID;
} else if (state.shapeType === ShapeType.SKELETON) {
activeControl = ActiveControl.DRAW_SKELETON;
}
dispatch({

@ -4,7 +4,7 @@
import { ActionUnion, createAction, ThunkAction } from 'utils/redux';
import { UserConfirmation } from 'components/register-page/register-form';
import getCore from 'cvat-core-wrapper';
import { getCore } from 'cvat-core-wrapper';
import isReachable from 'utils/url-checker';
const cvat = getCore();

@ -5,7 +5,7 @@
import {
ActionUnion, createAction, ThunkAction, ThunkDispatch,
} from 'utils/redux';
import getCore from 'cvat-core-wrapper';
import { getCore } from 'cvat-core-wrapper';
import { LogType } from 'cvat-logger';
import { computeZRange } from './annotation-actions';

@ -4,8 +4,8 @@
import { Dispatch, ActionCreator } from 'redux';
import { ActionUnion, createAction, ThunkAction } from 'utils/redux';
import getCore from 'cvat-core-wrapper';
import { CloudStoragesQuery, CloudStorage, Indexable } from 'reducers/interfaces';
import { getCore } from 'cvat-core-wrapper';
import { CloudStoragesQuery, CloudStorage, Indexable } from 'reducers';
const cvat = getCore();

@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT
import { ActionUnion, createAction, ThunkAction } from 'utils/redux';
import getCore from 'cvat-core-wrapper';
import { getCore } from 'cvat-core-wrapper';
const cvat = getCore();

@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT
import { createAction, ActionUnion, ThunkAction } from 'utils/redux';
import { CombinedState } from 'reducers/interfaces';
import { CombinedState } from 'reducers';
import { getProjectsAsync } from './projects-actions';
export enum ImportActionTypes {

@ -3,8 +3,8 @@
// SPDX-License-Identifier: MIT
import { ActionUnion, createAction, ThunkAction } from 'utils/redux';
import getCore from 'cvat-core-wrapper';
import { Indexable, JobsQuery } from 'reducers/interfaces';
import { getCore } from 'cvat-core-wrapper';
import { Indexable, JobsQuery } from 'reducers';
const cvat = getCore();

@ -3,8 +3,8 @@
// SPDX-License-Identifier: MIT
import { ActionUnion, createAction, ThunkAction } from 'utils/redux';
import { Model, ActiveInference, RQStatus } from 'reducers/interfaces';
import getCore from 'cvat-core-wrapper';
import { Model, ActiveInference, RQStatus } from 'reducers';
import { getCore } from 'cvat-core-wrapper';
export enum ModelsActionTypes {
GET_MODELS = 'GET_MODELS',

@ -4,7 +4,7 @@
import { Store } from 'antd/lib/form/interface';
import { User } from 'components/task-page/user-selector';
import getCore from 'cvat-core-wrapper';
import { getCore } from 'cvat-core-wrapper';
import { ActionUnion, createAction, ThunkAction } from 'utils/redux';
const core = getCore();

@ -3,8 +3,8 @@
// SPDX-License-Identifier: MIT
import { ActionUnion, createAction, ThunkAction } from 'utils/redux';
import { PluginsList } from 'reducers/interfaces';
import getCore from '../cvat-core-wrapper';
import { PluginsList } from 'reducers';
import { getCore } from 'cvat-core-wrapper';
const core = getCore();

@ -7,10 +7,10 @@ import { Dispatch, ActionCreator } from 'redux';
import { ActionUnion, createAction, ThunkAction } from 'utils/redux';
import {
ProjectsQuery, TasksQuery, CombinedState, Indexable,
} from 'reducers/interfaces';
} from 'reducers';
import { getTasksAsync } from 'actions/tasks-actions';
import { getCVATStore } from 'cvat-store';
import getCore from 'cvat-core-wrapper';
import { getCore } from 'cvat-core-wrapper';
const cvat = getCore();

@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT
import { ActionUnion, createAction, ThunkAction } from 'utils/redux';
import getCore from 'cvat-core-wrapper';
import { getCore } from 'cvat-core-wrapper';
const cvat = getCore();

@ -5,7 +5,7 @@
import { AnyAction } from 'redux';
import {
GridColor, ColorBy, SettingsState, ToolsBlockerState,
} from 'reducers/interfaces';
} from 'reducers';
export enum SettingsActionTypes {
SWITCH_ROTATE_ALL = 'SWITCH_ROTATE_ALL',
@ -24,6 +24,7 @@ export enum SettingsActionTypes {
SWITCH_RESET_ZOOM = 'SWITCH_RESET_ZOOM',
SWITCH_SMOOTH_IMAGE = 'SWITCH_SMOOTH_IMAGE',
SWITCH_TEXT_FONT_SIZE = 'SWITCH_TEXT_FONT_SIZE',
SWITCH_CONTROL_POINTS_SIZE = 'SWITCH_CONTROL_POINTS_SIZE',
SWITCH_TEXT_POSITION = 'SWITCH_TEXT_POSITION',
SWITCH_TEXT_CONTENT = 'SWITCH_TEXT_CONTENT',
CHANGE_BRIGHTNESS_LEVEL = 'CHANGE_BRIGHTNESS_LEVEL',
@ -190,6 +191,15 @@ export function switchTextFontSize(fontSize: number): AnyAction {
};
}
export function switchControlPointsSize(pointsSize: number): AnyAction {
return {
type: SettingsActionTypes.SWITCH_CONTROL_POINTS_SIZE,
payload: {
controlPointsSize: pointsSize,
},
};
}
export function switchTextPosition(position: 'auto' | 'center'): AnyAction {
return {
type: SettingsActionTypes.SWITCH_TEXT_POSITION,
@ -199,11 +209,11 @@ export function switchTextPosition(position: 'auto' | 'center'): AnyAction {
};
}
export function switchTextContent(textContent: string): AnyAction {
export function switchTextContent(textContent: string[]): AnyAction {
return {
type: SettingsActionTypes.SWITCH_TEXT_CONTENT,
payload: {
textContent,
textContent: textContent.join(','),
},
};
}

@ -3,9 +3,9 @@
// SPDX-License-Identifier: MIT
import { ActionUnion, createAction, ThunkAction } from 'utils/redux';
import getCore from 'cvat-core-wrapper';
import { getCore } from 'cvat-core-wrapper';
import { ShareFileInfo } from 'reducers/interfaces';
import { ShareFileInfo } from 'reducers';
const core = getCore();

@ -4,9 +4,9 @@
import { AnyAction, Dispatch, ActionCreator } from 'redux';
import { ThunkAction } from 'redux-thunk';
import { TasksQuery, CombinedState, Indexable } from 'reducers/interfaces';
import { TasksQuery, CombinedState, Indexable } from 'reducers';
import { getCVATStore } from 'cvat-store';
import getCore from 'cvat-core-wrapper';
import { getCore } from 'cvat-core-wrapper';
import { getInferenceStatusAsync } from './models-actions';
const cvat = getCore();

@ -3,8 +3,8 @@
// SPDX-License-Identifier: MIT
import { ActionUnion, createAction, ThunkAction } from 'utils/redux';
import getCore from 'cvat-core-wrapper';
import { UserAgreement } from 'reducers/interfaces';
import { getCore } from 'cvat-core-wrapper';
import { UserAgreement } from 'reducers';
const core = getCore();

@ -1 +1,8 @@
<svg width="40" height="40" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><circle stroke="#000" stroke-width="2" cx="20" cy="20" r="17"/><circle fill="#000" cx="20" cy="20" r="7.5"/></g></svg>
<?xml version="1.0" encoding="utf-8"?>
<svg viewBox="0 0 1024 1024" width="40" height="40" xmlns="http://www.w3.org/2000/svg">
<rect x="122.622" y="199.29" width="141.579" height="141.579" style="stroke: rgb(0, 0, 0); stroke-linejoin: round; stroke-linecap: round; fill-rule: evenodd; fill: none; stroke-width: 36px;"/>
<rect x="712.768" y="203.339" width="141.579" height="141.579" style="stroke: rgb(0, 0, 0); stroke-linejoin: round; stroke-linecap: round; fill-rule: evenodd; fill: none; stroke-width: 36px;"/>
<rect x="397.369" y="457.061" width="141.579" height="141.579" style="stroke: rgb(0, 0, 0); stroke-linejoin: round; stroke-linecap: round; fill-rule: evenodd; fill: none; stroke-width: 36px; paint-order: fill markers;"/>
<rect x="110.006" y="778.065" width="141.579" height="141.579" style="stroke: rgb(0, 0, 0); stroke-linejoin: round; stroke-linecap: round; fill-rule: evenodd; fill: none; stroke-width: 36px;"/>
<rect x="728.188" y="658.916" width="141.579" height="141.579" style="stroke: rgb(0, 0, 0); stroke-linejoin: round; stroke-linecap: round; fill-rule: evenodd; fill: none; stroke-width: 36px;"/>
</svg>

Before

Width:  |  Height:  |  Size: 216 B

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg">
<g xmlns="http://www.w3.org/2000/svg" version="1.1" x="0px" y="0px" viewBox="0 0 40 40">
<ellipse style="fill: rgb(216, 216, 216);" cx="20" cy="20" ry="11" rx="11"></ellipse>
<path d="M 39.462 2.335 C 39.134 2.204 38.755 2.278 38.507 2.518 L 34.866 6.053 C 30.955 2.151 25.733 0 20.162 0 C 18.985 0 17.795 0.098 16.63 0.298 C 8.691 1.637 2.262 7.4 0.248 14.988 C 0.181 15.241 0.24 15.514 0.404 15.722 C 0.572 15.931 0.824 16.053 1.097 16.053 L 6.677 16.053 C 7.043 16.053 7.375 15.829 7.501 15.494 C 9.133 11.155 13.19 7.849 17.837 7.065 C 18.602 6.935 19.38 6.869 20.153 6.869 C 23.845 6.869 27.293 8.302 29.862 10.91 L 26.162 14.502 C 25.914 14.747 25.838 15.11 25.973 15.429 C 26.107 15.747 26.427 15.955 26.78 15.955 L 39.125 15.955 C 39.609 15.955 40 15.576 40 15.106 L 40 3.118 C 40.004 2.776 39.79 2.465 39.462 2.335 Z" data-bx-origin="-0.660719 -0.618451"></path><path d="M 38.907 23.951 L 33.327 23.951 C 32.961 23.951 32.629 24.176 32.503 24.51 C 30.871 28.845 26.814 32.151 22.168 32.935 C 21.402 33.065 20.624 33.131 19.851 33.131 C 16.159 33.131 12.711 31.698 10.142 29.09 L 13.842 25.498 C 14.094 25.253 14.166 24.89 14.031 24.571 C 13.897 24.253 13.577 24.045 13.224 24.045 L 0.875 24.045 C 0.391 24.045 0 24.424 0 24.894 L 0 36.878 C 0 37.22 0.214 37.531 0.542 37.665 C 0.87 37.796 1.245 37.722 1.497 37.482 L 5.138 33.947 C 9.044 37.849 14.267 40 19.842 40 C 21.024 40 22.21 39.898 23.378 39.702 C 31.313 38.367 37.738 32.6 39.752 25.012 C 39.819 24.759 39.76 24.486 39.596 24.278 C 39.432 24.073 39.176 23.951 38.907 23.951 Z" data-bx-origin="-0.655168 -2.110973"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Downloaded from https://www.svgrepo.com/svg/109131/molecule -->
<!-- LICENSE: CC0 License -->
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg width="40" height="40" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 299.804 299.804" style="enable-background:new 0 0 299.804 299.804;" xml:space="preserve">
<g>
<g>
<path d="M230.816,221.246l-46.06-29.289c8.208-17.846,5.962-38.875-6.195-54.673l52.241-52.896
c26.33,17.897,62.197-1.061,62.197-32.979c0-21.974-17.878-39.851-39.852-39.851c-32.101,0-51.072,36.267-32.692,62.601
l-52.238,52.893c-18.971-14.23-45.044-14.211-64.003,0.091L56.2,78.794c12.273-20.103-2.286-46.056-25.888-46.056
C13.597,32.738,0,46.336,0,63.048c0,23.553,25.813,38.053,45.874,25.997l48.02,48.354c-26.674,34.86-1.796,85.694,42.378,85.694
c16.278,0,30.867-7.337,40.659-18.868l46.061,29.29c-10.578,26.177,8.847,54.731,36.959,54.731
c21.973,0,39.851-17.877,39.851-39.851C299.804,212.29,255.403,194.877,230.816,221.246z M253.147,26.108
c13.951,0,25.301,11.35,25.301,25.301c0,13.952-11.35,25.302-25.301,25.302c-13.952,0-25.302-11.35-25.302-25.302
S239.195,26.108,253.147,26.108z M30.311,78.808c-8.69,0.001-15.761-7.069-15.761-15.76s7.07-15.76,15.761-15.76
c8.69,0,15.76,7.07,15.76,15.76C46.07,71.738,39.001,78.808,30.311,78.808z M136.272,208.543
c-21.392,0-38.795-17.403-38.795-38.795c0-21.391,17.403-38.795,38.795-38.795c21.391,0,38.794,17.404,38.794,38.795
C175.068,191.14,157.664,208.543,136.272,208.543z M259.952,273.696c-13.951,0-25.301-11.35-25.301-25.301
c0-13.952,11.35-25.302,25.301-25.302c13.952,0,25.301,11.35,25.301,25.302C285.254,262.346,273.904,273.696,259.952,273.696z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

@ -11,7 +11,7 @@ import { LoadingOutlined } from '@ant-design/icons';
import { MenuInfo } from 'rc-menu/lib/interface';
import LoadSubmenu from './load-submenu';
import { DimensionType } from '../../reducers/interfaces';
import { DimensionType } from '../../reducers';
interface Props {
taskID: number;

@ -8,7 +8,7 @@ import Upload from 'antd/lib/upload';
import Button from 'antd/lib/button';
import Text from 'antd/lib/typography/Text';
import { UploadOutlined, LoadingOutlined } from '@ant-design/icons';
import { DimensionType } from '../../reducers/interfaces';
import { DimensionType } from '../../reducers';
interface Props {
menuKey: string;

@ -17,7 +17,7 @@ import TagAnnotationWorkspace from 'components/annotation-page/tag-annotation-wo
import FiltersModalComponent from 'components/annotation-page/top-bar/filters-modal';
import StatisticsModalComponent from 'components/annotation-page/top-bar/statistics-modal';
import AnnotationTopBarContainer from 'containers/annotation-page/top-bar/top-bar';
import { Workspace } from 'reducers/interfaces';
import { Workspace } from 'reducers';
import { usePrevious } from 'utils/hooks';
import './styles.scss';
import Button from 'antd/lib/button';

@ -14,7 +14,7 @@ import Button from 'antd/lib/button';
import ColorPicker from 'components/annotation-page/standard-workspace/objects-side-bar/color-picker';
import { ColorizeIcon } from 'icons';
import { ColorBy, CombinedState, DimensionType } from 'reducers/interfaces';
import { ColorBy, CombinedState, DimensionType } from 'reducers';
import { collapseAppearance as collapseAppearanceAction } from 'actions/annotation-actions';
import {
changeShapesColorBy as changeShapesColorByAction,

@ -23,7 +23,7 @@ import { ThunkDispatch } from 'utils/redux';
import AppearanceBlock from 'components/annotation-page/appearance-block';
import ObjectButtonsContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/object-buttons';
import { adjustContextImagePosition } from 'components/annotation-page/standard-workspace/context-image/context-image';
import { CombinedState, ObjectType } from 'reducers/interfaces';
import { CombinedState, ObjectType } from 'reducers';
import AttributeEditor from './attribute-editor';
import AttributeSwitcher from './attribute-switcher';
import ObjectBasicsEditor from './object-basics-edtior';
@ -84,7 +84,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
function mapDispatchToProps(dispatch: ThunkDispatch): DispatchToProps {
return {
activateObject(clientID: number, attrID: number): void {
dispatch(activateObjectAction(clientID, attrID));
dispatch(activateObjectAction(clientID, null, attrID));
},
updateAnnotations(states): void {
dispatch(updateAnnotationsAsync(states));

@ -8,14 +8,16 @@ import Menu from 'antd/lib/menu';
// eslint-disable-next-line import/no-extraneous-dependencies
import { MenuInfo } from 'rc-menu/lib/interface';
import ObjectItemElementComponent from 'components/annotation-page/standard-workspace/objects-side-bar/object-item-element';
import ObjectItemContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/object-item';
import { Workspace } from 'reducers/interfaces';
import { Workspace } from 'reducers';
import { rotatePoint } from 'utils/math';
import consts from 'consts';
interface Props {
readonly: boolean;
workspace: Workspace;
contextMenuParentID: number | null;
contextMenuClientID: number | null;
objectStates: any[];
visible: boolean;
@ -79,6 +81,7 @@ function ReviewContextMenu({
export default function CanvasContextMenu(props: Props): JSX.Element | null {
const {
contextMenuClientID,
contextMenuParentID,
objectStates,
visible,
left,
@ -153,6 +156,20 @@ export default function CanvasContextMenu(props: Props): JSX.Element | null {
);
}
if (Number.isInteger(contextMenuParentID)) {
return ReactDOM.createPortal(
<div className='cvat-canvas-context-menu' style={{ top, left }}>
<ObjectItemElementComponent
readonly={readonly}
key={contextMenuClientID}
clientID={contextMenuClientID}
parentID={contextMenuParentID as number}
/>
</div>,
window.document.body,
);
}
return ReactDOM.createPortal(
<div className='cvat-canvas-context-menu' style={{ top, left }}>
<ObjectItemContainer
@ -160,7 +177,6 @@ export default function CanvasContextMenu(props: Props): JSX.Element | null {
key={contextMenuClientID}
clientID={contextMenuClientID}
objectStates={objectStates}
initialCollapsed
/>
</div>,
window.document.body,

@ -8,7 +8,7 @@ import Button from 'antd/lib/button';
import { DeleteOutlined, EnvironmentOutlined } from '@ant-design/icons';
import { connect } from 'react-redux';
import { CombinedState, ContextMenuType } from 'reducers/interfaces';
import { CombinedState, ContextMenuType } from 'reducers';
import { updateAnnotationsAsync, updateCanvasContextMenu } from 'actions/annotation-actions';
import CVATTooltip from 'components/common/cvat-tooltip';

@ -11,11 +11,11 @@ import { PlusCircleOutlined, UpOutlined } from '@ant-design/icons';
import GlobalHotKeys, { KeyMap } from 'utils/mousetrap-react';
import {
ColorBy, GridColor, ObjectType, ContextMenuType, Workspace, ShapeType,
} from 'reducers/interfaces';
} from 'reducers';
import { LogType } from 'cvat-logger';
import { Canvas } from 'cvat-canvas-wrapper';
import { Canvas3d } from 'cvat-canvas3d-wrapper';
import getCore from 'cvat-core-wrapper';
import { getCore } from 'cvat-core-wrapper';
import consts from 'consts';
import CVATTooltip from 'components/common/cvat-tooltip';
import FrameTags from 'components/annotation-page/tag-annotation-workspace/frame-tags';
@ -31,6 +31,7 @@ interface Props {
canvasInstance: Canvas | Canvas3d | null;
jobInstance: any;
activatedStateID: number | null;
activatedElementID: number | null;
activatedAttributeID: number | null;
annotations: any[];
frameData: any;
@ -61,6 +62,7 @@ interface Props {
aamZoomMargin: number;
showObjectsTextAlways: boolean;
textFontSize: number;
controlPointsSize: number;
textPosition: 'auto' | 'center';
textContent: string;
showAllInterpolationTracks: boolean;
@ -85,7 +87,7 @@ interface Props {
onMergeAnnotations(sessionInstance: any, frame: number, states: any[]): void;
onGroupAnnotations(sessionInstance: any, frame: number, states: any[]): void;
onSplitAnnotations(sessionInstance: any, frame: number, state: any): void;
onActivateObject(activatedStateID: number | null): void;
onActivateObject(activatedStateID: number | null, activatedElementID?: number | null): void;
onUpdateContextMenu(visible: boolean, left: number, top: number, type: ContextMenuType, pointID?: number): void;
onAddZLayer(): void;
onSwitchZLayer(cur: number): void;
@ -110,10 +112,15 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
workspace,
showProjections,
selectedOpacity,
opacity,
smoothImage,
textFontSize,
controlPointsSize,
textPosition,
textContent,
colorBy,
outlined,
outlineColor,
} = this.props;
const { canvasInstance } = this.props as { canvasInstance: Canvas };
@ -123,14 +130,18 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
wrapper.appendChild(canvasInstance.html());
canvasInstance.configure({
smoothImage,
autoborders: automaticBordering,
forceDisableEditing: workspace === Workspace.REVIEW_WORKSPACE,
undefinedAttrValue: consts.UNDEFINED_ATTRIBUTE_VALUE,
displayAllText: showObjectsTextAlways,
forceDisableEditing: workspace === Workspace.REVIEW_WORKSPACE,
intelligentPolygonCrop,
autoborders: automaticBordering,
showProjections,
creationOpacity: selectedOpacity,
intelligentPolygonCrop,
selectedShapeOpacity: selectedOpacity,
controlPointsSize,
shapeOpacity: opacity,
smoothImage,
colorBy,
outlinedBorders: outlined ? outlineColor || 'black' : false,
textFontSize,
textPosition,
textContent,
@ -143,7 +154,6 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
public componentDidUpdate(prevProps: Props): void {
const {
opacity,
colorBy,
selectedOpacity,
outlined,
outlineColor,
@ -167,6 +177,7 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
frameFetching,
showObjectsTextAlways,
textFontSize,
controlPointsSize,
textPosition,
textContent,
showAllInterpolationTracks,
@ -174,19 +185,26 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
intelligentPolygonCrop,
showProjections,
canvasBackgroundColor,
colorBy,
onFetchAnnotation,
} = this.props;
const { canvasInstance } = this.props as { canvasInstance: Canvas };
if (
prevProps.showObjectsTextAlways !== showObjectsTextAlways ||
prevProps.automaticBordering !== automaticBordering ||
prevProps.showProjections !== showProjections ||
prevProps.intelligentPolygonCrop !== intelligentPolygonCrop ||
prevProps.opacity !== opacity ||
prevProps.selectedOpacity !== selectedOpacity ||
prevProps.smoothImage !== smoothImage ||
prevProps.textFontSize !== textFontSize ||
prevProps.controlPointsSize !== controlPointsSize ||
prevProps.textPosition !== textPosition ||
prevProps.textContent !== textContent
prevProps.textContent !== textContent ||
prevProps.colorBy !== colorBy ||
prevProps.outlineColor !== outlineColor ||
prevProps.outlined !== outlined
) {
canvasInstance.configure({
undefinedAttrValue: consts.UNDEFINED_ATTRIBUTE_VALUE,
@ -194,9 +212,13 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
autoborders: automaticBordering,
showProjections,
intelligentPolygonCrop,
creationOpacity: selectedOpacity,
selectedShapeOpacity: selectedOpacity,
shapeOpacity: opacity,
smoothImage,
colorBy,
outlinedBorders: outlined ? outlineColor || 'black' : false,
textFontSize,
controlPointsSize,
textPosition,
textContent,
});
@ -272,16 +294,6 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
);
}
if (
prevProps.opacity !== opacity ||
prevProps.outlined !== outlined ||
prevProps.outlineColor !== outlineColor ||
prevProps.selectedOpacity !== selectedOpacity ||
prevProps.colorBy !== colorBy
) {
this.updateShapesView();
}
if (prevProps.showBitmap !== showBitmap) {
canvasInstance.bitmap(showBitmap);
}
@ -386,9 +398,22 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
state.objectType = state.objectType || activeObjectType;
state.label = state.label || jobInstance.labels.filter((label: any) => label.id === activeLabelID)[0];
state.occluded = state.occluded || false;
state.frame = frame;
state.rotation = state.rotation || 0;
state.occluded = state.occluded || false;
state.outside = state.outside || false;
if (state.shapeType === ShapeType.SKELETON && Array.isArray(state.elements)) {
state.elements.forEach((element: Record<string, any>) => {
element.objectType = state.objectType;
element.label = element.label || state.label.structure
.sublabels.find((label: any) => label.id === element.labelID);
element.frame = state.frame;
element.rotation = 0;
element.occluded = element.occluded || false;
element.outside = element.outside || false;
});
}
const objectState = new cvat.classes.ObjectState(state);
onCreateAnnotations(jobInstance, frame, [objectState]);
};
@ -494,8 +519,14 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
};
private onCanvasShapeClicked = (e: any): void => {
const { clientID } = e.detail.state;
const sidebarItem = window.document.getElementById(`cvat-objects-sidebar-state-item-${clientID}`);
const { clientID, parentID } = e.detail.state;
let sidebarItem = null;
if (Number.isInteger(parentID)) {
sidebarItem = window.document.getElementById(`cvat-objects-sidebar-state-item-element-${clientID}`);
} else {
sidebarItem = window.document.getElementById(`cvat-objects-sidebar-state-item-${clientID}`);
}
if (sidebarItem) {
sidebarItem.scrollIntoView();
}
@ -515,7 +546,7 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
private onCanvasCursorMoved = async (event: any): Promise<void> => {
const {
jobInstance, activatedStateID, workspace, onActivateObject,
jobInstance, activatedStateID, activatedElementID, workspace, onActivateObject,
} = this.props;
if (![Workspace.STANDARD, Workspace.REVIEW_WORKSPACE].includes(workspace)) {
@ -525,14 +556,15 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
const result = await jobInstance.annotations.select(event.detail.states, event.detail.x, event.detail.y);
if (result && result.state) {
if (result.state.shapeType === 'polyline' || result.state.shapeType === 'points') {
if (['polyline', 'points'].includes(result.state.shapeType)) {
if (result.distance > MAX_DISTANCE_TO_OPEN_SHAPE) {
return;
}
}
if (activatedStateID !== result.state.clientID) {
onActivateObject(result.state.clientID);
const newActivatedElement = event.detail.activatedElementID || null;
if (activatedStateID !== result.state.clientID || activatedElementID !== newActivatedElement) {
onActivateObject(result.state.clientID, event.detail.activatedElementID || null);
}
}
};
@ -577,7 +609,6 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
private onCanvasSetup = (): void => {
const { onSetupCanvas } = this.props;
onSetupCanvas();
this.updateShapesView();
this.activateOnCanvas();
};
@ -593,7 +624,7 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
const result = await jobInstance.annotations.select(e.detail.states, e.detail.x, e.detail.y);
if (result && result.state) {
if (result.state.shapeType === 'polyline' || result.state.shapeType === 'points') {
if (['polyline', 'points'].includes(result.state.shapeType)) {
if (result.distance > MAX_DISTANCE_TO_OPEN_SHAPE) {
return;
}
@ -607,7 +638,7 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
const { activatedStateID, onUpdateContextMenu, annotations } = this.props;
const [state] = annotations.filter((el: any) => el.clientID === activatedStateID);
if (![ShapeType.CUBOID, ShapeType.RECTANGLE].includes(state.shapeType)) {
if (![ShapeType.CUBOID, ShapeType.RECTANGLE, ShapeType.ELLIPSE, ShapeType.SKELETON].includes(state.shapeType)) {
onUpdateContextMenu(
activatedStateID !== null,
e.detail.mouseEvent.clientX,
@ -648,36 +679,6 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
}
}
private updateShapesView(): void {
const {
annotations, opacity, colorBy, outlined, outlineColor,
} = this.props;
for (const state of annotations) {
let shapeColor = '';
if (colorBy === ColorBy.INSTANCE) {
shapeColor = state.color;
} else if (colorBy === ColorBy.GROUP) {
shapeColor = state.group.color;
} else if (colorBy === ColorBy.LABEL) {
shapeColor = state.label.color;
}
// TODO: In this approach CVAT-UI know details of implementations CVAT-CANVAS (svg.js)
const shapeView = window.document.getElementById(`cvat_canvas_shape_${state.clientID}`);
if (shapeView) {
const handler = (shapeView as any).instance.remember('_selectHandler');
if (handler && handler.nested) {
handler.nested.fill({ color: shapeColor });
}
(shapeView as any).instance.fill({ color: shapeColor, opacity });
(shapeView as any).instance.stroke({ color: outlined ? outlineColor : shapeColor });
}
}
}
private updateCanvas(): void {
const {
curZLayer, annotations, frameData, canvasInstance,

@ -12,7 +12,7 @@ import {
import { ResizableBox } from 'react-resizable';
import {
ColorBy, ContextMenuType, ObjectType, Workspace,
} from 'reducers/interfaces';
} from 'reducers';
import {
CameraAction, Canvas3d, ViewType, ViewsDOM,
} from 'cvat-canvas3d-wrapper';
@ -20,7 +20,7 @@ import { Canvas } from 'cvat-canvas-wrapper';
import ContextImage from 'components/annotation-page/standard-workspace/context-image/context-image';
import CVATTooltip from 'components/common/cvat-tooltip';
import { LogType } from 'cvat-logger';
import getCore from 'cvat-core-wrapper';
import { getCore } from 'cvat-core-wrapper';
const cvat = getCore();

@ -22,7 +22,7 @@ import {
changeGridSize,
} from 'actions/settings-actions';
import { clamp } from 'utils/math';
import { GridColor, CombinedState, PlayerSettingsState } from 'reducers/interfaces';
import { GridColor, CombinedState, PlayerSettingsState } from 'reducers';
const minGridSize = 5;
const maxGridSize = 1000;

@ -6,7 +6,7 @@ import React from 'react';
import Layout from 'antd/lib/layout';
import GlobalHotKeys, { KeyMap } from 'utils/mousetrap-react';
import { ActiveControl, Rotation } from 'reducers/interfaces';
import { ActiveControl, Rotation } from 'reducers';
import { Canvas } from 'cvat-canvas-wrapper';
import RotateControl from 'components/annotation-page/standard-workspace/controls-side-bar/rotate-control';

@ -5,7 +5,7 @@
import React from 'react';
import Icon from '@ant-design/icons';
import { ActiveControl } from 'reducers/interfaces';
import { ActiveControl } from 'reducers';
import { Canvas } from 'cvat-canvas-wrapper';
import { RectangleIcon } from 'icons';
import CVATTooltip from 'components/common/cvat-tooltip';

@ -6,7 +6,7 @@ import './styles.scss';
import React, { useState, useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { CombinedState } from 'reducers/interfaces';
import { CombinedState } from 'reducers';
import { Canvas } from 'cvat-canvas/src/typescript/canvas';
import { commentIssueAsync, resolveIssueAsync, reopenIssueAsync } from 'actions/review-actions';

@ -9,7 +9,7 @@ import { QuestionCircleOutlined, ShrinkOutlined } from '@ant-design/icons';
import Spin from 'antd/lib/spin';
import Image from 'antd/lib/image';
import { CombinedState } from 'reducers/interfaces';
import { CombinedState } from 'reducers';
import { hideShowContextImage, getContextImageAsync } from 'actions/annotation-actions';
import CVATTooltip from 'components/common/cvat-tooltip';

@ -5,9 +5,12 @@
import React from 'react';
import Layout from 'antd/lib/layout';
import { ActiveControl, Rotation } from 'reducers/interfaces';
import {
ActiveControl, ObjectType, Rotation, ShapeType,
} from 'reducers';
import GlobalHotKeys, { KeyMap } from 'utils/mousetrap-react';
import { Canvas } from 'cvat-canvas-wrapper';
import { Label } from 'components/labels-editor/common';
import ControlVisibilityObserver, { ExtraControlsControl } from './control-visibility-observer';
import RotateControl, { Props as RotateControlProps } from './rotate-control';
@ -23,6 +26,7 @@ import DrawPolylineControl, { Props as DrawPolylineControlProps } from './draw-p
import DrawPointsControl, { Props as DrawPointsControlProps } from './draw-points-control';
import DrawEllipseControl, { Props as DrawEllipseControlProps } from './draw-ellipse-control';
import DrawCuboidControl, { Props as DrawCuboidControlProps } from './draw-cuboid-control';
import DrawSkeletonControl, { Props as DrawSkeletonControlProps } from './draw-skeleton-control';
import SetupTagControl, { Props as SetupTagControlProps } from './setup-tag-control';
import MergeControl, { Props as MergeControlProps } from './merge-control';
import GroupControl, { Props as GroupControlProps } from './group-control';
@ -61,6 +65,7 @@ const ObservedDrawPolylineControl = ControlVisibilityObserver<DrawPolylineContro
const ObservedDrawPointsControl = ControlVisibilityObserver<DrawPointsControlProps>(DrawPointsControl);
const ObservedDrawEllipseControl = ControlVisibilityObserver<DrawEllipseControlProps>(DrawEllipseControl);
const ObservedDrawCuboidControl = ControlVisibilityObserver<DrawCuboidControlProps>(DrawCuboidControl);
const ObservedDrawSkeletonControl = ControlVisibilityObserver<DrawSkeletonControlProps>(DrawSkeletonControl);
const ObservedSetupTagControl = ControlVisibilityObserver<SetupTagControlProps>(SetupTagControl);
const ObservedMergeControl = ControlVisibilityObserver<MergeControlProps>(MergeControl);
const ObservedGroupControl = ControlVisibilityObserver<GroupControlProps>(GroupControl);
@ -85,6 +90,24 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element {
} = props;
const controlsDisabled = !labels.length || frameData.deleted;
const withUnspecifiedType = labels.some((label: any) => label.type === 'any' && !label.hasParent);
let rectangleControlVisible = withUnspecifiedType;
let polygonControlVisible = withUnspecifiedType;
let polylineControlVisible = withUnspecifiedType;
let pointsControlVisible = withUnspecifiedType;
let ellipseControlVisible = withUnspecifiedType;
let cuboidControlVisible = withUnspecifiedType;
let tagControlVisible = withUnspecifiedType;
const skeletonControlVisible = labels.some((label: Label) => label.type === 'skeleton');
labels.forEach((label: Label) => {
rectangleControlVisible = rectangleControlVisible || label.type === ShapeType.RECTANGLE;
polygonControlVisible = polygonControlVisible || label.type === ShapeType.POLYGON;
polylineControlVisible = polylineControlVisible || label.type === ShapeType.POLYLINE;
pointsControlVisible = pointsControlVisible || label.type === ShapeType.POINTS;
ellipseControlVisible = ellipseControlVisible || label.type === ShapeType.ELLIPSE;
cuboidControlVisible = cuboidControlVisible || label.type === ShapeType.CUBOID;
tagControlVisible = tagControlVisible || label.type === ObjectType.TAG;
});
const preventDefault = (event: KeyboardEvent | undefined): void => {
if (event) {
@ -131,6 +154,8 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element {
ActiveControl.DRAW_POLYLINE,
ActiveControl.DRAW_RECTANGLE,
ActiveControl.DRAW_CUBOID,
ActiveControl.DRAW_ELLIPSE,
ActiveControl.DRAW_SKELETON,
ActiveControl.AI_TOOLS,
ActiveControl.OPENCV_TOOLS,
].includes(activeControl);
@ -227,38 +252,77 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element {
<hr />
<ObservedToolsControl />
<ObservedOpenCVControl />
<ObservedDrawRectangleControl
canvasInstance={canvasInstance}
isDrawing={activeControl === ActiveControl.DRAW_RECTANGLE}
disabled={controlsDisabled}
/>
<ObservedDrawPolygonControl
canvasInstance={canvasInstance}
isDrawing={activeControl === ActiveControl.DRAW_POLYGON}
disabled={controlsDisabled}
/>
<ObservedDrawPolylineControl
canvasInstance={canvasInstance}
isDrawing={activeControl === ActiveControl.DRAW_POLYLINE}
disabled={controlsDisabled}
/>
<ObservedDrawPointsControl
canvasInstance={canvasInstance}
isDrawing={activeControl === ActiveControl.DRAW_POINTS}
disabled={controlsDisabled}
/>
<ObservedDrawEllipseControl
canvasInstance={canvasInstance}
isDrawing={activeControl === ActiveControl.DRAW_ELLIPSE}
disabled={controlsDisabled}
/>
<ObservedDrawCuboidControl
canvasInstance={canvasInstance}
isDrawing={activeControl === ActiveControl.DRAW_CUBOID}
disabled={controlsDisabled}
/>
<ObservedSetupTagControl canvasInstance={canvasInstance} isDrawing={false} disabled={controlsDisabled} />
{
rectangleControlVisible && (
<ObservedDrawRectangleControl
canvasInstance={canvasInstance}
isDrawing={activeControl === ActiveControl.DRAW_RECTANGLE}
disabled={controlsDisabled}
/>
)
}
{
polygonControlVisible && (
<ObservedDrawPolygonControl
canvasInstance={canvasInstance}
isDrawing={activeControl === ActiveControl.DRAW_POLYGON}
disabled={controlsDisabled}
/>
)
}
{
polylineControlVisible && (
<ObservedDrawPolylineControl
canvasInstance={canvasInstance}
isDrawing={activeControl === ActiveControl.DRAW_POLYLINE}
disabled={controlsDisabled}
/>
)
}
{
pointsControlVisible && (
<ObservedDrawPointsControl
canvasInstance={canvasInstance}
isDrawing={activeControl === ActiveControl.DRAW_POINTS}
disabled={controlsDisabled}
/>
)
}
{
ellipseControlVisible && (
<ObservedDrawEllipseControl
canvasInstance={canvasInstance}
isDrawing={activeControl === ActiveControl.DRAW_ELLIPSE}
disabled={controlsDisabled}
/>
)
}
{
cuboidControlVisible && (
<ObservedDrawCuboidControl
canvasInstance={canvasInstance}
isDrawing={activeControl === ActiveControl.DRAW_CUBOID}
disabled={controlsDisabled}
/>
)
}
{
skeletonControlVisible && (
<ObservedDrawSkeletonControl
canvasInstance={canvasInstance}
isDrawing={activeControl === ActiveControl.DRAW_SKELETON}
disabled={controlsDisabled}
/>
)
}
{
tagControlVisible && (
<ObservedSetupTagControl
canvasInstance={canvasInstance}
disabled={controlsDisabled}
/>
)
}
<hr />
<ObservedMergeControl

@ -6,7 +6,7 @@ import React from 'react';
import Icon from '@ant-design/icons';
import { CursorIcon } from 'icons';
import { ActiveControl } from 'reducers/interfaces';
import { ActiveControl } from 'reducers';
import { Canvas } from 'cvat-canvas-wrapper';
import { Canvas3d } from 'cvat-canvas3d-wrapper';
import CVATTooltip from 'components/common/cvat-tooltip';

@ -8,7 +8,7 @@ import Icon from '@ant-design/icons';
import { Canvas } from 'cvat-canvas-wrapper';
import { Canvas3d } from 'cvat-canvas3d-wrapper';
import { ShapeType } from 'reducers/interfaces';
import { ShapeType } from 'reducers';
import { CubeIcon } from 'icons';
@ -22,7 +22,7 @@ export interface Props {
}
const CustomPopover = withVisibilityHandling(Popover, 'draw-cuboid');
function DrawPolygonControl(props: Props): JSX.Element {
function DrawCuboidControl(props: Props): JSX.Element {
const { canvasInstance, isDrawing, disabled } = props;
const dynamicPopoverProps = isDrawing ? {
overlayStyle: {
@ -53,4 +53,4 @@ function DrawPolygonControl(props: Props): JSX.Element {
);
}
export default React.memo(DrawPolygonControl);
export default React.memo(DrawCuboidControl);

@ -8,7 +8,7 @@ import Icon from '@ant-design/icons';
import { Canvas } from 'cvat-canvas-wrapper';
import { EllipseIcon } from 'icons';
import { ShapeType } from 'reducers/interfaces';
import { ShapeType } from 'reducers';
import DrawShapePopoverContainer from 'containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover';
import withVisibilityHandling from './handle-popover-visibility';
@ -20,7 +20,7 @@ export interface Props {
}
const CustomPopover = withVisibilityHandling(Popover, 'draw-ellipse');
function DrawPointsControl(props: Props): JSX.Element {
function DrawEllipseControl(props: Props): JSX.Element {
const { canvasInstance, isDrawing, disabled } = props;
const dynamicPopoverProps = isDrawing ? {
overlayStyle: {
@ -51,4 +51,4 @@ function DrawPointsControl(props: Props): JSX.Element {
);
}
export default React.memo(DrawPointsControl);
export default React.memo(DrawEllipseControl);

@ -8,7 +8,7 @@ import Icon from '@ant-design/icons';
import { Canvas } from 'cvat-canvas-wrapper';
import { PointIcon } from 'icons';
import { ShapeType } from 'reducers/interfaces';
import { ShapeType } from 'reducers';
import DrawShapePopoverContainer from 'containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover';
import withVisibilityHandling from './handle-popover-visibility';

@ -8,7 +8,7 @@ import Icon from '@ant-design/icons';
import { Canvas } from 'cvat-canvas-wrapper';
import { PolygonIcon } from 'icons';
import { ShapeType } from 'reducers/interfaces';
import { ShapeType } from 'reducers';
import DrawShapePopoverContainer from 'containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover';
import withVisibilityHandling from './handle-popover-visibility';

@ -8,7 +8,7 @@ import Icon from '@ant-design/icons';
import { Canvas } from 'cvat-canvas-wrapper';
import { PolylineIcon } from 'icons';
import { ShapeType } from 'reducers/interfaces';
import { ShapeType } from 'reducers';
import DrawShapePopoverContainer from 'containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover';
import withVisibilityHandling from './handle-popover-visibility';

@ -8,7 +8,7 @@ import Icon from '@ant-design/icons';
import { Canvas } from 'cvat-canvas-wrapper';
import { RectangleIcon } from 'icons';
import { ShapeType } from 'reducers/interfaces';
import { ShapeType } from 'reducers';
import DrawShapePopoverContainer from 'containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover';
import withVisibilityHandling from './handle-popover-visibility';

@ -10,10 +10,11 @@ import Radio, { RadioChangeEvent } from 'antd/lib/radio';
import Text from 'antd/lib/typography/Text';
import { RectDrawingMethod, CuboidDrawingMethod } from 'cvat-canvas-wrapper';
import { DimensionType, ShapeType } from 'reducers/interfaces';
import { DimensionType, ShapeType } from 'reducers';
import { clamp } from 'utils/math';
import LabelSelector from 'components/label-selector/label-selector';
import CVATTooltip from 'components/common/cvat-tooltip';
import { Label } from 'components/labels-editor/common';
interface Props {
shapeType: ShapeType;
@ -22,9 +23,9 @@ interface Props {
rectDrawingMethod?: RectDrawingMethod;
cuboidDrawingMethod?: CuboidDrawingMethod;
numberOfPoints?: number;
selectedLabelID: number;
selectedLabelID: number | null;
repeatShapeShortcut: string;
onChangeLabel(value: string): void;
onChangeLabel(value: Label | null): void;
onChangePoints(value: number | undefined): void;
onChangeRectDrawingMethod(event: RadioChangeEvent): void;
onChangeCuboidDrawingMethod(event: RadioChangeEvent): void;
@ -53,7 +54,6 @@ function DrawShapePopoverComponent(props: Props): JSX.Element {
} = props;
const is2D = jobInstance.dimension === DimensionType.DIM_2D;
return (
<div className='cvat-draw-shape-popover-content'>
<Row justify='start'>
@ -126,7 +126,7 @@ function DrawShapePopoverComponent(props: Props): JSX.Element {
</Row>
</>
)}
{is2D && ![ShapeType.RECTANGLE, ShapeType.CUBOID, ShapeType.ELLIPSE].includes(shapeType) ? (
{is2D && [ShapeType.POLYGON, ShapeType.POLYLINE, ShapeType.POINTS].includes(shapeType) ? (
<Row justify='space-around' align='middle'>
<Col span={14}>
<Text className='cvat-text-color'> Number of points: </Text>

@ -0,0 +1,52 @@
import React from 'react';
import Popover from 'antd/lib/popover';
import Icon from '@ant-design/icons';
import { Canvas } from 'cvat-canvas-wrapper';
import { Canvas3d } from 'cvat-canvas3d-wrapper';
import { ShapeType } from 'reducers';
import { SkeletonIcon } from 'icons';
import DrawShapePopoverContainer from 'containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover';
import withVisibilityHandling from './handle-popover-visibility';
export interface Props {
canvasInstance: Canvas | Canvas3d;
isDrawing: boolean;
disabled: boolean;
}
const CustomPopover = withVisibilityHandling(Popover, 'draw-skeleton');
function DrawSkeletonControl(props: Props): JSX.Element {
const { canvasInstance, isDrawing, disabled } = props;
const dynamicPopoverProps = isDrawing ? {
overlayStyle: {
display: 'none',
},
} : {};
const dynamicIconProps = isDrawing ? {
className: 'cvat-draw-skeleton-control cvat-active-canvas-control',
onClick: (): void => {
canvasInstance.draw({ enabled: false });
},
} : {
className: 'cvat-draw-skeleton-control',
};
return disabled ? (
<Icon className='cvat-draw-skeleton-control cvat-disabled-canvas-control' component={SkeletonIcon} />
) : (
<CustomPopover
{...dynamicPopoverProps}
overlayClassName='cvat-draw-shape-popover'
placement='right'
content={<DrawShapePopoverContainer shapeType={ShapeType.SKELETON} />}
>
<Icon {...dynamicIconProps} component={SkeletonIcon} />
</CustomPopover>
);
}
export default React.memo(DrawSkeletonControl);

@ -8,7 +8,7 @@ import Icon from '@ant-design/icons';
import { GroupIcon } from 'icons';
import { Canvas } from 'cvat-canvas-wrapper';
import { Canvas3d } from 'cvat-canvas3d-wrapper';
import { ActiveControl, DimensionType } from 'reducers/interfaces';
import { ActiveControl, DimensionType } from 'reducers';
import CVATTooltip from 'components/common/cvat-tooltip';
export interface Props {

@ -7,7 +7,7 @@ import Icon from '@ant-design/icons';
import { MergeIcon } from 'icons';
import { Canvas } from 'cvat-canvas-wrapper';
import { ActiveControl } from 'reducers/interfaces';
import { ActiveControl } from 'reducers';
import CVATTooltip from 'components/common/cvat-tooltip';
export interface Props {

@ -6,7 +6,7 @@ import React from 'react';
import Icon from '@ant-design/icons';
import { MoveIcon } from 'icons';
import { ActiveControl } from 'reducers/interfaces';
import { ActiveControl } from 'reducers';
import { Canvas } from 'cvat-canvas-wrapper';
import { Canvas3d } from 'cvat-canvas3d-wrapper';
import CVATTooltip from 'components/common/cvat-tooltip';

@ -17,12 +17,12 @@ import message from 'antd/lib/message';
import { OpenCVIcon } from 'icons';
import { Canvas, convertShapesForInteractor } from 'cvat-canvas-wrapper';
import getCore from 'cvat-core-wrapper';
import { getCore } from 'cvat-core-wrapper';
import openCVWrapper from 'utils/opencv-wrapper/opencv-wrapper';
import { IntelligentScissors } from 'utils/opencv-wrapper/intelligent-scissors';
import {
CombinedState, ActiveControl, OpenCVTool, ObjectType, ShapeType, ToolsBlockerState,
} from 'reducers/interfaces';
} from 'reducers';
import {
interactWithCanvas,
fetchAnnotationsAsync,

@ -6,7 +6,7 @@ import React from 'react';
import Icon from '@ant-design/icons';
import { ZoomIcon } from 'icons';
import { ActiveControl } from 'reducers/interfaces';
import { ActiveControl } from 'reducers';
import { Canvas } from 'cvat-canvas-wrapper';
import CVATTooltip from 'components/common/cvat-tooltip';

@ -7,7 +7,7 @@ import Icon from '@ant-design/icons';
import Popover from 'antd/lib/popover';
import { RotateIcon } from 'icons';
import { Rotation } from 'reducers/interfaces';
import { Rotation } from 'reducers';
import CVATTooltip from 'components/common/cvat-tooltip';
import withVisibilityHandling from './handle-popover-visibility';

@ -14,25 +14,17 @@ import withVisibilityHandling from './handle-popover-visibility';
export interface Props {
canvasInstance: Canvas;
isDrawing: boolean;
disabled?: boolean;
}
const CustomPopover = withVisibilityHandling(Popover, 'setup-tag');
function SetupTagControl(props: Props): JSX.Element {
const { isDrawing, disabled } = props;
const dynamicPopoverProps = isDrawing ?
{
overlayStyle: {
display: 'none',
},
} :
{};
const { disabled } = props;
return disabled ? (
<Icon className='cvat-setup-tag-control cvat-disabled-canvas-control' component={TagIcon} />
) : (
<CustomPopover {...dynamicPopoverProps} placement='right' content={<SetupTagPopoverContainer />}>
<CustomPopover placement='right' content={<SetupTagPopoverContainer />}>
<Icon className='cvat-setup-tag-control' component={TagIcon} />
</CustomPopover>
);

@ -13,10 +13,10 @@ import CVATTooltip from 'components/common/cvat-tooltip';
interface Props {
labels: any[];
selectedLabelID: number;
selectedLabelID: number | null;
repeatShapeShortcut: string;
onChangeLabel(value: string): void;
onSetup(labelID: number): void;
onSetup(): void;
}
function SetupTagPopover(props: Props): JSX.Element {
@ -44,13 +44,13 @@ function SetupTagPopover(props: Props): JSX.Element {
labels={labels}
value={selectedLabelID}
onChange={onChangeLabel}
onEnterPress={() => onSetup(selectedLabelID)}
onEnterPress={() => onSetup()}
/>
<CVATTooltip title={`Press ${repeatShapeShortcut} to add a tag again`}>
<Button
type='primary'
className='cvat-add-tag-button'
onClick={() => onSetup(selectedLabelID)}
onClick={() => onSetup()}
icon={<PlusOutlined />}
/>
</CVATTooltip>

@ -7,7 +7,7 @@ import Icon from '@ant-design/icons';
import { SplitIcon } from 'icons';
import { Canvas } from 'cvat-canvas-wrapper';
import { ActiveControl } from 'reducers/interfaces';
import { ActiveControl } from 'reducers';
import CVATTooltip from 'components/common/cvat-tooltip';
export interface Props {

@ -25,11 +25,11 @@ import lodash from 'lodash';
import { AIToolsIcon } from 'icons';
import { Canvas, convertShapesForInteractor } from 'cvat-canvas-wrapper';
import getCore from 'cvat-core-wrapper';
import { getCore } from 'cvat-core-wrapper';
import openCVWrapper from 'utils/opencv-wrapper/opencv-wrapper';
import {
CombinedState, ActiveControl, Model, ObjectType, ShapeType, ToolsBlockerState, ModelAttribute,
} from 'reducers/interfaces';
} from 'reducers';
import {
interactWithCanvas,
switchNavigationBlocked as switchNavigationBlockedAction,
@ -756,7 +756,7 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
});
// eslint-disable-next-line no-await-in-loop
const response = await core.lambda.call(jobInstance.taskId, tracker, {
frame: frame - 1,
frame: frame ,
shapes: trackableObjects.shapes,
states: trackableObjects.states,
});

@ -10,7 +10,7 @@ import Popover from 'antd/lib/popover';
import Text from 'antd/lib/typography/Text';
import { SketchPicker } from 'react-color';
import getCore from 'cvat-core-wrapper';
import { getCore } from 'cvat-core-wrapper';
import CVATTooltip from 'components/common/cvat-tooltip';
const core = getCore();

@ -14,7 +14,7 @@ import { Row, Col } from 'antd/lib/grid';
import { changeFrameAsync } from 'actions/annotation-actions';
import { reviewActions } from 'actions/review-actions';
import CVATTooltip from 'components/common/cvat-tooltip';
import { CombinedState } from 'reducers/interfaces';
import { CombinedState } from 'reducers';
export default function LabelsListComponent(): JSX.Element {
const dispatch = useDispatch();

@ -9,7 +9,7 @@ import Button from 'antd/lib/button';
import { Row, Col } from 'antd/lib/grid';
import Text from 'antd/lib/typography/Text';
import { CombinedState } from 'reducers/interfaces';
import { CombinedState } from 'reducers';
import CVATTooltip from 'components/common/cvat-tooltip';
interface LabelKeySelectorPopoverProps {

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save