From 1ef96316def82044a07260bea0d7005f4016fb14 Mon Sep 17 00:00:00 2001 From: meyg8600 Date: Fri, 24 Apr 2020 17:11:14 +0800 Subject: [PATCH 1/2] Added fixes from PR #1370 of opencv/cvat --- cvat-canvas/src/typescript/canvasModel.ts | 4 + cvat-canvas/src/typescript/canvasView.ts | 8 +- cvat-core/src/annotations-collection.js | 74 ++--- .../annotation-page/annotation-page.tsx | 22 +- .../components/create-task-page/styles.scss | 2 +- cvat-ui/src/components/tasks-page/styles.scss | 6 +- .../objects-side-bar/object-item.tsx | 60 ++--- .../objects-side-bar/objects-list.tsx | 11 +- .../annotation-page/top-bar/top-bar.tsx | 79 +++--- cvat-ui/src/cvat-canvas.ts | 6 + cvat-ui/src/styles.scss | 1 - .../engine/static/engine/js/shapeMerger.js | 8 +- .../engine/templates/engine/annotation.html | 253 ++++++++++-------- 13 files changed, 296 insertions(+), 238 deletions(-) diff --git a/cvat-canvas/src/typescript/canvasModel.ts b/cvat-canvas/src/typescript/canvasModel.ts index 66a2717b0562..ffd3c86c5fa5 100644 --- a/cvat-canvas/src/typescript/canvasModel.ts +++ b/cvat-canvas/src/typescript/canvasModel.ts @@ -304,6 +304,10 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { } public setup(frameData: any, objectStates: any[]): void { + if ([Mode.EDIT, Mode.DRAG, Mode.RESIZE].includes(this.data.mode)) { + throw Error(`Canvas is busy. Action: ${this.data.mode}`); + } + if (frameData.number === this.data.imageID) { this.data.objects = objectStates; this.notify(UpdateReasons.OBJECTS_UPDATED); diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index f691e718b382..d41d1eb73cb4 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -799,7 +799,7 @@ export class CanvasViewImpl implements CanvasView, Listener { const shape = this.svgShapes[this.activeElement.clientID]; if (trackStart === 0) { - console.log('not tracking'); + trackStart = 0 } else if (trackStart === 1) { // startX = e.clientX // startY = e.clientY @@ -1202,7 +1202,7 @@ export class CanvasViewImpl implements CanvasView, Listener { (this.svgShapes[clientID] as any).clear(); this.svgShapes[clientID].attr('points', stringified); - if (state.shapeType === 'points') { + if (state.shapeType === 'points' && !state.hidden) { this.selectize(false, this.svgShapes[clientID]); this.setupPoints(this.svgShapes[clientID] as SVG.PolyLine, state); } @@ -1350,7 +1350,7 @@ export class CanvasViewImpl implements CanvasView, Listener { (shape as any).off('resizestart'); (shape as any).off('resizing'); (shape as any).off('resizedone'); - (shape as any).resize(false); + (shape as any).resize('stop'); // TODO: Hide text only if it is hidden by settings const text = this.svgTexts[clientID]; @@ -1730,6 +1730,8 @@ export class CanvasViewImpl implements CanvasView, Listener { group.on('click.canvas', (event: MouseEvent): void => { // Need to redispatch the event on another element basicPolyline.fire(new MouseEvent('click', event)); + // redispatch event to canvas to be able merge points clicking them + this.content.dispatchEvent(new MouseEvent('click', event)); }); group.bbox = basicPolyline.bbox.bind(basicPolyline); diff --git a/cvat-core/src/annotations-collection.js b/cvat-core/src/annotations-collection.js index e3ba2735bc96..c1d2dc560711 100644 --- a/cvat-core/src/annotations-collection.js +++ b/cvat-core/src/annotations-collection.js @@ -46,22 +46,22 @@ let shapeModel = null; switch (type) { - case 'rectangle': - shapeModel = new RectangleShape(shapeData, clientID, color, injection); - break; - case 'polygon': - shapeModel = new PolygonShape(shapeData, clientID, color, injection); - break; - case 'polyline': - shapeModel = new PolylineShape(shapeData, clientID, color, injection); - break; - case 'points': - shapeModel = new PointsShape(shapeData, clientID, color, injection); - break; - default: - throw new DataError( - `An unexpected type of shape "${type}"`, - ); + case 'rectangle': + shapeModel = new RectangleShape(shapeData, clientID, color, injection); + break; + case 'polygon': + shapeModel = new PolygonShape(shapeData, clientID, color, injection); + break; + case 'polyline': + shapeModel = new PolylineShape(shapeData, clientID, color, injection); + break; + case 'points': + shapeModel = new PointsShape(shapeData, clientID, color, injection); + break; + default: + throw new DataError( + `An unexpected type of shape "${type}"`, + ); } return shapeModel; @@ -75,22 +75,22 @@ let trackModel = null; switch (type) { - case 'rectangle': - trackModel = new RectangleTrack(trackData, clientID, color, injection); - break; - case 'polygon': - trackModel = new PolygonTrack(trackData, clientID, color, injection); - break; - case 'polyline': - trackModel = new PolylineTrack(trackData, clientID, color, injection); - break; - case 'points': - trackModel = new PointsTrack(trackData, clientID, color, injection); - break; - default: - throw new DataError( - `An unexpected type of track "${type}"`, - ); + case 'rectangle': + trackModel = new RectangleTrack(trackData, clientID, color, injection); + break; + case 'polygon': + trackModel = new PolygonTrack(trackData, clientID, color, injection); + break; + case 'polyline': + trackModel = new PolylineTrack(trackData, clientID, color, injection); + break; + case 'points': + trackModel = new PointsTrack(trackData, clientID, color, injection); + break; + default: + throw new DataError( + `An unexpected type of track "${type}"`, + ); } return trackModel; @@ -373,7 +373,7 @@ } else { throw new ArgumentError( `Trying to merge unknown object type: ${object.constructor.name}. ` - + 'Only shapes and tracks are expected.', + + 'Only shapes and tracks are expected.', ); } } @@ -737,7 +737,7 @@ if (!Object.values(ObjectShape).includes(state.shapeType)) { throw new ArgumentError( 'Object shape must be one of: ' - + `${JSON.stringify(Object.values(ObjectShape))}`, + + `${JSON.stringify(Object.values(ObjectShape))}`, ); } @@ -773,7 +773,7 @@ } else { throw new ArgumentError( 'Object type must be one of: ' - + `${JSON.stringify(Object.values(ObjectType))}`, + + `${JSON.stringify(Object.values(ObjectType))}`, ); } } @@ -871,8 +871,10 @@ // In particular consider first and last frame as keyframes for all frames const statesData = [].concat( (frame in this.shapes ? this.shapes[frame] : []) + .filter((shape) => !shape.removed) .map((shape) => shape.get(frame)), (frame in this.tags ? this.tags[frame] : []) + .filter((tag) => !tag.removed) .map((tag) => tag.get(frame)), ); const tracks = Object.values(this.tracks) @@ -880,7 +882,7 @@ frame in track.shapes || frame === frameFrom || frame === frameTo - )); + )).filter((track) => !track.removed); statesData.push( ...tracks.map((track) => track.get(frame)) .filter((state) => !state.outside), diff --git a/cvat-ui/src/components/annotation-page/annotation-page.tsx b/cvat-ui/src/components/annotation-page/annotation-page.tsx index db5b6500e9b6..cec3495e75e9 100644 --- a/cvat-ui/src/components/annotation-page/annotation-page.tsx +++ b/cvat-ui/src/components/annotation-page/annotation-page.tsx @@ -36,7 +36,17 @@ export default function AnnotationPageComponent(props: Props): JSX.Element { useEffect(() => { saveLogs(); - return saveLogs; + const root = window.document.getElementById('root'); + if (root) { + root.style.minHeight = '768px'; + } + + return () => { + saveLogs(); + if (root) { + root.style.minHeight = ''; + } + }; }, []); if (job === null) { @@ -63,15 +73,15 @@ export default function AnnotationPageComponent(props: Props): JSX.Element { - { workspace === Workspace.STANDARD ? ( + {workspace === Workspace.STANDARD ? ( ) : ( - - - - )} + + + + )} ); diff --git a/cvat-ui/src/components/create-task-page/styles.scss b/cvat-ui/src/components/create-task-page/styles.scss index 3419d8608342..20ec119ac6ba 100644 --- a/cvat-ui/src/components/create-task-page/styles.scss +++ b/cvat-ui/src/components/create-task-page/styles.scss @@ -15,7 +15,7 @@ } .cvat-create-task-content { - margin-top: 20px; + padding-top: 20px; width: 100%; height: auto; border: 1px solid $border-color-1; diff --git a/cvat-ui/src/components/tasks-page/styles.scss b/cvat-ui/src/components/tasks-page/styles.scss index b2fbc9be79c2..66cd8d4c3349 100644 --- a/cvat-ui/src/components/tasks-page/styles.scss +++ b/cvat-ui/src/components/tasks-page/styles.scss @@ -9,7 +9,7 @@ height: 100%; > div:nth-child(1) { - margin-bottom: 10px; + padding-bottom: 10px; div > { span { @@ -36,11 +36,11 @@ > div:nth-child(3) { height: 83%; - margin-top: 10px; + padding-top: 10px; } > div:nth-child(4) { - margin-top: 10px; + padding-top: 10px; } } diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx index 5a49fa2882c7..a4b0799b817b 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx @@ -7,6 +7,7 @@ import copy from 'copy-to-clipboard'; import { connect } from 'react-redux'; import { LogType } from 'cvat-logger'; +import { Canvas, isAbleToChangeFrame } from 'cvat-canvas'; import { ActiveControl, CombinedState, ColorBy } from 'reducers/interfaces'; import { collapseObjectItems, @@ -24,7 +25,6 @@ import { import ObjectStateItemComponent from 'components/annotation-page/standard-workspace/objects-side-bar/object-item'; - interface OwnProps { clientID: number; } @@ -44,6 +44,7 @@ interface StateToProps { minZLayer: number; maxZLayer: number; normalizedKeyMap: Record; + canvasInstance: Canvas; } interface DispatchToProps { @@ -84,6 +85,7 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps { canvas: { ready, activeControl, + instance: canvasInstance, }, colors, }, @@ -119,6 +121,7 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps { minZLayer, maxZLayer, normalizedKeyMap, + canvasInstance, }; } @@ -166,71 +169,47 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { type Props = StateToProps & DispatchToProps; class ObjectItemContainer extends React.PureComponent { private navigateFirstKeyframe = (): void => { - const { - objectState, - changeFrame, - frameNumber, - } = this.props; + const { objectState, frameNumber } = this.props; const { first } = objectState.keyframes; if (first !== frameNumber) { - changeFrame(first); + this.changeFrame(first); } }; private navigatePrevKeyframe = (): void => { - const { - objectState, - changeFrame, - frameNumber, - } = this.props; + const { objectState, frameNumber } = this.props; const { prev } = objectState.keyframes; if (prev !== null && prev !== frameNumber) { - changeFrame(prev); + this.changeFrame(prev); } }; private navigateNextKeyframe = (): void => { - const { - objectState, - changeFrame, - frameNumber, - } = this.props; - + const { objectState, frameNumber } = this.props; const { next } = objectState.keyframes; if (next !== null && next !== frameNumber) { - changeFrame(next); + this.changeFrame(next); } }; private navigateLastKeyframe = (): void => { - const { - objectState, - changeFrame, - frameNumber, - } = this.props; - + const { objectState, frameNumber } = this.props; const { last } = objectState.keyframes; if (last !== frameNumber) { - changeFrame(last); + this.changeFrame(last); } }; private copy = (): void => { - const { - objectState, - copyShape, - } = this.props; + const { objectState, copyShape } = this.props; copyShape(objectState); }; private propagate = (): void => { - const { - objectState, - propagateObject, - } = this.props; + const { objectState, propagateObject } = this.props; propagateObject(objectState); }; @@ -422,6 +401,13 @@ class ObjectItemContainer extends React.PureComponent { this.commit(); }; + private changeFrame(frame: number): void { + const { changeFrame, canvasInstance } = this.props; + if (isAbleToChangeFrame(canvasInstance)) { + changeFrame(frame); + } + } + // EDITED FOR INTEGRATION private autoSnap = (): void => { const { @@ -429,7 +415,7 @@ class ObjectItemContainer extends React.PureComponent { jobInstance, frameNumber, } = this.props; - + let result = jobInstance.annotations.snap(objectState.serverID, frameNumber, objectState.points); result.then((data: any) => { objectState.points = data.points; @@ -544,7 +530,7 @@ class ObjectItemContainer extends React.PureComponent { collapse={this.collapse} // EDITED FOR INTEGRATION autoSnap={this.autoSnap} - // EDITED END + // EDITED END /> ); } diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx index 49e61a27041e..df533b87e7e3 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx @@ -15,6 +15,7 @@ import { copyShape as copyShapeAction, propagateObject as propagateObjectAction, } from 'actions/annotation-actions'; +import { Canvas, isAbleToChangeFrame } from 'cvat-canvas'; import { CombinedState, StatesOrdering, ObjectType } from 'reducers/interfaces'; interface StateToProps { @@ -32,6 +33,7 @@ interface StateToProps { annotationsFiltersHistory: string[]; keyMap: Record; normalizedKeyMap: Record; + canvasInstance: Canvas; } interface DispatchToProps { @@ -65,6 +67,9 @@ function mapStateToProps(state: CombinedState): StateToProps { number: frameNumber, }, }, + canvas: { + instance: canvasInstance, + }, tabContentHeight: listHeight, }, shortcuts: { @@ -104,6 +109,7 @@ function mapStateToProps(state: CombinedState): StateToProps { annotationsFiltersHistory, keyMap, normalizedKeyMap, + canvasInstance, }; } @@ -254,6 +260,7 @@ class ObjectsListContainer extends React.PureComponent { minZLayer, keyMap, normalizedKeyMap, + canvasInstance, } = this.props; const { sortedStatesID, @@ -388,7 +395,7 @@ class ObjectsListContainer extends React.PureComponent { if (state && state.objectType === ObjectType.TRACK) { const frame = typeof (state.keyframes.next) === 'number' ? state.keyframes.next : null; - if (frame !== null) { + if (frame !== null && isAbleToChangeFrame(canvasInstance)) { changeFrame(frame); } } @@ -399,7 +406,7 @@ class ObjectsListContainer extends React.PureComponent { if (state && state.objectType === ObjectType.TRACK) { const frame = typeof (state.keyframes.prev) === 'number' ? state.keyframes.prev : null; - if (frame !== null) { + if (frame !== null && isAbleToChangeFrame(canvasInstance)) { changeFrame(frame); } } diff --git a/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx b/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx index 549e3840c362..e81426c03df7 100644 --- a/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx +++ b/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx @@ -24,6 +24,7 @@ import { activateObject, switchTrack, } from 'actions/annotation-actions'; +import { Canvas, isAbleToChangeFrame } from 'cvat-canvas'; import AnnotationTopBarComponent from 'components/annotation-page/top-bar/top-bar'; import { CombinedState, FrameSpeed, Workspace } from 'reducers/interfaces'; @@ -46,7 +47,8 @@ interface StateToProps { workspace: Workspace; keyMap: Record; normalizedKeyMap: Record; - tracking:boolean; // EDITED FOR USER STORY 12/13 + tracking: boolean; // EDITED FOR USER STORY 12/13 + canvasInstance: Canvas; } interface DispatchToProps { @@ -85,6 +87,7 @@ function mapStateToProps(state: CombinedState): StateToProps { }, canvas: { ready: canvasIsReady, + instance: canvasInstance, }, workspace, }, @@ -123,6 +126,7 @@ function mapStateToProps(state: CombinedState): StateToProps { keyMap, normalizedKeyMap, tracking,// EDITED FOR USER STORY 12/13 + canvasInstance, }; } @@ -207,6 +211,7 @@ class AnnotationTopBarContainer extends React.PureComponent { frameDelay, playing, canvasIsReady, + canvasInstance, onSwitchPlay, onChangeFrame, } = this.props; @@ -227,10 +232,14 @@ class AnnotationTopBarContainer extends React.PureComponent { setTimeout(() => { const { playing: stillPlaying } = this.props; if (stillPlaying) { - onChangeFrame( - frameNumber + 1 + framesSkiped, - stillPlaying, framesSkiped + 1, - ); + if (isAbleToChangeFrame(canvasInstance)) { + onChangeFrame( + frameNumber + 1 + framesSkiped, + stillPlaying, framesSkiped + 1, + ); + } else { + onSwitchPlay(false); + } } }, frameDelay); } else { @@ -250,9 +259,12 @@ class AnnotationTopBarContainer extends React.PureComponent { undo, jobInstance, frameNumber, + canvasInstance, } = this.props; - undo(jobInstance, frameNumber); + if (isAbleToChangeFrame(canvasInstance)) { + undo(jobInstance, frameNumber); + } }; private redo = (): void => { @@ -260,9 +272,12 @@ class AnnotationTopBarContainer extends React.PureComponent { redo, jobInstance, frameNumber, + canvasInstance, } = this.props; - redo(jobInstance, frameNumber); + if (isAbleToChangeFrame(canvasInstance)) { + redo(jobInstance, frameNumber); + } }; private showStatistics = (): void => { @@ -284,8 +299,10 @@ class AnnotationTopBarContainer extends React.PureComponent { if (playing) { onSwitchPlay(false); + console.log('stop playing'); } else if (frameNumber < jobInstance.stopFrame) { onSwitchPlay(true); + console.log('start playing'); } }; // EDITED FOR USER STORY 12/13 @@ -300,7 +317,7 @@ class AnnotationTopBarContainer extends React.PureComponent { onSwitchTrack(false); } else if (frameNumber < jobInstance.stopFrame) { onSwitchTrack(true); - }else if(!tracking){ + } else if (!tracking) { onSwitchTrack(true); } }; @@ -312,7 +329,6 @@ class AnnotationTopBarContainer extends React.PureComponent { jobInstance, playing, onSwitchPlay, - onChangeFrame, } = this.props; const newFrame = jobInstance.startFrame; @@ -320,7 +336,7 @@ class AnnotationTopBarContainer extends React.PureComponent { if (playing) { onSwitchPlay(false); } - onChangeFrame(newFrame); + this.changeFrame(newFrame); } }; @@ -331,7 +347,6 @@ class AnnotationTopBarContainer extends React.PureComponent { jobInstance, playing, onSwitchPlay, - onChangeFrame, } = this.props; const newFrame = Math @@ -340,7 +355,7 @@ class AnnotationTopBarContainer extends React.PureComponent { if (playing) { onSwitchPlay(false); } - onChangeFrame(newFrame); + this.changeFrame(newFrame); } }; @@ -350,7 +365,6 @@ class AnnotationTopBarContainer extends React.PureComponent { jobInstance, playing, onSwitchPlay, - onChangeFrame, } = this.props; const newFrame = Math @@ -359,7 +373,7 @@ class AnnotationTopBarContainer extends React.PureComponent { if (playing) { onSwitchPlay(false); } - onChangeFrame(newFrame); + this.changeFrame(newFrame); } }; @@ -369,7 +383,6 @@ class AnnotationTopBarContainer extends React.PureComponent { jobInstance, playing, onSwitchPlay, - onChangeFrame, } = this.props; const newFrame = Math @@ -378,7 +391,7 @@ class AnnotationTopBarContainer extends React.PureComponent { if (playing) { onSwitchPlay(false); } - onChangeFrame(newFrame); + this.changeFrame(newFrame); } }; @@ -389,7 +402,6 @@ class AnnotationTopBarContainer extends React.PureComponent { jobInstance, playing, onSwitchPlay, - onChangeFrame, } = this.props; const newFrame = Math @@ -398,7 +410,7 @@ class AnnotationTopBarContainer extends React.PureComponent { if (playing) { onSwitchPlay(false); } - onChangeFrame(newFrame); + this.changeFrame(newFrame); } }; @@ -408,7 +420,6 @@ class AnnotationTopBarContainer extends React.PureComponent { jobInstance, playing, onSwitchPlay, - onChangeFrame, } = this.props; const newFrame = jobInstance.stopFrame; @@ -416,7 +427,7 @@ class AnnotationTopBarContainer extends React.PureComponent { if (playing) { onSwitchPlay(false); } - onChangeFrame(newFrame); + this.changeFrame(newFrame); } }; @@ -430,22 +441,16 @@ class AnnotationTopBarContainer extends React.PureComponent { }; private onChangePlayerSliderValue = (value: SliderValue): void => { - const { - playing, - onSwitchPlay, - onChangeFrame, - } = this.props; - + const { playing, onSwitchPlay } = this.props; if (playing) { onSwitchPlay(false); } - onChangeFrame(value as number); + this.changeFrame(value as number); }; private onChangePlayerInputValue = (value: number): void => { const { onSwitchPlay, - onChangeFrame, playing, frameNumber, } = this.props; @@ -454,7 +459,7 @@ class AnnotationTopBarContainer extends React.PureComponent { if (playing) { onSwitchPlay(false); } - onChangeFrame(value); + this.changeFrame(value); } }; @@ -468,6 +473,13 @@ class AnnotationTopBarContainer extends React.PureComponent { copy(url); }; + private changeFrame(frame: number): void { + const { onChangeFrame, canvasInstance } = this.props; + if (isAbleToChangeFrame(canvasInstance)) { + onChangeFrame(frame); + } + } + private beforeUnloadCallback(event: BeforeUnloadEvent): any { const { jobInstance } = this.props; if (jobInstance.annotations.hasUnsavedChanges()) { @@ -500,6 +512,7 @@ class AnnotationTopBarContainer extends React.PureComponent { changeWorkspace, keyMap, normalizedKeyMap, + canvasInstance, } = this.props; const preventDefault = (event: KeyboardEvent | undefined): void => { @@ -566,13 +579,17 @@ class AnnotationTopBarContainer extends React.PureComponent { }, SEARCH_FORWARD: (event: KeyboardEvent | undefined) => { preventDefault(event); - if (frameNumber + 1 <= stopFrame && canvasIsReady) { + if (frameNumber + 1 <= stopFrame && canvasIsReady + && isAbleToChangeFrame(canvasInstance) + ) { searchAnnotations(jobInstance, frameNumber + 1, stopFrame); } }, SEARCH_BACKWARD: (event: KeyboardEvent | undefined) => { preventDefault(event); - if (frameNumber - 1 >= startFrame && canvasIsReady) { + if (frameNumber - 1 >= startFrame && canvasIsReady + && isAbleToChangeFrame(canvasInstance) + ) { searchAnnotations(jobInstance, frameNumber - 1, startFrame); } }, diff --git a/cvat-ui/src/cvat-canvas.ts b/cvat-ui/src/cvat-canvas.ts index 6317c43567e7..1a21be5a3718 100644 --- a/cvat-ui/src/cvat-canvas.ts +++ b/cvat-ui/src/cvat-canvas.ts @@ -9,9 +9,15 @@ import { RectDrawingMethod, } from '../../cvat-canvas/src/typescript/canvas'; +function isAbleToChangeFrame(canvas: Canvas): boolean { + return ![CanvasMode.DRAG, CanvasMode.EDIT, CanvasMode.RESIZE] + .includes(canvas.mode()); +} + export { Canvas, CanvasMode, CanvasVersion, RectDrawingMethod, + isAbleToChangeFrame, }; diff --git a/cvat-ui/src/styles.scss b/cvat-ui/src/styles.scss index 965ea7567361..cbd6a4c2e763 100644 --- a/cvat-ui/src/styles.scss +++ b/cvat-ui/src/styles.scss @@ -48,5 +48,4 @@ hr { height: 100%; display: grid; min-width: 1280px; - min-height: 768px; } diff --git a/cvat/apps/engine/static/engine/js/shapeMerger.js b/cvat/apps/engine/static/engine/js/shapeMerger.js index c7fc478c540b..e7476d1a1d22 100644 --- a/cvat/apps/engine/static/engine/js/shapeMerger.js +++ b/cvat/apps/engine/static/engine/js/shapeMerger.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 Intel Corporation + * Copyright (C) 2018-2020 Intel Corporation * * SPDX-License-Identifier: MIT */ @@ -75,7 +75,7 @@ class ShapeMergerModel extends Listener { } } - let sortedFrames = Object.keys(shapeDict).map(x => +x).sort((a,b) => a - b); + let sortedFrames = Object.keys(shapeDict).map(x => +x).sort((a, b) => a - b); // remove all outside in begin of the track while (shapeDict[sortedFrames[0]].interpolation.position.outside) { @@ -145,7 +145,7 @@ class ShapeMergerModel extends Listener { let nextFrame = frame + 1; let stopFrame = window.cvat.player.frames.stop; let type = shapeDict[frame].shape.type; - if (type === 'annotation_box' && !(nextFrame in shapeDict) && nextFrame <= stopFrame) { + if (type.startsWith('annotation_') && !(nextFrame in shapeDict) && nextFrame <= stopFrame) { let copy = Object.assign({}, object.shapes[object.shapes.length - 1]); copy.outside = true; copy.frame += 1; @@ -234,7 +234,7 @@ class ShapeMergerController { function setupMergeShortkeys() { let shortkeys = window.cvat.config.shortkeys; - let switchMergeHandler = Logger.shortkeyLogDecorator(function() { + let switchMergeHandler = Logger.shortkeyLogDecorator(function () { this.switch(); }.bind(this)); diff --git a/cvat/apps/engine/templates/engine/annotation.html b/cvat/apps/engine/templates/engine/annotation.html index 66cf69fad65a..87ddf7228222 100644 --- a/cvat/apps/engine/templates/engine/annotation.html +++ b/cvat/apps/engine/templates/engine/annotation.html @@ -7,66 +7,66 @@ {% load static %} {% block head_css %} - {{ block.super }} - - {% for css_file in css_3rdparty %} - - {% endfor %} +{{ block.super }} + +{% for css_file in css_3rdparty %} + +{% endfor %} {% endblock %} {% block head_js_3rdparty %} - {{ block.super }} - - - - - - - - - - - {% for js_file in js_3rdparty %} - - {% endfor %} +{{ block.super }} + + + + + + + + + + +{% for js_file in js_3rdparty %} + +{% endfor %} {% endblock %} {% block head_js_cvat %} - {{ block.super }} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +{{ block.super }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {% endblock %} @@ -76,7 +76,7 @@
@@ -86,7 +86,8 @@ - + @@ -117,18 +118,19 @@
- + - + - + - + - + - + - + - -
+ +
- + - - + - +
@@ -173,43 +180,48 @@
-
+
-
+
-
+
-
+
- +
- +
- +
-
+ -
+
+
@@ -254,7 +273,8 @@ - + @@ -271,15 +291,17 @@ - + - + - + @@ -318,27 +340,28 @@
+
- + - + - + - + - + - + - +
@@ -356,7 +379,7 @@ @@ -366,15 +389,16 @@ - +
-
+
- {% for status in status_list %} - + {% endfor %}
@@ -423,8 +447,8 @@ T S T - - + +
@@ -459,7 +483,8 @@
- +
-{% endblock %} +{% endblock %} \ No newline at end of file From 138e53fb9110649d6c45e4132b2592d4d1358cd6 Mon Sep 17 00:00:00 2001 From: Maverick Rivera Date: Fri, 24 Apr 2020 18:07:22 +0800 Subject: [PATCH 2/2] Add disabler for autosnap when resizing Cleaned the code Removed console logs and unused variables Added comments to indicate code for bug fixes and for tracking feature Cleaned the code Removed comments on features coming from opencv/cvat Merged tracking feature from old_jeff to jeff Clean some code Changed scroll while tracking can now change size/width/height Middle mouse click switches between scroll to change size/width/height Fix tracking only finishes on left click, not middle(scroll) click or right click Add scroll to increase/decrease box size while tracking Add box following mouse while tracking Connect tracking feature in cvat-canvas and cvat-ui Add tracking feature in cvat-canvas Add new props (tracking, trackedStateID) Added shortcut 't' to for tracking Cleaned the code Remove unused function Uncommented code that triggers user story 2 Interpolate using resized frames while tracking Fix box size resets when leaving and reentering frame while tracking --- cvat-canvas/src/typescript/canvas.ts | 9 + .../src/typescript/canvasController.ts | 12 + cvat-canvas/src/typescript/canvasModel.ts | 82 +++++ cvat-canvas/src/typescript/canvasView.ts | 318 +++++++++++++++--- cvat-ui/src/actions/annotation-actions.ts | 70 ++-- .../standard-workspace/canvas-wrapper.tsx | 122 +++---- .../top-bar/player-buttons.tsx | 29 +- .../annotation-page/top-bar/top-bar.tsx | 23 +- .../standard-workspace/canvas-wrapper.tsx | 23 +- .../objects-side-bar/object-item.tsx | 8 +- .../annotation-page/top-bar/top-bar.tsx | 89 ++--- cvat-ui/src/reducers/annotation-reducer.ts | 34 +- cvat-ui/src/reducers/interfaces.ts | 6 + cvat-ui/src/reducers/shortcuts-reducer.ts | 6 +- cvat/apps/engine/views.py | 2 +- 15 files changed, 591 insertions(+), 242 deletions(-) diff --git a/cvat-canvas/src/typescript/canvas.ts b/cvat-canvas/src/typescript/canvas.ts index 04a99d5b1925..389cab1fd92f 100644 --- a/cvat-canvas/src/typescript/canvas.ts +++ b/cvat-canvas/src/typescript/canvas.ts @@ -56,6 +56,9 @@ interface Canvas { mode(): Mode; cancel(): void; + // EDITED FOR USER STORY 12/13 + trackObject(enable: boolean, objectID: number | null): void; + // EDITED END configure(configuration: Configuration): void; } @@ -74,6 +77,12 @@ class CanvasImpl implements Canvas { return this.view.html(); } + // EDITED FOR USER STORY 12/13 + public trackObject(enable: boolean, objectID: number | null): void { + this.model.trackObject(enable, objectID); + } + // EDITED END + public setZLayer(zLayer: number | null): void { this.model.setZLayer(zLayer); } diff --git a/cvat-canvas/src/typescript/canvasController.ts b/cvat-canvas/src/typescript/canvasController.ts index 179f9b32e869..42a9d006478a 100644 --- a/cvat-canvas/src/typescript/canvasController.ts +++ b/cvat-canvas/src/typescript/canvasController.ts @@ -37,6 +37,8 @@ export interface CanvasController { drag(x: number, y: number): void; disableDrag(): void; fit(): void; + + trackingElement: any; // EDITED FOR USER STORY 12/13 } export class CanvasControllerImpl implements CanvasController { @@ -147,4 +149,14 @@ export class CanvasControllerImpl implements CanvasController { public get mode(): Mode { return this.model.mode; } + + // EDITED FOR USER STORY 12/13 + public set trackingElement(value: any) { + this.model.trackingElement = value; + } + + public get trackingElement(): any { + return this.model.trackingElement; + } + // EDITED END } diff --git a/cvat-canvas/src/typescript/canvasModel.ts b/cvat-canvas/src/typescript/canvasModel.ts index 15a92f62d760..08bde0cb79d5 100644 --- a/cvat-canvas/src/typescript/canvasModel.ts +++ b/cvat-canvas/src/typescript/canvasModel.ts @@ -107,6 +107,7 @@ export enum UpdateReasons { DRAG_CANVAS = 'drag_canvas', ZOOM_CANVAS = 'zoom_canvas', CONFIG_UPDATED = 'config_updated', + SWITCH_TRACKING = 'SWITCH_TRACKING', // EDITED START FOR USER STORY 12/13 } export enum Mode { @@ -163,6 +164,12 @@ export interface CanvasModel { configure(configuration: Configuration): void; cancel(): void; + + + // EDITED FOR USER STORY 12/13 + trackObject(enable: boolean, trackedStateID: number | null): void; + trackingElement: any; + // EDITED END } export class CanvasModelImpl extends MasterImpl implements CanvasModel { @@ -189,6 +196,24 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { splitData: SplitData; selected: any; mode: Mode; + // EDITED START FOR USER STORY 12/13 + trackingElement: { + enable: boolean; + trackID: number | null; + mousecoords: any[]; + scalemode: number; + trackrect: any | null; + trackedStates: any[]; + trackedframes: any[]; + trackedcentroids: any[]; + trackedwidthheight: any[]; + interpolatekeyframes: any[]; + frameindex: { + frame: number | null; + index: number | null; + } + } + // EDITED END }; public constructor() { @@ -244,9 +269,56 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { }, selected: null, mode: Mode.IDLE, + + // EDITED START FOR USER STORY 12/13 + trackingElement: { + enable: false, + trackID: null, + trackrect: null, + mousecoords: [], + scalemode: 0, + trackedStates: [], + trackedframes: [], + trackedcentroids: [], + trackedwidthheight: [], + interpolatekeyframes: [], + frameindex: { + frame: null, + index: null, + } + } + // EDITED END }; } + // EDITED FOR USER STORY 12/13 + public trackObject(enable: boolean, trackedStateID: number | null): void { + if (enable) { + this.trackingElement.enable = true; + this.data.trackingElement.trackID = trackedStateID; + } else { + this.data.trackingElement = { + enable: false, + trackID: null, + mousecoords: this.data.trackingElement.mousecoords, + scalemode: 0, + trackrect: this.data.trackingElement.trackrect, + trackedStates: [], + trackedframes: [], + trackedcentroids: [], + trackedwidthheight: [], + interpolatekeyframes: [], + frameindex: { + frame: null, + index: null, + } + } + } + + this.notify(UpdateReasons.SWITCH_TRACKING); + } + // EDITED END + public setZLayer(zLayer: number | null): void { this.data.zLayer = zLayer; this.notify(UpdateReasons.SET_Z_LAYER); @@ -631,4 +703,14 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { public get mode(): Mode { return this.data.mode; } + + // EDITED FOR USER STORY 12/13 + public set trackingElement(value: any) { + this.data.trackingElement = value; + } + + public get trackingElement(): any { + return this.data.trackingElement; + } + // EDITED END } diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index 67e5ae125fb8..bb6fe2f9afa3 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -84,6 +84,16 @@ export class CanvasViewImpl implements CanvasView, Listener { private magnifyingGlassRadius: number; // EDITED END + // EDITED FOR USER STORY 12/13 + public set trackingElement(value: any) { + this.controller.trackingElement = value; + } + + public get trackingElement(): any { + return this.controller.trackingElement; + } + // EDITED END + private set mode(value: Mode) { this.controller.mode = value; } @@ -415,6 +425,90 @@ export class CanvasViewImpl implements CanvasView, Listener { .filter((id: number): boolean => !newIDs.includes(id)) .map((id: number): any => this.drawnStates[id]); + // EDITED START FOR USER STORY 12/13 + if (this.trackingElement.trackID !== null) { + const trackID = this.trackingElement.trackID; + if (newIDs.includes(trackID)) { + const [trackstate] = states.filter((el: any) => (el.clientID === trackID)); + + if (!this.trackingElement.trackedframes.includes(trackstate.frame)) { + this.trackingElement.trackedframes.push(trackstate.frame); + } + this.trackingElement.frameindex = { + frame: trackstate.frame, + index: this.trackingElement.trackedframes.indexOf(trackstate.frame), + } + + this.trackingElement.trackedStates[this.trackingElement.frameindex.index] = trackstate; + this.trackingElement.trackedcentroids[this.trackingElement.frameindex.index] = this.trackingElement.mousecoords; + + let resizewidth: any = null; + let resizeheight: any = null; + + if (this.trackingElement.interpolatekeyframes.includes(this.trackingElement.frameindex.frame)) { + resizewidth = this.trackingElement.trackedwidthheight[this.trackingElement.frameindex.index][0]; + resizeheight = this.trackingElement.trackedwidthheight[this.trackingElement.frameindex.index][1]; + } else { + resizewidth = trackstate.points[2] - trackstate.points[0]; + resizeheight = trackstate.points[3] - trackstate.points[1]; + } + + if (this.trackingElement.interpolatekeyframes.length != 0) { + const interpolatekeyframes = [ + Math.min(...this.trackingElement.trackedframes), + ...this.trackingElement.interpolatekeyframes, + Math.max(...this.trackingElement.trackedframes), + ].sort((a, b) => (a - b)); + + let borderframes: any = []; + for (let i = 0; i < interpolatekeyframes.length; i++) { + if (interpolatekeyframes[i] < this.trackingElement.frameindex.frame && interpolatekeyframes[i + 1] > this.trackingElement.frameindex.frame) { + borderframes = [interpolatekeyframes[i], interpolatekeyframes[i + 1]]; + break; + } + } + + if (borderframes.length !== 0) { + let borderframesindex: any = [ + this.trackingElement.trackedframes.indexOf(borderframes[0]), + this.trackingElement.trackedframes.indexOf(borderframes[1]), + ] + resizewidth = this.trackingElement.trackedwidthheight[borderframesindex[0]][0] + + (this.trackingElement.trackedwidthheight[borderframesindex[1]][0] + - this.trackingElement.trackedwidthheight[borderframesindex[0]][0]) + * (trackstate.frame - borderframes[0]) + / (borderframes[1] - borderframes[0]); + + resizeheight = this.trackingElement.trackedwidthheight[borderframesindex[0]][1] + + (this.trackingElement.trackedwidthheight[borderframesindex[1]][1] + - this.trackingElement.trackedwidthheight[borderframesindex[0]][1]) + * (trackstate.frame - borderframes[0]) + / (borderframes[1] - borderframes[0]); + console.log(borderframes); + } else { + console.log("that is a key frame!"); + } + + } + + this.trackingElement.trackedwidthheight[this.trackingElement.frameindex.index] = [ + resizewidth, + resizeheight, + ]; + if (this.trackingElement.trackrect) { + const { offset } = this.controller.geometry; + this.trackingElement.trackrect.size(resizewidth, resizeheight) + .center(this.trackingElement.mousecoords[0] + offset, this.trackingElement.mousecoords[1] + offset); + } + } else { + this.trackingElement.frameindex = { + frame: null, + index: null, + } + } + } + // EDITED END + if (deleted.length || updated.length || created.length) { if (this.activeElement.clientID !== null) { this.deactivate(); @@ -563,10 +657,6 @@ export class CanvasViewImpl implements CanvasView, Listener { } public constructor(model: CanvasModel & Master, controller: CanvasController) { - var trackStart = 0; - var startX = 0; - var startY = 0; - var counter = 0; this.controller = controller; this.geometry = controller.geometry; this.svgShapes = {}; @@ -743,21 +833,6 @@ export class CanvasViewImpl implements CanvasView, Listener { e.preventDefault(); }); - // EDITED START user story 12/13 - window.document.addEventListener('keydown', (event): void => { - if (event.which === 84) { - if (trackStart === 0) { - trackStart = 1; - console.log('Track is activated, t press'); - } else if (trackStart === 2) { - trackStart = 3; - console.log('Track is deactivated, t press'); - } - - } - }); - // EDITED END user story 12/13 - this.content.addEventListener('mousedown', (event): void => { if ([1, 2].includes(event.which)) { if (![Mode.ZOOM_CANVAS, Mode.GROUP].includes(this.mode) || event.which === 2) { @@ -803,28 +878,6 @@ export class CanvasViewImpl implements CanvasView, Listener { }); this.content.addEventListener('mousemove', (e): void => { - // EDITED START User story 12/13 - const shape = this.svgShapes[this.activeElement.clientID]; - - if (trackStart === 0) { - trackStart = 0 - } else if (trackStart === 1) { - // startX = e.clientX - // startY = e.clientY - // self.controller.enableDrag(e.clientX, e.clientY); - // if (shape) { - // (shape as any).draggable(); - // this.mode = Mode.DRAG; - // } - console.log('enabled drag'); - trackStart = 2 - } else if (trackStart === 3) { - // self.controller.disableDrag(); - trackStart = 0; - console.log('disable drag'); - } - // EDITED END User story 12/13 - self.controller.drag(e.clientX, e.clientY); // EDITED START MAGNIFYING GLASS @@ -841,6 +894,12 @@ export class CanvasViewImpl implements CanvasView, Listener { const { offset } = this.controller.geometry; const [x, y] = translateToSVG(this.content, [e.clientX, e.clientY]); + + // EDITED START FOR USER STORY 12/13 + this.trackingElement.mousecoords = [x - offset, y - offset]; + this.trackingElement.trackedcentroids[this.trackingElement.frameindex.index] = this.trackingElement.mousecoords; + // EDITED END + const event: CustomEvent = new CustomEvent('canvas.moved', { bubbles: false, cancelable: true, @@ -1022,6 +1081,14 @@ export class CanvasViewImpl implements CanvasView, Listener { } else if (this.mode === Mode.GROUP) { this.groupHandler.select(this.controller.selected); } + // EDITED START FOR USER STORY 12/13 + } else if (reason === UpdateReasons.SWITCH_TRACKING) { + if (this.trackingElement.enable) { + this.startTracking(); + } else { + this.stopTracking(); + } + // EDITED END } else if (reason === UpdateReasons.CANCEL) { if (this.mode === Mode.DRAW) { this.drawHandler.cancel(); @@ -1063,6 +1130,175 @@ export class CanvasViewImpl implements CanvasView, Listener { return this.canvas; } + // EDITED START FOR USER STORY 12/13 + private trackingMouseClickEventHandler = (event: any): void => { + if (event.which === 1) { + const states = this.trackingElement.trackedStates; + + for (var i = 0; i < this.trackingElement.trackedframes.length; i++) { + if (this.trackingElement.interpolatekeyframes.length != 0) { + const interpolatekeyframes = [ + Math.min(...this.trackingElement.trackedframes), + ...this.trackingElement.interpolatekeyframes, + Math.max(...this.trackingElement.trackedframes), + ].sort((a, b) => (a - b)); + + let borderframes: any = []; + for (let k = 0; k < interpolatekeyframes.length; k++) { + if (interpolatekeyframes[k] < states[i].frame && interpolatekeyframes[k + 1] > states[i].frame) { + borderframes = [interpolatekeyframes[k], interpolatekeyframes[k + 1]]; + break; + } + } + console.log(borderframes); + if (borderframes.length !== 0) { + let borderframesindex: any = [ + this.trackingElement.trackedframes.indexOf(borderframes[0]), + this.trackingElement.trackedframes.indexOf(borderframes[1]), + ] + this.trackingElement.trackedwidthheight[i][0] = this.trackingElement.trackedwidthheight[borderframesindex[0]][0] + + (this.trackingElement.trackedwidthheight[borderframesindex[1]][0] + - this.trackingElement.trackedwidthheight[borderframesindex[0]][0]) + * (states[i].frame - borderframes[0]) + / (borderframes[1] - borderframes[0]); + + this.trackingElement.trackedwidthheight[i][1] = this.trackingElement.trackedwidthheight[borderframesindex[0]][1] + + (this.trackingElement.trackedwidthheight[borderframesindex[1]][1] + - this.trackingElement.trackedwidthheight[borderframesindex[0]][1]) + * (states[i].frame - borderframes[0]) + / (borderframes[1] - borderframes[0]); + } + console.log('interpolate'); + } + states[i].points = [ + this.trackingElement.trackedcentroids[i][0] - this.trackingElement.trackedwidthheight[i][0] / 2, + this.trackingElement.trackedcentroids[i][1] - this.trackingElement.trackedwidthheight[i][1] / 2, + this.trackingElement.trackedcentroids[i][0] + this.trackingElement.trackedwidthheight[i][0] / 2, + this.trackingElement.trackedcentroids[i][1] + this.trackingElement.trackedwidthheight[i][1] / 2, + ]; + } + + this.canvas.dispatchEvent(new CustomEvent('canvas.trackingdone', { + bubbles: false, + cancelable: true, + detail: { + states, + }, + })); + } else if (event.which === 2) { + this.trackingElement.scalemode = (this.trackingElement.scalemode + 1) % 3; + } + event.preventDefault(); + } + + private trackingMouseMoveEventHandler = (event: any): void => { + if (this.trackingElement.trackrect) { + const { offset } = this.controller.geometry; + this.trackingElement.trackrect.center(this.trackingElement.mousecoords[0] + offset, this.trackingElement.mousecoords[1] + offset); + } + } + + private trackingWheelEventHandler = (event: any): void => { + let width = this.trackingElement.trackedwidthheight[this.trackingElement.frameindex.index][0]; + let height = this.trackingElement.trackedwidthheight[this.trackingElement.frameindex.index][1]; + + if (event.deltaY < 0) { + // SCROLL UP + switch (this.trackingElement.scalemode) { + case 0: { + width = width + 10; + height = height + 10; + break; + } + case 1: { + width = width + 5; + break; + } + case 2: { + height = height + 5; + break; + } + default: { + break; + } + } + } else if (event.deltaY > 0) { + // SCROLL DOWN + switch (this.trackingElement.scalemode) { + case 0: { + width = (width - 10 > 0) ? width - 10 : 1; + height = (height - 10 > 0) ? height - 10 : 1; + break; + } + case 1: { + width = (width - 5 > 0) ? width - 5 : 1; + break; + } + case 2: { + height = (height - 5 > 0) ? height - 5 : 1; + break; + } + default: { + break; + } + } + } + + + this.trackingElement.trackedwidthheight[this.trackingElement.frameindex.index] = [width, height]; + if (this.trackingElement.trackrect) { + const { offset } = this.controller.geometry; + this.trackingElement.trackrect.size(width, height) + .center(this.trackingElement.mousecoords[0] + offset, this.trackingElement.mousecoords[1] + offset); + } + + this.trackingElement.interpolatekeyframes[this.trackingElement.frameindex.index] = this.trackingElement.frameindex.frame; + event.stopImmediatePropagation(); + event.preventDefault(); + } + + private startTracking(): void { + const [state] = this.controller.objects.filter((el: any) => (el.clientID === this.trackingElement.trackID));; + + this.trackingElement.trackedframes.push(state.frame); + this.trackingElement.frameindex = { + frame: state.frame, + index: this.trackingElement.trackedframes.indexOf(state.frame), + } + + this.trackingElement.trackedStates[this.trackingElement.frameindex.index] = state; + this.trackingElement.trackedcentroids[this.trackingElement.frameindex.index] = this.trackingElement.mousecoords; + + this.trackingElement.trackedwidthheight[this.trackingElement.frameindex.index] = [ + state.points[2] - state.points[0], + state.points[3] - state.points[1], + ]; + + const { offset } = this.controller.geometry; + this.trackingElement.trackrect = this.adoptedContent.rect().size(this.trackingElement.trackedwidthheight[0][0], this.trackingElement.trackedwidthheight[0][1]).attr({ + fill: '#000', + 'fill-opacity': 0.2, + 'shape-rendering': 'geometricprecision', + stroke: '#000', + 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, + }).center(this.trackingElement.mousecoords[0] + offset, this.trackingElement.mousecoords[1] + offset) + .on('mousewheel', this.trackingWheelEventHandler); + + this.canvas.addEventListener('canvas.moved', this.trackingMouseMoveEventHandler, true); + this.content.addEventListener('mousedown', this.trackingMouseClickEventHandler, true); + } + + private stopTracking(): void { + this.canvas.removeEventListener('canvas.moved', this.trackingMouseMoveEventHandler, true); + this.content.removeEventListener('mousedown', this.trackingMouseClickEventHandler, true); + + if (this.trackingElement.trackrect) { + this.trackingElement.trackrect.remove(); + this.trackingElement.trackrect = null; + } + } + // EDITED END + // EDITED START FOR MAGNIFYING GLASS private magnifyingGlassParameters = { cursorX: 0, diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index cdc78bd12a4b..02d2d1a30ee7 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -187,11 +187,25 @@ export enum AnnotationActionTypes { CHANGE_WORKSPACE = 'CHANGE_WORKSPACE', SAVE_LOGS_SUCCESS = 'SAVE_LOGS_SUCCESS', SAVE_LOGS_FAILED = 'SAVE_LOGS_FAILED', - SWITCH_TRACK = "SWITCH_TRACK" + SWITCH_TRACKING = 'SWITCH_TRACKING', // EDITED FOR USER STORY 12/13 } +// EDITED FOR USER STORY 12/13 +export function switchTracking(tracking: boolean, trackedStateID: number | null): AnyAction { + return { + type: AnnotationActionTypes.SWITCH_TRACKING, + payload: { + tracking, + trackedStateID, + }, + }; +} +// EDITED END + + + export function saveLogsAsync(): -ThunkAction, {}, {}, AnyAction> { + ThunkAction, {}, {}, AnyAction> { return async (dispatch: ActionCreator) => { try { await logger.save(); @@ -308,7 +322,7 @@ export function updateCanvasContextMenu( } export function removeAnnotationsAsync(sessionInstance: any): -ThunkAction, {}, {}, AnyAction> { + ThunkAction, {}, {}, AnyAction> { return async (dispatch: ActionCreator): Promise => { try { await sessionInstance.annotations.clear(); @@ -333,7 +347,7 @@ ThunkAction, {}, {}, AnyAction> { } export function uploadJobAnnotationsAsync(job: any, loader: any, file: File): -ThunkAction, {}, {}, AnyAction> { + ThunkAction, {}, {}, AnyAction> { return async (dispatch: ActionCreator): Promise => { try { const state: CombinedState = getStore().getState(); @@ -359,8 +373,8 @@ ThunkAction, {}, {}, AnyAction> { await job.logger.log( LogType.uploadAnnotations, { - ...(await jobInfoGenerator(job)), - }, + ...(await jobInfoGenerator(job)), + }, ); // One more update to escape some problems @@ -402,7 +416,7 @@ ThunkAction, {}, {}, AnyAction> { } export function changeJobStatusAsync(jobInstance: any, status: string): -ThunkAction, {}, {}, AnyAction> { + ThunkAction, {}, {}, AnyAction> { return async (dispatch: ActionCreator): Promise => { const oldStatus = jobInstance.status; try { @@ -433,7 +447,7 @@ ThunkAction, {}, {}, AnyAction> { } export function collectStatisticsAsync(sessionInstance: any): -ThunkAction, {}, {}, AnyAction> { + ThunkAction, {}, {}, AnyAction> { return async (dispatch: ActionCreator): Promise => { try { dispatch({ @@ -539,7 +553,7 @@ export function changePropagateFrames(frames: number): AnyAction { } export function removeObjectAsync(sessionInstance: any, objectState: any, force: boolean): -ThunkAction, {}, {}, AnyAction> { + ThunkAction, {}, {}, AnyAction> { return async (dispatch: ActionCreator): Promise => { try { await sessionInstance.logger.log(LogType.deleteObject, { count: 1 }); @@ -654,19 +668,9 @@ export function switchPlay(playing: boolean): AnyAction { }, }; } -// EDITED FOR USER STORY 12/13 -export function switchTrack(tracking: boolean): AnyAction { - return { - type: AnnotationActionTypes.SWITCH_TRACK, - payload: { - tracking, - }, - }; -} -// EDITED END export function changeFrameAsync(toFrame: number, fillBuffer?: boolean, frameStep?: number): -ThunkAction, {}, {}, AnyAction> { + ThunkAction, {}, {}, AnyAction> { return async (dispatch: ActionCreator): Promise => { const state: CombinedState = getStore().getState(); const { instance: job } = state.annotation.job; @@ -704,9 +708,9 @@ ThunkAction, {}, {}, AnyAction> { await job.logger.log( LogType.changeFrame, { - from: frame, - to: toFrame, - }, + from: frame, + to: toFrame, + }, ); const data = await job.frames.get(toFrame, fillBuffer, frameStep); const states = await job.annotations.get(toFrame, showAllInterpolationTracks, filters); @@ -758,7 +762,7 @@ ThunkAction, {}, {}, AnyAction> { } export function undoActionAsync(sessionInstance: any, frame: number): -ThunkAction, {}, {}, AnyAction> { + ThunkAction, {}, {}, AnyAction> { return async (dispatch: ActionCreator): Promise => { try { const state = getStore().getState(); @@ -801,7 +805,7 @@ ThunkAction, {}, {}, AnyAction> { } export function redoActionAsync(sessionInstance: any, frame: number): -ThunkAction, {}, {}, AnyAction> { + ThunkAction, {}, {}, AnyAction> { return async (dispatch: ActionCreator): Promise => { try { const state = getStore().getState(); @@ -939,9 +943,9 @@ export function getJobAsync( const loadJobEvent = await logger.log( LogType.loadJob, { - task_id: tid, - job_id: jid, - }, true, + task_id: tid, + job_id: jid, + }, true, ); // Check state if the task is already there @@ -1000,7 +1004,7 @@ export function getJobAsync( } export function saveAnnotationsAsync(sessionInstance: any): -ThunkAction, {}, {}, AnyAction> { + ThunkAction, {}, {}, AnyAction> { return async (dispatch: ActionCreator): Promise => { const { filters, frame, showAllInterpolationTracks } = receiveAnnotationsParameters(); @@ -1116,7 +1120,7 @@ export function splitTrack(enabled: boolean): AnyAction { } export function updateAnnotationsAsync(statesToUpdate: any[]): -ThunkAction, {}, {}, AnyAction> { + ThunkAction, {}, {}, AnyAction> { return async (dispatch: ActionCreator): Promise => { const { jobInstance, @@ -1161,7 +1165,7 @@ ThunkAction, {}, {}, AnyAction> { } export function createAnnotationsAsync(sessionInstance: any, frame: number, statesToCreate: any[]): -ThunkAction, {}, {}, AnyAction> { + ThunkAction, {}, {}, AnyAction> { return async (dispatch: ActionCreator): Promise => { try { const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); @@ -1189,7 +1193,7 @@ ThunkAction, {}, {}, AnyAction> { } export function mergeAnnotationsAsync(sessionInstance: any, frame: number, statesToMerge: any[]): -ThunkAction, {}, {}, AnyAction> { + ThunkAction, {}, {}, AnyAction> { return async (dispatch: ActionCreator): Promise => { try { const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); @@ -1263,7 +1267,7 @@ export function groupAnnotationsAsync( } export function splitAnnotationsAsync(sessionInstance: any, frame: number, stateToSplit: any): -ThunkAction, {}, {}, AnyAction> { + ThunkAction, {}, {}, AnyAction> { return async (dispatch: ActionCreator): Promise => { const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); try { diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx index d0ad1fd5c455..0445ed35382e 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx @@ -22,15 +22,12 @@ import { import { LogType } from 'cvat-logger'; import { Canvas } from 'cvat-canvas'; import getCore from 'cvat-core'; -import getSnap from 'cvat-snap'; -import { CanvasController } from '../../../../../cvat-canvas/src/typescript/canvasController'; import consts from 'consts'; const cvat = getCore(); -const snap = getSnap(); -var finishedSnapping = 0; +var finishedSnapping = false; // EDITED FOR User story 2 const MAX_DISTANCE_TO_OPEN_SHAPE = 50; @@ -70,8 +67,6 @@ interface Props { showObjectsTextAlways: boolean; workspace: Workspace; keyMap: Record; - tracking: boolean; // EDITED FOR USER STORY 12/13 - playing: boolean; onSetupCanvas: () => void; onDragCanvas: (enabled: boolean) => void; onZoomCanvas: (enabled: boolean) => void; @@ -98,7 +93,11 @@ interface Props { onChangeGridOpacity(opacity: number): void; onChangeGridColor(color: GridColor): void; onSwitchGrid(enabled: boolean): void; - + // EDITED START FOR USER STORY 12/13 + tracking: boolean; + trackedStateID: number | null; + onSwitchTracking(tracking: boolean, trackedStateID: number | null): void; + // EDITED END } export default class CanvasWrapperComponent extends React.PureComponent { @@ -149,11 +148,11 @@ export default class CanvasWrapperComponent extends React.PureComponent { workspace, frameFetching, showObjectsTextAlways, + // EDITED START FOR USER STORY 12/13 tracking, - playing, + trackedStateID, + // EDITED END } = this.props; - console.log('tracking', tracking); - // console.log('playing',playing); if (prevProps.showObjectsTextAlways !== showObjectsTextAlways) { canvasInstance.configure({ @@ -217,31 +216,19 @@ export default class CanvasWrapperComponent extends React.PureComponent { // EDITED START for USER STORY 2 if (annotations.length > prevProps.annotations.length) { this.contextMenuOnDraw() - finishedSnapping = 1; + finishedSnapping = true; } else { - if (finishedSnapping === 1) { - finishedSnapping = 0; + if (finishedSnapping) { + finishedSnapping = false; } else { this.removeContextMenu() } } // EDITED END - // EDITED START FOR USER STORY 12/13 - if (prevProps.frameData !== frameData && tracking) { - this.objectFollowMouse(); - } - - } - - if (prevProps.tracking !== tracking) { - console.log('tracking value changed from ', prevProps.tracking, ' to ', tracking); - this.objectFollowMouse(); } - // EDITED END - if (prevProps.frame !== frameData.number && resetZoom && workspace !== Workspace.ATTRIBUTE_ANNOTATION @@ -264,6 +251,12 @@ export default class CanvasWrapperComponent extends React.PureComponent { canvasInstance.rotate(frameAngle); } + // EDITED START FOR USER STORY 12/13 + if (prevProps.tracking !== tracking) { + canvasInstance.trackObject(tracking, trackedStateID); + } + // EDITED END + const loadingAnimation = window.document.getElementById('cvat_canvas_loading_animation'); if (loadingAnimation && frameFetching !== prevProps.frameFetching) { if (frameFetching) { @@ -307,58 +300,22 @@ export default class CanvasWrapperComponent extends React.PureComponent { // EDITED FOR INTEGRATION canvasInstance.html().removeEventListener('canvas.dblclicked', this.onShapedblClicked); // EDITED END - // EDITED FOR USER STORY 12/13 - // TEMPORARY PLACEMENT - canvasInstance.html().removeEventListener('canvas.moved', this.getCursorLocation); - // EDITED END - canvasInstance.html().removeEventListener('point.contextmenu', this.onCanvasPointContextMenu); - + canvasInstance.html().removeEventListener('canvas.trackingdone', this.trackingDone); // EDITED FOR USER STORY 12/13 window.removeEventListener('resize', this.fitCanvas); } - // EDITED START FOR USER STORY 12/13 - // TEMPORARY IMPLEMENTATION - private cursorLocation = { - x: 0, - y: 0, - } - - // TEMPORARY IMPLEMENTATION - private getCursorLocation = async (event: any): Promise => { - const mx = event.detail.x; - const my = event.detail.y; - - this.cursorLocation.x = mx; - this.cursorLocation.y = my; - }; - - private objectFollowMouse(): void { + // EDITED FOR USER STORY 12/13 + private trackingDone = (event: any): void => { const { + onSwitchTracking, onUpdateAnnotations, - activatedStateID, - annotations, - jobInstance, } = this.props - if (activatedStateID != null) { - const [state] = annotations.filter((el: any) => (el.clientID === activatedStateID)); - - const width = state.points[2] - state.points[0]; - const height = state.points[3] - state.points[1]; - state.points = [this.cursorLocation.x - width / 2, - this.cursorLocation.y - height / 2, - this.cursorLocation.x + width / 2, - this.cursorLocation.y + height / 2]; - console.log('annotations: ', annotations); - console.log('state', state); - console.log('jobInstance: ', jobInstance); - - onUpdateAnnotations([state]); - } + onUpdateAnnotations(event.detail.states); + onSwitchTracking(false, null); } - - // EDITED END + // EDITED END // EDITED START for USER STORY 2 private contextMenuOnDraw(): void { @@ -398,12 +355,10 @@ export default class CanvasWrapperComponent extends React.PureComponent { const [state] = annotations.filter((el: any) => (el.clientID === clientID)) - console.log('snapping...'); let result = jobInstance.annotations.snap(state.clientID, frame, state.points); result.then((data: any) => { state.points = data.points; onUpdateAnnotations([state]); - console.log('done snapping...'); }); }; @@ -415,15 +370,10 @@ export default class CanvasWrapperComponent extends React.PureComponent { onUpdateAnnotations, } = this.props; - console.log(jobInstance); - console.log(frame); - console.log(annotations[annotations.length - 1].clientID); - const state = annotations[annotations.length - 1]; let result = jobInstance.annotations.snap(state.clientID, frame, state.points); result.then((data: any) => { state.points = data.points; - console.log(data); onUpdateAnnotations([state]); }); } @@ -546,11 +496,17 @@ export default class CanvasWrapperComponent extends React.PureComponent { jobInstance.logger.log(LogType.dragObject, { id }); }; + // EDITED START for issue #8 fix private onCanvasShapeResized = (e: any): void => { - const { jobInstance } = this.props; - const { id } = e.detail; - jobInstance.logger.log(LogType.resizeObject, { id }); + if (!finishedSnapping) { + //pass + } else { + const { jobInstance } = this.props; + const { id } = e.detail; + jobInstance.logger.log(LogType.resizeObject, { id }); + } }; + // EDITED END private onCanvasImageFitted = (): void => { const { jobInstance } = this.props; @@ -847,11 +803,8 @@ export default class CanvasWrapperComponent extends React.PureComponent { // EDITED FOR INTEGRATION canvasInstance.html().addEventListener('canvas.dblclicked', this.onShapedblClicked); // EDITED END - // TEMPORARY PLACEMENT - canvasInstance.html().addEventListener('canvas.moved', this.getCursorLocation); - // EDITED END - canvasInstance.html().addEventListener('point.contextmenu', this.onCanvasPointContextMenu); + canvasInstance.html().addEventListener('canvas.trackingdone', this.trackingDone); // EDITED FOR USER STORY 12/13 } public render(): JSX.Element { @@ -892,7 +845,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { INCREASE_GRID_OPACITY: keyMap.INCREASE_GRID_OPACITY, DECREASE_GRID_OPACITY: keyMap.DECREASE_GRID_OPACITY, CHANGE_GRID_COLOR: keyMap.CHANGE_GRID_COLOR, - AUTOSNAP: keyMap.AUTOSNAP, + AUTOSNAP: keyMap.AUTOSNAP, // EDITED FOR INTEGRATION OF AUTOSNAP }; const step = 10; @@ -969,11 +922,12 @@ export default class CanvasWrapperComponent extends React.PureComponent { const color = colors[indexOf >= colors.length ? 0 : indexOf]; onChangeGridColor(color); }, + // EDITED START FOR INTEGRATION OF AUTOSNAP AUTOSNAP: (event: KeyboardEvent | undefined) => { preventDefault(event); - console.log('key s pressed'); this.autoSnap(); }, + // EDITED END }; return ( diff --git a/cvat-ui/src/components/annotation-page/top-bar/player-buttons.tsx b/cvat-ui/src/components/annotation-page/top-bar/player-buttons.tsx index 45ac29625a2a..931dafc4a3a2 100644 --- a/cvat-ui/src/components/annotation-page/top-bar/player-buttons.tsx +++ b/cvat-ui/src/components/annotation-page/top-bar/player-buttons.tsx @@ -20,14 +20,12 @@ import { } from 'icons'; interface Props { - tracking: boolean; // EDITED FOR USER STORY 12/13 playing: boolean; playPauseShortcut: string; nextFrameShortcut: string; previousFrameShortcut: string; forwardShortcut: string; backwardShortcut: string; - onSwitchTrack(): void; onSwitchPlay(): void; onPrevFrame(): void; onNextFrame(): void; @@ -35,14 +33,15 @@ interface Props { onBackward(): void; onFirstFrame(): void; onLastFrame(): void; + // EDITED FOR USER STORY 12/13 + switchTrackShortcut: string; + onSwitchTracking(): void; + tracking: boolean; + // EDITED END } function PlayerButtons(props: Props): JSX.Element { const { - // EDITED FOR USER STORY 12/13 - tracking, - onSwitchTrack, - // EDITED END playing, playPauseShortcut, nextFrameShortcut, @@ -56,6 +55,11 @@ function PlayerButtons(props: Props): JSX.Element { onBackward, onFirstFrame, onLastFrame, + // EDITED FOR USER STORY 12/13 + onSwitchTracking, + tracking, + switchTrackShortcut, + // EDITED END } = props; return ( @@ -87,7 +91,7 @@ function PlayerButtons(props: Props): JSX.Element { /> )} - + @@ -97,23 +101,26 @@ function PlayerButtons(props: Props): JSX.Element { + + {/* EDITED START FOR USER STORY 12/13 */} {!tracking ? ( - + ) : ( - + )} + {/* EDITED END */} ); } diff --git a/cvat-ui/src/components/annotation-page/top-bar/top-bar.tsx b/cvat-ui/src/components/annotation-page/top-bar/top-bar.tsx index 5d0c6a5c52a2..8ffc3b1f855e 100644 --- a/cvat-ui/src/components/annotation-page/top-bar/top-bar.tsx +++ b/cvat-ui/src/components/annotation-page/top-bar/top-bar.tsx @@ -15,10 +15,6 @@ import PlayerNavigation from './player-navigation'; import PlayerButtons from './player-buttons'; interface Props { - // EDITED FOR USER STORY 12/13 - tracking:boolean; - onSwitchTrack(): void; - // EDITED END playing: boolean; saving: boolean; savingStatuses: string[]; @@ -54,6 +50,11 @@ interface Props { onURLIconClick(): void; onUndoClick(): void; onRedoClick(): void; + // EDITED START FOR USER STORY 12/13 + onSwitchTracking(): void; + tracking: boolean; + switchTrackingShortcut: string; + // EDITED END } export default function AnnotationTopBarComponent(props: Props): JSX.Element { @@ -62,7 +63,6 @@ export default function AnnotationTopBarComponent(props: Props): JSX.Element { savingStatuses, undoAction, redoAction, - tracking,// EDITED FOR USER STORY 12/13 playing, frameNumber, frameFilename, @@ -94,7 +94,11 @@ export default function AnnotationTopBarComponent(props: Props): JSX.Element { onURLIconClick, onUndoClick, onRedoClick, - onSwitchTrack, + // EDITED START FOR USER STORY 12/13 + onSwitchTracking, + tracking, + switchTrackingShortcut, + // EDITED END } = props; return ( @@ -114,7 +118,6 @@ export default function AnnotationTopBarComponent(props: Props): JSX.Element { ; - // EDITED FOR USER STORY 12/13 - playing:boolean; + // EDITED START FOR USER STORY 12/13 tracking: boolean; + trackedStateID: number | null; // EDITED END } @@ -115,6 +116,7 @@ interface DispatchToProps { onChangeGridOpacity(opacity: number): void; onChangeGridColor(color: GridColor): void; onSwitchGrid(enabled: boolean): void; + onSwitchTracking(tracking: boolean, trackedStateID: number | null); // EDITED FOR USER STORY 12/13 } function mapStateToProps(state: CombinedState): StateToProps { @@ -135,8 +137,6 @@ function mapStateToProps(state: CombinedState): StateToProps { instance: jobInstance, }, player: { - tracking, - playing, frame: { data: frameData, number: frame, @@ -155,6 +155,12 @@ function mapStateToProps(state: CombinedState): StateToProps { max: maxZLayer, }, }, + // EDITED START FOR USER STORY 12/13 + trackobject: { + tracking, + trackedStateID, + }, + // EDITED END sidebarCollapsed, workspace, }, @@ -190,7 +196,6 @@ function mapStateToProps(state: CombinedState): StateToProps { sidebarCollapsed, canvasInstance, jobInstance, - playing, frameData, frameAngle: frameAngles[frame - jobInstance.startFrame], frameFetching, @@ -223,7 +228,10 @@ function mapStateToProps(state: CombinedState): StateToProps { contextType, workspace, keyMap, + // EDITED START FOR USER STORY 12/13 tracking, + trackedStateID, + // EDITED END }; } @@ -309,6 +317,11 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { onSwitchGrid(enabled: boolean): void { dispatch(switchGrid(enabled)); }, + // EDITED FOR USER STORY 12/13 + onSwitchTracking(tracking: boolean, trackedStateID: number | null): void { + dispatch(switchTracking(tracking, trackedStateID)); + }, + // EDITED END }; } diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx index 2633038c1b86..9f3ee8848608 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx @@ -397,13 +397,6 @@ class ObjectItemContainer extends React.PureComponent { this.commit(); }; - private changeFrame(frame: number): void { - const { changeFrame, canvasInstance } = this.props; - if (isAbleToChangeFrame(canvasInstance)) { - changeFrame(frame); - } - } - // EDITED FOR INTEGRATION private autoSnap = (): void => { const { @@ -420,6 +413,7 @@ class ObjectItemContainer extends React.PureComponent { }); } // EDITED END + private changeFrame(frame: number): void { const { changeFrame, canvasInstance } = this.props; if (isAbleToChangeFrame(canvasInstance)) { diff --git a/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx b/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx index b281e2b84041..3d3134163a3c 100644 --- a/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx +++ b/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx @@ -22,7 +22,7 @@ import { searchAnnotationsAsync, changeWorkspace as changeWorkspaceAction, activateObject, - switchTrack, + switchTracking, // EDITED FOR USER STORY 12/13 } from 'actions/annotation-actions'; import { Canvas, isAbleToChangeFrame } from 'cvat-canvas'; @@ -47,27 +47,30 @@ interface StateToProps { workspace: Workspace; keyMap: Record; normalizedKeyMap: Record; - tracking: boolean; // EDITED FOR USER STORY 12/13 + // EDITED FOR USER STORY 12/13 + tracking: boolean; + trackedStateID: number | null; + activatedStateID: number | null; + // EDITED END canvasInstance: Canvas; } interface DispatchToProps { onChangeFrame(frame: number, fillBuffer?: boolean, frameStep?: number): void; onSwitchPlay(playing: boolean): void; - onSwitchTrack(tracking: boolean): void; // EDITED FOR USER STORY 12/13 onSaveAnnotation(sessionInstance: any): void; showStatistics(sessionInstance: any): void; undo(sessionInstance: any, frameNumber: any): void; redo(sessionInstance: any, frameNumber: any): void; searchAnnotations(sessionInstance: any, frameFrom: any, frameTo: any): void; changeWorkspace(workspace: Workspace): void; + onSwitchTracking(tracking: boolean, trackedStateID: number | null): void; // EDITED FOR USER STORY 12/13 } function mapStateToProps(state: CombinedState): StateToProps { const { annotation: { player: { - tracking, playing, frame: { filename: frameFilename, @@ -76,6 +79,7 @@ function mapStateToProps(state: CombinedState): StateToProps { }, }, annotations: { + activatedStateID, // EDITED FOR USER STORY 12/13 saving: { uploading: saving, statuses: savingStatuses, @@ -90,6 +94,12 @@ function mapStateToProps(state: CombinedState): StateToProps { instance: canvasInstance, }, workspace, + // EDITED FOR USER STORY 12/13 + trackobject: { + tracking, + trackedStateID, + }, + // EDITED END }, settings: { player: { @@ -125,24 +135,28 @@ function mapStateToProps(state: CombinedState): StateToProps { workspace, keyMap, normalizedKeyMap, - tracking,// EDITED FOR USER STORY 12/13 canvasInstance, + // EDITED FOR USER STORY 12/13 + tracking, + trackedStateID, + activatedStateID, + // EDITED END }; } function mapDispatchToProps(dispatch: any): DispatchToProps { return { + // EDITED FOR USER STORY 12/13 + onSwitchTracking(tracking: boolean, trackedStateID: number | null): void { + dispatch(switchTracking(tracking, trackedStateID)); + }, + // EDITED END onChangeFrame(frame: number, fillBuffer?: boolean, frameStep?: number): void { dispatch(changeFrameAsync(frame, fillBuffer, frameStep)); }, onSwitchPlay(playing: boolean): void { dispatch(switchPlay(playing)); }, - // EDITED FOR USER STORY 12/13 - onSwitchTrack(tracking: boolean): void { - dispatch(switchTrack(tracking)); - }, - // EDITED END onSaveAnnotation(sessionInstance: any): void { dispatch(saveAnnotationsAsync(sessionInstance)); }, @@ -254,6 +268,22 @@ class AnnotationTopBarContainer extends React.PureComponent { this.unblock(); } + // EDITED FOR USER STORY 12/13 + private onSwitchTracking = (): void => { + const { + tracking, + onSwitchTracking, + activatedStateID, + } = this.props + + if (!tracking && activatedStateID !== null) { + onSwitchTracking(true, activatedStateID); + } else { + onSwitchTracking(false, null); + } + } + // EDITED END + private undo = (): void => { const { undo, @@ -299,29 +329,10 @@ class AnnotationTopBarContainer extends React.PureComponent { if (playing) { onSwitchPlay(false); - console.log('stop playing'); } else if (frameNumber < jobInstance.stopFrame) { onSwitchPlay(true); - console.log('start playing'); } }; - // EDITED FOR USER STORY 12/13 - private onSwitchTrack = (): void => { - const { - frameNumber, - jobInstance, - onSwitchTrack, - tracking, - } = this.props; - if (tracking) { - onSwitchTrack(false); - } else if (frameNumber < jobInstance.stopFrame) { - onSwitchTrack(true); - } else if (!tracking) { - onSwitchTrack(true); - } - }; - // EDITED END private onFirstFrame = (): void => { const { @@ -493,7 +504,6 @@ class AnnotationTopBarContainer extends React.PureComponent { public render(): JSX.Element { const { - tracking,// EDITED FOR USER STORY 12/13 playing, saving, savingStatuses, @@ -512,6 +522,7 @@ class AnnotationTopBarContainer extends React.PureComponent { changeWorkspace, keyMap, normalizedKeyMap, + tracking, // EDITED FOR USER STORY 12/13 canvasInstance, } = this.props; @@ -533,7 +544,7 @@ class AnnotationTopBarContainer extends React.PureComponent { SEARCH_BACKWARD: keyMap.SEARCH_BACKWARD, PLAY_PAUSE: keyMap.PLAY_PAUSE, FOCUS_INPUT_FRAME: keyMap.FOCUS_INPUT_FRAME, - TRACK_BOUNDING_BOX: keyMap.TRACK_BOUNDING_BOX, + SWITCH_TRACKING: keyMap.SWITCH_TRACKING, // EDITED FOR USER STORY 12/13 }; const handlers = { @@ -606,11 +617,10 @@ class AnnotationTopBarContainer extends React.PureComponent { } }, // EDITED FOR USER STORY 12/13 - TRACK_BOUNDING_BOX: (event: KeyboardEvent | undefined) => { + SWITCH_TRACKING: (event: KeyboardEvent | undefined) => { preventDefault(event); - this.onSwitchTrack(); - console.log('shortcut t, pressed'); - }, + this.onSwitchTracking(); + } // EDITED END }; @@ -633,10 +643,6 @@ class AnnotationTopBarContainer extends React.PureComponent { changeWorkspace={changeWorkspace} workspace={workspace} playing={playing} - // EDITED FOR USER STORY 12/13 - tracking={tracking} - onSwitchTrack={this.onSwitchTrack} - // EDITED END saving={saving} savingStatuses={savingStatuses} startFrame={startFrame} @@ -657,6 +663,11 @@ class AnnotationTopBarContainer extends React.PureComponent { focusFrameInputShortcut={normalizedKeyMap.FOCUS_INPUT_FRAME} onUndoClick={this.undo} onRedoClick={this.redo} + // EDITED FOR USER STORY 12/13 + onSwitchTracking={this.onSwitchTracking} + tracking={tracking} + switchTrackingShortcut={normalizedKeyMap.SWITCH_TRACKING} + // EDITED END /> ); diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index 65d65fa88ded..59a7a5ab5be7 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -49,7 +49,6 @@ const defaultState: AnnotationState = { delay: 0, changeTime: null, }, - tracking: false, playing: false, frameAngles: [], }, @@ -83,6 +82,12 @@ const defaultState: AnnotationState = { cur: 0, }, }, + // EDITED FOR USER STORY 12/13 + trackobject: { + tracking: false, + trackedStateID: null, + }, + // EDITED END propagate: { objectState: null, frames: 50, @@ -101,6 +106,22 @@ const defaultState: AnnotationState = { export default (state = defaultState, action: AnyAction): AnnotationState => { switch (action.type) { + // EDITED FOR USER STORY 12/13 + case AnnotationActionTypes.SWITCH_TRACKING: { + const { tracking, trackedStateID } = action.payload; + + console.log(tracking); + console.log(trackedStateID); + return { + ...state, + trackobject: { + ...state.trackobject, + tracking, + trackedStateID, + } + }; + } + // EDITED END case AnnotationActionTypes.GET_JOB: { return { ...state, @@ -324,17 +345,6 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { }, }; } - case AnnotationActionTypes.SWITCH_TRACK: { - const { tracking } = action.payload; - - return { - ...state, - player: { - ...state.player, - tracking, - }, - }; - } case AnnotationActionTypes.COLLAPSE_SIDEBAR: { return { ...state, diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index d72b0a005716..6d32cb581ab7 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -379,6 +379,12 @@ export interface AnnotationState { appearanceCollapsed: boolean; tabContentHeight: number; workspace: Workspace; + // EDITED FOR USER STORY 12/13 + trackobject: { + tracking: boolean; + trackedStateID: number | null; + } + // EDITED END } export enum Workspace { diff --git a/cvat-ui/src/reducers/shortcuts-reducer.ts b/cvat-ui/src/reducers/shortcuts-reducer.ts index c081faf0f691..67f43badcac8 100644 --- a/cvat-ui/src/reducers/shortcuts-reducer.ts +++ b/cvat-ui/src/reducers/shortcuts-reducer.ts @@ -314,18 +314,22 @@ const defaultKeyMap = { sequences: ['`', '~'], action: 'keydown', }, - TRACK_BOUNDING_BOX: { + // EDITED START for User story 12/13 + SWITCH_TRACKING: { name: 'Track bounding box', description: 'Make bounding box follow the mouse while playing', sequences: ['t'], action: 'keydown', }, + // EDITED END + // EDITED START for Integration of autoSnap AUTOSNAP: { name: 'Autosnap', description: 'Snap bounding box to edges of selected object', sequences: ['s'], action: 'keydown', }, + // EDITED END } as any as Record; diff --git a/cvat/apps/engine/views.py b/cvat/apps/engine/views.py index cb4422f04811..1009aeb51070 100644 --- a/cvat/apps/engine/views.py +++ b/cvat/apps/engine/views.py @@ -11,7 +11,7 @@ from tempfile import mkstemp from django.views.generic import RedirectView -from django.http import HttpResponse, HttpResponseNotFound, JsonResponse +from django.http import HttpResponse, HttpResponseNotFound from django.shortcuts import render from django.conf import settings from sendfile import sendfile