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

Hide mask during editing #8554

Merged
merged 34 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
b1b89e7
added information about edited state to cvat-ui
klakhov Oct 16, 2024
dc04778
hide mask on shortcut
klakhov Oct 16, 2024
65a13b2
fixed missing cursor
klakhov Oct 17, 2024
0684a9a
add hide icon
klakhov Oct 17, 2024
c6aecf3
refactored code, fixed issues
klakhov Oct 18, 2024
60005d0
Merge branch 'develop' into kl/hide-mask
klakhov Oct 18, 2024
a47e5e7
added changelog, fixed some issues
klakhov Oct 18, 2024
c7a2ac2
Merge branch 'kl/hide-mask' of https://github.com/cvat-ai/cvat into k…
klakhov Oct 18, 2024
0b81b8b
fixed sonar cloud issue
klakhov Oct 18, 2024
dd28e80
Merge branch 'develop' into kl/hide-mask
klakhov Oct 18, 2024
2f8f8db
fixed resetting edited state
klakhov Oct 21, 2024
15d0c0b
small refactoring
klakhov Oct 21, 2024
58f970a
stop drawing polygon on hide
klakhov Oct 21, 2024
920dbc1
fix create hidden mask
klakhov Oct 21, 2024
0610dd4
added tooltip, fixed single shape
klakhov Oct 21, 2024
cc3a215
Merge branch 'develop' into kl/hide-mask
klakhov Oct 21, 2024
70b65cf
Merge branch 'develop' into kl/hide-mask
klakhov Oct 22, 2024
b662c37
Merge branch 'develop' into kl/hide-mask
klakhov Oct 28, 2024
8866cac
fixed cvat-ui comments
klakhov Oct 28, 2024
8ef68c9
Merge branch 'develop' into kl/hide-mask
klakhov Oct 29, 2024
bda9424
fix hiding polygon during hide
klakhov Oct 29, 2024
2548ed6
refactored code duplication
klakhov Oct 29, 2024
4a658ae
fixed excessive configurate calls
klakhov Oct 29, 2024
c05f45b
fixed remove, renamed hide variable
klakhov Oct 29, 2024
3c7d622
Merge branch 'develop' into kl/hide-mask
klakhov Oct 30, 2024
6b1c612
reset hide on new mask draw/edit
klakhov Oct 30, 2024
1770803
show new mask even if hide mode is enabled
klakhov Oct 30, 2024
9ecfc2c
fix hide in single shape mode
klakhov Oct 30, 2024
617569c
reverted some changes, updated package
klakhov Oct 30, 2024
b908ccf
fix moving from single shape workspace
klakhov Oct 30, 2024
6ad833e
hide tooltip on mask finish
klakhov Oct 30, 2024
c00529c
Merge branch 'develop' into kl/hide-mask
bsekachev Oct 30, 2024
6f06e91
Merge branch 'develop' into kl/hide-mask
klakhov Oct 31, 2024
56e6f0d
Merge branch 'kl/hide-mask' of https://github.com/cvat-ai/cvat into k…
klakhov Oct 31, 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
3 changes: 3 additions & 0 deletions changelog.d/20241018_142148_klakhov_hide_mask.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Added

- Feature to hide a mask during editing (<https://github.com/cvat-ai/cvat/pull/8554>)
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.20.9",
"version": "2.20.10",
"type": "module",
"description": "Part of Computer Vision Annotation Tool which presents its canvas library",
"main": "src/canvas.ts",
Expand Down
6 changes: 6 additions & 0 deletions cvat-canvas/src/typescript/canvasModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export interface Configuration {
controlPointsSize?: number;
outlinedBorders?: string | false;
resetZoom?: boolean;
hideEditedObject?: boolean;
}

export interface BrushTool {
Expand Down Expand Up @@ -416,6 +417,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
textPosition: consts.DEFAULT_SHAPE_TEXT_POSITION,
textContent: consts.DEFAULT_SHAPE_TEXT_CONTENT,
undefinedAttrValue: consts.DEFAULT_UNDEFINED_ATTR_VALUE,
hideEditedObject: false,
},
imageBitmap: false,
image: null,
Expand Down Expand Up @@ -981,6 +983,10 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
this.data.configuration.CSSImageFilter = configuration.CSSImageFilter;
}

if (typeof configuration.hideEditedObject === 'boolean') {
this.data.configuration.hideEditedObject = configuration.hideEditedObject;
}

this.notify(UpdateReasons.CONFIG_UPDATED);
}

Expand Down
2 changes: 1 addition & 1 deletion cvat-canvas/src/typescript/editHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,7 @@ export class EditHandlerImpl implements EditHandler {

const paintHandler = this.editLine.remember('_paintHandler');

for (const point of (paintHandler as any).set.members) {
for (const point of paintHandler.set.members) {
point.attr('stroke-width', `${consts.POINTS_STROKE_WIDTH / geometry.scale}`);
point.attr('r', `${this.controlPointsSize / geometry.scale}`);
}
Expand Down
123 changes: 81 additions & 42 deletions cvat-canvas/src/typescript/masksHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import debounce from 'lodash/debounce';

import {
DrawData, MasksEditData, Geometry, Configuration, BrushTool, ColorBy,
DrawData, MasksEditData, Geometry, Configuration, BrushTool, ColorBy, Position,
} from './canvasModel';
import consts from './consts';
import { DrawHandler } from './drawHandler';
Expand Down Expand Up @@ -61,10 +61,11 @@
private editData: MasksEditData | null;

private colorBy: ColorBy;
private latestMousePos: { x: number; y: number; };
private latestMousePos: Position;
private startTimestamp: number;
private geometry: Geometry;
private drawingOpacity: number;
private isHidden: boolean;

private keepDrawnPolygon(): void {
const canvasWrapper = this.canvas.getElement().parentElement;
Expand Down Expand Up @@ -217,12 +218,73 @@
private imageDataFromCanvas(wrappingBBox: WrappingBBox): Uint8ClampedArray {
const imageData = this.canvas.toCanvasElement()
.getContext('2d').getImageData(
wrappingBBox.left, wrappingBBox.top,
wrappingBBox.right - wrappingBBox.left + 1, wrappingBBox.bottom - wrappingBBox.top + 1,
wrappingBBox.left,
wrappingBBox.top,
wrappingBBox.right - wrappingBBox.left + 1,
wrappingBBox.bottom - wrappingBBox.top + 1,
).data;
return imageData;
}

private startPolygonDrawing() {

Check warning on line 229 in cvat-canvas/src/typescript/masksHandler.ts

View workflow job for this annotation

GitHub Actions / Linter

Missing return type on function
if (this.tool?.type?.startsWith('polygon-')) {
this.isPolygonDrawing = true;
this.vectorDrawHandler.draw({
enabled: true,
shapeType: 'polygon',
onDrawDone: (data: { points: number[] } | null) => {
if (!data) return;
const points = data.points.reduce((acc: fabric.Point[], _: number, idx: number) => {
if (idx % 2) {
acc.push(new fabric.Point(data.points[idx - 1], data.points[idx]));
}

return acc;
}, []);

const color = fabric.Color.fromHex(this.tool.color);
color.setAlpha(this.tool.type === 'polygon-minus' ? 1 : this.drawingOpacity);
const polygon = new fabric.Polygon(points, {
fill: color.toRgba(),
selectable: false,
objectCaching: false,
absolutePositioned: true,
globalCompositeOperation: this.tool.type === 'polygon-minus' ? 'destination-out' : 'xor',
});

this.canvas.add(polygon);
this.drawnObjects.push(polygon);
this.canvas.renderAll();
},
}, this.geometry);

const canvasWrapper = this.canvas.getElement().parentElement as HTMLDivElement;
canvasWrapper.style.pointerEvents = 'none';
canvasWrapper.style.zIndex = '0';
}
}

private updateHidden(value: boolean) {

Check warning on line 267 in cvat-canvas/src/typescript/masksHandler.ts

View workflow job for this annotation

GitHub Actions / Linter

Missing return type on function
this.isHidden = value;

if (value && this.isPolygonDrawing) {
this.vectorDrawHandler.cancel();
} else if (this.isPolygonDrawing) {
this.startPolygonDrawing();
}

// Need to update style of upper canvas explicitly because update of default cursor is not applied immediately
// https://github.com/fabricjs/fabric.js/issues/1456
const newOpacity = value ? '0' : '';
const newCursor = value ? 'inherit' : 'none';
this.canvas.getElement().parentElement.style.opacity = newOpacity;
const upperCanvas = this.canvas.getElement().parentElement.querySelector('.upper-canvas') as HTMLElement;
if (upperCanvas) {
upperCanvas.style.cursor = newCursor;
}
this.canvas.defaultCursor = newCursor;
}

private updateBrushTools(brushTool?: BrushTool, opts: Partial<BrushTool> = {}): void {
if (this.isPolygonDrawing) {
// tool was switched from polygon to brush for example
Expand Down Expand Up @@ -259,41 +321,7 @@
this.updateBlockedTools();
}

if (this.tool?.type?.startsWith('polygon-')) {
this.isPolygonDrawing = true;
this.vectorDrawHandler.draw({
enabled: true,
shapeType: 'polygon',
onDrawDone: (data: { points: number[] } | null) => {
if (!data) return;
const points = data.points.reduce((acc: fabric.Point[], _: number, idx: number) => {
if (idx % 2) {
acc.push(new fabric.Point(data.points[idx - 1], data.points[idx]));
}

return acc;
}, []);

const color = fabric.Color.fromHex(this.tool.color);
color.setAlpha(this.tool.type === 'polygon-minus' ? 1 : this.drawingOpacity);
const polygon = new fabric.Polygon(points, {
fill: color.toRgba(),
selectable: false,
objectCaching: false,
absolutePositioned: true,
globalCompositeOperation: this.tool.type === 'polygon-minus' ? 'destination-out' : 'xor',
});

this.canvas.add(polygon);
this.drawnObjects.push(polygon);
this.canvas.renderAll();
},
}, this.geometry);

const canvasWrapper = this.canvas.getElement().parentElement as HTMLDivElement;
canvasWrapper.style.pointerEvents = 'none';
canvasWrapper.style.zIndex = '0';
}
this.startPolygonDrawing();
}

private updateBlockedTools(): void {
Expand Down Expand Up @@ -350,6 +378,7 @@
this.editData = null;
this.drawingOpacity = 0.5;
this.brushMarker = null;
this.isHidden = false;
this.colorBy = ColorBy.LABEL;
this.onDrawDone = onDrawDone;
this.onDrawRepeat = onDrawRepeat;
Expand Down Expand Up @@ -452,7 +481,7 @@
this.canvas.renderAll();
}

if (isMouseDown && !isBrushSizeChanging && ['brush', 'eraser'].includes(tool?.type)) {
if (isMouseDown && !this.isHidden && !isBrushSizeChanging && ['brush', 'eraser'].includes(tool?.type)) {
const color = fabric.Color.fromHex(tool.color);
color.setAlpha(tool.type === 'eraser' ? 1 : 0.5);

Expand Down Expand Up @@ -530,6 +559,10 @@

public configurate(configuration: Configuration): void {
this.colorBy = configuration.colorBy;

if (this.isHidden !== configuration.hideEditedObject) {
this.updateHidden(configuration.hideEditedObject);
}
}

public transform(geometry: Geometry): void {
Expand Down Expand Up @@ -563,7 +596,10 @@
const color = fabric.Color.fromHex(this.getStateColor(drawData.initialState)).getSource();
const [left, top, right, bottom] = points.slice(-4);
const imageBitmap = expandChannels(color[0], color[1], color[2], points);
imageDataToDataURL(imageBitmap, right - left + 1, bottom - top + 1,
imageDataToDataURL(
imageBitmap,
right - left + 1,
bottom - top + 1,
(dataURL: string) => new Promise((resolve) => {
fabric.Image.fromURL(dataURL, (image: fabric.Image) => {
try {
Expand Down Expand Up @@ -654,7 +690,10 @@
const color = fabric.Color.fromHex(this.getStateColor(editData.state)).getSource();
const [left, top, right, bottom] = points.slice(-4);
const imageBitmap = expandChannels(color[0], color[1], color[2], points);
imageDataToDataURL(imageBitmap, right - left + 1, bottom - top + 1,
imageDataToDataURL(
imageBitmap,
right - left + 1,
bottom - top + 1,
(dataURL: string) => new Promise((resolve) => {
fabric.Image.fromURL(dataURL, (image: fabric.Image) => {
try {
Expand Down
2 changes: 2 additions & 0 deletions cvat-core/src/annotations-collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1026,6 +1026,7 @@ export default class Collection {
label_id: state.label.id,
outside: state.outside || false,
occluded: state.occluded || false,
hidden: state.hidden || false,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure that creating a new object already hidden makes sense as it confuses the user
They press N and expect a new object to be appear, I think.
If the object is hidden, they may think that something is going wrong and start drawing it again (especially when there are a lot of objects in the sidebar, newly added is not visible in the list)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, maybe it is for new objects.
But what about edited ones? Should this hide feature affect state.hidden at all then?

points: state.shapeType === 'mask' ? (() => {
const { width, height } = this.frameMeta[state.frame];
return cropMask(state.points, width, height);
Expand All @@ -1045,6 +1046,7 @@ export default class Collection {
z_order: 0,
outside: element.outside || false,
occluded: element.occluded || false,
hidden: state.hidden || false,
klakhov marked this conversation as resolved.
Show resolved Hide resolved
})) : undefined,
});
} else if (state.objectType === 'track') {
Expand Down
2 changes: 1 addition & 1 deletion cvat-core/src/annotations-objects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ class Drawn extends Annotation {
super(data, clientID, color, injection);
this.frameMeta = injection.frameMeta;
this.descriptions = data.descriptions || [];
this.hidden = false;
this.hidden = data.hidden || false;
this.pinned = true;
this.shapeType = null;
}
Expand Down
2 changes: 1 addition & 1 deletion cvat-core/src/object-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ export default class ObjectState {
zOrder: 0,
lock: serialized.lock || false,
color: '#000000',
hidden: false,
hidden: serialized.hidden || false,
pinned: false,
source: serialized.source || Source.MANUAL,
keyframes: serialized.keyframes || null,
Expand Down
2 changes: 1 addition & 1 deletion cvat-ui/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cvat-ui",
"version": "1.66.1",
"version": "1.66.2",
"description": "CVAT single-page application",
"main": "src/index.tsx",
"scripts": {
Expand Down
84 changes: 84 additions & 0 deletions cvat-ui/src/actions/annotation-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@ export enum AnnotationActionTypes {
COLLAPSE_APPEARANCE = 'COLLAPSE_APPEARANCE',
COLLAPSE_OBJECT_ITEMS = 'COLLAPSE_OBJECT_ITEMS',
ACTIVATE_OBJECT = 'ACTIVATE_OBJECT',
UPDATE_EDITED_STATE = 'UPDATE_EDITED_STATE',
HIDE_EDITED_STATE = 'HIDE_EDITED_STATE',
RESET_EDITED_STATE = 'RESET_EDITED_STATE',
REMOVE_OBJECT = 'REMOVE_OBJECT',
REMOVE_OBJECT_SUCCESS = 'REMOVE_OBJECT_SUCCESS',
REMOVE_OBJECT_FAILED = 'REMOVE_OBJECT_FAILED',
Expand Down Expand Up @@ -1608,3 +1611,84 @@ export function restoreFrameAsync(frame: number): ThunkAction {
}
};
}

export function updateEditedStateAsync(
shapeType: ShapeType | null,
editedStateInstance: ObjectState | null,
): ThunkAction {
return async (dispatch: ThunkDispatch, getState): Promise<void> => {
if (editedStateInstance) {
const state = getState();
const { instance: canvas } = state.annotation.canvas;
if (canvas) {
(canvas as Canvas).configure({
hideEditedObject: editedStateInstance.hidden,
});
}

dispatch({
type: AnnotationActionTypes.HIDE_EDITED_STATE,
payload: {
hide: editedStateInstance.hidden,
},
});
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we have three actions changeHideEditedStateAsync, updateEditedStateAsync, resetEditedStateAsync doing the same thing (updating the same key in store).

Probably we may combine them to reduce the code duplication. Let it accept shapeType, objectState and isHidden. And use these arguments to update store and objectState properly

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Considering my general review comment it should only accept objectState: ObjectState | null

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now we have two actions: updateEditedStateAsync for manipulation of edited state object, another - changeHideActiveObjectAsync to change the hide flag. I put the hide flag in canvas part of the store.

dispatch({
type: AnnotationActionTypes.UPDATE_EDITED_STATE,
payload: {
shapeType,
editedStateInstance,
},
});
};
}

export function resetEditedStateAsync(): ThunkAction {
return async (dispatch: ThunkDispatch, getState): Promise<void> => {
const state = getState();
const { instance: canvas } = state.annotation.canvas;
const { editedStateHidden } = state.annotation.editing;
if (canvas && editedStateHidden) {
(canvas as Canvas).configure({
hideEditedObject: false,
});
}

dispatch({
type: AnnotationActionTypes.RESET_EDITED_STATE,
payload: {},
});
klakhov marked this conversation as resolved.
Show resolved Hide resolved
};
}

export function resetEditedState(): AnyAction {
return {
type: AnnotationActionTypes.RESET_EDITED_STATE,
payload: {},
};
}

export function changeHideEditedStateAsync(hide: boolean): ThunkAction {
return async (dispatch: ThunkDispatch, getState): Promise<void> => {
const state = getState();
const { instance: canvas } = state.annotation.canvas;
if (canvas) {
(canvas as Canvas).configure({
hideEditedObject: hide,
});

dispatch({
type: AnnotationActionTypes.HIDE_EDITED_STATE,
payload: {
hide,
},
});

const { editedStateInstance } = state.annotation.editing;
if (editedStateInstance) {
editedStateInstance.hidden = hide;
await dispatch(updateAnnotationsAsync([editedStateInstance]));
}
}
};
}
Loading
Loading