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', 'plugin:import/typescript', 'plugin:@typescript-eslint/recommended', 'airbnb-typescript/base',
], ],
rules: { rules: {
'header/header': [2, 'line', [{ // 'header/header': [2, 'line', [{
pattern: ' {1}Copyright \\(C\\) (?:20\\d{2}-)?2022 Intel Corporation', // pattern: ' {1}Copyright \\(C\\) (?:20\\d{2}-)?2022 Intel Corporation',
template: ' Copyright (C) 2022 Intel Corporation' // template: ' Copyright (C) 2022 Intel Corporation'
}, '', ' SPDX-License-Identifier: MIT']], // }, '', ' SPDX-License-Identifier: MIT']],
'no-plusplus': 0, 'no-plusplus': 0,
'no-continue': 0, 'no-continue': 0,
'no-console': 0, 'no-console': 0,
@ -52,6 +52,7 @@ module.exports = {
'import/order': ['error', {'groups': ['builtin', 'external', 'internal']}], 'import/order': ['error', {'groups': ['builtin', 'external', 'internal']}],
'import/prefer-default-export': 0, // works incorrect with interfaces 'import/prefer-default-export': 0, // works incorrect with interfaces
'@typescript-eslint/ban-ts-comment': 0,
'@typescript-eslint/no-explicit-any': 0, '@typescript-eslint/no-explicit-any': 0,
'@typescript-eslint/indent': ['error', 4], '@typescript-eslint/indent': ['error', 4],
'@typescript-eslint/lines-between-class-members': 0, '@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 ## \[2.2.0] - Unreleased
### Added ### 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>) - Support of attributes returned by serverless functions based on (<https://github.com/openvinotoolkit/cvat/pull/4506>)
- Project/task backups uploading via chunk uploads - Project/task backups uploading via chunk uploads
- Fixed UX bug when jobs pagination is reset after changing a job - 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>) - 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>) - 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>) - 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 ### Changed
- Bumped nuclio version to 1.8.14 - 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>) - 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>) - 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>) - 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 ### Changed
- Added missing geos dependency into Dockerfile (<https://github.com/openvinotoolkit/cvat/pull/4451>) - Added missing geos dependency into Dockerfile (<https://github.com/openvinotoolkit/cvat/pull/4451>)

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

@ -10,6 +10,12 @@
stroke-opacity: 1; stroke-opacity: 1;
} }
g.cvat_canvas_shape {
> circle {
fill-opacity: 1;
}
}
polyline.cvat_canvas_shape { polyline.cvat_canvas_shape {
fill-opacity: 0; fill-opacity: 0;
} }
@ -120,7 +126,6 @@ polyline.cvat_canvas_shape_splitting {
@extend .cvat_shape_drawing_opacity; @extend .cvat_shape_drawing_opacity;
fill: white; fill: white;
stroke: black;
} }
.cvat_canvas_zoom_selection { .cvat_canvas_zoom_selection {
@ -134,6 +139,12 @@ polyline.cvat_canvas_shape_splitting {
stroke-dasharray: 5; stroke-dasharray: 5;
} }
g.cvat_canvas_shape_occluded {
> rect {
stroke-dasharray: 5;
}
}
.svg_select_points_rot { .svg_select_points_rot {
fill: white; 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 { .cvat_canvas_pixelized {
image-rendering: optimizeSpeed; /* Legal fallback */ image-rendering: optimizeSpeed; /* Legal fallback */
image-rendering: -moz-crisp-edges; /* Firefox */ image-rendering: -moz-crisp-edges; /* Firefox */

@ -5,7 +5,7 @@
import * as SVG from 'svg.js'; import * as SVG from 'svg.js';
import consts from './consts'; import consts from './consts';
import { Geometry } from './canvasModel'; import { Configuration, Geometry } from './canvasModel';
interface TransformedShape { interface TransformedShape {
points: string; points: string;
@ -14,6 +14,7 @@ interface TransformedShape {
export interface AutoborderHandler { export interface AutoborderHandler {
autoborder(enabled: boolean, currentShape?: SVG.Shape, currentID?: number): void; autoborder(enabled: boolean, currentShape?: SVG.Shape, currentID?: number): void;
configurate(configuration: Configuration): void;
transform(geometry: Geometry): void; transform(geometry: Geometry): void;
updateObjects(): void; updateObjects(): void;
} }
@ -24,19 +25,14 @@ export class AutoborderHandlerImpl implements AutoborderHandler {
private frameContent: SVGSVGElement; private frameContent: SVGSVGElement;
private enabled: boolean; private enabled: boolean;
private scale: number; private scale: number;
private controlPointsSize: number;
private groups: SVGGElement[]; private groups: SVGGElement[];
private auxiliaryGroupID: number | null; private auxiliaryGroupID: number | null;
private auxiliaryClicks: number[]; private auxiliaryClicks: number[];
private listeners: Record< private listeners: Record<number, Record<number, {
number,
Record<
number,
{
click: (event: MouseEvent) => void; click: (event: MouseEvent) => void;
dblclick: (event: MouseEvent) => void; dblclick: (event: MouseEvent) => void;
} }>>;
>
>;
public constructor(frameContent: SVGSVGElement) { public constructor(frameContent: SVGSVGElement) {
this.frameContent = frameContent; this.frameContent = frameContent;
@ -45,6 +41,7 @@ export class AutoborderHandlerImpl implements AutoborderHandler {
this.enabled = false; this.enabled = false;
this.scale = 1; this.scale = 1;
this.groups = []; this.groups = [];
this.controlPointsSize = consts.BASE_POINT_SIZE;
this.auxiliaryGroupID = null; this.auxiliaryGroupID = null;
this.auxiliaryClicks = []; this.auxiliaryClicks = [];
this.listeners = {}; this.listeners = {};
@ -126,7 +123,7 @@ export class AutoborderHandlerImpl implements AutoborderHandler {
circle.setAttribute('stroke-width', `${consts.POINTS_STROKE_WIDTH / this.scale}`); circle.setAttribute('stroke-width', `${consts.POINTS_STROKE_WIDTH / this.scale}`);
circle.setAttribute('cx', x); circle.setAttribute('cx', x);
circle.setAttribute('cy', y); 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 => { const click = (event: MouseEvent): void => {
event.stopPropagation(); event.stopPropagation();
@ -303,9 +300,13 @@ export class AutoborderHandlerImpl implements AutoborderHandler {
this.scale = geometry.scale; this.scale = geometry.scale;
this.groups.forEach((group: SVGGElement): void => { this.groups.forEach((group: SVGGElement): void => {
Array.from(group.children).forEach((circle: SVGCircleElement): 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}`); 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; forceDisableEditing?: boolean;
intelligentPolygonCrop?: boolean; intelligentPolygonCrop?: boolean;
forceFrameUpdate?: boolean; forceFrameUpdate?: boolean;
creationOpacity?: number;
CSSImageFilter?: string; CSSImageFilter?: string;
colorBy?: string;
selectedShapeOpacity?: number;
shapeOpacity?: number;
controlPointsSize?: number;
outlinedBorders?: string | false;
} }
export interface DrawData { export interface DrawData {
@ -73,6 +77,7 @@ export interface DrawData {
shapeType?: string; shapeType?: string;
rectDrawingMethod?: RectDrawingMethod; rectDrawingMethod?: RectDrawingMethod;
cuboidDrawingMethod?: CuboidDrawingMethod; cuboidDrawingMethod?: CuboidDrawingMethod;
skeletonSVG?: string;
numberOfPoints?: number; numberOfPoints?: number;
initialState?: any; initialState?: any;
crosshair?: boolean; crosshair?: boolean;
@ -265,12 +270,23 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
width: 0, width: 0,
}, },
configuration: { configuration: {
displayAllText: false, smoothImage: true,
autoborders: false, autoborders: false,
undefinedAttrValue: '', displayAllText: false,
textContent: 'id,label,attributes,source,descriptions', showProjections: false,
textPosition: 'auto', forceDisableEditing: false,
intelligentPolygonCrop: false,
forceFrameUpdate: false,
CSSImageFilter: '',
colorBy: 'Label',
selectedShapeOpacity: 0.5,
shapeOpacity: 0.2,
outlinedBorders: false,
textFontSize: consts.DEFAULT_SHAPE_TEXT_SIZE, 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, imageBitmap: false,
image: null, image: null,
@ -541,6 +557,10 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
} }
if (drawData.enabled) { 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) { if (this.data.drawData.enabled) {
throw new Error('Drawing has been already started'); throw new Error('Drawing has been already started');
} else if (!drawData.shapeType && !drawData.initialState) { } else if (!drawData.shapeType && !drawData.initialState) {
@ -670,6 +690,10 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
this.data.configuration.textFontSize = configuration.textFontSize; this.data.configuration.textFontSize = configuration.textFontSize;
} }
if (typeof configuration.controlPointsSize === 'number') {
this.data.configuration.controlPointsSize = configuration.controlPointsSize;
}
if (['auto', 'center'].includes(configuration.textPosition)) { if (['auto', 'center'].includes(configuration.textPosition)) {
this.data.configuration.textPosition = configuration.textPosition; this.data.configuration.textPosition = configuration.textPosition;
} }
@ -702,8 +726,17 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
if (typeof configuration.forceFrameUpdate === 'boolean') { if (typeof configuration.forceFrameUpdate === 'boolean') {
this.data.configuration.forceFrameUpdate = configuration.forceFrameUpdate; this.data.configuration.forceFrameUpdate = configuration.forceFrameUpdate;
} }
if (typeof configuration.creationOpacity === 'number') { if (typeof configuration.selectedShapeOpacity === 'number') {
this.data.configuration.creationOpacity = configuration.creationOpacity; 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') { 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_STROKE_WIDTH = 1.25;
const BASE_GRID_WIDTH = 2; const BASE_GRID_WIDTH = 2;
const BASE_POINT_SIZE = 5; const BASE_POINT_SIZE = 4;
const TEXT_MARGIN = 10; const TEXT_MARGIN = 10;
const AREA_THRESHOLD = 9; const AREA_THRESHOLD = 9;
const SIZE_THRESHOLD = 3; 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 BASE_PATTERN_SIZE = 5;
const SNAP_TO_ANGLE_RESIZE_DEFAULT = 0.1; const SNAP_TO_ANGLE_RESIZE_DEFAULT = 0.1;
const SNAP_TO_ANGLE_RESIZE_SHIFT = 15; const SNAP_TO_ANGLE_RESIZE_SHIFT = 15;
const DEFAULT_SHAPE_TEXT_SIZE = 12;
const MINIMUM_TEXT_FONT_SIZE = 8; 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 { export default {
BASE_STROKE_WIDTH, BASE_STROKE_WIDTH,
@ -40,5 +45,9 @@ export default {
SNAP_TO_ANGLE_RESIZE_DEFAULT, SNAP_TO_ANGLE_RESIZE_DEFAULT,
SNAP_TO_ANGLE_RESIZE_SHIFT, SNAP_TO_ANGLE_RESIZE_SHIFT,
DEFAULT_SHAPE_TEXT_SIZE, DEFAULT_SHAPE_TEXT_SIZE,
DEFAULT_SHAPE_TEXT_CONTENT,
DEFAULT_SHAPE_TEXT_POSITION,
DEFAULT_UNDEFINED_ATTR_VALUE,
MINIMUM_TEXT_FONT_SIZE, MINIMUM_TEXT_FONT_SIZE,
SKELETON_RECT_MARGIN,
}; };

@ -17,6 +17,11 @@ import {
Point, Point,
readPointsFromShape, readPointsFromShape,
clamp, clamp,
translateToCanvas,
computeWrappingBox,
makeSVGFromTemplate,
setupSkeletonEdges,
translateFromCanvas,
} from './shared'; } from './shared';
import Crosshair from './crosshair'; import Crosshair from './crosshair';
import consts from './consts'; import consts from './consts';
@ -83,9 +88,11 @@ export class DrawHandlerImpl implements DrawHandler {
private crosshair: Crosshair; private crosshair: Crosshair;
private drawData: DrawData; private drawData: DrawData;
private geometry: Geometry; private geometry: Geometry;
private configuration: Configuration;
private autoborderHandler: AutoborderHandler; private autoborderHandler: AutoborderHandler;
private autobordersEnabled: boolean; 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 // 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 // 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('mousedown.draw');
this.canvas.off('mousemove.draw'); this.canvas.off('mousemove.draw');
if (this.pointsGroup) {
this.pointsGroup.remove();
this.pointsGroup = null;
}
// Draw plugin in some cases isn't activated // Draw plugin in some cases isn't activated
// For example when draw from initialState // For example when draw from initialState
// Or when no drawn points, but we call cancel() drawing // Or when no drawn points, but we call cancel() drawing
// We check if it is activated with remember function // We check if it is activated with remember function
if (this.drawInstance.remember('_paintHandler')) { if (this.drawInstance.remember('_paintHandler')) {
if ( if (['polygon', 'polyline', 'points'].includes(this.drawData.shapeType) ||
['polygon', 'polyline', 'points'].includes(this.drawData.shapeType) ||
(this.drawData.shapeType === 'cuboid' && (this.drawData.shapeType === 'cuboid' &&
this.drawData.cuboidDrawingMethod === CuboidDrawingMethod.CORNER_POINTS) this.drawData.cuboidDrawingMethod === CuboidDrawingMethod.CORNER_POINTS)) {
) {
// Check for unsaved drawn shapes // Check for unsaved drawn shapes
this.drawInstance.draw('done'); this.drawInstance.draw('done');
} }
// Clear drawing // Clear drawing
this.drawInstance.draw('stop'); 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(); this.drawInstance.off();
@ -417,7 +424,8 @@ export class DrawHandlerImpl implements DrawHandler {
.addClass('cvat_canvas_shape_drawing') .addClass('cvat_canvas_shape_drawing')
.attr({ .attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, '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') .addClass('cvat_canvas_shape_drawing')
.attr({ .attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
'fill-opacity': this.configuration.creationOpacity, 'fill-opacity': this.selectedShapeOpacity,
stroke: this.outlinedBorders,
}); });
const initialPoint: { 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]); const translated = translateToSVG(this.canvas.node as any as SVGSVGElement, [e.clientX, e.clientY]);
[initialPoint.x, initialPoint.y] = translated; [initialPoint.x, initialPoint.y] = translated;
} else { } else {
const points = this.getFinalEllipseCoordinates(readPointsFromShape(this.drawInstance), false); this.drawInstance.fire('drawstop');
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,
);
}
} }
}); });
@ -472,6 +467,25 @@ export class DrawHandlerImpl implements DrawHandler {
this.shapeSizeElement.update(this.drawInstance); 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 { private drawBoxBy4Points(): void {
@ -612,7 +626,8 @@ export class DrawHandlerImpl implements DrawHandler {
.addClass('cvat_canvas_shape_drawing') .addClass('cvat_canvas_shape_drawing')
.attr({ .attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
'fill-opacity': this.configuration.creationOpacity, 'fill-opacity': this.selectedShapeOpacity,
stroke: this.outlinedBorders,
}); });
this.drawPolyshape(); this.drawPolyshape();
@ -628,6 +643,7 @@ export class DrawHandlerImpl implements DrawHandler {
.attr({ .attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
'fill-opacity': 0, 'fill-opacity': 0,
stroke: this.outlinedBorders,
}); });
this.drawPolyshape(); this.drawPolyshape();
@ -651,6 +667,7 @@ export class DrawHandlerImpl implements DrawHandler {
.addClass('cvat_canvas_shape_drawing') .addClass('cvat_canvas_shape_drawing')
.attr({ .attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
stroke: this.outlinedBorders,
}); });
this.drawPolyshape(); this.drawPolyshape();
} }
@ -681,7 +698,131 @@ export class DrawHandlerImpl implements DrawHandler {
.addClass('cvat_canvas_shape_drawing') .addClass('cvat_canvas_shape_drawing')
.attr({ .attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, '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 // Common settings for rectangle and polyshapes
private pasteShape(): void { private pasteShape(): void {
function moveShape(shape: SVG.Shape, x: number, y: number): void { const moveShape = (shape: SVG.Shape, x: number, y: number): void => {
const bbox = shape.bbox();
const { rotation } = shape.transform(); const { rotation } = shape.transform();
shape.untransform(); shape.untransform();
shape.move(x - bbox.width / 2, y - bbox.height / 2); shape.center(x, y);
shape.rotate(rotation); shape.rotate(rotation);
} };
const { x: initialX, y: initialY } = this.cursorPosition; const { x: initialX, y: initialY } = this.cursorPosition;
moveShape(this.drawInstance, initialX, initialY); moveShape(this.drawInstance, initialX, initialY);
this.canvas.on('mousemove.draw', (): void => { 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); moveShape(this.drawInstance, x, y);
}); });
} }
@ -741,11 +881,12 @@ export class DrawHandlerImpl implements DrawHandler {
private pasteBox(box: BBox, rotation: number): void { private pasteBox(box: BBox, rotation: number): void {
this.drawInstance = (this.canvas as any) this.drawInstance = (this.canvas as any)
.rect(box.width, box.height) .rect(box.width, box.height)
.move(box.x, box.y) .center(box.x, box.y)
.addClass('cvat_canvas_shape_drawing') .addClass('cvat_canvas_shape_drawing')
.attr({ .attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
'fill-opacity': this.configuration.creationOpacity, 'fill-opacity': this.selectedShapeOpacity,
stroke: this.outlinedBorders,
}).rotate(rotation); }).rotate(rotation);
this.pasteShape(); this.pasteShape();
@ -782,7 +923,8 @@ export class DrawHandlerImpl implements DrawHandler {
.addClass('cvat_canvas_shape_drawing') .addClass('cvat_canvas_shape_drawing')
.attr({ .attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
'fill-opacity': this.configuration.creationOpacity, 'fill-opacity': this.selectedShapeOpacity,
stroke: this.outlinedBorders,
}).rotate(rotation); }).rotate(rotation);
this.pasteShape(); this.pasteShape();
@ -820,7 +962,8 @@ export class DrawHandlerImpl implements DrawHandler {
.addClass('cvat_canvas_shape_drawing') .addClass('cvat_canvas_shape_drawing')
.attr({ .attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
'fill-opacity': this.configuration.creationOpacity, 'fill-opacity': this.selectedShapeOpacity,
stroke: this.outlinedBorders,
}); });
this.pasteShape(); this.pasteShape();
this.pastePolyshape(); this.pastePolyshape();
@ -832,6 +975,7 @@ export class DrawHandlerImpl implements DrawHandler {
.addClass('cvat_canvas_shape_drawing') .addClass('cvat_canvas_shape_drawing')
.attr({ .attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
stroke: this.outlinedBorders,
}); });
this.pasteShape(); this.pasteShape();
this.pastePolyshape(); this.pastePolyshape();
@ -843,26 +987,107 @@ export class DrawHandlerImpl implements DrawHandler {
.addClass('cvat_canvas_shape_drawing') .addClass('cvat_canvas_shape_drawing')
.attr({ .attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
'face-stroke': 'black', 'face-stroke': this.outlinedBorders,
'fill-opacity': this.configuration.creationOpacity, 'fill-opacity': this.selectedShapeOpacity,
stroke: this.outlinedBorders,
}); });
this.pasteShape(); this.pasteShape();
this.pastePolyshape(); 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 { 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(); const bbox = shape.bbox();
shape.move(x - bbox.width / 2, y - bbox.height / 2); shape.move(x - bbox.width / 2, y - bbox.height / 2);
const points = shape.attr('points').split(' '); 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 => { group.children().forEach((child: SVG.Element, idx: number): void => {
const [px, py] = points[idx].split(','); const [px, py] = points[idx].split(',');
child.move(px - radius / 2, py - radius / 2); child.move(px - radius / 2, py - radius / 2);
}); });
} };
const { x: initialX, y: initialY } = this.cursorPosition; const { x: initialX, y: initialY } = this.cursorPosition;
this.pointsGroup = this.canvas.group(); this.pointsGroup = this.canvas.group();
@ -873,7 +1098,7 @@ export class DrawHandlerImpl implements DrawHandler {
let numOfPoints = initialPoints.split(' ').length; let numOfPoints = initialPoints.split(' ').length;
while (numOfPoints) { while (numOfPoints) {
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; const stroke = consts.POINTS_STROKE_WIDTH / this.geometry.scale;
this.pointsGroup.circle().fill('white').stroke('black').attr({ this.pointsGroup.circle().fill('white').stroke('black').attr({
r: radius, r: radius,
@ -919,10 +1144,7 @@ export class DrawHandlerImpl implements DrawHandler {
if (this.drawData.initialState) { if (this.drawData.initialState) {
const { offset } = this.geometry; const { offset } = this.geometry;
if (this.drawData.shapeType === 'rectangle') { if (this.drawData.shapeType === 'rectangle') {
const [xtl, ytl, xbr, ybr] = this.drawData.initialState.points.map( const [xtl, ytl, xbr, ybr] = translateToCanvas(offset, this.drawData.initialState.points);
(coord: number): number => coord + offset,
);
this.pasteBox({ this.pasteBox({
x: xtl, x: xtl,
y: ytl, y: ytl,
@ -930,13 +1152,15 @@ export class DrawHandlerImpl implements DrawHandler {
height: ybr - ytl, height: ybr - ytl,
}, this.drawData.initialState.rotation); }, this.drawData.initialState.rotation);
} else if (this.drawData.shapeType === 'ellipse') { } else if (this.drawData.shapeType === 'ellipse') {
const [cx, cy, rightX, topY] = this.drawData.initialState.points.map( const [cx, cy, rightX, topY] = translateToCanvas(offset, this.drawData.initialState.points);
(coord: number): number => coord + offset,
);
this.pasteEllipse([cx, cy, rightX - cx, cy - topY], this.drawData.initialState.rotation); 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 { } 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); const stringifiedPoints = stringifyPoints(points);
if (this.drawData.shapeType === 'polygon') { if (this.drawData.shapeType === 'polygon') {
@ -975,6 +1199,8 @@ export class DrawHandlerImpl implements DrawHandler {
this.drawCuboid(); this.drawCuboid();
this.shapeSizeElement = displayShapeSize(this.canvas, this.text); this.shapeSizeElement = displayShapeSize(this.canvas, this.text);
} }
} else if (this.drawData.shapeType === 'skeleton') {
this.drawSkeleton();
} }
if (this.drawData.shapeType !== 'ellipse') { if (this.drawData.shapeType !== 'ellipse') {
@ -995,6 +1221,9 @@ export class DrawHandlerImpl implements DrawHandler {
configuration: Configuration, configuration: Configuration,
) { ) {
this.autoborderHandler = autoborderHandler; this.autoborderHandler = autoborderHandler;
this.controlPointsSize = configuration.controlPointsSize;
this.selectedShapeOpacity = configuration.selectedShapeOpacity;
this.outlinedBorders = configuration.outlinedBorders || 'black';
this.autobordersEnabled = false; this.autobordersEnabled = false;
this.startTimestamp = Date.now(); this.startTimestamp = Date.now();
this.onDrawDone = onDrawDone; this.onDrawDone = onDrawDone;
@ -1004,7 +1233,6 @@ export class DrawHandlerImpl implements DrawHandler {
this.canceled = false; this.canceled = false;
this.drawData = null; this.drawData = null;
this.geometry = geometry; this.geometry = geometry;
this.configuration = configuration;
this.crosshair = new Crosshair(); this.crosshair = new Crosshair();
this.drawInstance = null; this.drawInstance = null;
this.pointsGroup = null; this.pointsGroup = null;
@ -1023,7 +1251,9 @@ export class DrawHandlerImpl implements DrawHandler {
} }
public configurate(configuration: Configuration): void { 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 && const isFillableRect = this.drawData &&
this.drawData.shapeType === 'rectangle' && this.drawData.shapeType === 'rectangle' &&
@ -1034,17 +1264,23 @@ export class DrawHandlerImpl implements DrawHandler {
const isFilalblePolygon = this.drawData && this.drawData.shapeType === 'polygon'; const isFilalblePolygon = this.drawData && this.drawData.shapeType === 'polygon';
if (this.drawInstance && (isFillableRect || isFillableCuboid || isFilalblePolygon)) { if (this.drawInstance && (isFillableRect || isFillableCuboid || isFilalblePolygon)) {
this.drawInstance.fill({ opacity: configuration.creationOpacity }); this.drawInstance.fill({ opacity: configuration.selectedShapeOpacity });
} }
if (typeof configuration.autoborders === 'boolean') { if (this.drawInstance && this.drawInstance.attr('stroke')) {
this.autobordersEnabled = configuration.autoborders; this.drawInstance.attr('stroke', this.outlinedBorders);
if (this.drawInstance && !this.drawData.initialState) { }
if (this.autobordersEnabled) {
this.autoborderHandler.autoborder(true, this.drawInstance, this.drawData.redraw); if (this.pointsGroup && this.pointsGroup.attr('stroke')) {
} else { this.pointsGroup.attr('stroke', this.outlinedBorders);
this.autoborderHandler.autoborder(false); }
}
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) { if (this.pointsGroup) {
this.pointsGroup.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
});
for (const point of this.pointsGroup.children()) { for (const point of this.pointsGroup.children()) {
point.attr({ point.attr({
'stroke-width': consts.POINTS_STROKE_WIDTH / geometry.scale, '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) { for (const point of (paintHandler as any).set.members) {
point.attr('stroke-width', `${consts.POINTS_STROKE_WIDTH / geometry.scale}`); 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 editedShape: SVG.Shape;
private editLine: SVG.PolyLine; private editLine: SVG.PolyLine;
private clones: SVG.Polygon[]; private clones: SVG.Polygon[];
private controlPointsSize: number;
private autobordersEnabled: boolean; private autobordersEnabled: boolean;
private intelligentCutEnabled: boolean; private intelligentCutEnabled: boolean;
private outlinedBorders: string;
private setupTrailingPoint(circle: SVG.Circle): void { private setupTrailingPoint(circle: SVG.Circle): void {
const head = this.editedShape.attr('points').split(' ').slice(0, this.editData.pointID).join(' '); 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) (this.editLine as any)
.addClass('cvat_canvas_shape_drawing') .addClass('cvat_canvas_shape_drawing')
.style({ .style({
'pointer-events': 'none', 'pointer-events': 'none',
'fill-opacity': 0, 'fill-opacity': 0,
stroke: strokeColor,
}) })
.attr({ .attr({
'data-origin-client-id': this.editData.state.clientID, 'data-origin-client-id': this.editData.state.clientID,
stroke: this.editedShape.attr('stroke'),
}) })
.on('drawstart drawpoint', (e: CustomEvent): void => { .on('drawstart drawpoint', (e: CustomEvent): void => {
this.transform(this.geometry); this.transform(this.geometry);
@ -299,7 +300,7 @@ export class EditHandlerImpl implements EditHandler {
if (enabled) { if (enabled) {
(this.editedShape as any).selectize(true, { (this.editedShape as any).selectize(true, {
deepSelect: true, deepSelect: true,
pointSize: (2 * consts.BASE_POINT_SIZE) / getGeometry().scale, pointSize: (2 * this.controlPointsSize) / getGeometry().scale,
rotationPoint: false, rotationPoint: false,
pointType(cx: number, cy: number): SVG.Circle { pointType(cx: number, cy: number): SVG.Circle {
const circle: SVG.Circle = this.nested const circle: SVG.Circle = this.nested
@ -365,7 +366,9 @@ export class EditHandlerImpl implements EditHandler {
} }
private initEditing(): void { 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.setupPoints(true);
this.startEdit(); this.startEdit();
// draw points for this with selected and start editing till another point is clicked // 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.autoborderHandler = autoborderHandler;
this.autobordersEnabled = false; this.autobordersEnabled = false;
this.intelligentCutEnabled = false; this.intelligentCutEnabled = false;
this.controlPointsSize = consts.BASE_POINT_SIZE;
this.outlinedBorders = 'black';
this.onEditDone = onEditDone; this.onEditDone = onEditDone;
this.canvas = canvas; this.canvas = canvas;
this.editData = null; this.editData = null;
@ -416,20 +421,23 @@ export class EditHandlerImpl implements EditHandler {
} }
public configurate(configuration: Configuration): void { public configurate(configuration: Configuration): void {
if (typeof configuration.autoborders === 'boolean') { this.autobordersEnabled = configuration.autoborders;
this.autobordersEnabled = configuration.autoborders; this.outlinedBorders = configuration.outlinedBorders || 'black';
if (this.editLine) {
if (this.autobordersEnabled) { if (this.editedShape) {
this.autoborderHandler.autoborder(true, this.editLine, this.editData.state.clientID); this.editedShape.attr('stroke', this.outlinedBorders);
} else {
this.autoborderHandler.autoborder(false);
}
}
} }
if (typeof configuration.intelligentPolygonCrop === 'boolean') { if (this.editLine) {
this.intelligentCutEnabled = configuration.intelligentPolygonCrop; 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 { public transform(geometry: Geometry): void {
@ -453,7 +461,7 @@ export class EditHandlerImpl implements EditHandler {
for (const point of (paintHandler as any).set.members) { for (const point of (paintHandler as any).set.members) {
point.attr('stroke-width', `${consts.POINTS_STROKE_WIDTH / geometry.scale}`); 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 { export class InteractionHandlerImpl implements InteractionHandler {
private onInteraction: (shapes: InteractionResult[] | null, shapesUpdated?: boolean, isDone?: boolean) => void; private onInteraction: (shapes: InteractionResult[] | null, shapesUpdated?: boolean, isDone?: boolean) => void;
private configuration: Configuration;
private geometry: Geometry; private geometry: Geometry;
private canvas: SVG.Container; private canvas: SVG.Container;
private interactionData: InteractionData; private interactionData: InteractionData;
@ -37,6 +36,8 @@ export class InteractionHandlerImpl implements InteractionHandler {
private intermediateShape: PropType<InteractionData, 'intermediateShape'>; private intermediateShape: PropType<InteractionData, 'intermediateShape'>;
private drawnIntermediateShape: SVG.Shape; private drawnIntermediateShape: SVG.Shape;
private thresholdWasModified: boolean; private thresholdWasModified: boolean;
private controlPointsSize: number;
private selectedShapeOpacity: number;
private prepareResult(): InteractionResult[] { private prepareResult(): InteractionResult[] {
return this.interactionShapes.map( return this.interactionShapes.map(
@ -111,7 +112,7 @@ export class InteractionHandlerImpl implements InteractionHandler {
if (!this.isWithinThreshold(cx, cy)) return; if (!this.isWithinThreshold(cx, cy)) return;
this.currentInteractionShape = this.canvas this.currentInteractionShape = this.canvas
.circle((consts.BASE_POINT_SIZE * 2) / this.geometry.scale) .circle((this.controlPointsSize * 2) / this.geometry.scale)
.center(cx, cy) .center(cx, cy)
.fill('white') .fill('white')
.stroke(e.button === 0 ? 'green' : 'red') .stroke(e.button === 0 ? 'green' : 'red')
@ -137,7 +138,7 @@ export class InteractionHandlerImpl implements InteractionHandler {
self.addClass('cvat_canvas_removable_interaction_point'); self.addClass('cvat_canvas_removable_interaction_point');
self.attr({ self.attr({
'stroke-width': consts.POINTS_SELECTED_STROKE_WIDTH / this.geometry.scale, '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 => { self.on('mousedown', (_e: MouseEvent): void => {
@ -162,7 +163,7 @@ export class InteractionHandlerImpl implements InteractionHandler {
self.removeClass('cvat_canvas_removable_interaction_point'); self.removeClass('cvat_canvas_removable_interaction_point');
self.attr({ self.attr({
'stroke-width': consts.POINTS_STROKE_WIDTH / this.geometry.scale, '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'); self.off('mousedown');
@ -205,7 +206,7 @@ export class InteractionHandlerImpl implements InteractionHandler {
.attr({ .attr({
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, '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 { private initInteraction(): void {
@ -300,7 +301,7 @@ export class InteractionHandlerImpl implements InteractionHandler {
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
stroke: erroredShape ? 'red' : 'black', stroke: erroredShape ? 'red' : 'black',
}) })
.fill({ opacity: this.configuration.creationOpacity, color: 'white' }) .fill({ opacity: this.selectedShapeOpacity, color: 'white' })
.addClass('cvat_canvas_interact_intermediate_shape'); .addClass('cvat_canvas_interact_intermediate_shape');
this.selectize(true, this.drawnIntermediateShape, erroredShape); this.selectize(true, this.drawnIntermediateShape, erroredShape);
} else { } else {
@ -317,7 +318,7 @@ export class InteractionHandlerImpl implements InteractionHandler {
if (value) { if (value) {
(shape as any).selectize(value, { (shape as any).selectize(value, {
deepSelect: true, deepSelect: true,
pointSize: consts.BASE_POINT_SIZE / self.geometry.scale, pointSize: this.controlPointsSize / self.geometry.scale,
rotationPoint: false, rotationPoint: false,
classPoints: 'cvat_canvas_interact_intermediate_shape_point', classPoints: 'cvat_canvas_interact_intermediate_shape_point',
pointType(cx: number, cy: number): SVG.Circle { 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); onInteraction(shapes, shapesUpdated, isDone, this.threshold ? this.thresholdRectSize / 2 : null);
}; };
this.canvas = canvas; this.canvas = canvas;
this.configuration = configuration;
this.geometry = geometry; this.geometry = geometry;
this.shapesWereUpdated = false; this.shapesWereUpdated = false;
this.interactionShapes = []; this.interactionShapes = [];
@ -410,6 +410,8 @@ export class InteractionHandlerImpl implements InteractionHandler {
this.thresholdRectSize = 300; this.thresholdRectSize = 300;
this.intermediateShape = null; this.intermediateShape = null;
this.drawnIntermediateShape = null; this.drawnIntermediateShape = null;
this.controlPointsSize = configuration.controlPointsSize;
this.selectedShapeOpacity = configuration.selectedShapeOpacity;
this.cursorPosition = { this.cursorPosition = {
x: 0, x: 0,
y: 0, y: 0,
@ -477,10 +479,10 @@ export class InteractionHandlerImpl implements InteractionHandler {
for (const shape of shapesToBeScaled) { for (const shape of shapesToBeScaled) {
if (shape.type === 'circle') { if (shape.type === 'circle') {
if (shape.hasClass('cvat_canvas_removable_interaction_point')) { 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); shape.attr('stroke-width', consts.POINTS_SELECTED_STROKE_WIDTH / this.geometry.scale);
} else { } 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); shape.attr('stroke-width', consts.POINTS_STROKE_WIDTH / this.geometry.scale);
} }
} else { } else {
@ -490,7 +492,7 @@ export class InteractionHandlerImpl implements InteractionHandler {
for (const element of window.document.getElementsByClassName('cvat_canvas_interact_intermediate_shape_point')) { 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('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) { if (this.drawnIntermediateShape) {
@ -520,21 +522,23 @@ export class InteractionHandlerImpl implements InteractionHandler {
} }
public configurate(configuration: Configuration): void { public configurate(configuration: Configuration): void {
this.configuration = configuration; this.controlPointsSize = configuration.controlPointsSize;
this.selectedShapeOpacity = configuration.selectedShapeOpacity;
if (this.drawnIntermediateShape) { if (this.drawnIntermediateShape) {
this.drawnIntermediateShape.fill({ this.drawnIntermediateShape.fill({
opacity: configuration.creationOpacity, opacity: configuration.selectedShapeOpacity,
}); });
} }
// when interactRectangle // when interactRectangle
if (this.currentInteractionShape && this.currentInteractionShape.type === 'rect') { if (this.currentInteractionShape && this.currentInteractionShape.type === 'rect') {
this.currentInteractionShape.fill({ opacity: configuration.creationOpacity }); this.currentInteractionShape.fill({ opacity: configuration.selectedShapeOpacity });
} }
// when interactPoints with startwithbbox // when interactPoints with startwithbbox
if (this.interactionShapes[0] && this.interactionShapes[0].type === 'rect') { 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; updated: number;
frame: number; frame: number;
label: any; label: any;
group: any;
color: string;
elements: DrawnState[] | null;
} }
// Translate point array from the canvas coordinate system // Translate point array from the canvas coordinate system
@ -192,11 +195,13 @@ export function readPointsFromShape(shape: SVG.Shape): number[] {
let points = null; let points = null;
if (shape.type === 'ellipse') { if (shape.type === 'ellipse') {
const [rx, ry] = [+shape.attr('rx'), +shape.attr('ry')]; 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}`; points = `${cx},${cy} ${cx + rx},${cy - ry}`;
} else if (shape.type === 'rect') { } else if (shape.type === 'rect') {
points = `${shape.attr('x')},${shape.attr('y')} ` + points = `${shape.attr('x')},${shape.attr('y')} ` +
`${shape.attr('x') + shape.attr('width')},${shape.attr('y') + shape.attr('height')}`; `${shape.attr('x') + shape.attr('width')},${shape.attr('y') + shape.attr('height')}`;
} else if (shape.type === 'circle') {
points = `${shape.cx()},${shape.cy()}`;
} else { } else {
points = shape.attr('points'); points = shape.attr('points');
} }
@ -239,4 +244,121 @@ export function translateFromCanvas(offset: number, points: number[]): number[]
return points.map((coord: number): number => coord - offset); 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]; 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), (_state: any): boolean => _state.clientID === Number(intersects[0].object.name),
); );
if (item.length !== 0) { if (item.length !== 0) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
this.model.data.groupData.grouped = this.model.data.groupData.grouped.filter( this.model.data.groupData.grouped = this.model.data.groupData.grouped.filter(
(_state: any): boolean => _state.clientID !== Number(intersects[0].object.name), (_state: any): boolean => _state.clientID !== Number(intersects[0].object.name),

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

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

@ -2,188 +2,209 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
(() => { interface RawLoaderData {
/** name: string;
* Class representing an annotation loader ext: string;
* @memberof module:API.cvat.classes version: string;
* @hideconstructor enabled: boolean;
*/ dimension: '2d' | '3d';
class Loader { }
constructor(initialData) {
const data = {
name: initialData.name,
format: initialData.ext,
version: initialData.version,
enabled: initialData.enabled,
dimension: initialData.dimension,
};
Object.defineProperties(this, { /**
name: { * Class representing an annotation loader
/** * @memberof module:API.cvat.classes
* @name name * @hideconstructor
* @type {string} */
* @memberof module:API.cvat.classes.Loader export class Loader {
* @readonly public name: string;
* @instance public format: string;
*/ public version: string;
get: () => data.name, public enabled: boolean;
}, public dimension: '2d' | '3d';
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,
},
});
}
}
/** constructor(initialData: RawLoaderData) {
* Class representing an annotation dumper const data = {
* @memberof module:API.cvat.classes name: initialData.name,
* @hideconstructor format: initialData.ext,
*/ version: initialData.version,
class Dumper { enabled: initialData.enabled,
constructor(initialData) { dimension: initialData.dimension,
const data = { };
name: initialData.name,
format: initialData.ext,
version: initialData.version,
enabled: initialData.enabled,
dimension: initialData.dimension,
};
Object.defineProperties(this, { Object.defineProperties(this, {
name: { name: {
/** /**
* @name name * @name name
* @type {string} * @type {string}
* @memberof module:API.cvat.classes.Dumper * @memberof module:API.cvat.classes.Loader
* @readonly * @readonly
* @instance * @instance
*/ */
get: () => data.name, get: () => data.name,
}, },
format: { format: {
/** /**
* @name format * @name format
* @type {string} * @type {string}
* @memberof module:API.cvat.classes.Dumper * @memberof module:API.cvat.classes.Loader
* @readonly * @readonly
* @instance * @instance
*/ */
get: () => data.format, get: () => data.format,
}, },
version: { version: {
/** /**
* @name version * @name version
* @type {string} * @type {string}
* @memberof module:API.cvat.classes.Dumper * @memberof module:API.cvat.classes.Loader
* @readonly * @readonly
* @instance * @instance
*/ */
get: () => data.version, get: () => data.version,
}, },
enabled: { enabled: {
/** /**
* @name enabled * @name enabled
* @type {string} * @type {boolean}
* @memberof module:API.cvat.classes.Loader * @memberof module:API.cvat.classes.Loader
* @readonly * @readonly
* @instance * @instance
*/ */
get: () => data.enabled, get: () => data.enabled,
}, },
dimension: { dimension: {
/** /**
* @name dimension * @name dimension
* @type {string} * @type {module:API.cvat.enums.DimensionType}
* @memberof module:API.cvat.enums.DimensionType * @memberof module:API.cvat.classes.Loader
* @readonly * @readonly
* @instance * @instance
*/ */
get: () => data.dimension, get: () => data.dimension,
}, },
}); });
}
} }
}
type RawDumperData = RawLoaderData;
/** /**
* Class representing an annotation format * Class representing an annotation dumper
* @memberof module:API.cvat.classes * @memberof module:API.cvat.classes
* @hideconstructor * @hideconstructor
*/ */
class AnnotationFormats { export class Dumper {
constructor(initialData) { public name: string;
const data = { public format: string;
exporters: initialData.exporters.map((el) => new Dumper(el)), public version: string;
importers: initialData.importers.map((el) => new Loader(el)), public enabled: boolean;
}; public dimension: '2d' | '3d';
// Now all fields are readonly constructor(initialData: RawDumperData) {
Object.defineProperties(this, { const data = {
loaders: { name: initialData.name,
/** format: initialData.ext,
* @name loaders version: initialData.version,
* @type {module:API.cvat.classes.Loader[]} enabled: initialData.enabled,
* @memberof module:API.cvat.classes.AnnotationFormats dimension: initialData.dimension,
* @readonly };
* @instance
*/ Object.defineProperties(this, {
get: () => [...data.importers], name: {
}, /**
dumpers: { * @name name
/** * @type {string}
* @name dumpers * @memberof module:API.cvat.classes.Dumper
* @type {module:API.cvat.classes.Dumper[]} * @readonly
* @memberof module:API.cvat.classes.AnnotationFormats * @instance
* @readonly */
* @instance get: () => data.name,
*/ },
get: () => [...data.exporters], 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, * Class representing an annotation format
Loader, * @memberof module:API.cvat.classes
Dumper, * @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 { const {
RectangleShape, shapeFactory,
PolygonShape, trackFactory,
PolylineShape,
PointsShape,
EllipseShape,
CuboidShape,
RectangleTrack,
PolygonTrack,
PolylineTrack,
PointsTrack,
EllipseTrack,
CuboidTrack,
Track, Track,
Shape, Shape,
Tag, Tag,
objectStateFactory,
} = require('./annotations-objects'); } = require('./annotations-objects');
const AnnotationsFilter = require('./annotations-filter'); const AnnotationsFilter = require('./annotations-filter').default;
const { checkObjectType } = require('./common'); const { checkObjectType } = require('./common');
const Statistics = require('./statistics'); const Statistics = require('./statistics');
const { Label } = require('./labels'); const { Label } = require('./labels');
const { DataError, ArgumentError, ScriptingError } = require('./exceptions'); const { ArgumentError, ScriptingError } = require('./exceptions');
const ObjectState = require('./object-state').default;
const { const {
HistoryActions, ObjectShape, ObjectType, colors, HistoryActions, ShapeType, ObjectType, colors, Source,
} = require('./enums'); } = 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 { class Collection {
constructor(data) { constructor(data) {
@ -107,6 +29,10 @@
this.labels = data.labels.reduce((labelAccumulator, label) => { this.labels = data.labels.reduce((labelAccumulator, label) => {
labelAccumulator[label.id] = label; labelAccumulator[label.id] = label;
(label?.structure?.sublabels || []).forEach((sublabel) => {
labelAccumulator[sublabel.id] = sublabel;
});
return labelAccumulator; return labelAccumulator;
}, {}); }, {});
@ -126,6 +52,7 @@
groups: this.groups, groups: this.groups,
frameMeta: this.frameMeta, frameMeta: this.frameMeta,
history: this.history, history: this.history,
nextClientID: () => ++this.count,
groupColors: {}, groupColors: {},
}; };
} }
@ -202,10 +129,7 @@
const tags = this.tags[frame] || []; const tags = this.tags[frame] || [];
const objects = [].concat(tracks, shapes, tags); const objects = [].concat(tracks, shapes, tags);
const visible = { const visible = [];
models: [],
data: [],
};
for (const object of objects) { for (const object of objects) {
if (object.removed) { if (object.removed) {
@ -213,21 +137,19 @@
} }
const stateData = object.get(frame); const stateData = object.get(frame);
if (!allTracks && stateData.outside && !stateData.keyframe) { if (stateData.outside && !stateData.keyframe && !allTracks && object instanceof Track) {
continue; continue;
} }
visible.models.push(object); visible.push(stateData);
visible.data.push(stateData);
} }
const objectStates = []; 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)) { if (!filters.length || filtered.includes(stateData.clientID)) {
const model = visible.models[idx]; const objectState = new ObjectState(stateData);
const objectState = objectStateFactory.call(model, frame, stateData);
objectStates.push(objectState); objectStates.push(objectState);
} }
}); });
@ -255,7 +177,7 @@
throw new ArgumentError(`Unknown label for the task: ${label.id}`); 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}"`); throw new ArgumentError(`Got unknown shapeType "${shapeType}"`);
} }
@ -288,10 +210,14 @@
keyframes[object.frame] = { keyframes[object.frame] = {
type: shapeType, type: shapeType,
frame: object.frame, 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, occluded: object.occluded,
rotation: object.rotation, rotation: object.rotation,
zOrder: object.zOrder, z_order: object.zOrder,
outside: false, outside: false,
attributes: Object.keys(object.attributes).reduce((accumulator, attrID) => { attributes: Object.keys(object.attributes).reduce((accumulator, attrID) => {
// We save only mutable attributes inside a keyframe // 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] = JSON.parse(JSON.stringify(keyframes[object.frame]));
keyframes[object.frame + 1].outside = true; keyframes[object.frame + 1].outside = true;
keyframes[object.frame + 1].frame++; 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) { } else if (object instanceof Track) {
// If this object is track, iterate through all its // If this object is track, iterate through all its
// keyframes and push copies to new keyframes // keyframes and push copies to new keyframes
const attributes = {}; // id:value const attributes = {}; // id:value
for (const keyframe of Object.keys(object.shapes)) { const trackShapes = object.shapes;
const shape = object.shapes[keyframe]; 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 // Frame already saved and it is not outside
if (keyframe in keyframes && !keyframes[keyframe].outside) { if (keyframe in keyframes && !keyframes[keyframe].outside) {
// This shape is outside and non-outside shape already exists // This shape is outside and non-outside shape already exists
@ -341,11 +278,16 @@
keyframes[keyframe] = { keyframes[keyframe] = {
type: shapeType, type: shapeType,
frame: +keyframe, 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, rotation: shape.rotation,
occluded: shape.occluded, occluded: shape.occluded,
outside: shape.outside, outside: shape.outside,
zOrder: shape.zOrder, z_order: shape.zOrder,
attributes: updatedAttributes ? Object.keys(attributes).reduce((accumulator, attrID) => { attributes: updatedAttributes ? Object.keys(attributes).reduce((accumulator, attrID) => {
accumulator.push({ accumulator.push({
spec_id: +attrID, spec_id: +attrID,
@ -451,13 +393,30 @@
const exported = object.toJSON(); const exported = object.toJSON();
const position = { const position = {
type: objectState.shapeType, 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, rotation: objectState.rotation,
occluded: objectState.occluded, occluded: objectState.occluded,
outside: objectState.outside, outside: objectState.outside,
zOrder: objectState.zOrder, z_order: objectState.zOrder,
attributes: Object.keys(objectState.attributes).reduce((accumulator, attrID) => { attributes: Object.keys(objectState.attributes).reduce((accumulator, attrID) => {
if (!labelAttributes[attrID].mutable) { if (labelAttributes[attrID].mutable) {
accumulator.push({ accumulator.push({
spec_id: +attrID, spec_id: +attrID,
value: objectState.attributes[attrID], value: objectState.attributes[attrID],
@ -475,14 +434,19 @@
label_id: exported.label_id, label_id: exported.label_id,
attributes: exported.attributes, attributes: exported.attributes,
shapes: [], shapes: [],
source: Source.MANUAL,
}; };
const next = JSON.parse(JSON.stringify(prev)); const next = JSON.parse(JSON.stringify(prev));
next.frame = frame; next.frame = frame;
next.shapes.push(JSON.parse(JSON.stringify(position))); next.shapes.push(JSON.parse(JSON.stringify(position)));
exported.shapes.map((shape) => { exported.shapes.map((shape) => {
delete shape.id; delete shape.id;
(shape.elements || []).forEach((element) => {
delete element.id;
});
if (shape.frame < frame) { if (shape.frame < frame) {
prev.shapes.push(JSON.parse(JSON.stringify(shape))); prev.shapes.push(JSON.parse(JSON.stringify(shape)));
} else if (shape.frame > frame) { } else if (shape.frame > frame) {
@ -499,6 +463,9 @@
prev.shapes[prev.shapes.length - 2].frame -= 1; prev.shapes[prev.shapes.length - 2].frame -= 1;
} }
prev.shapes[prev.shapes.length - 1].outside = true; prev.shapes[prev.shapes.length - 1].outside = true;
(prev.shapes[prev.shapes.length - 1].elements || []).forEach((el) => {
el.outside = true;
});
let clientID = ++this.count; let clientID = ++this.count;
const prevTrack = trackFactory(prev, clientID, this.injection); const prevTrack = trackFactory(prev, clientID, this.injection);
@ -546,6 +513,7 @@
const undoGroups = objectsForGroup.map((object) => object.group); const undoGroups = objectsForGroup.map((object) => object.group);
for (const object of objectsForGroup) { for (const object of objectsForGroup) {
object.group = groupIdx; object.group = groupIdx;
object.updated = Date.now();
} }
const redoGroups = objectsForGroup.map((object) => object.group); const redoGroups = objectsForGroup.map((object) => object.group);
@ -554,11 +522,13 @@
() => { () => {
objectsForGroup.forEach((object, idx) => { objectsForGroup.forEach((object, idx) => {
object.group = undoGroups[idx]; object.group = undoGroups[idx];
object.updated = Date.now();
}); });
}, },
() => { () => {
objectsForGroup.forEach((object, idx) => { objectsForGroup.forEach((object, idx) => {
object.group = redoGroups[idx]; object.group = redoGroups[idx];
object.updated = Date.now();
}); });
}, },
objectsForGroup.map((object) => object.clientID), objectsForGroup.map((object) => object.clientID),
@ -579,8 +549,17 @@
tracks.forEach((track) => { tracks.forEach((track) => {
if (track.frame <= endframe) { if (track.frame <= endframe) {
if (delTrackKeyframesOnly) { if (delTrackKeyframesOnly) {
for (const keyframe in track.shapes) { for (const keyframe of Object.keys(track.shapes)) {
if (keyframe >= startframe && keyframe <= endframe) { delete track.shapes[keyframe]; } 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) { } else if (track.frame >= startframe) {
const index = tracks.indexOf(track); const index = tracks.indexOf(track);
@ -593,7 +572,7 @@
this.shapes = {}; this.shapes = {};
this.tags = {}; this.tags = {};
this.tracks = []; this.tracks = [];
this.objects = {}; // by id this.objects = {};
this.count = 0; this.count = 0;
this.flush = true; this.flush = true;
@ -606,42 +585,74 @@
statistics() { statistics() {
const labels = {}; const labels = {};
const skeleton = { const shapes = ['rectangle', 'polygon', 'polyline', 'points', 'ellipse', 'cuboid', 'skeleton'];
rectangle: { const body = {
shape: 0, ...(shapes.reduce((acc, val) => ({
track: 0, ...acc,
}, [val]: { shape: 0, track: 0 },
polygon: { }), {})),
shape: 0,
track: 0, tag: 0,
},
polyline: {
shape: 0,
track: 0,
},
points: {
shape: 0,
track: 0,
},
ellipse: {
shape: 0,
track: 0,
},
cuboid: {
shape: 0,
track: 0,
},
tags: 0,
manually: 0, manually: 0,
interpolated: 0, interpolated: 0,
total: 0, total: 0,
}; };
const total = JSON.parse(JSON.stringify(skeleton)); const sep = '{{cvat.skeleton.lbl.sep}}';
for (const label of Object.values(this.labels)) { const fillBody = (spec, prefix = ''): void => {
const { name } = label; const pref = prefix ? `${prefix}${sep}` : '';
labels[name] = JSON.parse(JSON.stringify(skeleton)); 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)) { for (const object of Object.values(this.objects)) {
if (object.removed) { if (object.removed) {
@ -659,59 +670,37 @@
throw new ScriptingError(`Unexpected object type: "${objectType}"`); throw new ScriptingError(`Unexpected object type: "${objectType}"`);
} }
const label = object.label.name; const { name: label } = object.label;
if (objectType === 'tag') { if (objectType === 'tag') {
labels[label].tags++; labels[label].tag++;
labels[label].manually++; labels[label].manually++;
labels[label].total++; labels[label].total++;
} else if (objectType === 'track') {
scanTrack(object);
} else { } else {
const { shapeType } = object; const { shapeType } = object;
labels[label][shapeType][objectType]++; labels[label][shapeType].shape++;
labels[label].manually++;
if (objectType === 'track') { labels[label].total++;
const keyframes = Object.keys(object.shapes) if (shapeType === ShapeType.SKELETON) {
.sort((a, b) => +a - +b) object.elements.forEach((element) => {
.map((el) => +el); const combinedName = [label, element.label.name].join(sep);
labels[combinedName][element.shapeType].shape++;
let prevKeyframe = keyframes[0]; labels[combinedName].manually++;
let visible = false; labels[combinedName].total++;
});
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++;
} }
} }
} }
for (const label of Object.keys(labels)) { for (const label of Object.keys(labels)) {
for (const key of Object.keys(labels[label])) { for (const shapeType of Object.keys(labels[label])) {
if (typeof labels[label][key] === 'object') { if (typeof labels[label][shapeType] === 'object') {
for (const objectType of Object.keys(labels[label][key])) { for (const objectType of Object.keys(labels[label][shapeType])) {
total[key][objectType] += labels[label][key][objectType]; total[shapeType][objectType] += labels[label][shapeType][objectType];
} }
} else { } else {
total[key] += labels[label][key]; total[shapeType] += labels[label][shapeType];
} }
} }
} }
@ -744,7 +733,7 @@
for (const state of objectStates) { for (const state of objectStates) {
checkObjectType('object state', state, null, ObjectState); 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 frame', state.frame, 'integer', null);
checkObjectType('state rotation', state.rotation || 0, 'number', null); checkObjectType('state rotation', state.rotation || 0, 'number', null);
checkObjectType('state attributes', state.attributes, null, Object); checkObjectType('state attributes', state.attributes, null, Object);
@ -775,9 +764,9 @@
checkObjectType('point coordinate', coord, 'number', null); checkObjectType('point coordinate', coord, 'number', null);
} }
if (!Object.values(ObjectShape).includes(state.shapeType)) { if (!Object.values(ShapeType).includes(state.shapeType)) {
throw new ArgumentError( 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, type: state.shapeType,
z_order: state.zOrder, z_order: state.zOrder,
source: state.source, 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') { } else if (state.objectType === 'track') {
constructed.tracks.push({ constructed.tracks.push({
@ -807,7 +808,7 @@
{ {
attributes: attributes.filter((attr) => labelAttributes[attr.spec_id].mutable), attributes: attributes.filter((attr) => labelAttributes[attr.spec_id].mutable),
frame: state.frame, frame: state.frame,
occluded: state.occluded || false, occluded: false,
outside: false, outside: false,
points: [...state.points], points: [...state.points],
rotation: state.rotation || 0, rotation: state.rotation || 0,
@ -815,6 +816,33 @@
z_order: state.zOrder, 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 { } else {
throw new ArgumentError( throw new ArgumentError(

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

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

File diff suppressed because it is too large Load Diff

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -5,7 +5,7 @@
import { AnyAction } from 'redux'; import { AnyAction } from 'redux';
import { import {
GridColor, ColorBy, SettingsState, ToolsBlockerState, GridColor, ColorBy, SettingsState, ToolsBlockerState,
} from 'reducers/interfaces'; } from 'reducers';
export enum SettingsActionTypes { export enum SettingsActionTypes {
SWITCH_ROTATE_ALL = 'SWITCH_ROTATE_ALL', SWITCH_ROTATE_ALL = 'SWITCH_ROTATE_ALL',
@ -24,6 +24,7 @@ export enum SettingsActionTypes {
SWITCH_RESET_ZOOM = 'SWITCH_RESET_ZOOM', SWITCH_RESET_ZOOM = 'SWITCH_RESET_ZOOM',
SWITCH_SMOOTH_IMAGE = 'SWITCH_SMOOTH_IMAGE', SWITCH_SMOOTH_IMAGE = 'SWITCH_SMOOTH_IMAGE',
SWITCH_TEXT_FONT_SIZE = 'SWITCH_TEXT_FONT_SIZE', SWITCH_TEXT_FONT_SIZE = 'SWITCH_TEXT_FONT_SIZE',
SWITCH_CONTROL_POINTS_SIZE = 'SWITCH_CONTROL_POINTS_SIZE',
SWITCH_TEXT_POSITION = 'SWITCH_TEXT_POSITION', SWITCH_TEXT_POSITION = 'SWITCH_TEXT_POSITION',
SWITCH_TEXT_CONTENT = 'SWITCH_TEXT_CONTENT', SWITCH_TEXT_CONTENT = 'SWITCH_TEXT_CONTENT',
CHANGE_BRIGHTNESS_LEVEL = 'CHANGE_BRIGHTNESS_LEVEL', 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 { export function switchTextPosition(position: 'auto' | 'center'): AnyAction {
return { return {
type: SettingsActionTypes.SWITCH_TEXT_POSITION, 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 { return {
type: SettingsActionTypes.SWITCH_TEXT_CONTENT, type: SettingsActionTypes.SWITCH_TEXT_CONTENT,
payload: { payload: {
textContent, textContent: textContent.join(','),
}, },
}; };
} }

@ -3,9 +3,9 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import { ActionUnion, createAction, ThunkAction } from 'utils/redux'; 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(); const core = getCore();

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

@ -3,8 +3,8 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import { ActionUnion, createAction, ThunkAction } from 'utils/redux'; import { ActionUnion, createAction, ThunkAction } from 'utils/redux';
import getCore from 'cvat-core-wrapper'; import { getCore } from 'cvat-core-wrapper';
import { UserAgreement } from 'reducers/interfaces'; import { UserAgreement } from 'reducers';
const core = getCore(); 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 { MenuInfo } from 'rc-menu/lib/interface';
import LoadSubmenu from './load-submenu'; import LoadSubmenu from './load-submenu';
import { DimensionType } from '../../reducers/interfaces'; import { DimensionType } from '../../reducers';
interface Props { interface Props {
taskID: number; taskID: number;

@ -8,7 +8,7 @@ import Upload from 'antd/lib/upload';
import Button from 'antd/lib/button'; import Button from 'antd/lib/button';
import Text from 'antd/lib/typography/Text'; import Text from 'antd/lib/typography/Text';
import { UploadOutlined, LoadingOutlined } from '@ant-design/icons'; import { UploadOutlined, LoadingOutlined } from '@ant-design/icons';
import { DimensionType } from '../../reducers/interfaces'; import { DimensionType } from '../../reducers';
interface Props { interface Props {
menuKey: string; 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 FiltersModalComponent from 'components/annotation-page/top-bar/filters-modal';
import StatisticsModalComponent from 'components/annotation-page/top-bar/statistics-modal'; import StatisticsModalComponent from 'components/annotation-page/top-bar/statistics-modal';
import AnnotationTopBarContainer from 'containers/annotation-page/top-bar/top-bar'; 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 { usePrevious } from 'utils/hooks';
import './styles.scss'; import './styles.scss';
import Button from 'antd/lib/button'; 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 ColorPicker from 'components/annotation-page/standard-workspace/objects-side-bar/color-picker';
import { ColorizeIcon } from 'icons'; 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 { collapseAppearance as collapseAppearanceAction } from 'actions/annotation-actions';
import { import {
changeShapesColorBy as changeShapesColorByAction, changeShapesColorBy as changeShapesColorByAction,

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

@ -8,14 +8,16 @@ import Menu from 'antd/lib/menu';
// eslint-disable-next-line import/no-extraneous-dependencies // eslint-disable-next-line import/no-extraneous-dependencies
import { MenuInfo } from 'rc-menu/lib/interface'; 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 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 { rotatePoint } from 'utils/math';
import consts from 'consts'; import consts from 'consts';
interface Props { interface Props {
readonly: boolean; readonly: boolean;
workspace: Workspace; workspace: Workspace;
contextMenuParentID: number | null;
contextMenuClientID: number | null; contextMenuClientID: number | null;
objectStates: any[]; objectStates: any[];
visible: boolean; visible: boolean;
@ -79,6 +81,7 @@ function ReviewContextMenu({
export default function CanvasContextMenu(props: Props): JSX.Element | null { export default function CanvasContextMenu(props: Props): JSX.Element | null {
const { const {
contextMenuClientID, contextMenuClientID,
contextMenuParentID,
objectStates, objectStates,
visible, visible,
left, 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( return ReactDOM.createPortal(
<div className='cvat-canvas-context-menu' style={{ top, left }}> <div className='cvat-canvas-context-menu' style={{ top, left }}>
<ObjectItemContainer <ObjectItemContainer
@ -160,7 +177,6 @@ export default function CanvasContextMenu(props: Props): JSX.Element | null {
key={contextMenuClientID} key={contextMenuClientID}
clientID={contextMenuClientID} clientID={contextMenuClientID}
objectStates={objectStates} objectStates={objectStates}
initialCollapsed
/> />
</div>, </div>,
window.document.body, window.document.body,

@ -8,7 +8,7 @@ import Button from 'antd/lib/button';
import { DeleteOutlined, EnvironmentOutlined } from '@ant-design/icons'; import { DeleteOutlined, EnvironmentOutlined } from '@ant-design/icons';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { CombinedState, ContextMenuType } from 'reducers/interfaces'; import { CombinedState, ContextMenuType } from 'reducers';
import { updateAnnotationsAsync, updateCanvasContextMenu } from 'actions/annotation-actions'; import { updateAnnotationsAsync, updateCanvasContextMenu } from 'actions/annotation-actions';
import CVATTooltip from 'components/common/cvat-tooltip'; 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 GlobalHotKeys, { KeyMap } from 'utils/mousetrap-react';
import { import {
ColorBy, GridColor, ObjectType, ContextMenuType, Workspace, ShapeType, ColorBy, GridColor, ObjectType, ContextMenuType, Workspace, ShapeType,
} from 'reducers/interfaces'; } from 'reducers';
import { LogType } from 'cvat-logger'; import { LogType } from 'cvat-logger';
import { Canvas } from 'cvat-canvas-wrapper'; import { Canvas } from 'cvat-canvas-wrapper';
import { Canvas3d } from 'cvat-canvas3d-wrapper'; import { Canvas3d } from 'cvat-canvas3d-wrapper';
import getCore from 'cvat-core-wrapper'; import { getCore } from 'cvat-core-wrapper';
import consts from 'consts'; import consts from 'consts';
import CVATTooltip from 'components/common/cvat-tooltip'; import CVATTooltip from 'components/common/cvat-tooltip';
import FrameTags from 'components/annotation-page/tag-annotation-workspace/frame-tags'; import FrameTags from 'components/annotation-page/tag-annotation-workspace/frame-tags';
@ -31,6 +31,7 @@ interface Props {
canvasInstance: Canvas | Canvas3d | null; canvasInstance: Canvas | Canvas3d | null;
jobInstance: any; jobInstance: any;
activatedStateID: number | null; activatedStateID: number | null;
activatedElementID: number | null;
activatedAttributeID: number | null; activatedAttributeID: number | null;
annotations: any[]; annotations: any[];
frameData: any; frameData: any;
@ -61,6 +62,7 @@ interface Props {
aamZoomMargin: number; aamZoomMargin: number;
showObjectsTextAlways: boolean; showObjectsTextAlways: boolean;
textFontSize: number; textFontSize: number;
controlPointsSize: number;
textPosition: 'auto' | 'center'; textPosition: 'auto' | 'center';
textContent: string; textContent: string;
showAllInterpolationTracks: boolean; showAllInterpolationTracks: boolean;
@ -85,7 +87,7 @@ interface Props {
onMergeAnnotations(sessionInstance: any, frame: number, states: any[]): void; onMergeAnnotations(sessionInstance: any, frame: number, states: any[]): void;
onGroupAnnotations(sessionInstance: any, frame: number, states: any[]): void; onGroupAnnotations(sessionInstance: any, frame: number, states: any[]): void;
onSplitAnnotations(sessionInstance: any, frame: number, state: 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; onUpdateContextMenu(visible: boolean, left: number, top: number, type: ContextMenuType, pointID?: number): void;
onAddZLayer(): void; onAddZLayer(): void;
onSwitchZLayer(cur: number): void; onSwitchZLayer(cur: number): void;
@ -110,10 +112,15 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
workspace, workspace,
showProjections, showProjections,
selectedOpacity, selectedOpacity,
opacity,
smoothImage, smoothImage,
textFontSize, textFontSize,
controlPointsSize,
textPosition, textPosition,
textContent, textContent,
colorBy,
outlined,
outlineColor,
} = this.props; } = this.props;
const { canvasInstance } = this.props as { canvasInstance: Canvas }; const { canvasInstance } = this.props as { canvasInstance: Canvas };
@ -123,14 +130,18 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
wrapper.appendChild(canvasInstance.html()); wrapper.appendChild(canvasInstance.html());
canvasInstance.configure({ canvasInstance.configure({
smoothImage, forceDisableEditing: workspace === Workspace.REVIEW_WORKSPACE,
autoborders: automaticBordering,
undefinedAttrValue: consts.UNDEFINED_ATTRIBUTE_VALUE, undefinedAttrValue: consts.UNDEFINED_ATTRIBUTE_VALUE,
displayAllText: showObjectsTextAlways, displayAllText: showObjectsTextAlways,
forceDisableEditing: workspace === Workspace.REVIEW_WORKSPACE, autoborders: automaticBordering,
intelligentPolygonCrop,
showProjections, showProjections,
creationOpacity: selectedOpacity, intelligentPolygonCrop,
selectedShapeOpacity: selectedOpacity,
controlPointsSize,
shapeOpacity: opacity,
smoothImage,
colorBy,
outlinedBorders: outlined ? outlineColor || 'black' : false,
textFontSize, textFontSize,
textPosition, textPosition,
textContent, textContent,
@ -143,7 +154,6 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
public componentDidUpdate(prevProps: Props): void { public componentDidUpdate(prevProps: Props): void {
const { const {
opacity, opacity,
colorBy,
selectedOpacity, selectedOpacity,
outlined, outlined,
outlineColor, outlineColor,
@ -167,6 +177,7 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
frameFetching, frameFetching,
showObjectsTextAlways, showObjectsTextAlways,
textFontSize, textFontSize,
controlPointsSize,
textPosition, textPosition,
textContent, textContent,
showAllInterpolationTracks, showAllInterpolationTracks,
@ -174,19 +185,26 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
intelligentPolygonCrop, intelligentPolygonCrop,
showProjections, showProjections,
canvasBackgroundColor, canvasBackgroundColor,
colorBy,
onFetchAnnotation, onFetchAnnotation,
} = this.props; } = this.props;
const { canvasInstance } = this.props as { canvasInstance: Canvas }; const { canvasInstance } = this.props as { canvasInstance: Canvas };
if ( if (
prevProps.showObjectsTextAlways !== showObjectsTextAlways || prevProps.showObjectsTextAlways !== showObjectsTextAlways ||
prevProps.automaticBordering !== automaticBordering || prevProps.automaticBordering !== automaticBordering ||
prevProps.showProjections !== showProjections || prevProps.showProjections !== showProjections ||
prevProps.intelligentPolygonCrop !== intelligentPolygonCrop || prevProps.intelligentPolygonCrop !== intelligentPolygonCrop ||
prevProps.opacity !== opacity ||
prevProps.selectedOpacity !== selectedOpacity || prevProps.selectedOpacity !== selectedOpacity ||
prevProps.smoothImage !== smoothImage || prevProps.smoothImage !== smoothImage ||
prevProps.textFontSize !== textFontSize || prevProps.textFontSize !== textFontSize ||
prevProps.controlPointsSize !== controlPointsSize ||
prevProps.textPosition !== textPosition || prevProps.textPosition !== textPosition ||
prevProps.textContent !== textContent prevProps.textContent !== textContent ||
prevProps.colorBy !== colorBy ||
prevProps.outlineColor !== outlineColor ||
prevProps.outlined !== outlined
) { ) {
canvasInstance.configure({ canvasInstance.configure({
undefinedAttrValue: consts.UNDEFINED_ATTRIBUTE_VALUE, undefinedAttrValue: consts.UNDEFINED_ATTRIBUTE_VALUE,
@ -194,9 +212,13 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
autoborders: automaticBordering, autoborders: automaticBordering,
showProjections, showProjections,
intelligentPolygonCrop, intelligentPolygonCrop,
creationOpacity: selectedOpacity, selectedShapeOpacity: selectedOpacity,
shapeOpacity: opacity,
smoothImage, smoothImage,
colorBy,
outlinedBorders: outlined ? outlineColor || 'black' : false,
textFontSize, textFontSize,
controlPointsSize,
textPosition, textPosition,
textContent, 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) { if (prevProps.showBitmap !== showBitmap) {
canvasInstance.bitmap(showBitmap); canvasInstance.bitmap(showBitmap);
} }
@ -386,9 +398,22 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
state.objectType = state.objectType || activeObjectType; state.objectType = state.objectType || activeObjectType;
state.label = state.label || jobInstance.labels.filter((label: any) => label.id === activeLabelID)[0]; state.label = state.label || jobInstance.labels.filter((label: any) => label.id === activeLabelID)[0];
state.occluded = state.occluded || false;
state.frame = frame; state.frame = frame;
state.rotation = state.rotation || 0; 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); const objectState = new cvat.classes.ObjectState(state);
onCreateAnnotations(jobInstance, frame, [objectState]); onCreateAnnotations(jobInstance, frame, [objectState]);
}; };
@ -494,8 +519,14 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
}; };
private onCanvasShapeClicked = (e: any): void => { private onCanvasShapeClicked = (e: any): void => {
const { clientID } = e.detail.state; const { clientID, parentID } = e.detail.state;
const sidebarItem = window.document.getElementById(`cvat-objects-sidebar-state-item-${clientID}`); 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) { if (sidebarItem) {
sidebarItem.scrollIntoView(); sidebarItem.scrollIntoView();
} }
@ -515,7 +546,7 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
private onCanvasCursorMoved = async (event: any): Promise<void> => { private onCanvasCursorMoved = async (event: any): Promise<void> => {
const { const {
jobInstance, activatedStateID, workspace, onActivateObject, jobInstance, activatedStateID, activatedElementID, workspace, onActivateObject,
} = this.props; } = this.props;
if (![Workspace.STANDARD, Workspace.REVIEW_WORKSPACE].includes(workspace)) { 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); const result = await jobInstance.annotations.select(event.detail.states, event.detail.x, event.detail.y);
if (result && result.state) { 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) { if (result.distance > MAX_DISTANCE_TO_OPEN_SHAPE) {
return; return;
} }
} }
if (activatedStateID !== result.state.clientID) { const newActivatedElement = event.detail.activatedElementID || null;
onActivateObject(result.state.clientID); 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 => { private onCanvasSetup = (): void => {
const { onSetupCanvas } = this.props; const { onSetupCanvas } = this.props;
onSetupCanvas(); onSetupCanvas();
this.updateShapesView();
this.activateOnCanvas(); 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); const result = await jobInstance.annotations.select(e.detail.states, e.detail.x, e.detail.y);
if (result && result.state) { 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) { if (result.distance > MAX_DISTANCE_TO_OPEN_SHAPE) {
return; return;
} }
@ -607,7 +638,7 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
const { activatedStateID, onUpdateContextMenu, annotations } = this.props; const { activatedStateID, onUpdateContextMenu, annotations } = this.props;
const [state] = annotations.filter((el: any) => el.clientID === activatedStateID); 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( onUpdateContextMenu(
activatedStateID !== null, activatedStateID !== null,
e.detail.mouseEvent.clientX, 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 { private updateCanvas(): void {
const { const {
curZLayer, annotations, frameData, canvasInstance, curZLayer, annotations, frameData, canvasInstance,

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

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

@ -6,7 +6,7 @@ import React from 'react';
import Layout from 'antd/lib/layout'; import Layout from 'antd/lib/layout';
import GlobalHotKeys, { KeyMap } from 'utils/mousetrap-react'; 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 { Canvas } from 'cvat-canvas-wrapper';
import RotateControl from 'components/annotation-page/standard-workspace/controls-side-bar/rotate-control'; import RotateControl from 'components/annotation-page/standard-workspace/controls-side-bar/rotate-control';

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

@ -6,7 +6,7 @@ import './styles.scss';
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux'; import { useSelector, useDispatch } from 'react-redux';
import { CombinedState } from 'reducers/interfaces'; import { CombinedState } from 'reducers';
import { Canvas } from 'cvat-canvas/src/typescript/canvas'; import { Canvas } from 'cvat-canvas/src/typescript/canvas';
import { commentIssueAsync, resolveIssueAsync, reopenIssueAsync } from 'actions/review-actions'; 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 Spin from 'antd/lib/spin';
import Image from 'antd/lib/image'; import Image from 'antd/lib/image';
import { CombinedState } from 'reducers/interfaces'; import { CombinedState } from 'reducers';
import { hideShowContextImage, getContextImageAsync } from 'actions/annotation-actions'; import { hideShowContextImage, getContextImageAsync } from 'actions/annotation-actions';
import CVATTooltip from 'components/common/cvat-tooltip'; import CVATTooltip from 'components/common/cvat-tooltip';

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

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

@ -8,7 +8,7 @@ import Icon from '@ant-design/icons';
import { Canvas } from 'cvat-canvas-wrapper'; import { Canvas } from 'cvat-canvas-wrapper';
import { Canvas3d } from 'cvat-canvas3d-wrapper'; import { Canvas3d } from 'cvat-canvas3d-wrapper';
import { ShapeType } from 'reducers/interfaces'; import { ShapeType } from 'reducers';
import { CubeIcon } from 'icons'; import { CubeIcon } from 'icons';
@ -22,7 +22,7 @@ export interface Props {
} }
const CustomPopover = withVisibilityHandling(Popover, 'draw-cuboid'); const CustomPopover = withVisibilityHandling(Popover, 'draw-cuboid');
function DrawPolygonControl(props: Props): JSX.Element { function DrawCuboidControl(props: Props): JSX.Element {
const { canvasInstance, isDrawing, disabled } = props; const { canvasInstance, isDrawing, disabled } = props;
const dynamicPopoverProps = isDrawing ? { const dynamicPopoverProps = isDrawing ? {
overlayStyle: { 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 { Canvas } from 'cvat-canvas-wrapper';
import { EllipseIcon } from 'icons'; 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 DrawShapePopoverContainer from 'containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover';
import withVisibilityHandling from './handle-popover-visibility'; import withVisibilityHandling from './handle-popover-visibility';
@ -20,7 +20,7 @@ export interface Props {
} }
const CustomPopover = withVisibilityHandling(Popover, 'draw-ellipse'); const CustomPopover = withVisibilityHandling(Popover, 'draw-ellipse');
function DrawPointsControl(props: Props): JSX.Element { function DrawEllipseControl(props: Props): JSX.Element {
const { canvasInstance, isDrawing, disabled } = props; const { canvasInstance, isDrawing, disabled } = props;
const dynamicPopoverProps = isDrawing ? { const dynamicPopoverProps = isDrawing ? {
overlayStyle: { 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 { Canvas } from 'cvat-canvas-wrapper';
import { PointIcon } from 'icons'; 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 DrawShapePopoverContainer from 'containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover';
import withVisibilityHandling from './handle-popover-visibility'; import withVisibilityHandling from './handle-popover-visibility';

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

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

@ -8,7 +8,7 @@ import Icon from '@ant-design/icons';
import { Canvas } from 'cvat-canvas-wrapper'; import { Canvas } from 'cvat-canvas-wrapper';
import { RectangleIcon } from 'icons'; 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 DrawShapePopoverContainer from 'containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover';
import withVisibilityHandling from './handle-popover-visibility'; 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 Text from 'antd/lib/typography/Text';
import { RectDrawingMethod, CuboidDrawingMethod } from 'cvat-canvas-wrapper'; import { RectDrawingMethod, CuboidDrawingMethod } from 'cvat-canvas-wrapper';
import { DimensionType, ShapeType } from 'reducers/interfaces'; import { DimensionType, ShapeType } from 'reducers';
import { clamp } from 'utils/math'; import { clamp } from 'utils/math';
import LabelSelector from 'components/label-selector/label-selector'; import LabelSelector from 'components/label-selector/label-selector';
import CVATTooltip from 'components/common/cvat-tooltip'; import CVATTooltip from 'components/common/cvat-tooltip';
import { Label } from 'components/labels-editor/common';
interface Props { interface Props {
shapeType: ShapeType; shapeType: ShapeType;
@ -22,9 +23,9 @@ interface Props {
rectDrawingMethod?: RectDrawingMethod; rectDrawingMethod?: RectDrawingMethod;
cuboidDrawingMethod?: CuboidDrawingMethod; cuboidDrawingMethod?: CuboidDrawingMethod;
numberOfPoints?: number; numberOfPoints?: number;
selectedLabelID: number; selectedLabelID: number | null;
repeatShapeShortcut: string; repeatShapeShortcut: string;
onChangeLabel(value: string): void; onChangeLabel(value: Label | null): void;
onChangePoints(value: number | undefined): void; onChangePoints(value: number | undefined): void;
onChangeRectDrawingMethod(event: RadioChangeEvent): void; onChangeRectDrawingMethod(event: RadioChangeEvent): void;
onChangeCuboidDrawingMethod(event: RadioChangeEvent): void; onChangeCuboidDrawingMethod(event: RadioChangeEvent): void;
@ -53,7 +54,6 @@ function DrawShapePopoverComponent(props: Props): JSX.Element {
} = props; } = props;
const is2D = jobInstance.dimension === DimensionType.DIM_2D; const is2D = jobInstance.dimension === DimensionType.DIM_2D;
return ( return (
<div className='cvat-draw-shape-popover-content'> <div className='cvat-draw-shape-popover-content'>
<Row justify='start'> <Row justify='start'>
@ -126,7 +126,7 @@ function DrawShapePopoverComponent(props: Props): JSX.Element {
</Row> </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'> <Row justify='space-around' align='middle'>
<Col span={14}> <Col span={14}>
<Text className='cvat-text-color'> Number of points: </Text> <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 { GroupIcon } from 'icons';
import { Canvas } from 'cvat-canvas-wrapper'; import { Canvas } from 'cvat-canvas-wrapper';
import { Canvas3d } from 'cvat-canvas3d-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'; import CVATTooltip from 'components/common/cvat-tooltip';
export interface Props { export interface Props {

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

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

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

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

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

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

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

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

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

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

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

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

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

Loading…
Cancel
Save