Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove empty masks #7295

Merged
merged 47 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
aef1993
added draft solution
klakhov Dec 26, 2023
c35916e
removed error, throw argument error in case of creating empty mask
klakhov Dec 27, 2023
d52e797
removed notification
klakhov Dec 27, 2023
a7b0a36
remove empty underlying masks in cvat-core
klakhov Dec 27, 2023
9946b54
added history actions, ui notification
klakhov Dec 27, 2023
94f1438
Updated package versions
klakhov Dec 27, 2023
8cb735f
removed excessive action type
klakhov Dec 27, 2023
26dfea7
Merge branch 'develop' into kl/remove-empty-masks
klakhov Dec 27, 2023
5676dfe
fixed one more `if`
klakhov Dec 27, 2023
deb3fc5
Merge branch 'kl/remove-empty-masks' of https://github.com/cvat-ai/cv…
klakhov Dec 27, 2023
b707ba2
fixed notification text
klakhov Dec 29, 2023
4578f4c
undo redo for underlying pixels
klakhov Dec 29, 2023
5a961f3
moved check to checkNumberOfPoints
klakhov Dec 29, 2023
bebf85a
Merge branch 'develop' into kl/remove-empty-masks
klakhov Dec 29, 2023
1d2d206
Merge branch 'develop' into kl/remove-empty-masks
klakhov Jan 9, 2024
a64def0
do not fire `drawn` and `edited` events if mask is empty
klakhov Jan 9, 2024
3565edf
reset eraser, polygon-minus on start
klakhov Jan 9, 2024
1a461b5
fixed cancel event on edit done
klakhov Jan 10, 2024
49a5c79
added blocking of eraser&poly-minus tools
klakhov Jan 10, 2024
27aa4c6
removed excessive proxy method
klakhov Jan 12, 2024
7cac308
Merge branch 'develop' into kl/remove-empty-masks
klakhov Jan 12, 2024
e27b907
fixed eslint
klakhov Jan 12, 2024
badfcaa
updated headers
klakhov Jan 12, 2024
d4f6de9
added changelog
klakhov Jan 12, 2024
7f5a9b0
fixed edit event
klakhov Jan 12, 2024
a6416c7
Reset lock file
bsekachev Jan 15, 2024
4955113
Merge branch 'develop' into kl/remove-empty-masks
bsekachev Jan 15, 2024
6628e61
Updated lock
bsekachev Jan 15, 2024
add50d3
fixed changelog
klakhov Jan 15, 2024
194b854
renamed callback
klakhov Jan 16, 2024
8f080b0
call drawDone/editDone with null instead of check in canvasView
klakhov Jan 16, 2024
6b40acf
fixed typo
klakhov Jan 16, 2024
58be39f
fixed bug with copying/inserting masks
klakhov Jan 17, 2024
2533317
Merge branch 'develop' into kl/remove-empty-masks
klakhov Jan 17, 2024
75a8bfa
fixed typo
klakhov Jan 17, 2024
d23b7c9
added history restriction on remove underlying pixels
klakhov Jan 17, 2024
5be1d61
Merge branch 'kl/remove-empty-masks' of https://github.com/cvat-ai/cv…
klakhov Jan 17, 2024
97dd673
moved check empty to updateBrushTools
klakhov Jan 17, 2024
05c4076
renamed check function
klakhov Jan 17, 2024
2584150
fixed remark
klakhov Jan 17, 2024
74f9695
fixed package json
klakhov Jan 18, 2024
fda509a
applied code style comments
klakhov Jan 18, 2024
70ab111
adjusted check number of points
klakhov Jan 18, 2024
0704998
fixed undo/redo
klakhov Jan 18, 2024
91e1da5
fixed colon
klakhov Jan 18, 2024
85abbbb
reverted a couple of header changes
klakhov Jan 18, 2024
7ccc1f5
Merge branch 'develop' into kl/remove-empty-masks
bsekachev Jan 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions changelog.d/20240112_123610_klakhov_remove_empty_masks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
### Fixed

- Empty masks might be created with `polygon-minus` tool (<https://github.com/opencv/cvat/pull/7295>)
- Empty masks might be created as a result of removing underlying pixels (<https://github.com/opencv/cvat/pull/7295>)
3 changes: 2 additions & 1 deletion cvat-canvas/src/typescript/canvasModel.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 @@ -103,6 +103,7 @@ export interface BrushTool {
color: string;
form: 'circle' | 'square';
size: number;
onBlockUpdated: (blockedTools: Record<'eraser' | 'polygon-minus', boolean>) => void;
}

export interface DrawData {
Expand Down
77 changes: 64 additions & 13 deletions cvat-canvas/src/typescript/masksHandler.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// Copyright (C) 2022-2023 CVAT.ai Corporation
// Copyright (C) 2022-2024 CVAT.ai Corporation
//
// SPDX-License-Identifier: MIT

import { fabric } from 'fabric';
import debounce from 'lodash/debounce';

import {
DrawData, MasksEditData, Geometry, Configuration, BrushTool, ColorBy,
Expand Down Expand Up @@ -120,7 +121,7 @@ export class MasksHandlerImpl implements MasksHandler {
this.canvas.clear();
this.canvas.renderAll();
this.isInsertion = false;
this.drawnObjects = [];
this.drawnObjects = this.createDrawnObjectsArray();
this.onDrawDone(null);
}

Expand All @@ -136,7 +137,7 @@ export class MasksHandlerImpl implements MasksHandler {
this.isDrawing = false;
this.isInsertion = false;
this.redraw = null;
this.drawnObjects = [];
this.drawnObjects = this.createDrawnObjectsArray();
this.onDrawDone(null);
}

Expand All @@ -150,7 +151,7 @@ export class MasksHandlerImpl implements MasksHandler {
this.canvas.clear();
this.canvas.renderAll();
this.isEditing = false;
this.drawnObjects = [];
this.drawnObjects = this.createDrawnObjectsArray();
this.onEditDone(null, null);
}

Expand Down Expand Up @@ -255,6 +256,8 @@ export class MasksHandlerImpl implements MasksHandler {
if (this.isDrawing || this.isEditing) {
this.setupBrushMarker();
}

this.updateBlockedTools();
}

if (this.tool?.type?.startsWith('polygon-')) {
Expand Down Expand Up @@ -294,6 +297,42 @@ export class MasksHandlerImpl implements MasksHandler {
}
}

private updateBlockedTools(): void {
if (this.drawnObjects.length === 0) {
this.tool.onBlockUpdated({
eraser: true,
'polygon-minus': true,
});
return;
}
const wrappingBbox = this.getDrawnObjectsWrappingBox();
if (this.brushMarker) {
this.canvas.remove(this.brushMarker);
}
const imageData = this.imageDataFromCanvas(wrappingBbox);
if (this.brushMarker) {
this.canvas.add(this.brushMarker);
}
const rle = zipChannels(imageData);
const emptyMask = rle.length < 2;
this.tool.onBlockUpdated({
eraser: emptyMask,
'polygon-minus': emptyMask,
});
}

private createDrawnObjectsArray(): MasksHandlerImpl['drawnObjects'] {
const drawnObjects = [];
const updateBlockedToolsDebounced = debounce(this.updateBlockedTools.bind(this), 250);
return new Proxy(drawnObjects, {
set(target, property, value) {
target[property] = value;
updateBlockedToolsDebounced();
return true;
},
});
}

public constructor(
onDrawDone: MasksHandlerImpl['onDrawDone'],
onDrawRepeat: MasksHandlerImpl['onDrawRepeat'],
Expand All @@ -310,7 +349,6 @@ export class MasksHandlerImpl implements MasksHandler {
this.isPolygonDrawing = false;
this.drawData = null;
this.editData = null;
this.drawnObjects = [];
this.drawingOpacity = 0.5;
this.brushMarker = null;
this.colorBy = ColorBy.LABEL;
Expand All @@ -326,6 +364,7 @@ export class MasksHandlerImpl implements MasksHandler {
defaultCursor: 'inherit',
});
this.canvas.imageSmoothingEnabled = false;
this.drawnObjects = this.createDrawnObjectsArray();

klakhov marked this conversation as resolved.
Show resolved Hide resolved
this.canvas.getElement().parentElement.addEventListener('contextmenu', (e: MouseEvent) => e.preventDefault());
this.latestMousePos = { x: -1, y: -1 };
Expand All @@ -349,6 +388,7 @@ export class MasksHandlerImpl implements MasksHandler {
this.onDrawDone({
shapeType: this.drawData.shapeType,
points: rle,
label: this.drawData.initialState.label,
}, Date.now() - this.startTimestamp, continueInserting, this.drawData);

if (!continueInserting) {
Expand Down Expand Up @@ -445,7 +485,7 @@ export class MasksHandlerImpl implements MasksHandler {
}

this.canvas.add(shape);
if (tool.type === 'brush') {
if (['brush', 'eraser'].includes(tool?.type)) {
this.drawnObjects.push(shape);
}

Expand All @@ -467,7 +507,7 @@ export class MasksHandlerImpl implements MasksHandler {
});

this.canvas.add(line);
if (tool.type === 'brush') {
if (['brush', 'eraser'].includes(tool?.type)) {
this.drawnObjects.push(line);
}
}
Expand Down Expand Up @@ -563,11 +603,17 @@ export class MasksHandlerImpl implements MasksHandler {
const imageData = this.imageDataFromCanvas(wrappingBbox);
const rle = zipChannels(imageData);
rle.push(wrappingBbox.left, wrappingBbox.top, wrappingBbox.right, wrappingBbox.bottom);
this.onDrawDone({
shapeType: this.drawData.shapeType,
points: rle,
...(Number.isInteger(this.redraw) ? { clientID: this.redraw } : {}),
}, Date.now() - this.startTimestamp, drawData.continue, this.drawData);

const isEmptyMask = rle.length < 6;
if (isEmptyMask) {
this.onDrawDone(null);
} else {
this.onDrawDone({
shapeType: this.drawData.shapeType,
points: rle,
...(Number.isInteger(this.redraw) ? { clientID: this.redraw } : {}),
}, Date.now() - this.startTimestamp, drawData.continue, this.drawData);
}
}
} finally {
this.releaseDraw();
Expand Down Expand Up @@ -634,7 +680,12 @@ export class MasksHandlerImpl implements MasksHandler {
const imageData = this.imageDataFromCanvas(wrappingBbox);
const rle = zipChannels(imageData);
rle.push(wrappingBbox.left, wrappingBbox.top, wrappingBbox.right, wrappingBbox.bottom);
this.onEditDone(this.editData.state, rle);
const isEmptyMask = rle.length < 6;
if (isEmptyMask) {
this.onEditDone(null, null);
} else {
this.onEditDone(this.editData.state, rle);
}
}
} finally {
this.releaseEdit();
Expand Down
12 changes: 11 additions & 1 deletion cvat-core/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
// Copyright (C) 2018-2022 Intel Corporation
// Copyright (C) 2023 CVAT.ai Corporation
// Copyright (C) 2023-2024 CVAT.ai Corporation
//
// SPDX-License-Identifier: MIT

const { join } = require('path');

module.exports = {
ignorePatterns: [
'.eslintrc.cjs',
Expand All @@ -16,4 +18,12 @@ module.exports = {
project: './tsconfig.json',
tsconfigRootDir: __dirname,
},
rules: {
'import/no-extraneous-dependencies': [
'error',
{
packageDir: [__dirname, join(__dirname, '../')]
},
],
}
};
4 changes: 1 addition & 3 deletions cvat-core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cvat-core",
"version": "14.0.4",
"version": "14.1.0",
"type": "module",
"description": "Part of Computer Vision Tool which presents an interface for client-side integration",
"main": "src/api.ts",
Expand Down Expand Up @@ -28,7 +28,6 @@
"jest-junit": "^6.4.0"
},
"dependencies": {
"@types/lodash": "^4.14.191",
"axios": "^1.6.0",
"axios-retry": "^4.0.0",
"cvat-data": "link:./../cvat-data",
Expand All @@ -37,7 +36,6 @@
"form-data": "^4.0.0",
"js-cookie": "^3.0.1",
"json-logic-js": "^2.0.1",
"lodash": "^4.17.21",
"platform": "^1.3.5",
"quickhull": "^1.0.3",
"store": "^2.0.12",
Expand Down
36 changes: 31 additions & 5 deletions cvat-core/src/annotations-collection.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 @@ -1006,6 +1006,10 @@ export default class Collection {
);
}

if (state.shapeType === 'mask' && state.points.length < 6) {
throw new ArgumentError('Could not create empty mask');
}

if (state.objectType === 'shape') {
constructed.shapes.push({
attributes,
Expand Down Expand Up @@ -1095,27 +1099,49 @@ export default class Collection {
// eslint-disable-next-line no-unsanitized/method
const imported = this.import(constructed);
const importedArray = imported.tags.concat(imported.tracks).concat(imported.shapes);
const additionalUndo = [];
const additionalRedo = [];
const additionalClientIDs = [];
let globalEmptyMaskOccurred = false;
for (const object of importedArray) {
if (object.shapeType === ShapeType.MASK && config.removeUnderlyingMaskPixels) {
(object as MaskShape).removeUnderlyingPixels(object.frame);
if (object.shapeType === ShapeType.MASK && config.removeUnderlyingMaskPixels.enabled) {
const {
clientIDs,
emptyMaskOccurred,
undo: undoWithUnderlyingPixels,
redo: redoWithUnderlyingPixels,
} = (object as MaskShape).removeUnderlyingPixels(object.frame);
additionalUndo.push(undoWithUnderlyingPixels);
additionalRedo.push(redoWithUnderlyingPixels);
additionalClientIDs.push(clientIDs);
globalEmptyMaskOccurred = emptyMaskOccurred || globalEmptyMaskOccurred;
}
}

if (config.removeUnderlyingMaskPixels.enabled && globalEmptyMaskOccurred) {
config.removeUnderlyingMaskPixels?.onEmptyMaskOccurrence();
}
if (objectStates.length) {
this.history.do(
HistoryActions.CREATED_OBJECTS,
() => {
importedArray.forEach((object) => {
object.removed = true;
});
additionalUndo.forEach((undo) => {
undo();
});
},
() => {
importedArray.forEach((object) => {
object.removed = false;
object.serverID = undefined;
});

additionalRedo.forEach((redo) => {
redo();
});
},
importedArray.map((object) => object.clientID),
[...importedArray.map((object) => object.clientID), ...additionalClientIDs.flat()],
objectStates[0].frame,
);
}
Expand Down
Loading
Loading