Skip to content

Commit 26f1f78

Browse files
authored
Do not block canvas navigation when frame is being fetched (cvat-ai#8284)
1 parent a9d250d commit 26f1f78

File tree

8 files changed

+73
-90
lines changed

8 files changed

+73
-90
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
### Changed
2+
3+
- Player navigation not blocked anymore if a frame is being loaded from the server
4+
(<https://github.com/cvat-ai/cvat/pull/8284>)

cvat-canvas/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "cvat-canvas",
3-
"version": "2.20.6",
3+
"version": "2.20.7",
44
"type": "module",
55
"description": "Part of Computer Vision Annotation Tool which presents its canvas library",
66
"main": "src/canvas.ts",

cvat-canvas/src/typescript/canvasModel.ts

+12-7
Original file line numberDiff line numberDiff line change
@@ -549,14 +549,24 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
549549
) {
550550
this.data.zLayer = zLayer;
551551
this.data.objects = objectStates;
552-
this.notify(UpdateReasons.OBJECTS_UPDATED);
552+
if (this.data.image) {
553+
// display objects only if there is a drawn image
554+
// if there is not, UpdateReasons.OBJECTS_UPDATED will be triggered after image is set
555+
// it covers cases when annotations are changed while image is being received from the server
556+
// e.g. with UI buttons (lock, unlock), shortcuts, delete/restore frames,
557+
// and anytime when a list of objects updated in cvat-ui
558+
this.notify(UpdateReasons.OBJECTS_UPDATED);
559+
}
553560
return;
554561
}
555562

556563
this.data.imageID = frameData.number;
564+
this.data.imageIsDeleted = frameData.deleted;
565+
if (this.data.imageIsDeleted) {
566+
this.data.angle = 0;
567+
}
557568

558569
const { zLayer: prevZLayer, objects: prevObjects } = this.data;
559-
560570
frameData
561571
.data((): void => {
562572
this.data.image = null;
@@ -580,11 +590,6 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
580590
};
581591

582592
this.data.image = data;
583-
this.data.imageIsDeleted = frameData.deleted;
584-
if (this.data.imageIsDeleted) {
585-
this.data.angle = 0;
586-
}
587-
588593
this.fit();
589594

590595
// restore correct image position after switching to a new frame

cvat-canvas/src/typescript/canvasView.ts

+20-12
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
6262
private controller: CanvasController;
6363
private svgShapes: Record<number, SVG.Shape>;
6464
private svgTexts: Record<number, SVG.Text>;
65+
private isImageLoading: boolean;
6566
private issueRegionPattern_1: SVG.Pattern;
6667
private issueRegionPattern_2: SVG.Pattern;
6768
private drawnStates: Record<number, DrawnState>;
@@ -1437,6 +1438,8 @@ export class CanvasViewImpl implements CanvasView, Listener {
14371438
editHidden: {},
14381439
sliceHidden: {},
14391440
};
1441+
1442+
this.isImageLoading = true;
14401443
this.draggableShape = null;
14411444
this.resizableShape = null;
14421445

@@ -1643,19 +1646,21 @@ export class CanvasViewImpl implements CanvasView, Listener {
16431646
if (this.mode !== Mode.IDLE) return;
16441647
if (e.ctrlKey || e.altKey) return;
16451648

1646-
const { offset } = this.controller.geometry;
1647-
const [x, y] = translateToSVG(this.content, [e.clientX, e.clientY]);
1648-
const event: CustomEvent = new CustomEvent('canvas.moved', {
1649-
bubbles: false,
1650-
cancelable: true,
1651-
detail: {
1652-
x: x - offset,
1653-
y: y - offset,
1654-
states: this.controller.objects,
1655-
},
1656-
});
1649+
if (!this.isImageLoading) {
1650+
const { offset } = this.controller.geometry;
1651+
const [x, y] = translateToSVG(this.content, [e.clientX, e.clientY]);
1652+
const event: CustomEvent = new CustomEvent('canvas.moved', {
1653+
bubbles: false,
1654+
cancelable: true,
1655+
detail: {
1656+
x: x - offset,
1657+
y: y - offset,
1658+
states: this.controller.objects,
1659+
},
1660+
});
16571661

1658-
this.canvas.dispatchEvent(event);
1662+
this.canvas.dispatchEvent(event);
1663+
}
16591664
});
16601665

16611666
this.content.oncontextmenu = (): boolean => false;
@@ -1787,6 +1792,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
17871792
} else if (reason === UpdateReasons.IMAGE_CHANGED) {
17881793
const { image } = model;
17891794
if (image) {
1795+
this.isImageLoading = false;
17901796
const ctx = this.background.getContext('2d');
17911797
this.background.setAttribute('width', `${image.renderWidth}px`);
17921798
this.background.setAttribute('height', `${image.renderHeight}px`);
@@ -1819,6 +1825,8 @@ export class CanvasViewImpl implements CanvasView, Listener {
18191825
this.moveCanvas();
18201826
this.resizeCanvas();
18211827
this.transformCanvas();
1828+
} else {
1829+
this.isImageLoading = true;
18221830
}
18231831
} else if (reason === UpdateReasons.FITTED_CANVAS) {
18241832
// Canvas geometry is going to be changed. Old object positions aren't valid any more

cvat-ui/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "cvat-ui",
3-
"version": "1.64.3",
3+
"version": "1.64.4",
44
"description": "CVAT single-page application",
55
"main": "src/index.tsx",
66
"scripts": {

cvat-ui/src/components/annotation-page/canvas/views/canvas2d/canvas-wrapper.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -926,7 +926,7 @@ class CanvasWrapperComponent extends React.PureComponent<Props> {
926926

927927
if (activatedStateID !== null) {
928928
const [activatedState] = annotations.filter((state: any): boolean => state.clientID === activatedStateID);
929-
if (workspace === Workspace.ATTRIBUTES) {
929+
if (activatedState && workspace === Workspace.ATTRIBUTES) {
930930
if (activatedState.objectType !== ObjectType.TAG) {
931931
canvasInstance.focus(activatedStateID, aamZoomMargin);
932932
} else {
@@ -959,8 +959,8 @@ class CanvasWrapperComponent extends React.PureComponent<Props> {
959959
const proxy = new Proxy(frameData, {
960960
get: (_frameData, prop, receiver) => {
961961
if (prop === 'data') {
962-
return async () => {
963-
const originalImage = await _frameData.data();
962+
return async (...args: any[]) => {
963+
const originalImage = await _frameData.data(...args);
964964
const imageIsNotProcessed = imageFilters.some((imageFilter: ImageFilter) => (
965965
imageFilter.modifier.currentProcessedImage !== frame
966966
));

cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx

+22-21
Original file line numberDiff line numberDiff line change
@@ -295,17 +295,17 @@ class AnnotationTopBarContainer extends React.PureComponent<Props> {
295295
}
296296

297297
private undo = (): void => {
298-
const { undo, canvasIsReady, undoAction } = this.props;
298+
const { undo, undoAction } = this.props;
299299

300-
if (isAbleToChangeFrame() && canvasIsReady && undoAction) {
300+
if (isAbleToChangeFrame() && undoAction) {
301301
undo();
302302
}
303303
};
304304

305305
private redo = (): void => {
306-
const { redo, canvasIsReady, redoAction } = this.props;
306+
const { redo, redoAction } = this.props;
307307

308-
if (isAbleToChangeFrame() && canvasIsReady && redoAction) {
308+
if (isAbleToChangeFrame() && redoAction) {
309309
redo();
310310
}
311311
};
@@ -335,12 +335,12 @@ class AnnotationTopBarContainer extends React.PureComponent<Props> {
335335
private onFirstFrame = async (): Promise<void> => {
336336
const {
337337
frameNumber, jobInstance, playing,
338-
onSwitchPlay, showDeletedFrames, canvasIsReady,
338+
onSwitchPlay, showDeletedFrames,
339339
} = this.props;
340340

341341
const newFrame =
342342
await jobInstance.frames.search({ notDeleted: !showDeletedFrames }, jobInstance.startFrame, frameNumber);
343-
if (newFrame !== frameNumber && newFrame !== null && canvasIsReady) {
343+
if (newFrame !== frameNumber && newFrame !== null) {
344344
if (playing) {
345345
onSwitchPlay(false);
346346
}
@@ -351,7 +351,7 @@ class AnnotationTopBarContainer extends React.PureComponent<Props> {
351351
private onBackward = async (): Promise<void> => {
352352
const {
353353
frameNumber, frameStep, jobInstance, playing,
354-
onSwitchPlay, showDeletedFrames, canvasIsReady,
354+
onSwitchPlay, showDeletedFrames,
355355
} = this.props;
356356

357357
const newFrame = await jobInstance.frames.search(
@@ -360,7 +360,7 @@ class AnnotationTopBarContainer extends React.PureComponent<Props> {
360360
jobInstance.startFrame,
361361
);
362362

363-
if (newFrame !== frameNumber && newFrame !== null && canvasIsReady) {
363+
if (newFrame !== frameNumber && newFrame !== null) {
364364
if (playing) {
365365
onSwitchPlay(false);
366366
}
@@ -371,7 +371,7 @@ class AnnotationTopBarContainer extends React.PureComponent<Props> {
371371
private onPrevFrame = async (): Promise<void> => {
372372
const {
373373
frameNumber, jobInstance, playing, searchAnnotations,
374-
onSwitchPlay, showDeletedFrames, canvasIsReady, navigationType,
374+
onSwitchPlay, showDeletedFrames, navigationType,
375375
} = this.props;
376376
const { startFrame } = jobInstance;
377377

@@ -382,7 +382,7 @@ class AnnotationTopBarContainer extends React.PureComponent<Props> {
382382
jobInstance.startFrame,
383383
);
384384

385-
if (newFrame !== frameNumber && newFrame !== null && canvasIsReady && isAbleToChangeFrame()) {
385+
if (newFrame !== frameNumber && newFrame !== null && isAbleToChangeFrame()) {
386386
if (playing) {
387387
onSwitchPlay(false);
388388
}
@@ -400,7 +400,7 @@ class AnnotationTopBarContainer extends React.PureComponent<Props> {
400400
private onNextFrame = async (): Promise<void> => {
401401
const {
402402
frameNumber, jobInstance, playing, searchAnnotations,
403-
onSwitchPlay, showDeletedFrames, canvasIsReady, navigationType,
403+
onSwitchPlay, showDeletedFrames, navigationType,
404404
} = this.props;
405405
const { stopFrame } = jobInstance;
406406

@@ -410,7 +410,7 @@ class AnnotationTopBarContainer extends React.PureComponent<Props> {
410410
frameFrom,
411411
jobInstance.stopFrame,
412412
);
413-
if (newFrame !== frameNumber && newFrame !== null && canvasIsReady && isAbleToChangeFrame()) {
413+
if (newFrame !== frameNumber && newFrame !== null && isAbleToChangeFrame()) {
414414
if (playing) {
415415
onSwitchPlay(false);
416416
}
@@ -428,7 +428,7 @@ class AnnotationTopBarContainer extends React.PureComponent<Props> {
428428
private onForward = async (): Promise<void> => {
429429
const {
430430
frameNumber, frameStep, jobInstance, playing,
431-
onSwitchPlay, showDeletedFrames, canvasIsReady,
431+
onSwitchPlay, showDeletedFrames,
432432
} = this.props;
433433

434434
const newFrame = await jobInstance.frames.search(
@@ -437,7 +437,7 @@ class AnnotationTopBarContainer extends React.PureComponent<Props> {
437437
jobInstance.stopFrame,
438438
);
439439

440-
if (newFrame !== frameNumber && newFrame !== null && canvasIsReady) {
440+
if (newFrame !== frameNumber && newFrame !== null) {
441441
if (playing) {
442442
onSwitchPlay(false);
443443
}
@@ -448,12 +448,12 @@ class AnnotationTopBarContainer extends React.PureComponent<Props> {
448448
private onLastFrame = async (): Promise<void> => {
449449
const {
450450
frameNumber, jobInstance, playing,
451-
onSwitchPlay, showDeletedFrames, canvasIsReady,
451+
onSwitchPlay, showDeletedFrames,
452452
} = this.props;
453453

454454
const newFrame =
455455
await jobInstance.frames.search({ notDeleted: !showDeletedFrames }, jobInstance.stopFrame, frameNumber);
456-
if (newFrame !== frameNumber && frameNumber !== null && canvasIsReady) {
456+
if (newFrame !== frameNumber && newFrame !== null) {
457457
if (playing) {
458458
onSwitchPlay(false);
459459
}
@@ -463,12 +463,11 @@ class AnnotationTopBarContainer extends React.PureComponent<Props> {
463463

464464
private searchAnnotations = (direction: 'forward' | 'backward'): void => {
465465
const {
466-
frameNumber, jobInstance,
467-
canvasIsReady, searchAnnotations,
466+
frameNumber, jobInstance, searchAnnotations,
468467
} = this.props;
469468
const { startFrame, stopFrame } = jobInstance;
470469

471-
if (isAbleToChangeFrame() && canvasIsReady) {
470+
if (isAbleToChangeFrame()) {
472471
if (direction === 'forward' && frameNumber + 1 <= stopFrame) {
473472
searchAnnotations(jobInstance, frameNumber + 1, stopFrame);
474473
} else if (direction === 'backward' && frameNumber - 1 >= startFrame) {
@@ -557,9 +556,11 @@ class AnnotationTopBarContainer extends React.PureComponent<Props> {
557556

558557
private onDeleteFrame = (): void => {
559558
const {
560-
deleteFrame, frameNumber, jobInstance, canvasIsReady,
559+
deleteFrame, frameNumber, jobInstance,
561560
} = this.props;
562-
if (canvasIsReady && jobInstance.type !== JobType.GROUND_TRUTH) deleteFrame(frameNumber);
561+
if (jobInstance.type !== JobType.GROUND_TRUTH) {
562+
deleteFrame(frameNumber);
563+
}
563564
};
564565

565566
private onRestoreFrame = (): void => {

cvat-ui/src/reducers/annotation-reducer.ts

+10-45
Original file line numberDiff line numberDiff line change
@@ -587,15 +587,17 @@ export default (state = defaultState, action: AnyAction): AnnotationState => {
587587
}
588588
case AnnotationActionTypes.ACTIVATE_OBJECT: {
589589
const { activatedStateID, activatedElementID, activatedAttributeID } = action.payload;
590-
591590
const {
592591
canvas: { activeControl, instance },
593-
annotations: { highlightedConflict },
592+
annotations: { highlightedConflict, states },
594593
} = state;
595594

596-
if (activeControl !== ActiveControl.CURSOR ||
597-
(instance as Canvas | Canvas3d).mode() !== CanvasMode.IDLE ||
598-
highlightedConflict) {
595+
const objectDoesNotExist = activatedStateID !== null &&
596+
!states.some((_state) => _state.clientID === activatedStateID);
597+
const canvasIsNotReady = (instance as Canvas | Canvas3d)
598+
.mode() !== CanvasMode.IDLE || activeControl !== ActiveControl.CURSOR;
599+
600+
if (objectDoesNotExist || canvasIsNotReady || highlightedConflict) {
599601
return state;
600602
}
601603

@@ -1008,56 +1010,19 @@ export default (state = defaultState, action: AnyAction): AnnotationState => {
10081010
},
10091011
};
10101012
}
1011-
case AnnotationActionTypes.DELETE_FRAME:
1012-
case AnnotationActionTypes.RESTORE_FRAME: {
1013-
return {
1014-
...state,
1015-
player: {
1016-
...state.player,
1017-
frame: {
1018-
...state.player.frame,
1019-
fetching: true,
1020-
},
1021-
},
1022-
canvas: {
1023-
...state.canvas,
1024-
ready: false,
1025-
},
1026-
};
1027-
}
1028-
case AnnotationActionTypes.DELETE_FRAME_FAILED:
1029-
case AnnotationActionTypes.RESTORE_FRAME_FAILED: {
1030-
return {
1031-
...state,
1032-
player: {
1033-
...state.player,
1034-
frame: {
1035-
...state.player.frame,
1036-
fetching: false,
1037-
},
1038-
},
1039-
canvas: {
1040-
...state.canvas,
1041-
ready: true,
1042-
},
1043-
};
1044-
}
10451013
case AnnotationActionTypes.DELETE_FRAME_SUCCESS:
10461014
case AnnotationActionTypes.RESTORE_FRAME_SUCCESS: {
1015+
const { data } = action.payload;
1016+
10471017
return {
10481018
...state,
10491019
player: {
10501020
...state.player,
10511021
frame: {
10521022
...state.player.frame,
1053-
data: action.payload.data,
1054-
fetching: false,
1023+
data,
10551024
},
10561025
},
1057-
canvas: {
1058-
...state.canvas,
1059-
ready: true,
1060-
},
10611026
};
10621027
}
10631028
case AnnotationActionTypes.HIGHLIGHT_CONFLICT: {

0 commit comments

Comments
 (0)