CVAT 3D Milestone-5 (#3079)

3D bounding box manipulation: side view, top view, front view surrounding the object.
Allow user to place 3D bounding boxes & tag labels on specific area using point cloud.

Co-authored-by: cdp <cdp123>
Co-authored-by: Jayraj <jayrajsolanki96@gmail.com>
main
manasars 5 years ago committed by GitHub
parent 11d967d208
commit f74a496733
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -168,7 +168,11 @@ jobs:
npx cypress run --headless --browser chrome --spec 'cypress/integration/${{ matrix.specs }}/**/*.js'
mv ./.nyc_output/out.json ./.nyc_output/out_${{ matrix.specs }}.json
else
npx cypress run --headless --browser chrome --env coverage=false --spec 'cypress/integration/${{ matrix.specs }}/**/*.js'
if [[ ${{ matrix.specs }} != 'canvas3d_functionality' ]]; then
npx cypress run --headless --browser chrome --env coverage=false --spec 'cypress/integration/${{ matrix.specs }}/**/*.js'
else
npx cypress run --browser chrome --env coverage=false --spec 'cypress/integration/${{ matrix.specs }}/**/*.js'
fi
fi
- name: Creating a log file from "cvat" container logs
if: failure()

@ -164,7 +164,7 @@ Other ways to ask questions and get our support:
vision AI platform that fully integrates CVAT with scalable data processing
and parallelized training pipelines.
- [DataIsKey](https://dataiskey.eu/annotation-tool/) uses CVAT as their prime data labeling tool
to offer annotation services for projects of any size.
to offer annotation services for projects of any size.
<!-- prettier-ignore-start -->
<!-- Badges -->

@ -26,11 +26,20 @@ npm run build -- --mode=development # without a minification
```ts
interface Canvas3d {
html(): HTMLDivElement;
setup(frameData: any): void;
mode(): Mode;
html(): ViewsDOM;
setup(frameData: any, objectStates: any[]): void;
isAbleToChangeFrame(): boolean;
mode(): Mode;
render(): void;
keyControls(keys: KeyboardEvent): void;
draw(drawData: DrawData): void;
cancel(): void;
dragCanvas(enable: boolean): void;
activate(clientID: number | null, attributeID?: number): void;
configureShapes(shapeProperties: ShapeProperties): void;
fitCanvas(): void;
fit(): void;
group(groupData: GroupData): void;
}
```
@ -44,5 +53,9 @@ console.log('Version ', window.canvas.CanvasVersion);
console.log('Current mode is ', window.canvas.mode());
// Put canvas to a html container
htmlContainer.appendChild(canvas.html());
const views = canvas.html();
htmlContainer.appendChild(views.perspective);
htmlContainer.appendChild(views.top);
htmlContainer.appendChild(views.side);
htmlContainer.appendChild(views.front);
```

@ -5,10 +5,17 @@
import pjson from '../../package.json';
import { Canvas3dController, Canvas3dControllerImpl } from './canvas3dController';
import {
Canvas3dModel, Canvas3dModelImpl, Mode, DrawData, ViewType, MouseInteraction,
Canvas3dModel,
Canvas3dModelImpl,
Mode,
DrawData,
ViewType,
MouseInteraction,
ShapeProperties,
GroupData,
} from './canvas3dModel';
import {
Canvas3dView, Canvas3dViewImpl, ViewsDOM, CAMERA_ACTION,
Canvas3dView, Canvas3dViewImpl, ViewsDOM, CameraAction,
} from './canvas3dView';
import { Master } from './master';
@ -16,19 +23,24 @@ const Canvas3dVersion = pjson.version;
interface Canvas3d {
html(): ViewsDOM;
setup(frameData: any): void;
setup(frameData: any, objectStates: any[]): void;
isAbleToChangeFrame(): boolean;
mode(): Mode;
render(): void;
keyControls(keys: KeyboardEvent): void;
mouseControls(type: string, event: MouseEvent): void;
draw(drawData: DrawData): void;
cancel(): void;
dragCanvas(enable: boolean): void;
activate(clientID: number | null, attributeID?: number): void;
configureShapes(shapeProperties: ShapeProperties): void;
fitCanvas(): void;
fit(): void;
group(groupData: GroupData): void;
}
class Canvas3dImpl implements Canvas3d {
private model: Canvas3dModel & Master;
private controller: Canvas3dController;
private readonly model: Canvas3dModel & Master;
private readonly controller: Canvas3dController;
private view: Canvas3dView;
public constructor() {
@ -45,10 +57,6 @@ class Canvas3dImpl implements Canvas3d {
this.view.keyControls(keys);
}
public mouseControls(type: MouseInteraction, event: MouseEvent): void {
this.view.mouseControls(type, event);
}
public render(): void {
this.view.render();
}
@ -57,14 +65,18 @@ class Canvas3dImpl implements Canvas3d {
this.model.draw(drawData);
}
public setup(frameData: any): void {
this.model.setup(frameData);
public setup(frameData: any, objectStates: any[]): void {
this.model.setup(frameData, objectStates);
}
public mode(): Mode {
return this.model.mode;
}
public group(groupData: GroupData): void {
this.model.group(groupData);
}
public isAbleToChangeFrame(): boolean {
return this.model.isAbleToChangeFrame();
}
@ -72,8 +84,28 @@ class Canvas3dImpl implements Canvas3d {
public cancel(): void {
this.model.cancel();
}
public dragCanvas(enable: boolean): void {
this.model.dragCanvas(enable);
}
public configureShapes(shapeProperties: ShapeProperties): void {
this.model.configureShapes(shapeProperties);
}
public activate(clientID: number | null, attributeID: number | null = null): void {
this.model.activate(String(clientID), attributeID);
}
public fit(): void {
this.model.fit();
}
public fitCanvas(): void {
this.model.fit();
}
}
export {
Canvas3dImpl as Canvas3d, Canvas3dVersion, ViewType, MouseInteraction, CAMERA_ACTION,
Canvas3dImpl as Canvas3d, Canvas3dVersion, ViewType, MouseInteraction, CameraAction, ViewsDOM,
};

@ -2,11 +2,18 @@
//
// SPDX-License-Identifier: MIT
import { Canvas3dModel, Mode, DrawData } from './canvas3dModel';
import {
Canvas3dModel, Mode, DrawData, ActiveElement, FocusData, GroupData,
} from './canvas3dModel';
export interface Canvas3dController {
readonly drawData: DrawData;
readonly activeElement: ActiveElement;
readonly selected: any;
readonly focused: FocusData;
readonly groupData: GroupData;
mode: Mode;
group(groupData: GroupData): void;
}
export class Canvas3dControllerImpl implements Canvas3dController {
@ -27,4 +34,24 @@ export class Canvas3dControllerImpl implements Canvas3dController {
public get drawData(): DrawData {
return this.model.data.drawData;
}
public get activeElement(): ActiveElement {
return this.model.data.activeElement;
}
public get selected(): any {
return this.model.data.selected;
}
public get focused(): any {
return this.model.data.focusData;
}
public get groupData(): GroupData {
return this.model.groupData;
}
public group(groupData: GroupData): void {
this.model.group(groupData);
}
}

@ -9,6 +9,16 @@ export interface Size {
height: number;
}
export interface ActiveElement {
clientID: string | null;
attributeID: number | null;
}
export interface GroupData {
enabled: boolean;
grouped?: [];
}
export interface Image {
renderWidth: number;
renderHeight: number;
@ -19,6 +29,7 @@ export interface DrawData {
enabled: boolean;
initialState?: any;
redraw?: number;
shapeType?: string;
}
export enum FrameZoom {
@ -26,6 +37,13 @@ export enum FrameZoom {
MAX = 10,
}
export enum Planes {
TOP = 'topPlane',
SIDE = 'sidePlane',
FRONT = 'frontPlane',
PERSPECTIVE = 'perspectivePlane',
}
export enum ViewType {
PERSPECTIVE = 'perspective',
TOP = 'top',
@ -39,14 +57,29 @@ export enum MouseInteraction {
HOVER = 'hover',
}
export interface FocusData {
clientID: string | null;
}
export interface ShapeProperties {
opacity: number;
outlined: boolean;
outlineColor: string;
selectedOpacity: number;
colorBy: string;
}
export enum UpdateReasons {
IMAGE_CHANGED = 'image_changed',
OBJECTS_UPDATED = 'objects_updated',
FITTED_CANVAS = 'fitted_canvas',
DRAW = 'draw',
SELECT = 'select',
CANCEL = 'cancel',
DATA_FAILED = 'data_failed',
DRAG_CANVAS = 'drag_canvas',
SHAPE_ACTIVATED = 'shape_activated',
GROUP = 'group',
FITTED_CANVAS = 'fitted_canvas',
}
export enum Mode {
@ -56,9 +89,12 @@ export enum Mode {
DRAW = 'draw',
EDIT = 'edit',
INTERACT = 'interact',
DRAG_CANVAS = 'drag_canvas',
GROUP = 'group',
}
export interface Canvas3dDataModel {
activeElement: ActiveElement;
canvasSize: Size;
image: Image | null;
imageID: number | null;
@ -67,15 +103,27 @@ export interface Canvas3dDataModel {
drawData: DrawData;
mode: Mode;
exception: Error | null;
objects: any[];
groupedObjects: any[];
focusData: FocusData;
selected: any;
shapeProperties: ShapeProperties;
groupData: GroupData;
}
export interface Canvas3dModel {
mode: Mode;
data: Canvas3dDataModel;
setup(frameData: any): void;
readonly groupData: GroupData;
setup(frameData: any, objectStates: any[]): void;
isAbleToChangeFrame(): boolean;
draw(drawData: DrawData): void;
cancel(): void;
dragCanvas(enable: boolean): void;
activate(clientID: string | null, attributeID: number | null): void;
configureShapes(shapeProperties: any): void;
fit(): void;
group(groupData: GroupData): void;
}
export class Canvas3dModelImpl extends MasterImpl implements Canvas3dModel {
@ -84,10 +132,16 @@ export class Canvas3dModelImpl extends MasterImpl implements Canvas3dModel {
public constructor() {
super();
this.data = {
activeElement: {
clientID: null,
attributeID: null,
},
canvasSize: {
height: 0,
width: 0,
},
objects: [],
groupedObjects: [],
image: null,
imageID: null,
imageOffset: 0,
@ -101,37 +155,68 @@ export class Canvas3dModelImpl extends MasterImpl implements Canvas3dModel {
},
mode: Mode.IDLE,
exception: null,
focusData: {
clientID: null,
},
groupData: {
enabled: false,
grouped: [],
},
selected: null,
shapeProperties: {
opacity: 40,
outlined: false,
outlineColor: '#000000',
selectedOpacity: 60,
colorBy: 'Label',
},
};
}
public setup(frameData: any): void {
public setup(frameData: any, objectStates: any[]): void {
if (this.data.imageID !== frameData.number) {
this.data.imageID = frameData.number;
frameData
.data((): void => {
this.data.image = null;
this.notify(UpdateReasons.IMAGE_CHANGED);
})
.then((data: Image): void => {
if (frameData.number !== this.data.imageID) {
// already another image
return;
}
this.data.imageSize = {
height: frameData.height as number,
width: frameData.width as number,
};
this.data.image = data;
this.notify(UpdateReasons.IMAGE_CHANGED);
})
.catch((exception: any): void => {
this.data.exception = exception;
this.notify(UpdateReasons.DATA_FAILED);
throw exception;
});
if ([Mode.EDIT, Mode.DRAG, Mode.RESIZE].includes(this.data.mode)) {
throw Error(`Canvas is busy. Action: ${this.data.mode}`);
}
}
if ([Mode.EDIT].includes(this.data.mode)) {
return;
}
if (frameData.number === this.data.imageID) {
this.data.objects = objectStates;
this.notify(UpdateReasons.OBJECTS_UPDATED);
return;
}
this.data.imageID = frameData.number;
frameData
.data((): void => {
this.data.image = null;
this.notify(UpdateReasons.IMAGE_CHANGED);
})
.then((data: Image): void => {
if (frameData.number !== this.data.imageID) {
// already another image
return;
}
this.data.imageSize = {
height: frameData.height as number,
width: frameData.width as number,
};
this.data.image = data;
this.notify(UpdateReasons.IMAGE_CHANGED);
this.data.objects = objectStates;
this.notify(UpdateReasons.OBJECTS_UPDATED);
})
.catch((exception: any): void => {
this.data.exception = exception;
this.notify(UpdateReasons.DATA_FAILED);
throw exception;
});
}
public set mode(value: Mode) {
@ -145,7 +230,6 @@ export class Canvas3dModelImpl extends MasterImpl implements Canvas3dModel {
public isAbleToChangeFrame(): boolean {
const isUnable = [Mode.DRAG, Mode.EDIT, Mode.RESIZE, Mode.INTERACT].includes(this.data.mode)
|| (this.data.mode === Mode.DRAW && typeof this.data.drawData.redraw === 'number');
return !isUnable;
}
@ -153,13 +237,98 @@ export class Canvas3dModelImpl extends MasterImpl implements Canvas3dModel {
if (drawData.enabled && this.data.drawData.enabled) {
throw new Error('Drawing has been already started');
}
if ([Mode.DRAW, Mode.EDIT].includes(this.data.mode)) {
return;
}
this.data.drawData.enabled = drawData.enabled;
this.data.mode = Mode.DRAW;
if (typeof drawData.redraw === 'number') {
const clientID = drawData.redraw;
const [state] = this.data.objects.filter((_state: any): boolean => _state.clientID === clientID);
if (state) {
this.data.drawData = { ...drawData };
this.data.drawData.initialState = { ...this.data.drawData.initialState, label: state.label };
this.data.drawData.shapeType = state.shapeType;
} else {
return;
}
} else {
this.data.drawData = { ...drawData };
if (this.data.drawData.initialState) {
this.data.drawData.shapeType = this.data.drawData.initialState.shapeType;
}
}
this.notify(UpdateReasons.DRAW);
}
public cancel(): void {
this.notify(UpdateReasons.CANCEL);
}
public dragCanvas(enable: boolean): void {
if (enable && this.data.mode !== Mode.IDLE) {
throw Error(`Canvas is busy. Action: ${this.data.mode}`);
}
if (!enable && this.data.mode !== Mode.DRAG_CANVAS) {
throw Error(`Canvas is not in the drag mode. Action: ${this.data.mode}`);
}
this.data.mode = enable ? Mode.DRAG_CANVAS : Mode.IDLE;
this.notify(UpdateReasons.DRAG_CANVAS);
}
public activate(clientID: string, attributeID: number | null): void {
if (this.data.activeElement.clientID === clientID && this.data.activeElement.attributeID === attributeID) {
return;
}
if (this.data.mode !== Mode.IDLE) {
throw Error(`Canvas is busy. Action: ${this.data.mode}`);
}
if (typeof clientID === 'number') {
const [state] = this.data.objects.filter((_state: any): boolean => _state.clientID === clientID);
if (!state || state.objectType === 'tag') {
return;
}
}
this.data.activeElement = {
clientID,
attributeID,
};
this.notify(UpdateReasons.SHAPE_ACTIVATED);
}
public group(groupData: GroupData): void {
if (![Mode.IDLE, Mode.GROUP].includes(this.data.mode)) {
throw Error(`Canvas is busy. Action: ${this.data.mode}`);
}
if (this.data.groupData.enabled && groupData.enabled) {
return;
}
if (!this.data.groupData.enabled && !groupData.enabled) {
return;
}
this.data.mode = groupData.enabled ? Mode.GROUP : Mode.IDLE;
this.data.groupData = { ...this.data.groupData, ...groupData };
this.notify(UpdateReasons.GROUP);
}
public configureShapes(shapeProperties: ShapeProperties): void {
this.data.shapeProperties = {
...shapeProperties,
};
this.notify(UpdateReasons.OBJECTS_UPDATED);
}
public fit(): void {
this.notify(UpdateReasons.FITTED_CANVAS);
}
public get groupData(): GroupData {
return { ...this.data.groupData };
}
}

File diff suppressed because it is too large Load Diff

@ -8,6 +8,15 @@ const DOLLY_FACTOR = 5;
const MAX_DISTANCE = 100;
const MIN_DISTANCE = 0;
const ZOOM_FACTOR = 7;
const ROTATION_HELPER_OFFSET = 0.1;
const CAMERA_REFERENCE = 'camRef';
const CUBOID_EDGE_NAME = 'edges';
const ROTATION_HELPER = 'rotationHelper';
const ROTATION_SPEED = 80;
const FOV_DEFAULT = 1;
const FOV_MAX = 2;
const FOV_MIN = 0;
const FOV_INC = 0.08;
export default {
BASE_GRID_WIDTH,
@ -16,4 +25,13 @@ export default {
MAX_DISTANCE,
MIN_DISTANCE,
ZOOM_FACTOR,
ROTATION_HELPER_OFFSET,
CAMERA_REFERENCE,
CUBOID_EDGE_NAME,
ROTATION_HELPER,
ROTATION_SPEED,
FOV_DEFAULT,
FOV_MAX,
FOV_MIN,
FOV_INC,
};

@ -2,6 +2,13 @@
//
// SPDX-License-Identifier: MIT
import * as THREE from 'three';
import { BufferGeometryUtils } from 'three/examples/jsm/utils/BufferGeometryUtils';
import { ViewType } from './canvas3dModel';
import constants from './consts';
export interface Indexable {
[key: string]: any;
}
export class CuboidModel {
public perspective: THREE.Mesh;
@ -9,12 +16,174 @@ export class CuboidModel {
public side: THREE.Mesh;
public front: THREE.Mesh;
public constructor() {
public constructor(outline: string, outlineColor: string) {
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00, wireframe: true });
const material = new THREE.MeshBasicMaterial({
color: 0x00ff00,
wireframe: false,
transparent: true,
opacity: 0.4,
});
this.perspective = new THREE.Mesh(geometry, material);
const geo = new THREE.EdgesGeometry(this.perspective.geometry);
const wireframe = new THREE.LineSegments(
geo,
outline === 'line'
? new THREE.LineBasicMaterial({ color: outlineColor, linewidth: 4 })
: new THREE.LineDashedMaterial({
color: outlineColor,
dashSize: 0.05,
gapSize: 0.05,
}),
);
wireframe.computeLineDistances();
wireframe.renderOrder = 1;
this.perspective.add(wireframe);
this.top = new THREE.Mesh(geometry, material);
this.side = new THREE.Mesh(geometry, material);
this.front = new THREE.Mesh(geometry, material);
const camRotateHelper = new THREE.Object3D();
camRotateHelper.translateX(-2);
camRotateHelper.name = 'camRefRot';
camRotateHelper.up = new THREE.Vector3(0, 0, 1);
camRotateHelper.lookAt(new THREE.Vector3(0, 0, 0));
this.front.add(camRotateHelper.clone());
}
public setPosition(x: number, y: number, z: number): void {
[ViewType.PERSPECTIVE, ViewType.TOP, ViewType.SIDE, ViewType.FRONT].forEach((view): void => {
(this as Indexable)[view].position.set(x, y, z);
});
}
public setScale(x: number, y: number, z: number): void {
[ViewType.PERSPECTIVE, ViewType.TOP, ViewType.SIDE, ViewType.FRONT].forEach((view): void => {
(this as Indexable)[view].scale.set(x, y, z);
});
}
public setRotation(x: number, y: number, z: number): void {
[ViewType.PERSPECTIVE, ViewType.TOP, ViewType.SIDE, ViewType.FRONT].forEach((view): void => {
(this as Indexable)[view].rotation.set(x, y, z);
});
}
public attachCameraReference(): void {
// Attach Cam Reference
const topCameraReference = new THREE.Object3D();
topCameraReference.translateZ(2);
topCameraReference.name = constants.CAMERA_REFERENCE;
this.top.add(topCameraReference);
this.top.userData = { ...this.top.userData, camReference: topCameraReference };
const sideCameraReference = new THREE.Object3D();
sideCameraReference.translateY(2);
sideCameraReference.name = constants.CAMERA_REFERENCE;
this.side.add(sideCameraReference);
this.side.userData = { ...this.side.userData, camReference: sideCameraReference };
const frontCameraReference = new THREE.Object3D();
frontCameraReference.translateX(2);
frontCameraReference.name = constants.CAMERA_REFERENCE;
this.front.add(frontCameraReference);
this.front.userData = { ...this.front.userData, camReference: frontCameraReference };
}
public getReferenceCoordinates(viewType: string): THREE.Vector3 {
const { elements } = (this as Indexable)[viewType].getObjectByName(constants.CAMERA_REFERENCE).matrixWorld;
return new THREE.Vector3(elements[12], elements[13], elements[14]);
}
public setName(clientId: any): void {
[ViewType.PERSPECTIVE, ViewType.TOP, ViewType.SIDE, ViewType.FRONT].forEach((view): void => {
(this as Indexable)[view].name = clientId;
});
}
public setOriginalColor(color: string): void {
[ViewType.PERSPECTIVE, ViewType.TOP, ViewType.SIDE, ViewType.FRONT].forEach((view): void => {
((this as Indexable)[view] as any).originalColor = color;
});
}
public setColor(color: string): void {
[ViewType.PERSPECTIVE, ViewType.TOP, ViewType.SIDE, ViewType.FRONT].forEach((view): void => {
((this as Indexable)[view].material as THREE.MeshBasicMaterial).color.set(color);
});
}
public setOpacity(opacity: number): void {
[ViewType.PERSPECTIVE, ViewType.TOP, ViewType.SIDE, ViewType.FRONT].forEach((view): void => {
((this as Indexable)[view].material as THREE.MeshBasicMaterial).opacity = opacity / 100;
});
}
}
export function setEdges(instance: THREE.Mesh): THREE.LineSegments {
const edges = new THREE.EdgesGeometry(instance.geometry);
const line = new THREE.LineSegments(edges, new THREE.LineBasicMaterial({ color: '#ffffff', linewidth: 3 }));
line.name = constants.CUBOID_EDGE_NAME;
instance.add(line);
return line;
}
export function setTranslationHelper(instance: THREE.Mesh): void {
const sphereGeometry = new THREE.SphereGeometry(0.1);
const sphereMaterial = new THREE.MeshBasicMaterial({ color: '#ffffff', opacity: 1 });
instance.geometry.deleteAttribute('normal');
instance.geometry.deleteAttribute('uv');
// eslint-disable-next-line no-param-reassign
instance.geometry = BufferGeometryUtils.mergeVertices(instance.geometry);
const vertices = [];
const positionAttribute = instance.geometry.getAttribute('position');
for (let i = 0; i < positionAttribute.count; i++) {
const vertex = new THREE.Vector3();
vertex.fromBufferAttribute(positionAttribute, i);
vertices.push(vertex);
}
const helpers = [];
for (let i = 0; i < vertices.length; i++) {
helpers[i] = new THREE.Mesh(sphereGeometry.clone(), sphereMaterial.clone());
helpers[i].position.set(vertices[i].x, vertices[i].y, vertices[i].z);
helpers[i].up.set(0, 0, 1);
helpers[i].name = 'resizeHelper';
instance.add(helpers[i]);
helpers[i].scale.set(1 / instance.scale.x, 1 / instance.scale.y, 1 / instance.scale.z);
}
// eslint-disable-next-line no-param-reassign
instance.userData = { ...instance.userData, resizeHelpers: helpers };
}
export function createRotationHelper(instance: THREE.Mesh, viewType: ViewType): void {
const sphereGeometry = new THREE.SphereGeometry(0.1);
const sphereMaterial = new THREE.MeshBasicMaterial({ color: '#ffffff', opacity: 1 });
const rotationHelper = new THREE.Mesh(sphereGeometry, sphereMaterial);
rotationHelper.name = constants.ROTATION_HELPER;
switch (viewType) {
case ViewType.TOP:
rotationHelper.position.set(
(instance.geometry as THREE.BoxGeometry).parameters.height / 2 + constants.ROTATION_HELPER_OFFSET,
instance.position.y,
instance.position.z,
);
instance.add(rotationHelper.clone());
// eslint-disable-next-line no-param-reassign
instance.userData = { ...instance.userData, rotationHelpers: rotationHelper.clone() };
break;
case ViewType.SIDE:
case ViewType.FRONT:
rotationHelper.position.set(
instance.position.x,
instance.position.y,
(instance.geometry as THREE.BoxGeometry).parameters.depth / 2 + constants.ROTATION_HELPER_OFFSET,
);
instance.add(rotationHelper.clone());
// eslint-disable-next-line no-param-reassign
instance.userData = { ...instance.userData, rotationHelpers: rotationHelper.clone() };
break;
default:
break;
}
}

@ -1,4 +1,4 @@
// Copyright (C) 2019-2020 Intel Corporation
// Copyright (C) 2021 Intel Corporation
//
// SPDX-License-Identifier: MIT
@ -324,11 +324,16 @@
checkObjectType('points', data.points, null, Array);
checkNumberOfPoints(this.shapeType, data.points);
// cut points
const { width, height } = this.frameMeta[frame];
const { width, height, filename } = this.frameMeta[frame];
fittedPoints = fitPoints(this.shapeType, data.points, width, height);
if (!checkShapeArea(this.shapeType, fittedPoints) || checkOutside(fittedPoints, width, height)) {
fittedPoints = [];
let check = true;
if (filename && filename.slice(filename.length - 3) === 'pcd') {
check = false;
}
if (check) {
if (!checkShapeArea(this.shapeType, fittedPoints) || checkOutside(fittedPoints, width, height)) {
fittedPoints = [];
}
}
}

@ -1082,9 +1082,7 @@
const closureId = Date.now();
predictAnnotations.latestRequest.id = closureId;
const predicate = () => (
!predictAnnotations.latestRequest.fetching || predictAnnotations.latestRequest.id !== closureId
);
const predicate = () => !predictAnnotations.latestRequest.fetching || predictAnnotations.latestRequest.id !== closureId;
if (predictAnnotations.latestRequest.fetching) {
waitFor(5, predicate).then(() => {
if (predictAnnotations.latestRequest.id !== closureId) {

@ -7,7 +7,7 @@ import {
ActionCreator, AnyAction, Dispatch, Store,
} from 'redux';
import { ThunkAction } from 'utils/redux';
import { RectDrawingMethod } from 'cvat-canvas-wrapper';
import { RectDrawingMethod, Canvas } from 'cvat-canvas-wrapper';
import getCore from 'cvat-core-wrapper';
import logger, { LogType } from 'cvat-logger';
import { getCVATStore } from 'cvat-store';
@ -1435,8 +1435,9 @@ export function pasteShapeAsync(): ThunkAction {
activeControl,
},
});
canvasInstance.cancel();
if (canvasInstance instanceof Canvas) {
canvasInstance.cancel();
}
if (initialState.objectType === ObjectType.TAG) {
const objectState = new cvat.classes.ObjectState({
objectType: ObjectType.TAG,
@ -1493,7 +1494,7 @@ export function repeatDrawShapeAsync(): ThunkAction {
} = getStore().getState().annotation;
let activeControl = ActiveControl.CURSOR;
if (activeInteractor) {
if (activeInteractor && canvasInstance instanceof Canvas) {
if (activeInteractor.type === 'tracker') {
canvasInstance.interact({
enabled: true,
@ -1511,7 +1512,6 @@ export function repeatDrawShapeAsync(): ThunkAction {
return;
}
if (activeShapeType === ShapeType.RECTANGLE) {
activeControl = ActiveControl.DRAW_RECTANGLE;
} else if (activeShapeType === ShapeType.POINTS) {
@ -1523,7 +1523,6 @@ export function repeatDrawShapeAsync(): ThunkAction {
} else if (activeShapeType === ShapeType.CUBOID) {
activeControl = ActiveControl.DRAW_CUBOID;
}
dispatch({
type: AnnotationActionTypes.REPEAT_DRAW_SHAPE,
payload: {
@ -1531,7 +1530,9 @@ export function repeatDrawShapeAsync(): ThunkAction {
},
});
canvasInstance.cancel();
if (canvasInstance instanceof Canvas) {
canvasInstance.cancel();
}
if (activeObjectType === ObjectType.TAG) {
const objectState = new cvat.classes.ObjectState({
objectType: ObjectType.TAG,
@ -1580,8 +1581,9 @@ export function redrawShapeAsync(): ThunkAction {
activeControl,
},
});
canvasInstance.cancel();
if (canvasInstance instanceof Canvas) {
canvasInstance.cancel();
}
canvasInstance.draw({
enabled: true,
redraw: activatedStateID,

@ -14,7 +14,7 @@ import Button from 'antd/lib/button';
import ColorPicker from 'components/annotation-page/standard-workspace/objects-side-bar/color-picker';
import { ColorizeIcon } from 'icons';
import { ColorBy, CombinedState } from 'reducers/interfaces';
import { ColorBy, CombinedState, DimensionType } from 'reducers/interfaces';
import {
collapseAppearance as collapseAppearanceAction,
updateTabContentHeight as updateTabContentHeightAction,
@ -37,6 +37,7 @@ interface StateToProps {
outlineColor: string;
showBitmap: boolean;
showProjections: boolean;
jobInstance: any;
}
interface DispatchToProps {
@ -66,7 +67,10 @@ export function computeHeight(): number {
function mapStateToProps(state: CombinedState): StateToProps {
const {
annotation: { appearanceCollapsed },
annotation: {
appearanceCollapsed,
job: { instance: jobInstance },
},
settings: {
shapes: {
colorBy, opacity, selectedOpacity, outlined, outlineColor, showBitmap, showProjections,
@ -83,6 +87,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
outlineColor,
showBitmap,
showProjections,
jobInstance,
};
}
@ -144,8 +149,11 @@ function AppearanceBlock(props: Props): JSX.Element {
changeShapesOutlinedBorders,
changeShowBitmap,
changeShowProjections,
jobInstance,
} = props;
const is2D = jobInstance.task.dimension === DimensionType.DIM_2D;
return (
<Collapse
onChange={collapseAppearance}
@ -206,20 +214,24 @@ function AppearanceBlock(props: Props): JSX.Element {
</Button>
</ColorPicker>
</Checkbox>
<Checkbox
className='cvat-appearance-bitmap-checkbox'
onChange={changeShowBitmap}
checked={showBitmap}
>
Show bitmap
</Checkbox>
<Checkbox
className='cvat-appearance-cuboid-projections-checkbox'
onChange={changeShowProjections}
checked={showProjections}
>
Show projections
</Checkbox>
{is2D && (
<Checkbox
className='cvat-appearance-bitmap-checkbox'
onChange={changeShowBitmap}
checked={showBitmap}
>
Show bitmap
</Checkbox>
)}
{is2D && (
<Checkbox
className='cvat-appearance-cuboid-projections-checkbox'
onChange={changeShowProjections}
checked={showProjections}
>
Show projections
</Checkbox>
)}
</div>
</Collapse.Panel>
</Collapse>

@ -14,6 +14,7 @@ import {
} from 'reducers/interfaces';
import { LogType } from 'cvat-logger';
import { Canvas } from 'cvat-canvas-wrapper';
import { Canvas3d } from 'cvat-canvas3d-wrapper';
import getCore from 'cvat-core-wrapper';
import consts from 'consts';
import CVATTooltip from 'components/common/cvat-tooltip';
@ -26,7 +27,7 @@ const MAX_DISTANCE_TO_OPEN_SHAPE = 50;
interface Props {
sidebarCollapsed: boolean;
canvasInstance: Canvas;
canvasInstance: Canvas | Canvas3d;
jobInstance: any;
activatedStateID: number | null;
activatedAttributeID: number | null;
@ -103,10 +104,10 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
automaticBordering,
intelligentPolygonCrop,
showObjectsTextAlways,
canvasInstance,
workspace,
showProjections,
} = this.props;
const { canvasInstance } = this.props as { canvasInstance: Canvas };
// It's awful approach from the point of view React
// But we do not have another way because cvat-canvas returns regular DOM element
@ -139,7 +140,6 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
frameData,
frameAngle,
annotations,
canvasInstance,
sidebarCollapsed,
activatedStateID,
curZLayer,
@ -161,7 +161,7 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
canvasBackgroundColor,
onFetchAnnotation,
} = this.props;
const { canvasInstance } = this.props as { canvasInstance: Canvas };
if (
prevProps.showObjectsTextAlways !== showObjectsTextAlways ||
prevProps.automaticBordering !== automaticBordering ||
@ -306,7 +306,7 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
}
public componentWillUnmount(): void {
const { canvasInstance } = this.props;
const { canvasInstance } = this.props as { canvasInstance: Canvas };
canvasInstance.html().removeEventListener('mousedown', this.onCanvasMouseDown);
canvasInstance.html().removeEventListener('click', this.onCanvasClicked);
@ -432,7 +432,8 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
};
private onCanvasClicked = (): void => {
const { canvasInstance, onUpdateContextMenu } = this.props;
const { onUpdateContextMenu } = this.props;
const { canvasInstance } = this.props as { canvasInstance: Canvas };
onUpdateContextMenu(false, 0, 0, ContextMenuType.CANVAS_SHAPE);
if (!canvasInstance.html().contains(document.activeElement) && document.activeElement instanceof HTMLElement) {
document.activeElement.blur();
@ -562,7 +563,8 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
};
private onCanvasFindObject = async (e: any): Promise<void> => {
const { jobInstance, canvasInstance } = this.props;
const { jobInstance } = this.props;
const { canvasInstance } = this.props as { canvasInstance: Canvas };
const result = await jobInstance.annotations.select(e.detail.states, e.detail.x, e.detail.y);
@ -596,12 +598,12 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
const {
activatedStateID,
activatedAttributeID,
canvasInstance,
selectedOpacity,
aamZoomMargin,
workspace,
annotations,
} = this.props;
const { canvasInstance } = this.props as { canvasInstance: Canvas };
if (activatedStateID !== null) {
const [activatedState] = annotations.filter((state: any): boolean => state.clientID === activatedStateID);
@ -653,7 +655,8 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
}
private updateIssueRegions(): void {
const { canvasInstance, frameIssues } = this.props;
const { frameIssues } = this.props;
const { canvasInstance } = this.props as { canvasInstance: Canvas };
if (frameIssues === null) {
canvasInstance.setupIssueRegions({});
} else {
@ -688,12 +691,12 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
gridSize,
gridColor,
gridOpacity,
canvasInstance,
brightnessLevel,
contrastLevel,
saturationLevel,
canvasBackgroundColor,
} = this.props;
const { canvasInstance } = this.props as { canvasInstance: Canvas };
// Size
window.addEventListener('resize', this.fitCanvas);

@ -10,29 +10,55 @@ import {
ArrowDownOutlined, ArrowLeftOutlined, ArrowRightOutlined, ArrowUpOutlined,
} from '@ant-design/icons';
import { ResizableBox } from 'react-resizable';
import { Workspace } from 'reducers/interfaces';
import {
CAMERA_ACTION, Canvas3d, MouseInteraction, ViewType,
ColorBy, ContextMenuType, ObjectType, Workspace,
} from 'reducers/interfaces';
import {
CameraAction, Canvas3d, ViewType, ViewsDOM,
} from 'cvat-canvas3d-wrapper';
import { Canvas } from 'cvat-canvas-wrapper';
import ContextImage from 'components/annotation-page/standard-workspace/context-image/context-image';
import CVATTooltip from 'components/common/cvat-tooltip';
import { LogType } from 'cvat-logger';
import getCore from 'cvat-core-wrapper';
const cvat = getCore();
interface Props {
canvasInstance: Canvas3d;
opacity: number;
selectedOpacity: number;
outlined: boolean;
outlineColor: string;
colorBy: ColorBy;
canvasInstance: Canvas3d | Canvas;
jobInstance: any;
frameData: any;
curZLayer: number;
annotations: any[];
contextMenuVisibility: boolean;
activeLabelID: number;
activatedStateID: number | null;
activeObjectType: ObjectType;
onSetupCanvas: () => void;
onGroupObjects: (enabled: boolean) => void;
onResetCanvas(): void;
onCreateAnnotations(sessionInstance: any, frame: number, states: any[]): void;
onActivateObject(activatedStateID: number | null): void;
onUpdateAnnotations(states: any[]): void;
onUpdateContextMenu(visible: boolean, left: number, top: number, type: ContextMenuType, pointID?: number): void;
onGroupAnnotations(sessionInstance: any, frame: number, states: any[]): void;
onEditShape: (enabled: boolean) => void;
onDragCanvas: (enabled: boolean) => void;
onShapeDrawn: () => void;
workspace: Workspace;
animateID: any;
automaticBordering: boolean;
showObjectsTextAlways: boolean;
frame: number;
}
interface ViewSize {
fullHeight: number;
fullWidth: number;
vertical: number;
top: number;
side: number;
@ -41,7 +67,7 @@ interface ViewSize {
function viewSizeReducer(
state: ViewSize,
action: { type: ViewType | 'set'; e?: SyntheticEvent; data?: ViewSize },
action: { type: ViewType | 'set' | 'resize'; e?: SyntheticEvent; data?: ViewSize },
): ViewSize {
const event = (action.e as unknown) as MouseEvent;
const canvas3dContainer = document.getElementById('canvas3d-container');
@ -94,6 +120,33 @@ function viewSizeReducer(
};
case 'set':
return action.data as ViewSize;
case 'resize': {
const canvasPerspectiveContainer = document.getElementById('cvat-canvas3d-perspective');
let midState = { ...state };
if (canvasPerspectiveContainer) {
if (state.fullHeight !== canvas3dContainer.clientHeight) {
const diff = canvas3dContainer.clientHeight - state.fullHeight;
midState = {
...midState,
fullHeight: canvas3dContainer.clientHeight,
vertical: state.vertical + diff,
};
}
if (state.fullWidth !== canvasPerspectiveContainer.clientWidth) {
const oldWidth = state.fullWidth;
const width = canvasPerspectiveContainer.clientWidth;
midState = {
...midState,
fullWidth: width,
top: (state.top / oldWidth) * width,
side: (state.side / oldWidth) * width,
front: (state.front / oldWidth) * width,
};
}
return midState;
}
return state;
}
default:
throw new Error();
}
@ -105,6 +158,7 @@ const CanvasWrapperComponent = (props: Props): ReactElement => {
const animateId = useRef(0);
const [viewSize, setViewSize] = useReducer(viewSizeReducer, {
fullHeight: 0,
fullWidth: 0,
vertical: 0,
top: 0,
side: 0,
@ -115,70 +169,122 @@ const CanvasWrapperComponent = (props: Props): ReactElement => {
const sideView = useRef<HTMLDivElement | null>(null);
const frontView = useRef<HTMLDivElement | null>(null);
const { frameData, annotations, curZLayer } = props;
const {
opacity,
outlined,
outlineColor,
selectedOpacity,
colorBy,
contextMenuVisibility,
frameData,
onResetCanvas,
onSetupCanvas,
annotations,
frame,
jobInstance,
activeLabelID,
activeObjectType,
onShapeDrawn,
onCreateAnnotations,
} = props;
const { canvasInstance } = props as { canvasInstance: Canvas3d };
const onCanvasSetup = (): void => {
const { onSetupCanvas } = props;
onSetupCanvas();
};
const animateCanvas = (): void => {
const { canvasInstance } = props;
const onCanvasDragStart = (): void => {
const { onDragCanvas } = props;
onDragCanvas(true);
};
const onCanvasDragDone = (): void => {
const { onDragCanvas } = props;
onDragCanvas(false);
};
const animateCanvas = (): void => {
canvasInstance.render();
animateId.current = requestAnimationFrame(animateCanvas);
};
const updateCanvas = (): void => {
const { canvasInstance } = props;
if (frameData !== null) {
canvasInstance.setup(frameData);
canvasInstance.setup(
frameData,
annotations.filter((e) => e.objectType !== ObjectType.TAG),
);
}
};
const onMouseClick = (event: MouseEvent): void => {
const { canvasInstance } = props;
canvasInstance.mouseControls(MouseInteraction.CLICK, event);
const onCanvasCancel = (): void => {
onResetCanvas();
};
const onMouseDoubleClick = (event: MouseEvent): void => {
const { canvasInstance } = props;
canvasInstance.mouseControls(MouseInteraction.DOUBLE_CLICK, event);
};
const onCanvasShapeDrawn = (event: any): void => {
if (!event.detail.continue) {
onShapeDrawn();
}
const { state, duration } = event.detail;
const isDrawnFromScratch = !state.label;
if (isDrawnFromScratch) {
jobInstance.logger.log(LogType.drawObject, { count: 1, duration });
} else {
jobInstance.logger.log(LogType.pasteObject, { count: 1, duration });
}
const onMouseHover = (event: MouseEvent): void => {
const { canvasInstance } = props;
canvasInstance.mouseControls(MouseInteraction.HOVER, event);
state.objectType = state.objectType || activeObjectType;
state.label = state.label || jobInstance.task.labels.filter((label: any) => label.id === activeLabelID)[0];
state.occluded = state.occluded || false;
state.frame = frame;
state.zOrder = 0;
const objectState = new cvat.classes.ObjectState(state);
onCreateAnnotations(jobInstance, frame, [objectState]);
};
const onCanvasCancel = (): void => {
const { onResetCanvas } = props;
onResetCanvas();
const onCanvasClick = (e: MouseEvent): void => {
const { onUpdateContextMenu } = props;
if (contextMenuVisibility) {
onUpdateContextMenu(false, e.clientX, e.clientY, ContextMenuType.CANVAS_SHAPE);
}
};
const initialSetup = (): void => {
const { canvasInstance } = props;
const canvasInstanceDOM = canvasInstance.html();
// Events
const canvasInstanceDOM = canvasInstance.html() as ViewsDOM;
canvasInstanceDOM.perspective.addEventListener('canvas.setup', onCanvasSetup);
canvasInstanceDOM.perspective.addEventListener('mousemove', onMouseHover);
canvasInstanceDOM.perspective.addEventListener('canvas.canceled', onCanvasCancel);
canvasInstanceDOM.perspective.addEventListener(MouseInteraction.DOUBLE_CLICK, onMouseDoubleClick);
canvasInstanceDOM.perspective.addEventListener(MouseInteraction.CLICK, onMouseClick);
canvasInstanceDOM.perspective.addEventListener('canvas.dragstart', onCanvasDragStart);
canvasInstanceDOM.perspective.addEventListener('canvas.dragstop', onCanvasDragDone);
};
const keyControls = (key: KeyboardEvent): void => {
const { canvasInstance } = props;
const keyControlsKeyDown = (key: KeyboardEvent): void => {
canvasInstance.keyControls(key);
};
useEffect(() => {
const { canvasInstance } = props;
const keyControlsKeyUp = (key: KeyboardEvent): void => {
if (key.code === 'ControlLeft') {
canvasInstance.keyControls(key);
}
};
const canvasInstanceDOM = canvasInstance.html();
const onCanvasShapeSelected = (event: any): void => {
const { onActivateObject } = props;
const { clientID } = event.detail;
onActivateObject(clientID);
canvasInstance.activate(clientID);
};
const onCanvasEditDone = (event: any): void => {
const { onEditShape, onUpdateAnnotations } = props;
onEditShape(false);
const { state, points } = event.detail;
state.points = points;
onUpdateAnnotations([state]);
};
useEffect(() => {
const canvasInstanceDOM = canvasInstance.html();
if (
perspectiveView &&
perspectiveView.current &&
@ -200,6 +306,7 @@ const CanvasWrapperComponent = (props: Props): ReactElement => {
type: 'set',
data: {
fullHeight: canvas3dContainer.clientHeight,
fullWidth: canvas3dContainer.clientWidth,
vertical: canvas3dContainer.clientHeight / 2,
top: width,
side: width,
@ -209,7 +316,8 @@ const CanvasWrapperComponent = (props: Props): ReactElement => {
}
}
document.addEventListener('keydown', keyControls);
document.addEventListener('keydown', keyControlsKeyDown);
document.addEventListener('keyup', keyControlsKeyUp);
initialSetup();
updateCanvas();
@ -217,21 +325,80 @@ const CanvasWrapperComponent = (props: Props): ReactElement => {
return () => {
canvasInstanceDOM.perspective.removeEventListener('canvas.setup', onCanvasSetup);
canvasInstanceDOM.perspective.removeEventListener('mousemove', onMouseHover);
canvasInstanceDOM.perspective.removeEventListener('canvas.canceled', onCanvasCancel);
canvasInstanceDOM.perspective.removeEventListener(MouseInteraction.DOUBLE_CLICK, onMouseDoubleClick);
canvasInstanceDOM.perspective.removeEventListener(MouseInteraction.CLICK, onMouseClick);
document.removeEventListener('keydown', keyControls);
canvasInstanceDOM.perspective.removeEventListener('canvas.dragstart', onCanvasDragStart);
canvasInstanceDOM.perspective.removeEventListener('canvas.dragstop', onCanvasDragDone);
document.removeEventListener('keydown', keyControlsKeyDown);
document.removeEventListener('keyup', keyControlsKeyUp);
cancelAnimationFrame(animateId.current);
};
}, []);
const updateShapesView = (): void => {
(canvasInstance as Canvas3d).configureShapes({
opacity,
outlined,
outlineColor,
selectedOpacity,
colorBy,
});
};
const onContextMenu = (event: any): void => {
const { onUpdateContextMenu, onActivateObject } = props;
onActivateObject(event.detail.clientID);
onUpdateContextMenu(
event.detail.clientID !== null,
event.detail.clientX,
event.detail.clientY,
ContextMenuType.CANVAS_SHAPE,
);
};
const onResize = (): void => {
setViewSize({
type: 'resize',
});
};
const onCanvasObjectsGroupped = (event: any): void => {
const { onGroupAnnotations, onGroupObjects } = props;
onGroupObjects(false);
const { states } = event.detail;
onGroupAnnotations(jobInstance, frame, states);
};
useEffect(() => {
updateShapesView();
}, [opacity, outlined, outlineColor, selectedOpacity, colorBy]);
useEffect(() => {
const canvasInstanceDOM = canvasInstance.html() as ViewsDOM;
updateCanvas();
}, [frameData, annotations, curZLayer]);
canvasInstanceDOM.perspective.addEventListener('canvas.drawn', onCanvasShapeDrawn);
canvasInstanceDOM.perspective.addEventListener('canvas.selected', onCanvasShapeSelected);
canvasInstanceDOM.perspective.addEventListener('canvas.edited', onCanvasEditDone);
canvasInstanceDOM.perspective.addEventListener('canvas.contextmenu', onContextMenu);
canvasInstanceDOM.perspective.addEventListener('click', onCanvasClick);
canvasInstanceDOM.perspective.addEventListener('canvas.fit', onResize);
canvasInstanceDOM.perspective.addEventListener('canvas.groupped', onCanvasObjectsGroupped);
window.addEventListener('resize', onResize);
return () => {
canvasInstanceDOM.perspective.removeEventListener('canvas.drawn', onCanvasShapeDrawn);
canvasInstanceDOM.perspective.removeEventListener('canvas.selected', onCanvasShapeSelected);
canvasInstanceDOM.perspective.removeEventListener('canvas.edited', onCanvasEditDone);
canvasInstanceDOM.perspective.removeEventListener('canvas.contextmenu', onContextMenu);
canvasInstanceDOM.perspective.removeEventListener('click', onCanvasClick);
canvasInstanceDOM.perspective.removeEventListener('canvas.fit', onResize);
canvasInstanceDOM.perspective.removeEventListener('canvas.groupped', onCanvasObjectsGroupped);
window.removeEventListener('resize', onResize);
};
}, [frameData, annotations, activeLabelID, contextMenuVisibility]);
const screenKeyControl = (code: CAMERA_ACTION): void => {
const { canvasInstance } = props;
const screenKeyControl = (code: CameraAction): void => {
canvasInstance.keyControls(new KeyboardEvent('keydown', { code, altKey: true }));
};
@ -239,7 +406,8 @@ const CanvasWrapperComponent = (props: Props): ReactElement => {
<span className='cvat-canvas3d-perspective-arrow-directions'>
<CVATTooltip title='Arrow Up' placement='topRight'>
<button
onClick={() => screenKeyControl(CAMERA_ACTION.TILT_UP)}
data-cy='arrow-up'
onClick={() => screenKeyControl(CameraAction.TILT_UP)}
type='button'
className='cvat-canvas3d-perspective-arrow-directions-icons-up'
>
@ -249,7 +417,7 @@ const CanvasWrapperComponent = (props: Props): ReactElement => {
<br />
<CVATTooltip title='Arrow Left' placement='topRight'>
<button
onClick={() => screenKeyControl(CAMERA_ACTION.ROTATE_LEFT)}
onClick={() => screenKeyControl(CameraAction.ROTATE_LEFT)}
type='button'
className='cvat-canvas3d-perspective-arrow-directions-icons-bottom'
>
@ -258,7 +426,7 @@ const CanvasWrapperComponent = (props: Props): ReactElement => {
</CVATTooltip>
<CVATTooltip title='Arrow Bottom' placement='topRight'>
<button
onClick={() => screenKeyControl(CAMERA_ACTION.TILT_DOWN)}
onClick={() => screenKeyControl(CameraAction.TILT_DOWN)}
type='button'
className='cvat-canvas3d-perspective-arrow-directions-icons-bottom'
>
@ -267,7 +435,7 @@ const CanvasWrapperComponent = (props: Props): ReactElement => {
</CVATTooltip>
<CVATTooltip title='Arrow Right' placement='topRight'>
<button
onClick={() => screenKeyControl(CAMERA_ACTION.ROTATE_RIGHT)}
onClick={() => screenKeyControl(CameraAction.ROTATE_RIGHT)}
type='button'
className='cvat-canvas3d-perspective-arrow-directions-icons-bottom'
>
@ -281,7 +449,7 @@ const CanvasWrapperComponent = (props: Props): ReactElement => {
<span className='cvat-canvas3d-perspective-directions'>
<CVATTooltip title='Alt+U' placement='topLeft'>
<button
onClick={() => screenKeyControl(CAMERA_ACTION.MOVE_UP)}
onClick={() => screenKeyControl(CameraAction.MOVE_UP)}
type='button'
className='cvat-canvas3d-perspective-directions-icon'
>
@ -290,7 +458,7 @@ const CanvasWrapperComponent = (props: Props): ReactElement => {
</CVATTooltip>
<CVATTooltip title='Alt+I' placement='topLeft'>
<button
onClick={() => screenKeyControl(CAMERA_ACTION.ZOOM_IN)}
onClick={() => screenKeyControl(CameraAction.ZOOM_IN)}
type='button'
className='cvat-canvas3d-perspective-directions-icon'
>
@ -299,7 +467,7 @@ const CanvasWrapperComponent = (props: Props): ReactElement => {
</CVATTooltip>
<CVATTooltip title='Alt+O' placement='topLeft'>
<button
onClick={() => screenKeyControl(CAMERA_ACTION.MOVE_DOWN)}
onClick={() => screenKeyControl(CameraAction.MOVE_DOWN)}
type='button'
className='cvat-canvas3d-perspective-directions-icon'
>
@ -309,7 +477,7 @@ const CanvasWrapperComponent = (props: Props): ReactElement => {
<br />
<CVATTooltip title='Alt+J' placement='topLeft'>
<button
onClick={() => screenKeyControl(CAMERA_ACTION.MOVE_LEFT)}
onClick={() => screenKeyControl(CameraAction.MOVE_LEFT)}
type='button'
className='cvat-canvas3d-perspective-directions-icon'
>
@ -318,7 +486,7 @@ const CanvasWrapperComponent = (props: Props): ReactElement => {
</CVATTooltip>
<CVATTooltip title='Alt+K' placement='topLeft'>
<button
onClick={() => screenKeyControl(CAMERA_ACTION.ZOOM_OUT)}
onClick={() => screenKeyControl(CameraAction.ZOOM_OUT)}
type='button'
className='cvat-canvas3d-perspective-directions-icon'
>
@ -327,7 +495,7 @@ const CanvasWrapperComponent = (props: Props): ReactElement => {
</CVATTooltip>
<CVATTooltip title='Alt+L' placement='topLeft'>
<button
onClick={() => screenKeyControl(CAMERA_ACTION.MOVE_RIGHT)}
onClick={() => screenKeyControl(CameraAction.MOVE_RIGHT)}
type='button'
className='cvat-canvas3d-perspective-directions-icon'
>
@ -348,7 +516,7 @@ const CanvasWrapperComponent = (props: Props): ReactElement => {
handle={<span className='cvat-resizable-handle-horizontal' />}
onResize={(e: SyntheticEvent) => setViewSize({ type: ViewType.PERSPECTIVE, e })}
>
<div className='cvat-canvas3d-perspective'>
<div className='cvat-canvas3d-perspective' id='cvat-canvas3d-perspective'>
<div className='cvat-canvas-container cvat-canvas-container-overflow' ref={perspectiveView} />
<ArrowGroup />
<ControlGroup />

@ -8,10 +8,11 @@ import Icon from '@ant-design/icons';
import { CursorIcon } from 'icons';
import { ActiveControl } from 'reducers/interfaces';
import { Canvas } from 'cvat-canvas-wrapper';
import { Canvas3d } from 'cvat-canvas3d-wrapper';
import CVATTooltip from 'components/common/cvat-tooltip';
export interface Props {
canvasInstance: Canvas;
canvasInstance: Canvas | Canvas3d;
cursorShortkey: string;
activeControl: ActiveControl;
}

@ -7,6 +7,7 @@ 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/interfaces';
import { CubeIcon } from 'icons';
@ -15,7 +16,7 @@ import DrawShapePopoverContainer from 'containers/annotation-page/standard-works
import withVisibilityHandling from './handle-popover-visibility';
export interface Props {
canvasInstance: Canvas;
canvasInstance: Canvas | Canvas3d;
isDrawing: boolean;
disabled?: boolean;
}

@ -8,16 +8,14 @@ import Button from 'antd/lib/button';
import InputNumber from 'antd/lib/input-number';
import Radio, { RadioChangeEvent } from 'antd/lib/radio';
import Text from 'antd/lib/typography/Text';
import { Canvas, RectDrawingMethod, CuboidDrawingMethod } from 'cvat-canvas-wrapper';
import { Canvas3d } from 'cvat-canvas3d-wrapper';
import { RectDrawingMethod, CuboidDrawingMethod } from 'cvat-canvas-wrapper';
import { ShapeType } from 'reducers/interfaces';
import { DimensionType, ShapeType } from 'reducers/interfaces';
import { clamp } from 'utils/math';
import LabelSelector from 'components/label-selector/label-selector';
import CVATTooltip from 'components/common/cvat-tooltip';
interface Props {
canvasInstance: Canvas | Canvas3d;
shapeType: ShapeType;
labels: any[];
minimumPoints: number;
@ -32,6 +30,7 @@ interface Props {
onChangeCuboidDrawingMethod(event: RadioChangeEvent): void;
onDrawTrack(): void;
onDrawShape(): void;
jobInstance: any;
}
function DrawShapePopoverComponent(props: Props): JSX.Element {
@ -50,10 +49,10 @@ function DrawShapePopoverComponent(props: Props): JSX.Element {
onChangePoints,
onChangeRectDrawingMethod,
onChangeCuboidDrawingMethod,
canvasInstance,
jobInstance,
} = props;
const is2D = canvasInstance instanceof Canvas;
const is2D = jobInstance.task.dimension === DimensionType.DIM_2D;
return (
<div className='cvat-draw-shape-popover-content'>

@ -7,21 +7,29 @@ import Icon from '@ant-design/icons';
import { GroupIcon } from 'icons';
import { Canvas } from 'cvat-canvas-wrapper';
import { ActiveControl } from 'reducers/interfaces';
import { Canvas3d } from 'cvat-canvas3d-wrapper';
import { ActiveControl, DimensionType } from 'reducers/interfaces';
import CVATTooltip from 'components/common/cvat-tooltip';
export interface Props {
canvasInstance: Canvas;
canvasInstance: Canvas | Canvas3d;
activeControl: ActiveControl;
switchGroupShortcut: string;
resetGroupShortcut: string;
disabled?: boolean;
jobInstance?: any;
groupObjects(enabled: boolean): void;
}
function GroupControl(props: Props): JSX.Element {
const {
switchGroupShortcut, resetGroupShortcut, activeControl, canvasInstance, groupObjects, disabled,
switchGroupShortcut,
resetGroupShortcut,
activeControl,
canvasInstance,
groupObjects,
disabled,
jobInstance,
} = props;
const dynamicIconProps =
@ -43,7 +51,9 @@ function GroupControl(props: Props): JSX.Element {
};
const title = [
`Group shapes/tracks ${switchGroupShortcut}. `,
`Group shapes${
jobInstance && jobInstance.task.dimension === DimensionType.DIM_3D ? '' : '/tracks'
} ${switchGroupShortcut}. `,
`Select and press ${resetGroupShortcut} to reset a group.`,
].join(' ');

@ -8,10 +8,11 @@ import Icon from '@ant-design/icons';
import { MoveIcon } from 'icons';
import { ActiveControl } from 'reducers/interfaces';
import { Canvas } from 'cvat-canvas-wrapper';
import { Canvas3d } from 'cvat-canvas3d-wrapper';
import CVATTooltip from 'components/common/cvat-tooltip';
export interface Props {
canvasInstance: Canvas;
canvasInstance: Canvas | Canvas3d;
activeControl: ActiveControl;
}

@ -14,6 +14,7 @@ import LabelSelector from 'components/label-selector/label-selector';
import ItemMenu from './object-item-menu';
interface Props {
jobInstance: any;
readonly: boolean;
clientID: number;
serverID: number | undefined;
@ -76,6 +77,7 @@ function ItemTopComponent(props: Props): JSX.Element {
toForeground,
resetCuboidPerspective,
activateTracking,
jobInstance,
} = props;
const [menuVisible, setMenuVisible] = useState(false);
@ -124,6 +126,7 @@ function ItemTopComponent(props: Props): JSX.Element {
onVisibleChange={changeMenuVisible}
placement='bottomLeft'
overlay={ItemMenu({
jobInstance,
readonly,
serverID,
locked,

@ -19,7 +19,9 @@ import {
BackgroundIcon, ForegroundIcon, ResetPerspectiveIcon, ColorizeIcon,
} from 'icons';
import CVATTooltip from 'components/common/cvat-tooltip';
import { ObjectType, ShapeType, ColorBy } from 'reducers/interfaces';
import {
ObjectType, ShapeType, ColorBy, DimensionType,
} from 'reducers/interfaces';
import ColorPicker from './color-picker';
interface Props {
@ -49,6 +51,7 @@ interface Props {
resetCuboidPerspective(): void;
changeColorPickerVisible(visible: boolean): void;
activateTracking(): void;
jobInstance: any;
}
interface ItemProps {
@ -227,23 +230,25 @@ function RemoveItem(props: ItemProps): JSX.Element {
export default function ItemMenu(props: Props): JSX.Element {
const {
readonly, shapeType, objectType, colorBy,
readonly, shapeType, objectType, colorBy, jobInstance,
} = props;
const is2D = jobInstance.task.dimension === DimensionType.DIM_2D;
return (
<Menu className='cvat-object-item-menu' selectable={false}>
<CreateURLItem toolProps={props} />
{!readonly && <MakeCopyItem toolProps={props} />}
{!readonly && <PropagateItem toolProps={props} />}
{!readonly && objectType === ObjectType.TRACK && shapeType === ShapeType.RECTANGLE && (
{is2D && !readonly && objectType === ObjectType.TRACK && shapeType === ShapeType.RECTANGLE && (
<TrackingItem toolProps={props} />
)}
{!readonly && [ShapeType.POLYGON, ShapeType.POLYLINE, ShapeType.CUBOID].includes(shapeType) && (
{is2D && !readonly && [ShapeType.POLYGON, ShapeType.POLYLINE, ShapeType.CUBOID].includes(shapeType) && (
<SwitchOrientationItem toolProps={props} />
)}
{!readonly && shapeType === ShapeType.CUBOID && <ResetPerspectiveItem toolProps={props} />}
{!readonly && objectType !== ObjectType.TAG && <ToBackgroundItem toolProps={props} />}
{!readonly && objectType !== ObjectType.TAG && <ToForegroundItem toolProps={props} />}
{is2D && !readonly && shapeType === ShapeType.CUBOID && <ResetPerspectiveItem toolProps={props} />}
{is2D && objectType !== ObjectType.TAG && <ToBackgroundItem toolProps={props} />}
{is2D && !readonly && objectType !== ObjectType.TAG && <ToForegroundItem toolProps={props} />}
{[ColorBy.INSTANCE, ColorBy.GROUP].includes(colorBy) && <SwitchColorItem toolProps={props} />}
{!readonly && <RemoveItem toolProps={props} />}
</Menu>

@ -1,4 +1,4 @@
// Copyright (C) 2020 Intel Corporation
// Copyright (C) 2021 Intel Corporation
//
// SPDX-License-Identifier: MIT
@ -22,11 +22,10 @@ interface Props {
attrValues: Record<number, string>;
color: string;
colorBy: ColorBy;
labels: any[];
attributes: any[];
collapsed: boolean;
jobInstance: any;
activate(): void;
copy(): void;
propagate(): void;
@ -76,12 +75,10 @@ function ObjectItemComponent(props: Props): JSX.Element {
labelID,
color,
colorBy,
attributes,
labels,
collapsed,
normalizedKeyMap,
activate,
copy,
propagate,
@ -96,6 +93,7 @@ function ObjectItemComponent(props: Props): JSX.Element {
collapse,
resetCuboidPerspective,
activateTracking,
jobInstance,
} = props;
const type =
@ -117,6 +115,7 @@ function ObjectItemComponent(props: Props): JSX.Element {
style={{ backgroundColor: `${color}88` }}
>
<ItemBasics
jobInstance={jobInstance}
readonly={readonly}
serverID={serverID}
clientID={clientID}

@ -29,7 +29,7 @@
> .ant-collapse-content {
background: $background-color-2;
border-bottom: none;
height: 230px;
padding-bottom: $grid-unit-size * 3;
> .ant-collapse-content-box {
padding: 10px;

@ -6,31 +6,148 @@ import React from 'react';
import Layout from 'antd/lib/layout';
import { ActiveControl } from 'reducers/interfaces';
import { Canvas3d as Canvas } from 'cvat-canvas3d-wrapper';
import CursorControl from './cursor-control';
import MoveControl from './move-control';
import DrawCuboidControl from './draw-cuboid-control';
import MoveControl, {
Props as MoveControlProps,
} from 'components/annotation-page/standard-workspace/controls-side-bar/move-control';
import CursorControl, {
Props as CursorControlProps,
} from 'components/annotation-page/standard-workspace/controls-side-bar/cursor-control';
import DrawCuboidControl, {
Props as DrawCuboidControlProps,
} from 'components/annotation-page/standard-workspace/controls-side-bar/draw-cuboid-control';
import GroupControl, {
Props as GroupControlProps,
} from 'components/annotation-page/standard-workspace/controls-side-bar/group-control';
import GlobalHotKeys, { KeyMap } from 'utils/mousetrap-react';
import ControlVisibilityObserver from 'components/annotation-page/standard-workspace/controls-side-bar/control-visibility-observer';
interface Props {
keyMap: KeyMap;
canvasInstance: Canvas;
activeControl: ActiveControl;
normalizedKeyMap: Record<string, string>;
labels: any[];
jobInstance: any;
repeatDrawShape(): void;
redrawShape(): void;
pasteShape(): void;
groupObjects(enabled: boolean): void;
resetGroup(): void;
}
const ObservedCursorControl = ControlVisibilityObserver<CursorControlProps>(CursorControl);
const ObservedMoveControl = ControlVisibilityObserver<MoveControlProps>(MoveControl);
const ObservedDrawCuboidControl = ControlVisibilityObserver<DrawCuboidControlProps>(DrawCuboidControl);
const ObservedGroupControl = ControlVisibilityObserver<GroupControlProps>(GroupControl);
export default function ControlsSideBarComponent(props: Props): JSX.Element {
const { canvasInstance, activeControl, normalizedKeyMap } = props;
const {
canvasInstance,
pasteShape,
activeControl,
normalizedKeyMap,
keyMap,
labels,
redrawShape,
repeatDrawShape,
groupObjects,
resetGroup,
jobInstance,
} = props;
const preventDefault = (event: KeyboardEvent | undefined): void => {
if (event) {
event.preventDefault();
}
};
let subKeyMap: any = {
CANCEL: keyMap.CANCEL,
};
let handlers: any = {
CANCEL: (event: KeyboardEvent | undefined) => {
preventDefault(event);
if (activeControl !== ActiveControl.CURSOR) {
canvasInstance.cancel();
}
},
};
if (labels.length) {
handlers = {
...handlers,
PASTE_SHAPE: (event: KeyboardEvent | undefined) => {
preventDefault(event);
canvasInstance.cancel();
pasteShape();
},
SWITCH_DRAW_MODE: (event: KeyboardEvent | undefined) => {
preventDefault(event);
const drawing = [ActiveControl.DRAW_CUBOID].includes(activeControl);
if (!drawing) {
canvasInstance.cancel();
if (event && event.shiftKey) {
redrawShape();
} else {
repeatDrawShape();
}
} else {
canvasInstance.draw({ enabled: false });
}
},
SWITCH_GROUP_MODE: (event: KeyboardEvent | undefined) => {
preventDefault(event);
const grouping = activeControl === ActiveControl.GROUP;
if (!grouping) {
canvasInstance.cancel();
}
canvasInstance.group({ enabled: !grouping });
groupObjects(!grouping);
},
RESET_GROUP: (event: KeyboardEvent | undefined) => {
preventDefault(event);
const grouping = activeControl === ActiveControl.GROUP;
if (!grouping) {
return;
}
resetGroup();
canvasInstance.group({ enabled: false });
groupObjects(false);
},
};
subKeyMap = {
...subKeyMap,
PASTE_SHAPE: keyMap.PASTE_SHAPE,
SWITCH_DRAW_MODE: keyMap.SWITCH_DRAW_MODE,
SWITCH_GROUP_MODE: keyMap.SWITCH_GROUP_MODE,
RESET_GROUP: keyMap.RESET_GROUP,
};
}
return (
<Layout.Sider className='cvat-canvas-controls-sidebar' theme='light' width={44}>
<MoveControl canvasInstance={canvasInstance} activeControl={activeControl} />
<CursorControl
<GlobalHotKeys keyMap={subKeyMap} handlers={handlers} />
<ObservedCursorControl
cursorShortkey={normalizedKeyMap.CANCEL}
canvasInstance={canvasInstance}
activeControl={activeControl}
/>
<DrawCuboidControl
<ObservedMoveControl canvasInstance={canvasInstance} activeControl={activeControl} />
<ObservedDrawCuboidControl
canvasInstance={canvasInstance}
isDrawing={activeControl === ActiveControl.DRAW_CUBOID}
disabled={!labels.length}
/>
<ObservedGroupControl
switchGroupShortcut={normalizedKeyMap.SWITCH_GROUP_MODE}
resetGroupShortcut={normalizedKeyMap.RESET_GROUP}
canvasInstance={canvasInstance}
activeControl={activeControl}
groupObjects={groupObjects}
disabled={!labels.length}
jobInstance={jobInstance}
/>
</Layout.Sider>
);

@ -1,36 +0,0 @@
// Copyright (C) 2021 Intel Corporation
//
// SPDX-License-Identifier: MIT
import React from 'react';
import Icon from '@ant-design/icons';
import { CursorIcon } from 'icons';
import { ActiveControl } from 'reducers/interfaces';
import { Canvas3d as Canvas } from 'cvat-canvas3d-wrapper';
import CVATTooltip from 'components/common/cvat-tooltip';
interface Props {
canvasInstance: Canvas;
cursorShortkey: string;
activeControl: ActiveControl;
}
function CursorControl(props: Props): JSX.Element {
const { activeControl, cursorShortkey, canvasInstance } = props;
return (
<CVATTooltip title={`Cursor ${cursorShortkey}`} placement='right'>
<Icon
component={CursorIcon}
className={[
'cvat-cursor-control',
activeControl === ActiveControl.CURSOR ? 'cvat-active-canvas-control ' : '',
].join(' ')}
onClick={activeControl !== ActiveControl.CURSOR ? (): void => canvasInstance.cancel() : undefined}
/>
</CVATTooltip>
);
}
export default React.memo(CursorControl);

@ -1,55 +0,0 @@
// Copyright (C) 2021 Intel Corporation
//
// SPDX-License-Identifier: MIT
import React from 'react';
import Popover from 'antd/lib/popover';
import Icon from '@ant-design/icons';
import { Canvas3d as Canvas } from 'cvat-canvas3d-wrapper';
import { ShapeType } from 'reducers/interfaces';
import { CubeIcon } from 'icons';
import DrawShapePopoverContainer from 'containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover';
interface Props {
canvasInstance: Canvas;
isDrawing: boolean;
}
function DrawPolygonControl(props: Props): JSX.Element {
const { canvasInstance, isDrawing } = props;
const dynamcPopoverPros = isDrawing ?
{
overlayStyle: {
display: 'none',
},
} :
{};
const dynamicIconProps = isDrawing ?
{
className: 'cvat-draw-cuboid-control cvat-active-canvas-control',
onClick: (): void => {
canvasInstance.draw({ enabled: false });
},
} :
{
className: 'cvat-draw-cuboid-control',
};
return (
<Popover
{...dynamcPopoverPros}
overlayClassName='cvat-draw-shape-popover'
placement='right'
content={<DrawShapePopoverContainer shapeType={ShapeType.CUBOID} />}
>
<Icon {...dynamicIconProps} component={CubeIcon} />
</Popover>
);
}
export default React.memo(DrawPolygonControl);

@ -1,34 +0,0 @@
// Copyright (C) 2021 Intel Corporation
//
// SPDX-License-Identifier: MIT
import React from 'react';
import Icon from '@ant-design/icons';
import { MoveIcon } from 'icons';
import { ActiveControl } from 'reducers/interfaces';
import CVATTooltip from 'components/common/cvat-tooltip';
import { Canvas3d as Canvas } from 'cvat-canvas3d-wrapper';
interface Props {
canvasInstance: Canvas;
activeControl: ActiveControl;
}
function MoveControl(props: Props): JSX.Element {
const { activeControl } = props;
return (
<CVATTooltip title='Move the image' placement='right'>
<Icon
component={MoveIcon}
className={[
'cvat-move-control',
activeControl === ActiveControl.DRAG_CANVAS ? 'cvat-active-canvas-control' : '',
].join(' ')}
/>
</CVATTooltip>
);
}
export default React.memo(MoveControl);

@ -0,0 +1,38 @@
// Copyright (C) 2021 Intel Corporation
//
// SPDX-License-Identifier: MIT
import React from 'react';
import CameraIcon from '@ant-design/icons/CameraOutlined';
import CVATTooltip from 'components/common/cvat-tooltip';
import { Canvas3d } from 'cvat-canvas3d-wrapper';
import { Canvas } from 'cvat-canvas-wrapper';
import { ActiveControl } from 'reducers/interfaces';
interface Props {
canvasInstance: Canvas3d | Canvas;
activeControl: ActiveControl;
hideShowContextImage: (hidden: boolean) => void;
contextImageHide: boolean;
}
function PhotoContextControl(props: Props): JSX.Element {
const { activeControl, contextImageHide, hideShowContextImage } = props;
return (
<CVATTooltip title='Photo context show/hide' placement='right'>
<CameraIcon
className={`cvat-context-image-control
cvat-control-side-bar-icon-size ${
activeControl === ActiveControl.PHOTO_CONTEXT ? 'cvat-active-canvas-control' : ''
}`}
onClick={(): void => {
hideShowContextImage(!contextImageHide);
}}
/>
</CVATTooltip>
);
}
export default React.memo(PhotoContextControl);

@ -8,12 +8,21 @@ import Layout from 'antd/lib/layout';
import CanvasWrapperContainer from 'containers/annotation-page/canvas/canvas-wrapper3D';
import ControlsSideBarContainer from 'containers/annotation-page/standard3D-workspace/controls-side-bar/controls-side-bar';
import ObjectSideBarComponent from 'components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar';
import ObjectsListContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/objects-list';
import PropagateConfirmContainer from 'containers/annotation-page/standard-workspace/propagate-confirm';
import CanvasContextMenuContainer from 'containers/annotation-page/canvas/canvas-context-menu';
import CanvasPointContextMenuComponent from 'components/annotation-page/canvas/canvas-point-context-menu';
export default function StandardWorkspace3DComponent(): JSX.Element {
return (
<Layout hasSider className='cvat-standard-workspace'>
<ControlsSideBarContainer />
<CanvasWrapperContainer />
<ObjectSideBarComponent objectsList={<ObjectsListContainer />} />
<PropagateConfirmContainer />
<CanvasContextMenuContainer />
<CanvasPointContextMenuComponent />
</Layout>
);
}

@ -11,6 +11,7 @@ import { MenuInfo } from 'rc-menu/lib/interface';
import DumpSubmenu from 'components/actions-menu/dump-submenu';
import LoadSubmenu from 'components/actions-menu/load-submenu';
import ExportSubmenu from 'components/actions-menu/export-submenu';
import { DimensionType } from '../../../reducers/interfaces';
interface Props {
taskMode: string;
@ -158,6 +159,8 @@ export default function AnnotationMenuComponent(props: Props): JSX.Element {
}
}
const is2d = jobInstance.task.dimension === DimensionType.DIM_2D;
return (
<Menu onClick={onClickMenuWrapper} className='cvat-annotation-menu' selectable={false}>
{DumpSubmenu({
@ -189,7 +192,7 @@ export default function AnnotationMenuComponent(props: Props): JSX.Element {
Open the task
</a>
</Menu.Item>
{jobStatus === 'annotation' && <Menu.Item key={Actions.REQUEST_REVIEW}>Request a review</Menu.Item>}
{jobStatus === 'annotation' && is2d && <Menu.Item key={Actions.REQUEST_REVIEW}>Request a review</Menu.Item>}
{jobStatus === 'annotation' && <Menu.Item key={Actions.FINISH_JOB}>Finish the job</Menu.Item>}
{jobStatus === 'validation' && isReviewer && (
<Menu.Item key={Actions.SUBMIT_REVIEW}>Submit the review</Menu.Item>

@ -25,11 +25,8 @@ interface Props {
workspace: Workspace;
predictor: PredictorState;
isTrainingActive: boolean;
showStatistics(): void;
switchPredictor(predictorEnabled: boolean): void;
showFilters(): void;
changeWorkspace(workspace: Workspace): void;

@ -11,6 +11,7 @@ import Spin from 'antd/lib/spin';
import Text from 'antd/lib/typography/Text';
import CVATTooltip from 'components/common/cvat-tooltip';
import { DimensionType } from 'reducers/interfaces';
interface Props {
collecting: boolean;
@ -24,13 +25,25 @@ interface Props {
jobStatus: string;
savingJobStatus: boolean;
closeStatistics(): void;
jobInstance: any;
}
export default function StatisticsModalComponent(props: Props): JSX.Element {
const {
collecting, data, visible, assignee, reviewer, startFrame, stopFrame, bugTracker, closeStatistics,
collecting,
data,
visible,
assignee,
reviewer,
startFrame,
stopFrame,
bugTracker,
closeStatistics,
jobInstance,
} = props;
const is2D = jobInstance.task.dimension === DimensionType.DIM_2D;
const baseProps = {
cancelButtonProps: { style: { display: 'none' } },
okButtonProps: { style: { width: 100 } },
@ -77,7 +90,7 @@ export default function StatisticsModalComponent(props: Props): JSX.Element {
});
const makeShapesTracksTitle = (title: string): JSX.Element => (
<CVATTooltip title='Shapes / Tracks'>
<CVATTooltip title={is2D ? 'Shapes / Tracks' : 'Shapes'}>
<Text strong style={{ marginRight: 5 }}>
{title}
</Text>
@ -138,6 +151,24 @@ export default function StatisticsModalComponent(props: Props): JSX.Element {
},
];
const columns3D = [
{
title: <Text strong> Label </Text>,
dataIndex: 'label',
key: 'label',
},
{
title: makeShapesTracksTitle('Cuboids'),
dataIndex: 'cuboid',
key: 'cuboid',
},
{
title: <Text strong> Total </Text>,
dataIndex: 'total',
key: 'total',
},
];
return (
<Modal {...baseProps}>
<div className='cvat-job-info-modal-window'>
@ -191,7 +222,13 @@ export default function StatisticsModalComponent(props: Props): JSX.Element {
<Row justify='space-around' className='cvat-job-info-statistics'>
<Col span={24}>
<Text className='cvat-text'>Annotations statistics</Text>
<Table scroll={{ y: 400 }} bordered pagination={false} columns={columns} dataSource={rows} />
<Table
scroll={{ y: 400 }}
bordered
pagination={false}
columns={is2D ? columns : columns3D}
dataSource={rows}
/>
</Col>
</Row>
</div>

@ -13,7 +13,7 @@ const core = getCore();
type Props = {
value: number | null;
onSelect: (id: number | null) => void;
filter?: (value: Project, index: number, array: Project[]) => unknown
filter?: (value: Project, index: number, array: Project[]) => unknown;
};
type Project = {

@ -36,14 +36,10 @@ export default function LabelMapperItem(props: LabelMapperItemProps): JSX.Elemen
<Col span={6}>
{label.name.length > 12 ? (
<CVATTooltip overlay={label.name}>
<Tag color={label.color}>
{`${label.name.slice(0, 12)}...`}
</Tag>
<Tag color={label.color}>{`${label.name.slice(0, 12)}...`}</Tag>
</CVATTooltip>
) : (
<Tag color={label.color}>
{label.name}
</Tag>
<Tag color={label.color}>{label.name}</Tag>
)}
<ArrowRightOutlined />
</Col>
@ -57,13 +53,13 @@ export default function LabelMapperItem(props: LabelMapperItemProps): JSX.Elemen
newLabelName: _value as string,
})}
>
{projectLabels?.filter((_label) => (
!labelNames.includes(_label.name)
)).map((_label) => (
<Select.Option key={_label.id} value={_label.name}>
{_label.name}
</Select.Option>
))}
{projectLabels
?.filter((_label) => !labelNames.includes(_label.name))
.map((_label) => (
<Select.Option key={_label.id} value={_label.name}>
{_label.name}
</Select.Option>
))}
</Select>
</Col>
<Col>

@ -90,12 +90,8 @@ export default function MoveTaskModal(): JSX.Element {
const { labels } = _project[0];
const labelValues: { [key: string]: LabelMapperItemValue } = {};
Object.entries(labelMap).forEach(([id, label]) => {
const taskLabelName = task.labels.filter(
(_label: any) => (_label.id === label.labelId),
)[0].name;
const [autoNewLabel] = labels.filter((_label: any) => (
_label.name === taskLabelName
));
const taskLabelName = task.labels.filter((_label: any) => _label.id === label.labelId)[0].name;
const [autoNewLabel] = labels.filter((_label: any) => _label.name === taskLabelName);
labelValues[id] = {
labelId: label.labelId,
newLabelName: autoNewLabel ? autoNewLabel.name : null,
@ -142,7 +138,8 @@ export default function MoveTaskModal(): JSX.Element {
</Col>
</Row>
<Divider orientation='left'>Label mapping</Divider>
{!!Object.keys(labelMap).length && !taskUpdating &&
{!!Object.keys(labelMap).length &&
!taskUpdating &&
task?.labels.map((label: any) => (
<LabelMapperItem
label={label}

@ -50,10 +50,11 @@ import {
} from 'reducers/interfaces';
import { Canvas } from 'cvat-canvas-wrapper';
import { Canvas3d } from 'cvat-canvas3d-wrapper';
interface StateToProps {
sidebarCollapsed: boolean;
canvasInstance: Canvas;
canvasInstance: Canvas | Canvas3d;
jobInstance: any;
activatedStateID: number | null;
activatedAttributeID: number | null;

@ -5,38 +5,137 @@
import { connect } from 'react-redux';
import CanvasWrapperComponent from 'components/annotation-page/canvas/canvas-wrapper3D';
import { confirmCanvasReady, resetCanvas } from 'actions/annotation-actions';
import {
activateObject,
confirmCanvasReady,
createAnnotationsAsync,
dragCanvas,
editShape,
groupAnnotationsAsync,
groupObjects,
resetCanvas,
shapeDrawn,
updateAnnotationsAsync,
updateCanvasContextMenu,
} from 'actions/annotation-actions';
import { CombinedState } from 'reducers/interfaces';
import {
ActiveControl,
ColorBy,
CombinedState,
ContextMenuType,
GridColor,
ObjectType,
Workspace,
} from 'reducers/interfaces';
import { Canvas3d } from 'cvat-canvas3d-wrapper';
import { Canvas } from 'cvat-canvas-wrapper';
import { KeyMap } from '../../../utils/mousetrap-react';
interface StateToProps {
canvasInstance: Canvas3d;
canvasInstance: Canvas3d | Canvas;
jobInstance: any;
frameData: any;
curZLayer: number;
annotations: any[];
sidebarCollapsed: boolean;
activatedStateID: number | null;
activatedAttributeID: number | null;
selectedStatesID: number[];
frameIssues: any[] | null;
frameAngle: number;
frameFetching: boolean;
frame: number;
opacity: number;
colorBy: ColorBy;
selectedOpacity: number;
outlined: boolean;
outlineColor: string;
showBitmap: boolean;
showProjections: boolean;
grid: boolean;
gridSize: number;
gridColor: GridColor;
gridOpacity: number;
activeLabelID: number;
activeObjectType: ObjectType;
brightnessLevel: number;
contrastLevel: number;
saturationLevel: number;
resetZoom: boolean;
aamZoomMargin: number;
contextMenuVisibility: boolean;
showObjectsTextAlways: boolean;
showAllInterpolationTracks: boolean;
workspace: Workspace;
minZLayer: number;
maxZLayer: number;
automaticBordering: boolean;
switchableAutomaticBordering: boolean;
keyMap: KeyMap;
canvasBackgroundColor: string;
}
interface DispatchToProps {
onDragCanvas: (enabled: boolean) => void;
onSetupCanvas(): void;
onGroupObjects: (enabled: boolean) => void;
onResetCanvas(): void;
onCreateAnnotations(sessionInstance: any, frame: number, states: any[]): void;
onUpdateAnnotations(states: any[]): void;
onGroupAnnotations(sessionInstance: any, frame: number, states: any[]): void;
onActivateObject: (activatedStateID: number | null) => void;
onShapeDrawn: () => void;
onEditShape: (enabled: boolean) => void;
onUpdateContextMenu(visible: boolean, left: number, top: number, type: ContextMenuType, pointID?: number): void;
}
function mapStateToProps(state: CombinedState): StateToProps {
const {
annotation: {
canvas: { instance: canvasInstance },
canvas: {
activeControl,
instance: canvasInstance,
contextMenu: { visible: contextMenuVisibility },
},
drawing: { activeLabelID, activeObjectType },
job: { instance: jobInstance },
player: {
frame: { data: frameData },
frame: { data: frameData, number: frame, fetching: frameFetching },
frameAngles,
},
annotations: {
states: annotations,
zLayer: { cur: curZLayer },
activatedStateID,
activatedAttributeID,
selectedStatesID,
zLayer: { cur: curZLayer, min: minZLayer, max: maxZLayer },
},
sidebarCollapsed,
workspace,
},
settings: {
player: {
canvasBackgroundColor,
grid,
gridSize,
gridColor,
gridOpacity,
brightnessLevel,
contrastLevel,
saturationLevel,
resetZoom,
},
workspace: {
aamZoomMargin, showObjectsTextAlways, showAllInterpolationTracks, automaticBordering,
},
shapes: {
opacity, colorBy, selectedOpacity, outlined, outlineColor, showBitmap, showProjections,
},
},
review: { frameIssues, issuesHidden },
shortcuts: { keyMap },
} = state;
return {
@ -44,18 +143,95 @@ function mapStateToProps(state: CombinedState): StateToProps {
jobInstance,
frameData,
curZLayer,
contextMenuVisibility,
annotations,
sidebarCollapsed,
frameIssues:
issuesHidden || ![Workspace.REVIEW_WORKSPACE, Workspace.STANDARD].includes(workspace) ? null : frameIssues,
frameAngle: frameAngles[frame - jobInstance.startFrame],
frameFetching,
frame,
activatedStateID,
activatedAttributeID,
selectedStatesID,
opacity,
colorBy,
selectedOpacity,
outlined,
outlineColor,
showBitmap,
showProjections,
grid,
gridSize,
gridColor,
gridOpacity,
activeLabelID,
activeObjectType,
brightnessLevel,
contrastLevel,
saturationLevel,
resetZoom,
aamZoomMargin,
showObjectsTextAlways,
showAllInterpolationTracks,
minZLayer,
maxZLayer,
automaticBordering,
workspace,
keyMap,
canvasBackgroundColor,
switchableAutomaticBordering:
activeControl === ActiveControl.DRAW_POLYGON ||
activeControl === ActiveControl.DRAW_POLYLINE ||
activeControl === ActiveControl.EDIT,
};
}
function mapDispatchToProps(dispatch: any): DispatchToProps {
return {
onDragCanvas(enabled: boolean): void {
dispatch(dragCanvas(enabled));
},
onSetupCanvas(): void {
dispatch(confirmCanvasReady());
},
onResetCanvas(): void {
dispatch(resetCanvas());
},
onGroupObjects(enabled: boolean): void {
dispatch(groupObjects(enabled));
},
onCreateAnnotations(sessionInstance: any, frame: number, states: any[]): void {
dispatch(createAnnotationsAsync(sessionInstance, frame, states));
},
onShapeDrawn(): void {
dispatch(shapeDrawn());
},
onGroupAnnotations(sessionInstance: any, frame: number, states: any[]): void {
dispatch(groupAnnotationsAsync(sessionInstance, frame, states));
},
onActivateObject(activatedStateID: number | null): void {
if (activatedStateID === null) {
dispatch(updateCanvasContextMenu(false, 0, 0));
}
dispatch(activateObject(activatedStateID, null));
},
onEditShape(enabled: boolean): void {
dispatch(editShape(enabled));
},
onUpdateAnnotations(states: any[]): void {
dispatch(updateAnnotationsAsync(states));
},
onUpdateContextMenu(
visible: boolean,
left: number,
top: number,
type: ContextMenuType,
pointID?: number,
): void {
dispatch(updateCanvasContextMenu(visible, left, top, pointID, type));
},
};
}

@ -9,6 +9,7 @@ import { RadioChangeEvent } from 'antd/lib/radio';
import { CombinedState, ShapeType, ObjectType } from 'reducers/interfaces';
import { rememberObject } from 'actions/annotation-actions';
import { Canvas, RectDrawingMethod, CuboidDrawingMethod } from 'cvat-canvas-wrapper';
import { Canvas3d } from 'cvat-canvas3d-wrapper';
import DrawShapePopoverComponent from 'components/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover';
interface OwnProps {
@ -27,9 +28,10 @@ interface DispatchToProps {
interface StateToProps {
normalizedKeyMap: Record<string, string>;
canvasInstance: Canvas;
canvasInstance: Canvas | Canvas3d;
shapeType: ShapeType;
labels: any[];
jobInstance: any;
}
function mapDispatchToProps(dispatch: any): DispatchToProps {
@ -58,7 +60,7 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps {
const {
annotation: {
canvas: { instance: canvasInstance },
job: { labels },
job: { labels, instance: jobInstance },
},
shortcuts: { normalizedKeyMap },
} = state;
@ -68,6 +70,7 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps {
canvasInstance,
labels,
normalizedKeyMap,
jobInstance,
};
}
@ -165,12 +168,12 @@ class DrawShapePopoverContainer extends React.PureComponent<Props, State> {
} = this.state;
const {
normalizedKeyMap, labels, shapeType, canvasInstance,
normalizedKeyMap, labels, shapeType, jobInstance,
} = this.props;
return (
<DrawShapePopoverComponent
canvasInstance={canvasInstance}
jobInstance={jobInstance}
labels={labels}
shapeType={shapeType}
minimumPoints={this.minimumPoints}

@ -1,4 +1,4 @@
// Copyright (C) 2020 Intel Corporation
// Copyright (C) 2021 Intel Corporation
//
// SPDX-License-Identifier: MIT
@ -24,6 +24,8 @@ import {
import ObjectStateItemComponent from 'components/annotation-page/standard-workspace/objects-side-bar/object-item';
import { ToolsControlComponent } from 'components/annotation-page/standard-workspace/controls-side-bar/tools-control';
import { shift } from 'utils/math';
import { Canvas } from 'cvat-canvas-wrapper';
import { Canvas3d } from 'cvat-canvas3d-wrapper';
interface OwnProps {
readonly: boolean;
@ -47,6 +49,7 @@ interface StateToProps {
maxZLayer: number;
normalizedKeyMap: Record<string, string>;
aiToolsRef: MutableRefObject<ToolsControlComponent>;
canvasInstance: Canvas | Canvas3d;
}
interface DispatchToProps {
@ -72,7 +75,7 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps {
player: {
frame: { number: frameNumber },
},
canvas: { ready, activeControl },
canvas: { instance: canvasInstance, ready, activeControl },
aiToolsRef,
},
settings: {
@ -103,6 +106,7 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps {
maxZLayer,
normalizedKeyMap,
aiToolsRef,
canvasInstance,
};
}
@ -222,11 +226,14 @@ class ObjectItemContainer extends React.PureComponent<Props> {
private activate = (): void => {
const {
objectState, ready, activeControl, activateObject,
objectState, ready, activeControl, activateObject, canvasInstance,
} = this.props;
if (ready && activeControl === ActiveControl.CURSOR) {
activateObject(objectState.clientID);
if (canvasInstance instanceof Canvas3d) {
canvasInstance.activate(objectState.clientID);
}
}
};
@ -343,6 +350,7 @@ class ObjectItemContainer extends React.PureComponent<Props> {
colorBy,
normalizedKeyMap,
readonly,
jobInstance,
} = this.props;
let stateColor = '';
@ -356,6 +364,7 @@ class ObjectItemContainer extends React.PureComponent<Props> {
return (
<ObjectStateItemComponent
jobInstance={jobInstance}
readonly={readonly}
activated={activated}
objectType={objectState.objectType}

@ -3,24 +3,41 @@
// SPDX-License-Identifier: MIT
import { connect } from 'react-redux';
import { KeyMap } from 'utils/mousetrap-react';
import { Canvas } from 'cvat-canvas-wrapper';
import { Canvas3d } from 'cvat-canvas3d-wrapper';
import {
groupObjects,
pasteShapeAsync,
redrawShapeAsync,
repeatDrawShapeAsync,
resetAnnotationsGroup,
} from 'actions/annotation-actions';
import ControlsSideBarComponent from 'components/annotation-page/standard3D-workspace/controls-side-bar/controls-side-bar';
import { ActiveControl, CombinedState } from 'reducers/interfaces';
interface StateToProps {
canvasInstance: Canvas;
canvasInstance: Canvas | Canvas3d;
activeControl: ActiveControl;
keyMap: KeyMap;
normalizedKeyMap: Record<string, string>;
loaded: boolean;
labels: any[];
jobInstance: any;
}
interface DispatchToProps {
repeatDrawShape(): void;
redrawShape(): void;
pasteShape(): void;
resetGroup(): void;
groupObjects(enabled: boolean): void;
}
function mapStateToProps(state: CombinedState): StateToProps {
const {
annotation: {
canvas: { instance: canvasInstance, activeControl },
job: { labels, instance: jobInstance },
},
shortcuts: { keyMap, normalizedKeyMap },
} = state;
@ -30,7 +47,29 @@ function mapStateToProps(state: CombinedState): StateToProps {
activeControl,
normalizedKeyMap,
keyMap,
labels,
jobInstance,
};
}
function dispatchToProps(dispatch: any): DispatchToProps {
return {
repeatDrawShape(): void {
dispatch(repeatDrawShapeAsync());
},
redrawShape(): void {
dispatch(redrawShapeAsync());
},
pasteShape(): void {
dispatch(pasteShapeAsync());
},
groupObjects(enabled: boolean): void {
dispatch(groupObjects(enabled));
},
resetGroup(): void {
dispatch(resetAnnotationsGroup());
},
};
}
export default connect(mapStateToProps)(ControlsSideBarComponent);
export default connect(mapStateToProps, dispatchToProps)(ControlsSideBarComponent);

@ -1,4 +1,4 @@
// Copyright (C) 2020 Intel Corporation
// Copyright (C) 2020-2021 Intel Corporation
//
// SPDX-License-Identifier: MIT
@ -61,6 +61,7 @@ class StatisticsModalContainer extends React.PureComponent<Props> {
return (
<StatisticsModalComponent
jobInstance={jobInstance}
collecting={collecting}
data={data}
visible={visible}

@ -28,6 +28,7 @@ import {
} from 'actions/annotation-actions';
import AnnotationTopBarComponent from 'components/annotation-page/top-bar/top-bar';
import { Canvas } from 'cvat-canvas-wrapper';
import { Canvas3d } from 'cvat-canvas3d-wrapper';
import {
CombinedState, FrameSpeed, Workspace, PredictorState,
} from 'reducers/interfaces';
@ -51,7 +52,7 @@ interface StateToProps {
workspace: Workspace;
keyMap: KeyMap;
normalizedKeyMap: Record<string, string>;
canvasInstance: Canvas;
canvasInstance: Canvas | Canvas3d;
forceExit: boolean;
predictor: PredictorState;
isTrainingActive: boolean;

@ -7,9 +7,10 @@ import {
Canvas3dVersion,
MouseInteraction,
ViewType,
CAMERA_ACTION,
CameraAction,
ViewsDOM,
} from 'cvat-canvas3d/src/typescript/canvas3d';
export {
Canvas3d, Canvas3dVersion, MouseInteraction, ViewType, CAMERA_ACTION,
Canvas3d, Canvas3dVersion, MouseInteraction, ViewType, CameraAction, ViewsDOM,
};

@ -156,10 +156,13 @@ export default (state = defaultState, action: AnyAction): AnnotationState => {
const isReview = job.status === TaskStatus.REVIEW;
let workspaceSelected = Workspace.STANDARD;
let activeShapeType = ShapeType.RECTANGLE;
if (job.task.dimension === DimensionType.DIM_3D) {
workspaceSelected = Workspace.STANDARD3D;
activeShapeType = ShapeType.CUBOID;
}
return {
...state,
job: {
@ -201,6 +204,7 @@ export default (state = defaultState, action: AnyAction): AnnotationState => {
...state.drawing,
activeLabelID: job.task.labels.length ? job.task.labels[0].id : null,
activeObjectType: job.task.mode === 'interpolation' ? ObjectType.TRACK : ObjectType.SHAPE,
activeShapeType,
},
canvas: {
...state.canvas,

@ -10,7 +10,7 @@ import { SettingsActionTypes } from 'actions/settings-actions';
import { AnnotationActionTypes } from 'actions/annotation-actions';
import {
SettingsState, GridColor, FrameSpeed, ColorBy,
SettingsState, GridColor, FrameSpeed, ColorBy, DimensionType,
} from './interfaces';
const defaultState: SettingsState = {
@ -299,6 +299,15 @@ export default (state = defaultState, action: AnyAction): SettingsState => {
...state.player,
resetZoom: job && job.task.mode === 'annotation',
},
shapes: {
...defaultState.shapes,
...(job.task.dimension === DimensionType.DIM_3D ?
{
opacity: 40,
selectedOpacity: 60,
} :
{}),
},
};
}
case AuthActionTypes.LOGOUT_SUCCESS: {

@ -107,7 +107,7 @@ context('Canvas 3D functionality. Basic actions.', () => {
cy.get('.cvat-canvas-controls-sidebar')
.find('[role="img"]')
.then(($controlButtons) => {
expect($controlButtons.length).to.be.equal(3);
expect($controlButtons.length).to.be.equal(4);
});
cy.get('.cvat-canvas-controls-sidebar')
.should('exist')

@ -19,8 +19,17 @@ context('Canvas 3D functionality. Control button. Mouse interaction.', () => {
arrow,
) {
cy.get('.cvat-canvas3d-perspective').screenshot(screenshotNameBefore);
arrow ? cy.get(button).click() : cy.contains('button', new RegExp(`^${button}$`)).click();
cy.wait(300);
arrow
? cy.get(button).trigger('mouseover').click()
: cy
.contains('button', new RegExp(`^${button}$`))
.trigger('mouseover')
.click();
cy.contains(expectedTooltipText).should('exist').and('be.visible'); // Check tooltip
arrow
? cy.get(button).should('exist').click()
: cy.contains('button', new RegExp(`^${button}$`)).click({ force: true });
arrow
? cy.get(button).trigger('mouseout')
: cy.contains('button', new RegExp(`^${button}$`)).trigger('mouseout');

@ -4,12 +4,15 @@
/// <reference types="cypress" />
import { taskName } from '../../support/const_canvas3d';
import { taskName, labelName } from '../../support/const_canvas3d';
context('Canvas 3D functionality. Add cuboid.', () => {
const caseId = '64';
const screenshotsPath = 'cypress/screenshots/canvas3d_functionality/case_64_canvas3d_functionality_cuboid.js';
const cuboidCreationParams = {
labelName: labelName,
};
before(() => {
cy.openTaskJob(taskName);
@ -22,58 +25,59 @@ context('Canvas 3D functionality. Add cuboid.', () => {
describe(`Testing case "${caseId}"`, () => {
it('Add cuboid.', () => {
cy.get('.cvat-draw-cuboid-control').trigger('mouseover');
cy.get('.cvat-draw-shape-popover').find('button').click();
cy.get('.cvat-canvas3d-perspective').dblclick();
cy.create3DCuboid(cuboidCreationParams);
cy.get('.cvat-canvas3d-perspective').screenshot('canvas3d_perspective_after_add_cuboid'); // The cuboid displayed
cy.compareImagesAndCheckResult(
`${screenshotsPath}/canvas3d_perspective_before_all.png`,
`${screenshotsPath}/canvas3d_perspective_after_add_cuboid.png`,
);
['topview', 'sideview', 'frontview'].forEach((view) => {
cy.get(`.cvat-canvas3d-${view}`)
.find('.cvat-canvas3d-fullsize')
.screenshot(`canvas3d_${view}_add_cuboid`);
});
[
['canvas3d_topview_before_all.png', 'canvas3d_topview_add_cuboid.png'],
['canvas3d_sideview_before_all.png', 'canvas3d_sideview_add_cuboid.png'],
['canvas3d_frontview_before_all.png', 'canvas3d_frontview_add_cuboid.png'],
].forEach(([viewBefore, viewAfterAddCuboid]) => {
cy.compareImagesAndCheckResult(
`${screenshotsPath}/${viewBefore}`,
`${screenshotsPath}/${viewAfterAddCuboid}`,
);
});
});
it('Cuboid interaction by mouse.', () => {
// The cuboid does not have time to change color so we make the mouse movement 3 times.
cy.get('.cvat-canvas3d-perspective')
.trigger('mousemove', 500, 200)
.trigger('mousemove', 400, 200)
.trigger('mousemove', 300, 200); // The cuboid should change a color after movement cursor from it
cy.get('.cvat-canvas3d-perspective').screenshot('canvas3d_perspective_after_cursor_movements_from_cuboid');
cy.compareImagesAndCheckResult(
`${screenshotsPath}/canvas3d_perspective_after_add_cuboid.png`,
`${screenshotsPath}/canvas3d_perspective_after_cursor_movements_from_cuboid.png`,
);
// Move cursor to the cuboid. The cuboid does not have time to change color so we make the mouse movement 3 times
cy.get('.cvat-canvas3d-perspective').trigger('mousemove').trigger('mousemove').trigger('mousemove');
cy.get('.cvat-canvas3d-perspective').trigger('mousemove', 300, 200).click(300, 200); // Deactivate the cuboid
cy.get('.cvat-canvas3d-perspective').screenshot('canvas3d_perspective_deactivate_cuboid');
['topview', 'sideview', 'frontview'].forEach((view) => {
cy.get(`.cvat-canvas3d-${view}`)
.find('.cvat-canvas3d-fullsize')
.screenshot(`canvas3d_${view}_deactivate_cuboid`);
});
cy.get('.cvat-canvas3d-perspective').trigger('mousemove', 300, 200); // Interacting with the canvas before interacting with the cuboid.
cy.get('.cvat-canvas3d-perspective').trigger('mousemove'); // Move cursor to cuboid
cy.wait(1000); // Waiting for the reaction of the cuboid to interact with the mouse cursor
cy.get('.cvat-canvas3d-perspective').screenshot('canvas3d_perspective_after_cursor_movements_to_cuboid');
// The cuboid changed a color
cy.compareImagesAndCheckResult(
`${screenshotsPath}/canvas3d_perspective_after_cursor_movements_from_cuboid.png`,
`${screenshotsPath}/canvas3d_perspective_after_cursor_movements_to_cuboid.png`,
);
});
it('Top/side/front views should be changed afer dblclick on the cuboid on perspective view because of the drawing of the cuboid.', () => {
cy.get('.cvat-canvas3d-perspective').dblclick(); // Dblclick on the cuboid
// On the perspective view the cuboid should change a color also.
cy.get('.cvat-canvas3d-perspective').screenshot('canvas3d_perspective_after_dblclick_on_cuboid');
cy.compareImagesAndCheckResult(
`${screenshotsPath}/canvas3d_perspective_deactivate_cuboid.png`,
`${screenshotsPath}/canvas3d_perspective_after_cursor_movements_to_cuboid.png`,
`${screenshotsPath}/canvas3d_perspective_after_dblclick_on_cuboid.png`,
);
['topview', 'sideview', 'frontview'].forEach((view) => {
cy.get(`.cvat-canvas3d-${view}`)
.find('.cvat-canvas3d-fullsize')
.screenshot(`canvas3d_${view}_show_cuboid`);
.screenshot(`canvas3d_${view}_move_cursor_to_cuboid`);
});
[
['canvas3d_topview_before_all.png', 'canvas3d_topview_show_cuboid.png'],
['canvas3d_sideview_before_all.png', 'canvas3d_sideview_show_cuboid.png'],
['canvas3d_frontview_before_all.png', 'canvas3d_frontview_show_cuboid.png'],
].forEach(([viewBefore, viewAfter]) => {
cy.compareImagesAndCheckResult(`${screenshotsPath}/${viewBefore}`, `${screenshotsPath}/${viewAfter}`);
['canvas3d_topview_deactivate_cuboid.png', 'canvas3d_topview_move_cursor_to_cuboid.png'],
['canvas3d_sideview_deactivate_cuboid.png', 'canvas3d_sideview_move_cursor_to_cuboid.png'],
['canvas3d_frontview_deactivate_cuboid.png', 'canvas3d_frontview_move_cursor_to_cuboid.png'],
].forEach(([viewAfterAddCuboid, viewAfterMoveCursorToCuboid]) => {
cy.compareImagesAndCheckResult(
`${screenshotsPath}/${viewAfterAddCuboid}`,
`${screenshotsPath}/${viewAfterMoveCursorToCuboid}`,
);
});
});
});

@ -4,8 +4,23 @@
/// <reference types="cypress" />
Cypress.Commands.add('compareImagesAndCheckResult', (baseImage, afterImage) => {
Cypress.Commands.add('compareImagesAndCheckResult', (baseImage, afterImage, noChangesExpected) => {
cy.compareImages(baseImage, afterImage).then((diffPercent) => {
expect(diffPercent).to.be.gt(0);
noChangesExpected ? expect(diffPercent).to.be.eq(0) : expect(diffPercent).to.be.gt(0);
});
});
Cypress.Commands.add('create3DCuboid', (cuboidCreationParams) => {
cy.get('.cvat-draw-cuboid-control').trigger('mouseover');
cy.get('.cvat-draw-cuboid-popover-visible').find('[type="search"]').click({ force: true });
cy.get('.ant-select-dropdown')
.not('.ant-select-dropdown-hidden')
.within(() => {
cy.contains(new RegExp(`^${cuboidCreationParams.labelName}$`)).click();
});
cy.get('.cvat-draw-cuboid-popover-visible').find('button').click();
cy.get('.cvat-canvas3d-perspective')
.trigger('mousemove', cuboidCreationParams.x, cuboidCreationParams.y)
.dblclick(cuboidCreationParams.x, cuboidCreationParams.y);
cy.wait(1000); // Waiting for a cuboid creation
});

Loading…
Cancel
Save