Skip to content

Commit

Permalink
Single object annotation mode (#7486)
Browse files Browse the repository at this point in the history
  • Loading branch information
bsekachev authored Feb 27, 2024
1 parent 7f92660 commit 5f20678
Show file tree
Hide file tree
Showing 39 changed files with 1,369 additions and 697 deletions.
4 changes: 4 additions & 0 deletions changelog.d/20240219_101835_boris_single_object_mode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
### Added

- Single shape annotation mode allowing to easily annotate scenarious where a user
only needs to draw one object on one image (<https://github.com/opencv/cvat/pull/7486>)
2 changes: 1 addition & 1 deletion cvat-canvas/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cvat-canvas",
"version": "2.19.1",
"version": "2.20.0",
"type": "module",
"description": "Part of Computer Vision Annotation Tool which presents its canvas library",
"main": "src/canvas.ts",
Expand Down
37 changes: 1 addition & 36 deletions cvat-canvas/src/typescript/canvasController.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright (C) 2019-2022 Intel Corporation
// Copyright (C) 2022-2023 CVAT.ai Corporation
// Copyright (C) 2022-2024 CVAT.ai Corporation
//
// SPDX-License-Identifier: MIT

Expand Down Expand Up @@ -45,13 +45,6 @@ export interface CanvasController {
zoom(x: number, y: number, direction: number): void;
draw(drawData: DrawData): void;
edit(editData: MasksEditData): void;
interact(interactionData: InteractionData): void;
merge(mergeData: MergeData): void;
split(splitData: SplitData): void;
group(groupData: GroupData): void;
join(joinData: JoinData): void;
slice(sliceData: SliceData): void;
selectRegion(enabled: boolean): void;
enableDrag(x: number, y: number): void;
drag(x: number, y: number): void;
disableDrag(): void;
Expand Down Expand Up @@ -107,34 +100,6 @@ export class CanvasControllerImpl implements CanvasController {
this.model.edit(editData);
}

public interact(interactionData: InteractionData): void {
this.model.interact(interactionData);
}

public merge(mergeData: MergeData): void {
this.model.merge(mergeData);
}

public split(splitData: SplitData): void {
this.model.split(splitData);
}

public group(groupData: GroupData): void {
this.model.group(groupData);
}

public join(joinData: JoinData): void {
this.model.join(joinData);
}

public slice(sliceData: SliceData): void {
this.model.slice(sliceData);
}

public selectRegion(enable: boolean): void {
this.model.selectRegion(enable);
}

public get geometry(): Geometry {
return this.model.geometry;
}
Expand Down
138 changes: 40 additions & 98 deletions cvat-canvas/src/typescript/canvasView.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright (C) 2019-2022 Intel Corporation
// Copyright (C) 2022-2023 CVAT.ai Corporation
// Copyright (C) 2022-2024 CVAT.ai Corporation
//
// SPDX-License-Identifier: MIT

Expand Down Expand Up @@ -232,6 +232,16 @@ export class CanvasViewImpl implements CanvasView, Listener {
}
}

private dispatchCanceledEvent(): void {
this.mode = Mode.IDLE;
const event: CustomEvent = new CustomEvent('canvas.canceled', {
bubbles: false,
cancelable: true,
});

this.canvas.dispatchEvent(event);
}

private onInteraction = (
shapes: InteractionResult[] | null,
shapesUpdated = true,
Expand All @@ -256,16 +266,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
}

if (shapes === null || isDone) {
const event: CustomEvent = new CustomEvent('canvas.canceled', {
bubbles: false,
cancelable: true,
});

this.canvas.dispatchEvent(event);
this.mode = Mode.IDLE;
this.controller.interact({
enabled: false,
});
this.dispatchCanceledEvent();
}
};

Expand All @@ -290,13 +291,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
const [state] = this.controller.objects
.filter((_state: any): boolean => _state.clientID === clientID);
this.onEditDone(state, points);

const event: CustomEvent = new CustomEvent('canvas.canceled', {
bubbles: false,
cancelable: true,
});

this.canvas.dispatchEvent(event);
this.dispatchCanceledEvent();
return;
}

Expand All @@ -317,10 +312,7 @@ export class CanvasViewImpl implements CanvasView, Listener {

this.canvas.dispatchEvent(event);
} else if (!continueDraw) {
this.canvas.dispatchEvent(new CustomEvent('canvas.canceled', {
bubbles: false,
cancelable: true,
}));
this.dispatchCanceledEvent();
}

if (continueDraw) {
Expand All @@ -335,7 +327,8 @@ export class CanvasViewImpl implements CanvasView, Listener {
);
} else {
// when draw stops from inside canvas (for example if use predefined number of points)
this.controller.draw({ enabled: false });
this.mode = Mode.IDLE;
this.canvas.style.cursor = '';
}
};

Expand All @@ -361,6 +354,7 @@ export class CanvasViewImpl implements CanvasView, Listener {

private onEditDone = (state: any, points: number[], rotation?: number): void => {
this.canvas.style.cursor = '';
this.mode = Mode.IDLE;
if (state && points) {
const event: CustomEvent = new CustomEvent('canvas.edited', {
bubbles: false,
Expand All @@ -374,18 +368,12 @@ export class CanvasViewImpl implements CanvasView, Listener {

this.canvas.dispatchEvent(event);
} else {
const event: CustomEvent = new CustomEvent('canvas.canceled', {
bubbles: false,
cancelable: true,
});

this.canvas.dispatchEvent(event);
this.dispatchCanceledEvent();
}

for (const clientID of Object.keys(this.innerObjectsFlags.editHidden)) {
this.setupInnerFlags(+clientID, 'editHidden', false);
}
this.mode = Mode.IDLE;
};

private onMergeDone = (objects: any[] | null, duration?: number): void => {
Expand All @@ -399,18 +387,11 @@ export class CanvasViewImpl implements CanvasView, Listener {
},
});

this.mode = Mode.IDLE;
this.canvas.dispatchEvent(event);
} else {
const event: CustomEvent = new CustomEvent('canvas.canceled', {
bubbles: false,
cancelable: true,
});

this.canvas.dispatchEvent(event);
this.dispatchCanceledEvent();
}

this.controller.merge({ enabled: false });
this.mode = Mode.IDLE;
};

private onSplitDone = (object?: any, duration?: number): void => {
Expand All @@ -425,23 +406,23 @@ export class CanvasViewImpl implements CanvasView, Listener {
},
});

this.canvas.style.cursor = '';
this.mode = Mode.IDLE;
this.splitHandler.split({ enabled: false });
this.canvas.dispatchEvent(event);
} else {
const event: CustomEvent = new CustomEvent('canvas.canceled', {
bubbles: false,
cancelable: true,
});

this.canvas.dispatchEvent(event);
this.dispatchCanceledEvent();
}

this.controller.split({ enabled: false });
this.mode = Mode.IDLE;
};

private onSelectDone = (objects?: any[], duration?: number): void => {
if (this.mode === Mode.JOIN) {
this.onMessage(null, 'join');
}

if (objects && typeof duration !== 'undefined') {
if (this.mode === Mode.GROUP && objects.length > 1) {
this.mode = Mode.IDLE;
this.canvas.dispatchEvent(new CustomEvent('canvas.groupped', {
bubbles: false,
cancelable: true,
Expand All @@ -451,6 +432,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
},
}));
} else if (this.mode === Mode.JOIN && objects.length > 1) {
this.mode = Mode.IDLE;
let [left, top, right, bottom] = objects[0].points.slice(-4);
objects.forEach((state) => {
const [curLeft, curTop, curRight, curBottom] = state.points.slice(-4);
Expand Down Expand Up @@ -487,26 +469,14 @@ export class CanvasViewImpl implements CanvasView, Listener {
}).catch(this.onError);
}
} else {
const event: CustomEvent = new CustomEvent('canvas.canceled', {
bubbles: false,
cancelable: true,
});

this.canvas.dispatchEvent(event);
this.dispatchCanceledEvent();
}

if (this.mode === Mode.GROUP) {
this.controller.group({ enabled: false });
} else if (this.mode === Mode.JOIN) {
this.controller.join({ enabled: false });
this.onMessage(null, 'join');
}

this.mode = Mode.IDLE;
};

private onSliceDone = (state?: any, results?: number[][], duration?: number): void => {
if (state && results && typeof duration !== 'undefined') {
this.mode = Mode.IDLE;
this.sliceHandler.slice({ enabled: false });
this.canvas.dispatchEvent(new CustomEvent('canvas.sliced', {
bubbles: false,
cancelable: true,
Expand All @@ -517,40 +487,22 @@ export class CanvasViewImpl implements CanvasView, Listener {
},
}));
} else {
const event: CustomEvent = new CustomEvent('canvas.canceled', {
bubbles: false,
cancelable: true,
});

this.canvas.dispatchEvent(event);
this.dispatchCanceledEvent();
}

this.controller.slice({ enabled: false });
this.mode = Mode.IDLE;
};

private onRegionSelected = (points?: number[]): void => {
if (points) {
const event: CustomEvent = new CustomEvent('canvas.regionselected', {
this.canvas.dispatchEvent(new CustomEvent('canvas.regionselected', {
bubbles: false,
cancelable: true,
detail: {
points,
},
});

this.canvas.dispatchEvent(event);
}));
} else {
const event: CustomEvent = new CustomEvent('canvas.canceled', {
bubbles: false,
cancelable: true,
});

this.canvas.dispatchEvent(event);
this.dispatchCanceledEvent();
}

this.controller.selectRegion(false);
this.mode = Mode.IDLE;
};

private onFindObject = (e: MouseEvent): void => {
Expand Down Expand Up @@ -1711,19 +1663,15 @@ export class CanvasViewImpl implements CanvasView, Listener {
if (data.enabled) {
this.canvas.style.cursor = 'copy';
this.mode = Mode.MERGE;
} else {
this.canvas.style.cursor = '';
}
this.mergeHandler.merge(data);
} else if (reason === UpdateReasons.SPLIT) {
const data: SplitData = this.controller.splitData;
if (data.enabled) {
this.canvas.style.cursor = 'copy';
this.mode = Mode.SPLIT;
} else {
this.canvas.style.cursor = '';
this.splitHandler.split(data);
}
this.splitHandler.split(data);
} else if ([UpdateReasons.JOIN, UpdateReasons.GROUP].includes(reason)) {
let data: GroupData | JoinData = null;
if (reason === UpdateReasons.GROUP) {
Expand All @@ -1745,18 +1693,12 @@ export class CanvasViewImpl implements CanvasView, Listener {
objectType: ['shape'],
});
}

if (data.enabled) {
this.canvas.style.cursor = 'copy';
} else {
this.canvas.style.cursor = '';
}
} else if (reason === UpdateReasons.SLICE) {
const data = this.controller.sliceData;
if (data.enabled && this.mode === Mode.IDLE) {
this.mode = Mode.SLICE;
this.sliceHandler.slice(data);
}
this.sliceHandler.slice(data);
} else if (reason === UpdateReasons.SELECT) {
this.objectSelector.push(this.controller.selected);
if (this.mode === Mode.MERGE) {
Expand Down Expand Up @@ -1805,8 +1747,8 @@ export class CanvasViewImpl implements CanvasView, Listener {
}),
);
}
this.mode = Mode.IDLE;
this.canvas.style.cursor = '';
this.dispatchCanceledEvent();
} else if (reason === UpdateReasons.DATA_FAILED) {
this.onError(model.exception, 'data fetching');
} else if (reason === UpdateReasons.DESTROY) {
Expand Down
Loading

0 comments on commit 5f20678

Please sign in to comment.