From d3c4a37f35f90e66b84156c99ecedf9e7272e8c3 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 19 Aug 2021 10:13:38 +0300 Subject: [PATCH 01/19] Reworked function.yaml --- .../foolwood/siammask/nuclio/function.yaml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/serverless/pytorch/foolwood/siammask/nuclio/function.yaml b/serverless/pytorch/foolwood/siammask/nuclio/function.yaml index 5b078127c2b3..446bd5edaadb 100644 --- a/serverless/pytorch/foolwood/siammask/nuclio/function.yaml +++ b/serverless/pytorch/foolwood/siammask/nuclio/function.yaml @@ -18,10 +18,20 @@ spec: build: image: cvat/pth.foolwood.siammask - baseImage: continuumio/miniconda3 + baseImage: ubuntu:20.04 directives: preCopy: + - kind: ENV + value: PATH="/root/miniconda3/bin:${PATH}" + - kind: ARG + value: PATH="/root/miniconda3/bin:${PATH}" + - kind: RUN + value: apt update && apt install -y --no-install-recommends wget git ca-certificates libglib2.0-0 libsm6 libxrender1 libxext6 && rm -rf /var/lib/apt/lists/* + - kind: RUN + value: wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh && + chmod +x Miniconda3-latest-Linux-x86_64.sh && ./Miniconda3-latest-Linux-x86_64.sh -b && + rm -f Miniconda3-latest-Linux-x86_64.sh - kind: WORKDIR value: /opt/nuclio - kind: RUN @@ -49,6 +59,10 @@ spec: attributes: maxRequestBodySize: 33554432 # 32MB + resources: + limits: + nvidia.com/gpu: 1 + platform: attributes: restartPolicy: From 90e637ce31434721f2149b4682c8e292a5e142dc Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 19 Aug 2021 11:59:47 +0300 Subject: [PATCH 02/19] Added function-gpu --- .../siammask/nuclio/function-gpu.yaml | 73 +++++++++++++++++++ .../foolwood/siammask/nuclio/function.yaml | 6 +- 2 files changed, 74 insertions(+), 5 deletions(-) create mode 100644 serverless/pytorch/foolwood/siammask/nuclio/function-gpu.yaml diff --git a/serverless/pytorch/foolwood/siammask/nuclio/function-gpu.yaml b/serverless/pytorch/foolwood/siammask/nuclio/function-gpu.yaml new file mode 100644 index 000000000000..1962d216b851 --- /dev/null +++ b/serverless/pytorch/foolwood/siammask/nuclio/function-gpu.yaml @@ -0,0 +1,73 @@ +metadata: + name: pth-foolwood-siammask + namespace: cvat + annotations: + name: SiamMask + type: tracker + spec: + framework: pytorch + +spec: + description: Fast Online Object Tracking and Segmentation + runtime: 'python:3.6' + handler: main:handler + eventTimeout: 30s + env: + - name: PYTHONPATH + value: /opt/nuclio/SiamMask:/opt/nuclio/SiamMask/experiments/siammask_sharp + + build: + image: cvat/pth.foolwood.siammask + baseImage: nvidia/cuda:11.1-devel-ubuntu20.04 + + directives: + preCopy: + - kind: ENV + value: PATH="/root/miniconda3/bin:${PATH}" + - kind: ARG + value: PATH="/root/miniconda3/bin:${PATH}" + - kind: RUN + value: apt update && apt install -y --no-install-recommends wget git ca-certificates libglib2.0-0 libsm6 libxrender1 libxext6 && rm -rf /var/lib/apt/lists/* + - kind: RUN + value: wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh && + chmod +x Miniconda3-latest-Linux-x86_64.sh && ./Miniconda3-latest-Linux-x86_64.sh -b && + rm -f Miniconda3-latest-Linux-x86_64.sh + - kind: WORKDIR + value: /opt/nuclio + - kind: RUN + value: conda create -y -n siammask python=3.7 + - kind: SHELL + value: '["conda", "run", "-n", "siammask", "/bin/bash", "-c"]' + - kind: RUN + value: git clone https://github.com/foolwood/SiamMask.git + - kind: RUN + value: pip install -r SiamMask/requirements.txt jsonpickle + - kind: RUN + value: pip install torch==1.9.0+cu111 torchvision==0.10.0+cu111 torchaudio==0.9.0 -f https://download.pytorch.org/whl/torch_stable.html + - kind: RUN + value: conda install -y gcc_linux-64 + - kind: RUN + value: cd SiamMask && bash make.sh && cd - + - kind: RUN + value: wget -P SiamMask/experiments/siammask_sharp http://www.robots.ox.ac.uk/~qwang/SiamMask_DAVIS.pth + - kind: ENTRYPOINT + value: '["conda", "run", "-n", "siammask"]' + + triggers: + myHttpTrigger: + maxWorkers: 2 + kind: 'http' + workerAvailabilityTimeoutMilliseconds: 10000 + attributes: + maxRequestBodySize: 33554432 # 32MB + + resources: + limits: + nvidia.com/gpu: 1 + + platform: + attributes: + restartPolicy: + name: always + maximumRetryCount: 3 + mountMode: volume diff --git a/serverless/pytorch/foolwood/siammask/nuclio/function.yaml b/serverless/pytorch/foolwood/siammask/nuclio/function.yaml index 446bd5edaadb..ce9ea6a8d113 100644 --- a/serverless/pytorch/foolwood/siammask/nuclio/function.yaml +++ b/serverless/pytorch/foolwood/siammask/nuclio/function.yaml @@ -35,7 +35,7 @@ spec: - kind: WORKDIR value: /opt/nuclio - kind: RUN - value: conda create -y -n siammask python=3.6 + value: conda create -y -n siammask python=3.7 - kind: SHELL value: '["conda", "run", "-n", "siammask", "/bin/bash", "-c"]' - kind: RUN @@ -59,10 +59,6 @@ spec: attributes: maxRequestBodySize: 33554432 # 32MB - resources: - limits: - nvidia.com/gpu: 1 - platform: attributes: restartPolicy: From bb9fb6f081d0187bc9a508095bb80f390ccf6cff Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 19 Aug 2021 12:10:13 +0300 Subject: [PATCH 03/19] Changed python version --- serverless/pytorch/foolwood/siammask/nuclio/function.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/serverless/pytorch/foolwood/siammask/nuclio/function.yaml b/serverless/pytorch/foolwood/siammask/nuclio/function.yaml index ce9ea6a8d113..46d1c84927d7 100644 --- a/serverless/pytorch/foolwood/siammask/nuclio/function.yaml +++ b/serverless/pytorch/foolwood/siammask/nuclio/function.yaml @@ -35,7 +35,7 @@ spec: - kind: WORKDIR value: /opt/nuclio - kind: RUN - value: conda create -y -n siammask python=3.7 + value: conda create -y -n siammask python=3.6 - kind: SHELL value: '["conda", "run", "-n", "siammask", "/bin/bash", "-c"]' - kind: RUN From bc03050be29835c04ed86f794c965435756812bc Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 24 Aug 2021 18:41:59 +0300 Subject: [PATCH 04/19] Initial version of the new tracking --- .../controls-side-bar/tools-control.tsx | 221 ++++++++++++++++-- cvat/apps/lambda_manager/views.py | 4 +- .../pytorch/foolwood/siammask/nuclio/main.py | 13 +- .../foolwood/siammask/nuclio/model_handler.py | 2 +- 4 files changed, 220 insertions(+), 20 deletions(-) diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx index 64228519e3b5..4967dd3c0df0 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx @@ -64,7 +64,7 @@ interface DispatchToProps { updateAnnotations(statesToUpdate: any[]): void; createAnnotations(sessionInstance: any, frame: number, statesToCreate: any[]): void; fetchAnnotations(): void; - onSwitchToolsBlockerState(toolsBlockerState: ToolsBlockerState):void; + onSwitchToolsBlockerState(toolsBlockerState: ToolsBlockerState): void; } const core = getCore(); @@ -107,12 +107,20 @@ const mapDispatchToProps = { }; type Props = StateToProps & DispatchToProps; +interface TrackedShape { + clientID: number; + serverlessState: any; + shapePoints: number[]; + trackOnFrame: number; +} + interface State { activeInteractor: Model | null; activeLabelID: number; activeTracker: Model | null; trackingProgress: number | null; trackingFrames: number; + trackedShapes: TrackedShape[]; fetching: boolean; pointsRecieved: boolean; approxPolyAccuracy: number; @@ -143,6 +151,7 @@ export class ToolsControlComponent extends React.PureComponent { activeTracker: props.trackers.length ? props.trackers[0] : null, activeLabelID: props.labels.length ? props.labels[0].id : null, approxPolyAccuracy: props.defaultApproxPolyAccuracy, + trackedShapes: [], trackingProgress: null, trackingFrames: 10, fetching: false, @@ -211,6 +220,8 @@ export class ToolsControlComponent extends React.PureComponent { }); } } + + this.checkTrackedStates(prevProps); } public componentWillUnmount(): void { @@ -339,6 +350,7 @@ export class ToolsControlComponent extends React.PureComponent { }; private onTracking = async (e: Event): Promise => { + const { trackedShapes } = this.state; const { isActivated, jobInstance, frame, curZOrder, fetchAnnotations, } = this.props; @@ -370,13 +382,23 @@ export class ToolsControlComponent extends React.PureComponent { }); const [clientID] = await jobInstance.annotations.put([state]); + this.setState({ + trackedShapes: [ + ...trackedShapes, + { + clientID, + serverlessState: null, + shapePoints: points, + trackOnFrame: frame + 1, + }, + ], + }); // update annotations on a canvas fetchAnnotations(); - - const states = await jobInstance.annotations.get(frame); - const [objectState] = states.filter((_state: any): boolean => _state.clientID === clientID); - await this.trackState(objectState); + // const states = await jobInstance.annotations.get(frame); + // const [objectState] = states.filter((_state: any): boolean => _state.clientID === clientID); + // await this.trackState(objectState); } catch (err) { notification.error({ description: err.toString(), @@ -411,7 +433,7 @@ export class ToolsControlComponent extends React.PureComponent { }); }; - private onChangeToolsBlockerState = (event:string):void => { + private onChangeToolsBlockerState = (event: string): void => { const { isActivated, onSwitchToolsBlockerState } = this.props; if (isActivated && event === 'keydown') { onSwitchToolsBlockerState({ algorithmsLocked: true }); @@ -420,6 +442,180 @@ export class ToolsControlComponent extends React.PureComponent { } }; + private async checkTrackedStates(prevProps: Props): Promise { + const { + frame, jobInstance, states: objectStates, fetchAnnotations, + } = this.props; + const { trackedShapes, activeTracker } = this.state; + + type AccumulatorType = { + statefull: { + clientIDs: number[]; + states: any[]; + shapes: number[][]; + }; + stateless: { + clientIDs: number[]; + shapes: number[][]; + }; + }; + + if (prevProps.frame !== frame && trackedShapes.length && activeTracker) { + // 1. find all trackable objects on the current frame + // 2. devide them into two groups: with relevant state, without relevant state + const trackingData = trackedShapes + .filter((trackedShape: TrackedShape) => trackedShape.trackOnFrame === frame) + .reduce( + (acc: AccumulatorType, trackedShape: TrackedShape): AccumulatorType => { + const { serverlessState, shapePoints, clientID } = trackedShape; + const [clientState] = objectStates.filter( + (_state: any): boolean => _state.clientID === clientID, + ); + + if (clientState && !clientState.outside) { + const { points } = clientState; + const stateIsRelevant = + !!serverlessState && + points.length === shapePoints.length && + points.every((coord: number, i: number) => coord === shapePoints[i]); + if (stateIsRelevant) { + acc.statefull.clientIDs.push(clientID); + acc.statefull.shapes.push(points); + acc.statefull.states.push(serverlessState); + } else { + acc.stateless.clientIDs.push(clientID); + acc.stateless.shapes.push(points); + } + } + + return acc; + }, + { + statefull: { + clientIDs: [], + states: [], + shapes: [], + }, + stateless: { + clientIDs: [], + shapes: [], + }, + }, + ); + + // 3. get relevant state for the second group + if (trackingData.stateless.clientIDs.length) { + const hideMessage = message.loading('Tracker state is being initialized for some objects..', 0); + try { + const response = await core.lambda.call(jobInstance.task, activeTracker, { + task: jobInstance.task, + frame: frame - 1, + shapes: trackingData.stateless.shapes, + }); + + const { clientIDs: statelessClientIDs } = trackingData.stateless; + const { shapes, states: serverlessStates } = response; + Array.prototype.push.apply(trackingData.statefull.clientIDs, statelessClientIDs); + Array.prototype.push.apply(trackingData.statefull.shapes, shapes); + Array.prototype.push.apply(trackingData.statefull.states, serverlessStates); + } catch (error) { + notification.error({ + message: 'Tracker state initialization error', + description: error.toString(), + }); + } finally { + hideMessage(); + } + } + + if (trackingData.statefull.clientIDs.length) { + // 4. run tracking for all the objects + const hideMessage = message.loading('Objects are being tracked..', 0); + try { + const trackingResults = await core.lambda.call(jobInstance.task, activeTracker, { + task: jobInstance.task, + frame, + shapes: trackingData.statefull.shapes, + states: trackingData.statefull.states, + }); + + trackingResults.shapes = trackingResults.shapes.map((shape: number[]): number[] => + shape.reduce( + (acc: number[], value: number, index: number): number[] => { + if (index % 2) { + // y + acc[1] = Math.min(acc[1], value); + acc[3] = Math.max(acc[3], value); + } else { + // x + acc[0] = Math.min(acc[0], value); + acc[2] = Math.max(acc[2], value); + } + return acc; + }, + [ + Number.MAX_SAFE_INTEGER, + Number.MAX_SAFE_INTEGER, + Number.MIN_SAFE_INTEGER, + Number.MIN_SAFE_INTEGER, + ], + )); + + await Promise.all( + trackingData.statefull.clientIDs.map( + (clientID: number, i: number): Promise => { + const [objectState] = objectStates.filter( + (_state: any): boolean => _state.clientID === clientID, + ); + const points = trackingResults.shapes[i].reduce( + (acc: number[], value: number, index: number): number[] => { + if (index % 2) { + // y + acc[1] = Math.min(acc[1], value); + acc[3] = Math.max(acc[3], value); + } else { + // x + acc[0] = Math.min(acc[0], value); + acc[2] = Math.max(acc[2], value); + } + return acc; + }, + [ + Number.MAX_SAFE_INTEGER, + Number.MAX_SAFE_INTEGER, + Number.MIN_SAFE_INTEGER, + Number.MIN_SAFE_INTEGER, + ], + ); + objectState.points = points; + return objectState.save(); + }, + ), + ); + + trackingData.statefull.clientIDs.forEach((clientID: number, i: number) => { + const [trackedShape] = trackedShapes.filter( + (_trackedShape: TrackedShape) => _trackedShape.clientID === clientID, + ); + trackedShape.serverlessState = trackingResults.states[i]; + trackedShape.shapePoints = trackingResults.shapes[i]; + trackedShape.trackOnFrame = frame + 1; + }); + + fetchAnnotations(); + // TODO: block navigation + } catch (error) { + notification.error({ + message: 'Tracking error occured', + description: error.toString(), + }); + } finally { + hideMessage(); + } + } + } + } + private constructFromPoints(points: number[][]): void { const { frame, labels, curZOrder, jobInstance, activeLabelID, createAnnotations, @@ -468,7 +664,7 @@ export class ToolsControlComponent extends React.PureComponent { let response = await core.lambda.call(jobInstance.task, tracker, { task: jobInstance.task, frame, - shape: points, + shapes: [points], }); for (const offset of range(1, trackingFrames + 1)) { @@ -478,11 +674,11 @@ export class ToolsControlComponent extends React.PureComponent { response = await core.lambda.call(jobInstance.task, tracker, { task: jobInstance.task, frame: frame + offset, - shape: response.points, - state: response.state, + shapes: response.shapes, + states: response.states, }); - const reduced = response.shape.reduce( + const reduced = response.shapes[0].reduce( (acc: number[], value: number, index: number): number[] => { if (index % 2) { // y @@ -797,10 +993,7 @@ export class ToolsControlComponent extends React.PureComponent { - + {this.renderLabelBlock()} {this.renderInteractorBlock()} diff --git a/cvat/apps/lambda_manager/views.py b/cvat/apps/lambda_manager/views.py index f5a34217e244..eb759e4d789e 100644 --- a/cvat/apps/lambda_manager/views.py +++ b/cvat/apps/lambda_manager/views.py @@ -191,8 +191,8 @@ def invoke(self, db_task, data): elif self.kind == LambdaType.TRACKER: payload.update({ "image": self._get_image(db_task, data["frame"], quality), - "shape": data.get("shape", None), - "state": data.get("state", None) + "shapes": data.get("shapes", []), + "states": data.get("states", []) }) else: raise ValidationError( diff --git a/serverless/pytorch/foolwood/siammask/nuclio/main.py b/serverless/pytorch/foolwood/siammask/nuclio/main.py index ea3dc141f0ff..1376fc2b7761 100644 --- a/serverless/pytorch/foolwood/siammask/nuclio/main.py +++ b/serverless/pytorch/foolwood/siammask/nuclio/main.py @@ -17,11 +17,18 @@ def handler(context, event): context.logger.info("Run SiamMask model") data = event.body buf = io.BytesIO(base64.b64decode(data["image"])) - shape = data.get("shape") - state = data.get("state") + shapes = data.get("shapes") + states = data.get("states") image = Image.open(buf) - results = context.user_data.model.infer(image, shape, state) + results = { + 'shapes': [], + 'states': [] + } + for i, shape in enumerate(shapes): + shape, state = context.user_data.model.infer(image, shape, states[i] if i < len(states) else None) + results['shapes'].append(shape) + results['states'].append(state) return context.Response(body=json.dumps(results), headers={}, content_type='application/json', status_code=200) diff --git a/serverless/pytorch/foolwood/siammask/nuclio/model_handler.py b/serverless/pytorch/foolwood/siammask/nuclio/model_handler.py index 10f1b5028ba9..5fb2ed05bb2c 100644 --- a/serverless/pytorch/foolwood/siammask/nuclio/model_handler.py +++ b/serverless/pytorch/foolwood/siammask/nuclio/model_handler.py @@ -62,5 +62,5 @@ def infer(self, image, shape, state): shape = state['ploygon'].flatten().tolist() state = self.encode_state(state) - return {"shape": shape, "state": state} + return shape, state From 9f576f4c250a8576bbac8d9a2c5bcb9a280b7ab3 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Wed, 25 Aug 2021 12:10:50 +0300 Subject: [PATCH 05/19] Blocking navigation during tracking --- cvat-ui/src/actions/annotation-actions.ts | 18 +++++++- .../attribute-annotation-sidebar.tsx | 5 ++- .../controls-side-bar/tools-control.tsx | 13 ++++-- .../tag-annotation-sidebar.tsx | 3 +- .../objects-side-bar/object-buttons.tsx | 11 ++--- .../objects-side-bar/objects-list.tsx | 11 ++--- .../annotation-page/top-bar/top-bar.tsx | 43 ++++++++++--------- cvat-ui/src/reducers/annotation-reducer.ts | 10 +++++ cvat-ui/src/reducers/interfaces.ts | 3 +- cvat-ui/src/utils/is-able-to-change-frame.ts | 13 ++++++ 10 files changed, 85 insertions(+), 45 deletions(-) create mode 100644 cvat-ui/src/utils/is-able-to-change-frame.ts diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index 96bfb8d20dc6..2cd16c3dc5af 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -197,6 +197,7 @@ export enum AnnotationActionTypes { GET_CONTEXT_IMAGE = 'GET_CONTEXT_IMAGE', GET_CONTEXT_IMAGE_SUCCESS = 'GET_CONTEXT_IMAGE_SUCCESS', GET_CONTEXT_IMAGE_FAILED = 'GET_CONTEXT_IMAGE_FAILED', + SWITCH_NAVIGATION_BLOCKED = 'SWITCH_NAVIGATION_BLOCKED', } export function saveLogsAsync(): ThunkAction { @@ -689,8 +690,12 @@ export function getPredictionsAsync(): ThunkAction { }; } -export function changeFrameAsync(toFrame: number, fillBuffer?: boolean, frameStep?: number, - forceUpdate?: boolean): ThunkAction { +export function changeFrameAsync( + toFrame: number, + fillBuffer?: boolean, + frameStep?: number, + forceUpdate?: boolean, +): ThunkAction { return async (dispatch: ActionCreator): Promise => { const state: CombinedState = getStore().getState(); const { instance: job } = state.annotation.job; @@ -1666,3 +1671,12 @@ export function getContextImageAsync(): ThunkAction { } }; } + +export function switchNavigationBlocked(navigationBlocked: boolean): AnyAction { + return { + type: AnnotationActionTypes.SWITCH_NAVIGATION_BLOCKED, + payload: { + navigationBlocked, + }, + }; +} diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx index 4b70c1a1f150..e9b12a564048 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx @@ -17,6 +17,7 @@ import { changeFrameAsync, updateAnnotationsAsync, } from 'actions/annotation-actions'; +import isAbleToChangeFrame from 'utils/is-able-to-change-frame'; import GlobalHotKeys, { KeyMap } from 'utils/mousetrap-react'; import { ThunkDispatch } from 'utils/redux'; import AppearanceBlock from 'components/annotation-page/appearance-block'; @@ -266,7 +267,7 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX. if (activeObjectState && activeObjectState.objectType === ObjectType.TRACK) { const frame = typeof activeObjectState.keyframes.next === 'number' ? activeObjectState.keyframes.next : null; - if (frame !== null && canvasInstance.isAbleToChangeFrame()) { + if (frame !== null && isAbleToChangeFrame()) { changeFrame(frame); } } @@ -276,7 +277,7 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX. if (activeObjectState && activeObjectState.objectType === ObjectType.TRACK) { const frame = typeof activeObjectState.keyframes.prev === 'number' ? activeObjectState.keyframes.prev : null; - if (frame !== null && canvasInstance.isAbleToChangeFrame()) { + if (frame !== null && isAbleToChangeFrame()) { changeFrame(frame); } } diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx index 4967dd3c0df0..81af983e90a6 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx @@ -29,6 +29,7 @@ import { } from 'reducers/interfaces'; import { interactWithCanvas, + switchNavigationBlocked as switchNavigationBlockedAction, fetchAnnotationsAsync, updateAnnotationsAsync, createAnnotationsAsync, @@ -65,6 +66,7 @@ interface DispatchToProps { createAnnotations(sessionInstance: any, frame: number, statesToCreate: any[]): void; fetchAnnotations(): void; onSwitchToolsBlockerState(toolsBlockerState: ToolsBlockerState): void; + switchNavigationBlocked(navigationBlocked: boolean): void; } const core = getCore(); @@ -104,6 +106,7 @@ const mapDispatchToProps = { createAnnotations: createAnnotationsAsync, fetchAnnotations: fetchAnnotationsAsync, onSwitchToolsBlockerState: switchToolsBlockerState, + switchNavigationBlocked: switchNavigationBlockedAction, }; type Props = StateToProps & DispatchToProps; @@ -177,7 +180,9 @@ export class ToolsControlComponent extends React.PureComponent { } public componentDidUpdate(prevProps: Props, prevState: State): void { - const { isActivated, defaultApproxPolyAccuracy, canvasInstance } = this.props; + const { + isActivated, defaultApproxPolyAccuracy, canvasInstance, switchNavigationBlocked, + } = this.props; const { approxPolyAccuracy, mode } = this.state; if (prevProps.isActivated && !isActivated) { @@ -221,7 +226,10 @@ export class ToolsControlComponent extends React.PureComponent { } } - this.checkTrackedStates(prevProps); + switchNavigationBlocked(true); + this.checkTrackedStates(prevProps).finally(() => { + switchNavigationBlocked(false); + }); } public componentWillUnmount(): void { @@ -377,7 +385,6 @@ export class ToolsControlComponent extends React.PureComponent { points, frame, occluded: false, - source: 'auto', attributes: {}, }); diff --git a/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/tag-annotation-sidebar.tsx b/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/tag-annotation-sidebar.tsx index c55e49f16ecd..0923745fdc59 100644 --- a/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/tag-annotation-sidebar.tsx +++ b/cvat-ui/src/components/annotation-page/tag-annotation-workspace/tag-annotation-sidebar/tag-annotation-sidebar.tsx @@ -26,6 +26,7 @@ import { CombinedState, ObjectType } from 'reducers/interfaces'; import { adjustContextImagePosition } from 'components/annotation-page/standard-workspace/context-image/context-image'; import LabelSelector from 'components/label-selector/label-selector'; import getCore from 'cvat-core-wrapper'; +import isAbleToChangeFrame from 'utils/is-able-to-change-frame'; import GlobalHotKeys, { KeyMap } from 'utils/mousetrap-react'; import ShortcutsSelect from './shortcuts-select'; @@ -168,7 +169,7 @@ function TagAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.Elemen const onChangeFrame = (): void => { const frame = Math.min(jobInstance.stopFrame, frameNumber + 1); - if (canvasInstance.isAbleToChangeFrame()) { + if (isAbleToChangeFrame()) { changeFrame(frame); } }; diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-buttons.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-buttons.tsx index cdb5960e5ed6..f4c61da681fc 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-buttons.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-buttons.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2021 Intel Corporation // // SPDX-License-Identifier: MIT @@ -6,7 +6,7 @@ import React from 'react'; import { connect } from 'react-redux'; import { LogType } from 'cvat-logger'; -import { Canvas } from 'cvat-canvas-wrapper'; +import isAbleToChangeFrame from 'utils/is-able-to-change-frame'; import { ThunkDispatch } from 'utils/redux'; import { updateAnnotationsAsync, changeFrameAsync } from 'actions/annotation-actions'; import { CombinedState } from 'reducers/interfaces'; @@ -25,7 +25,6 @@ interface StateToProps { jobInstance: any; frameNumber: number; normalizedKeyMap: Record; - canvasInstance: Canvas; outsideDisabled: boolean; hiddenDisabled: boolean; keyframeDisabled: boolean; @@ -44,7 +43,6 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps { player: { frame: { number: frameNumber }, }, - canvas: { instance: canvasInstance }, }, shortcuts: { normalizedKeyMap }, } = state; @@ -59,7 +57,6 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps { normalizedKeyMap, frameNumber, jobInstance, - canvasInstance, outsideDisabled: typeof outsideDisabled === 'undefined' ? false : outsideDisabled, hiddenDisabled: typeof hiddenDisabled === 'undefined' ? false : hiddenDisabled, keyframeDisabled: typeof keyframeDisabled === 'undefined' ? false : keyframeDisabled, @@ -217,8 +214,8 @@ class ItemButtonsWrapper extends React.PureComponent; - canvasInstance: Canvas | Canvas3d; } interface DispatchToProps { @@ -70,7 +68,6 @@ function mapStateToProps(state: CombinedState): StateToProps { player: { frame: { number: frameNumber }, }, - canvas: { instance: canvasInstance }, colors, }, settings: { @@ -108,7 +105,6 @@ function mapStateToProps(state: CombinedState): StateToProps { maxZLayer, keyMap, normalizedKeyMap, - canvasInstance, }; } @@ -257,7 +253,6 @@ class ObjectsListContainer extends React.PureComponent { minZLayer, keyMap, normalizedKeyMap, - canvasInstance, colors, colorBy, readonly, @@ -437,7 +432,7 @@ class ObjectsListContainer extends React.PureComponent { const state = activatedStated(); if (state && state.objectType === ObjectType.TRACK) { const frame = typeof state.keyframes.next === 'number' ? state.keyframes.next : null; - if (frame !== null && canvasInstance.isAbleToChangeFrame()) { + if (frame !== null && isAbleToChangeFrame()) { changeFrame(frame); } } @@ -447,7 +442,7 @@ class ObjectsListContainer extends React.PureComponent { const state = activatedStated(); if (state && state.objectType === ObjectType.TRACK) { const frame = typeof state.keyframes.prev === 'number' ? state.keyframes.prev : null; - if (frame !== null && canvasInstance.isAbleToChangeFrame()) { + if (frame !== null && isAbleToChangeFrame()) { 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 d922a0000253..94c95c50f43b 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 @@ -30,8 +30,15 @@ import AnnotationTopBarComponent from 'components/annotation-page/top-bar/top-ba import { Canvas } from 'cvat-canvas-wrapper'; import { Canvas3d } from 'cvat-canvas3d-wrapper'; import { - CombinedState, FrameSpeed, Workspace, PredictorState, DimensionType, ActiveControl, ToolsBlockerState, + CombinedState, + FrameSpeed, + Workspace, + PredictorState, + DimensionType, + ActiveControl, + ToolsBlockerState, } from 'reducers/interfaces'; +import isAbleToChangeFrame from 'utils/is-able-to-change-frame'; import GlobalHotKeys, { KeyMap } from 'utils/mousetrap-react'; import { switchToolsBlockerState } from 'actions/settings-actions'; @@ -171,7 +178,7 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { dispatch(getPredictionsAsync()); } }, - onSwitchToolsBlockerState(toolsBlockerState: ToolsBlockerState):void{ + onSwitchToolsBlockerState(toolsBlockerState: ToolsBlockerState): void { dispatch(switchToolsBlockerState(toolsBlockerState)); }, }; @@ -245,21 +252,17 @@ class AnnotationTopBarContainer extends React.PureComponent { } private undo = (): void => { - const { - undo, jobInstance, frameNumber, canvasInstance, - } = this.props; + const { undo, jobInstance, frameNumber } = this.props; - if (canvasInstance.isAbleToChangeFrame()) { + if (isAbleToChangeFrame()) { undo(jobInstance, frameNumber); } }; private redo = (): void => { - const { - redo, jobInstance, frameNumber, canvasInstance, - } = this.props; + const { redo, jobInstance, frameNumber } = this.props; - if (canvasInstance.isAbleToChangeFrame()) { + if (isAbleToChangeFrame()) { redo(jobInstance, frameNumber); } }; @@ -484,7 +487,6 @@ class AnnotationTopBarContainer extends React.PureComponent { frameDelay, playing, canvasIsReady, - canvasInstance, onSwitchPlay, onChangeFrame, } = this.props; @@ -502,7 +504,7 @@ class AnnotationTopBarContainer extends React.PureComponent { setTimeout(() => { const { playing: stillPlaying } = this.props; if (stillPlaying) { - if (canvasInstance.isAbleToChangeFrame()) { + if (isAbleToChangeFrame()) { onChangeFrame(frameNumber + 1 + framesSkipped, stillPlaying, framesSkipped + 1); } else if (jobInstance.task.dimension === DimensionType.DIM_2D) { onSwitchPlay(false); @@ -526,22 +528,22 @@ class AnnotationTopBarContainer extends React.PureComponent { } private changeFrame(frame: number): void { - const { onChangeFrame, canvasInstance } = this.props; - if (canvasInstance.isAbleToChangeFrame()) { + const { onChangeFrame } = this.props; + if (isAbleToChangeFrame()) { onChangeFrame(frame); } } private searchAnnotations(start: number, stop: number): void { - const { canvasInstance, jobInstance, searchAnnotations } = this.props; - if (canvasInstance.isAbleToChangeFrame()) { + const { jobInstance, searchAnnotations } = this.props; + if (isAbleToChangeFrame()) { searchAnnotations(jobInstance, start, stop); } } private searchEmptyFrame(start: number, stop: number): void { - const { canvasInstance, jobInstance, searchEmptyFrame } = this.props; - if (canvasInstance.isAbleToChangeFrame()) { + const { jobInstance, searchEmptyFrame } = this.props; + if (isAbleToChangeFrame()) { searchEmptyFrame(jobInstance, start, stop); } } @@ -562,7 +564,6 @@ class AnnotationTopBarContainer extends React.PureComponent { canvasIsReady, keyMap, normalizedKeyMap, - canvasInstance, predictor, isTrainingActive, activeControl, @@ -637,13 +638,13 @@ class AnnotationTopBarContainer extends React.PureComponent { }, SEARCH_FORWARD: (event: KeyboardEvent | undefined) => { preventDefault(event); - if (frameNumber + 1 <= stopFrame && canvasIsReady && canvasInstance.isAbleToChangeFrame()) { + if (frameNumber + 1 <= stopFrame && canvasIsReady && isAbleToChangeFrame()) { searchAnnotations(jobInstance, frameNumber + 1, stopFrame); } }, SEARCH_BACKWARD: (event: KeyboardEvent | undefined) => { preventDefault(event); - if (frameNumber - 1 >= startFrame && canvasIsReady && canvasInstance.isAbleToChangeFrame()) { + if (frameNumber - 1 >= startFrame && canvasIsReady && isAbleToChangeFrame()) { searchAnnotations(jobInstance, frameNumber - 1, startFrame); } }, diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index eb71b8fa16f0..bf369716a249 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -64,6 +64,7 @@ const defaultState: AnnotationState = { }, playing: false, frameAngles: [], + navigationBlocked: false, contextImage: { fetching: false, data: null, @@ -1219,6 +1220,15 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { }, }; } + case AnnotationActionTypes.SWITCH_NAVIGATION_BLOCKED: { + return { + ...state, + player: { + ...state.player, + navigationBlocked: action.payload.navigationBlocked, + }, + }; + } case AnnotationActionTypes.CLOSE_JOB: case AuthActionTypes.LOGOUT_SUCCESS: { return { ...defaultState }; diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index ef11a8207463..e49ee67bb6e9 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -185,7 +185,7 @@ export interface Model { framework: string; description: string; type: string; - onChangeToolsBlockerState: (event:string) => void; + onChangeToolsBlockerState: (event: string) => void; tip: { message: string; gif: string; @@ -454,6 +454,7 @@ export interface AnnotationState { delay: number; changeTime: number | null; }; + navigationBlocked: boolean; playing: boolean; frameAngles: number[]; contextImage: { diff --git a/cvat-ui/src/utils/is-able-to-change-frame.ts b/cvat-ui/src/utils/is-able-to-change-frame.ts new file mode 100644 index 000000000000..0425811b6caf --- /dev/null +++ b/cvat-ui/src/utils/is-able-to-change-frame.ts @@ -0,0 +1,13 @@ +// Copyright (C) 2021 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import { getCVATStore } from 'cvat-store'; +import { CombinedState } from 'reducers/interfaces'; + +export default function isAbleToChangeFrame(): boolean { + const store = getCVATStore(); + + const state: CombinedState = store.getState(); + return state.annotation.canvas.instance.isAbleToChangeFrame() && !state.annotation.player.navigationBlocked; +} From 88db3f42c31d06ada435c6022c15672aee0d4581 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Wed, 25 Aug 2021 12:32:33 +0300 Subject: [PATCH 06/19] Removed outdated code --- .../controls-side-bar/tools-control.tsx | 136 +++--------------- cvat-ui/src/utils/range.ts | 27 ---- 2 files changed, 16 insertions(+), 147 deletions(-) delete mode 100644 cvat-ui/src/utils/range.ts diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx index 81af983e90a6..b4f133930834 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx @@ -14,14 +14,11 @@ import Tabs from 'antd/lib/tabs'; import { Row, Col } from 'antd/lib/grid'; import notification from 'antd/lib/notification'; import message from 'antd/lib/message'; -import Progress from 'antd/lib/progress'; -import InputNumber from 'antd/lib/input-number'; import Dropdown from 'antd/lib/dropdown'; import lodash from 'lodash'; import { AIToolsIcon } from 'icons'; import { Canvas, convertShapesForInteractor } from 'cvat-canvas-wrapper'; -import range from 'utils/range'; import getCore from 'cvat-core-wrapper'; import openCVWrapper from 'utils/opencv-wrapper/opencv-wrapper'; import { @@ -121,8 +118,6 @@ interface State { activeInteractor: Model | null; activeLabelID: number; activeTracker: Model | null; - trackingProgress: number | null; - trackingFrames: number; trackedShapes: TrackedShape[]; fetching: boolean; pointsRecieved: boolean; @@ -155,8 +150,6 @@ export class ToolsControlComponent extends React.PureComponent { activeLabelID: props.labels.length ? props.labels[0].id : null, approxPolyAccuracy: props.defaultApproxPolyAccuracy, trackedShapes: [], - trackingProgress: null, - trackingFrames: 10, fetching: false, pointsRecieved: false, mode: 'interaction', @@ -403,9 +396,6 @@ export class ToolsControlComponent extends React.PureComponent { // update annotations on a canvas fetchAnnotations(); - // const states = await jobInstance.annotations.get(frame); - // const [objectState] = states.filter((_state: any): boolean => _state.clientID === clientID); - // await this.trackState(objectState); } catch (err) { notification.error({ description: err.toString(), @@ -610,7 +600,6 @@ export class ToolsControlComponent extends React.PureComponent { }); fetchAnnotations(); - // TODO: block navigation } catch (error) { notification.error({ message: 'Tracking error occured', @@ -660,70 +649,6 @@ export class ToolsControlComponent extends React.PureComponent { return points; } - public async trackState(state: any): Promise { - const { jobInstance, frame, fetchAnnotations } = this.props; - const { activeTracker, trackingFrames } = this.state; - const { clientID, points } = state; - - const tracker = activeTracker as Model; - try { - this.setState({ trackingProgress: 0, fetching: true }); - let response = await core.lambda.call(jobInstance.task, tracker, { - task: jobInstance.task, - frame, - shapes: [points], - }); - - for (const offset of range(1, trackingFrames + 1)) { - /* eslint-disable no-await-in-loop */ - const states = await jobInstance.annotations.get(frame + offset); - const [objectState] = states.filter((_state: any): boolean => _state.clientID === clientID); - response = await core.lambda.call(jobInstance.task, tracker, { - task: jobInstance.task, - frame: frame + offset, - shapes: response.shapes, - states: response.states, - }); - - const reduced = response.shapes[0].reduce( - (acc: number[], value: number, index: number): number[] => { - if (index % 2) { - // y - acc[1] = Math.min(acc[1], value); - acc[3] = Math.max(acc[3], value); - } else { - // x - acc[0] = Math.min(acc[0], value); - acc[2] = Math.max(acc[2], value); - } - return acc; - }, - [ - Number.MAX_SAFE_INTEGER, - Number.MAX_SAFE_INTEGER, - Number.MIN_SAFE_INTEGER, - Number.MIN_SAFE_INTEGER, - ], - ); - - objectState.points = reduced; - await objectState.save(); - - this.setState({ trackingProgress: offset / trackingFrames }); - } - } finally { - this.setState({ trackingProgress: null, fetching: false }); - fetchAnnotations(); - } - } - - public trackingAvailable(): boolean { - const { activeTracker, trackingFrames } = this.state; - const { trackers } = this.props; - - return !!trackingFrames && !!trackers.length && activeTracker !== null; - } - private renderLabelBlock(): JSX.Element { const { labels } = this.props; const { activeLabelID } = this.state; @@ -752,9 +677,7 @@ export class ToolsControlComponent extends React.PureComponent { const { trackers, canvasInstance, jobInstance, frame, onInteractionStart, } = this.props; - const { - activeTracker, activeLabelID, fetching, trackingFrames, - } = this.state; + const { activeTracker, activeLabelID, fetching } = this.state; if (!trackers.length) { return ( @@ -792,27 +715,6 @@ export class ToolsControlComponent extends React.PureComponent { - - - Tracking frames - - - { - if (typeof value !== 'undefined' && value !== null) { - this.setState({ - trackingFrames: +value, - }); - } - }} - /> - - - - - ); -} - function SwitchOrientationItem(props: ItemProps): JSX.Element { const { toolProps, ...rest } = props; const { switchOrientation } = toolProps; @@ -240,9 +220,6 @@ export default function ItemMenu(props: Props): JSX.Element { {!readonly && } {!readonly && } - {is2D && !readonly && objectType === ObjectType.TRACK && shapeType === ShapeType.RECTANGLE && ( - - )} {is2D && !readonly && [ShapeType.POLYGON, ShapeType.POLYLINE, ShapeType.CUBOID].includes(shapeType) && ( )} diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx index 426ad7b671b8..c406fd33b7d4 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/object-item.tsx @@ -39,7 +39,6 @@ interface Props { changeColor(color: string): void; collapse(): void; resetCuboidPerspective(): void; - activateTracking(): void; } function objectItemsAreEqual(prevProps: Props, nextProps: Props): boolean { @@ -92,7 +91,6 @@ function ObjectItemComponent(props: Props): JSX.Element { changeColor, collapse, resetCuboidPerspective, - activateTracking, jobInstance, } = props; @@ -144,7 +142,6 @@ function ObjectItemComponent(props: Props): JSX.Element { toBackground={toBackground} toForeground={toForeground} resetCuboidPerspective={resetCuboidPerspective} - activateTracking={activateTracking} /> {!!attributes.length && ( 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 c8f788b930ec..07cb2cc92cfa 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 @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: MIT -import React, { MutableRefObject } from 'react'; +import React from 'react'; import copy from 'copy-to-clipboard'; import { connect } from 'react-redux'; @@ -22,7 +22,6 @@ import { ActiveControl, CombinedState, ColorBy, ShapeType, } from 'reducers/interfaces'; import ObjectStateItemComponent from 'components/annotation-page/standard-workspace/objects-side-bar/object-item'; -import { ToolsControlComponent } from 'components/annotation-page/standard-workspace/controls-side-bar/tools-control'; import { shift } from 'utils/math'; import { Canvas } from 'cvat-canvas-wrapper'; import { Canvas3d } from 'cvat-canvas3d-wrapper'; @@ -48,7 +47,6 @@ interface StateToProps { minZLayer: number; maxZLayer: number; normalizedKeyMap: Record; - aiToolsRef: MutableRefObject; canvasInstance: Canvas | Canvas3d; } @@ -76,7 +74,6 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps { frame: { number: frameNumber }, }, canvas: { instance: canvasInstance, ready, activeControl }, - aiToolsRef, }, settings: { shapes: { colorBy }, @@ -105,7 +102,6 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps { minZLayer, maxZLayer, normalizedKeyMap, - aiToolsRef, canvasInstance, }; } @@ -243,13 +239,6 @@ class ObjectItemContainer extends React.PureComponent { collapseOrExpand([objectState], !collapsed); }; - private activateTracking = (): void => { - const { objectState, readonly, aiToolsRef } = this.props; - if (!readonly && aiToolsRef.current && aiToolsRef.current.trackingAvailable()) { - aiToolsRef.current.trackState(objectState); - } - }; - private changeColor = (color: string): void => { const { objectState, colorBy, changeGroupColor } = this.props; @@ -392,7 +381,6 @@ class ObjectItemContainer extends React.PureComponent { changeLabel={this.changeLabel} changeAttribute={this.changeAttribute} collapse={this.collapse} - activateTracking={this.activateTracking} resetCuboidPerspective={() => this.resetCuboidPerspective()} /> ); diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index bf369716a249..d574d76ae2f5 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: MIT -import React from 'react'; import { AnyAction } from 'redux'; import { AnnotationActionTypes } from 'actions/annotation-actions'; import { AuthActionTypes } from 'actions/auth-actions'; @@ -109,7 +108,6 @@ const defaultState: AnnotationState = { collecting: false, data: null, }, - aiToolsRef: React.createRef(), colors: [], sidebarCollapsed: false, appearanceCollapsed: false, diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index e49ee67bb6e9..b1706bd54eb5 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: MIT -import { MutableRefObject } from 'react'; import { Canvas3d } from 'cvat-canvas3d/src/typescript/canvas3d'; import { Canvas, RectDrawingMethod, CuboidDrawingMethod } from 'cvat-canvas-wrapper'; import { IntelligentScissors } from 'utils/opencv-wrapper/intelligent-scissors'; @@ -514,7 +513,6 @@ export interface AnnotationState { appearanceCollapsed: boolean; workspace: Workspace; predictor: PredictorState; - aiToolsRef: MutableRefObject; } export enum Workspace { From c7c09effe86ca77a7a6d4bf0ecea09539e4df2c1 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 21 Sep 2021 20:19:23 +0300 Subject: [PATCH 18/19] Fixed navigation --- .../controls-side-bar/tools-control.tsx | 206 +++++++++--------- 1 file changed, 107 insertions(+), 99 deletions(-) diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx index 21b58c452de8..61743f5d2639 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx @@ -199,12 +199,7 @@ export class ToolsControlComponent extends React.PureComponent { public componentDidUpdate(prevProps: Props, prevState: State): void { const { - isActivated, - defaultApproxPolyAccuracy, - canvasInstance, - switchNavigationBlocked, - states, - fetchAnnotations, + isActivated, defaultApproxPolyAccuracy, canvasInstance, states, fetchAnnotations, } = this.props; const { approxPolyAccuracy, mode, activeTracker, trackedShapes, @@ -326,10 +321,7 @@ export class ToolsControlComponent extends React.PureComponent { } } - switchNavigationBlocked(true); - this.checkTrackedStates(prevProps).finally(() => { - switchNavigationBlocked(false); - }); + this.checkTrackedStates(prevProps); } public componentWillUnmount(): void { @@ -548,9 +540,15 @@ export class ToolsControlComponent extends React.PureComponent { private async checkTrackedStates(prevProps: Props): Promise { const { - frame, jobInstance, states: objectStates, fetchAnnotations, trackers, + frame, + jobInstance, + states: objectStates, + trackers, + fetchAnnotations, + switchNavigationBlocked, } = this.props; const { trackedShapes } = this.state; + let withServerRequest = false; type AccumulatorType = { statefull: { @@ -590,6 +588,7 @@ export class ToolsControlComponent extends React.PureComponent { if (clientState && !clientState.outside) { const { points } = clientState; + withServerRequest = true; const stateIsRelevant = serverlessState !== null && points.length === shapePoints.length && @@ -623,102 +622,111 @@ export class ToolsControlComponent extends React.PureComponent { }, ); - // 3. get relevant state for the second group - for (const trackerID of Object.keys(trackingData.stateless)) { - let hideMessage = null; - try { - const [tracker] = trackers.filter((_tracker: Model) => _tracker.id === trackerID); - if (!tracker) { - throw new Error(`Suitable tracker with ID ${trackerID} not found in tracker list`); - } - - const trackableObjects = trackingData.stateless[trackerID]; - const numOfObjects = trackableObjects.clientIDs.length; - hideMessage = message.loading( - `${tracker.name}: states are being initialized for ${numOfObjects} ${ - numOfObjects > 1 ? 'objects' : 'object' - } ..`, - 0, - ); - // eslint-disable-next-line no-await-in-loop - const response = await core.lambda.call(jobInstance.task, tracker, { - task: jobInstance.task, - frame: frame - 1, - shapes: trackableObjects.shapes, - }); - - const { states: serverlessStates } = response; - const statefullContainer = trackingData.statefull[trackerID] || { - clientIDs: [], - shapes: [], - states: [], - }; - - Array.prototype.push.apply(statefullContainer.clientIDs, trackableObjects.clientIDs); - Array.prototype.push.apply(statefullContainer.shapes, trackableObjects.shapes); - Array.prototype.push.apply(statefullContainer.states, serverlessStates); - trackingData.statefull[trackerID] = statefullContainer; - delete trackingData.stateless[trackerID]; - } catch (error) { - notification.error({ - message: 'Tracker initialization error', - description: error.toString(), - }); - } finally { - if (hideMessage) hideMessage(); + try { + if (withServerRequest) { + switchNavigationBlocked(true); } - } + // 3. get relevant state for the second group + for (const trackerID of Object.keys(trackingData.stateless)) { + let hideMessage = null; + try { + const [tracker] = trackers.filter((_tracker: Model) => _tracker.id === trackerID); + if (!tracker) { + throw new Error(`Suitable tracker with ID ${trackerID} not found in tracker list`); + } - for (const trackerID of Object.keys(trackingData.statefull)) { - // 4. run tracking for all the objects - let hideMessage = null; - try { - const [tracker] = trackers.filter((_tracker: Model) => _tracker.id === trackerID); - if (!tracker) { - throw new Error(`Suitable tracker with ID ${trackerID} not found in tracker list`); + const trackableObjects = trackingData.stateless[trackerID]; + const numOfObjects = trackableObjects.clientIDs.length; + hideMessage = message.loading( + `${tracker.name}: states are being initialized for ${numOfObjects} ${ + numOfObjects > 1 ? 'objects' : 'object' + } ..`, + 0, + ); + // eslint-disable-next-line no-await-in-loop + const response = await core.lambda.call(jobInstance.task, tracker, { + task: jobInstance.task, + frame: frame - 1, + shapes: trackableObjects.shapes, + }); + + const { states: serverlessStates } = response; + const statefullContainer = trackingData.statefull[trackerID] || { + clientIDs: [], + shapes: [], + states: [], + }; + + Array.prototype.push.apply(statefullContainer.clientIDs, trackableObjects.clientIDs); + Array.prototype.push.apply(statefullContainer.shapes, trackableObjects.shapes); + Array.prototype.push.apply(statefullContainer.states, serverlessStates); + trackingData.statefull[trackerID] = statefullContainer; + delete trackingData.stateless[trackerID]; + } catch (error) { + notification.error({ + message: 'Tracker initialization error', + description: error.toString(), + }); + } finally { + if (hideMessage) hideMessage(); } + } - const trackableObjects = trackingData.statefull[trackerID]; - const numOfObjects = trackableObjects.clientIDs.length; - hideMessage = message.loading( - `${tracker.name}: ${numOfObjects} ${ - numOfObjects > 1 ? 'objects are' : 'object is' - } being tracked..`, - 0, - ); - // eslint-disable-next-line no-await-in-loop - const response = await core.lambda.call(jobInstance.task, tracker, { - task: jobInstance.task, - frame: frame - 1, - shapes: trackableObjects.shapes, - states: trackableObjects.states, - }); + for (const trackerID of Object.keys(trackingData.statefull)) { + // 4. run tracking for all the objects + let hideMessage = null; + try { + const [tracker] = trackers.filter((_tracker: Model) => _tracker.id === trackerID); + if (!tracker) { + throw new Error(`Suitable tracker with ID ${trackerID} not found in tracker list`); + } - response.shapes = response.shapes.map(trackedRectangleMapper); - for (let i = 0; i < trackableObjects.clientIDs.length; i++) { - const clientID = trackableObjects.clientIDs[i]; - const shape = response.shapes[i]; - const state = response.states[i]; - const [objectState] = objectStates.filter( - (_state: any): boolean => _state.clientID === clientID, - ); - const [trackedShape] = trackedShapes.filter( - (_trackedShape: TrackedShape) => _trackedShape.clientID === clientID, + const trackableObjects = trackingData.statefull[trackerID]; + const numOfObjects = trackableObjects.clientIDs.length; + hideMessage = message.loading( + `${tracker.name}: ${numOfObjects} ${ + numOfObjects > 1 ? 'objects are' : 'object is' + } being tracked..`, + 0, ); - objectState.points = shape; - objectState.save().then(() => { - trackedShape.serverlessState = state; - trackedShape.shapePoints = shape; + // eslint-disable-next-line no-await-in-loop + const response = await core.lambda.call(jobInstance.task, tracker, { + task: jobInstance.task, + frame: frame - 1, + shapes: trackableObjects.shapes, + states: trackableObjects.states, + }); + + response.shapes = response.shapes.map(trackedRectangleMapper); + for (let i = 0; i < trackableObjects.clientIDs.length; i++) { + const clientID = trackableObjects.clientIDs[i]; + const shape = response.shapes[i]; + const state = response.states[i]; + const [objectState] = objectStates.filter( + (_state: any): boolean => _state.clientID === clientID, + ); + const [trackedShape] = trackedShapes.filter( + (_trackedShape: TrackedShape) => _trackedShape.clientID === clientID, + ); + objectState.points = shape; + objectState.save().then(() => { + trackedShape.serverlessState = state; + trackedShape.shapePoints = shape; + }); + } + } catch (error) { + notification.error({ + message: 'Tracking error', + description: error.toString(), }); + } finally { + if (hideMessage) hideMessage(); + fetchAnnotations(); } - } catch (error) { - notification.error({ - message: 'Tracking error', - description: error.toString(), - }); - } finally { - if (hideMessage) hideMessage(); - fetchAnnotations(); + } + } finally { + if (withServerRequest) { + switchNavigationBlocked(false); } } } From 3e7dc1f5b3561b7414d5c7d2884a6ec2d13bdf7e Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Mon, 27 Sep 2021 16:35:53 +0300 Subject: [PATCH 19/19] Fixed issue with removing annotations, fixing issue with initial mounting & history --- cvat-ui/src/actions/annotation-actions.ts | 2 + .../controls-side-bar/tools-control.tsx | 194 +++++++++++------- cvat-ui/src/reducers/annotation-reducer.ts | 5 +- 3 files changed, 126 insertions(+), 75 deletions(-) diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index e47d82336569..7624785d1dae 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -258,12 +258,14 @@ export function fetchAnnotationsAsync(): ThunkAction { filters, frame, showAllInterpolationTracks, jobInstance, } = receiveAnnotationsParameters(); const states = await jobInstance.annotations.get(frame, showAllInterpolationTracks, filters); + const history = await jobInstance.actions.get(); const [minZ, maxZ] = computeZRange(states); dispatch({ type: AnnotationActionTypes.FETCH_ANNOTATIONS_SUCCESS, payload: { states, + history, minZ, maxZ, }, diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx index 61743f5d2639..89a30a5de2bf 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/tools-control.tsx @@ -150,6 +150,40 @@ function trackedRectangleMapper(shape: number[]): number[] { ); } +function registerPlugin(): (callback: null | (() => void)) => void { + let onTrigger: null | (() => void) = null; + const listener = { + name: 'Remove annotations listener', + description: 'Tracker needs to know when annotations is reset in the job', + cvat: { + classes: { + Job: { + prototype: { + annotations: { + clear: { + leave(self: any, result: any) { + if (typeof onTrigger === 'function') { + onTrigger(); + } + return result; + }, + }, + }, + }, + }, + }, + }, + }; + + core.plugins.register(listener); + + return (callback: null | (() => void)) => { + onTrigger = callback; + }; +} + +const onRemoveAnnotations = registerPlugin(); + export class ToolsControlComponent extends React.PureComponent { private interaction: { id: string | null; @@ -193,90 +227,26 @@ export class ToolsControlComponent extends React.PureComponent { public componentDidMount(): void { const { canvasInstance } = this.props; + onRemoveAnnotations(() => { + this.setState({ trackedShapes: [] }); + }); + + this.setState({ + portals: this.collectTrackerPortals(), + }); canvasInstance.html().addEventListener('canvas.interacted', this.interactionListener); canvasInstance.html().addEventListener('canvas.canceled', this.cancelListener); } public componentDidUpdate(prevProps: Props, prevState: State): void { const { - isActivated, defaultApproxPolyAccuracy, canvasInstance, states, fetchAnnotations, + isActivated, defaultApproxPolyAccuracy, canvasInstance, states, } = this.props; - const { - approxPolyAccuracy, mode, activeTracker, trackedShapes, - } = this.state; + const { approxPolyAccuracy, mode, activeTracker } = this.state; if (prevProps.states !== states || prevState.activeTracker !== activeTracker) { - const trackedClientIDs = trackedShapes.map((trackedShape: TrackedShape) => trackedShape.clientID); - const portals = !activeTracker ? - [] : - states - .filter( - (objectState) => objectState.objectType === 'track' && objectState.shapeType === 'rectangle', - ) - .map((objectState: any): React.ReactPortal | null => { - const { clientID } = objectState; - const selectorID = `#cvat-objects-sidebar-state-item-${clientID}`; - let targetElement = window.document.querySelector( - `${selectorID} .cvat-object-item-button-prev-keyframe`, - ) as HTMLElement; - - const isTracked = trackedClientIDs.includes(clientID); - if (targetElement) { - targetElement = targetElement.parentElement?.parentElement as HTMLElement; - return ReactDOM.createPortal( - - {isTracked ? ( - - { - const filteredStates = trackedShapes.filter( - (trackedShape: TrackedShape) => - trackedShape.clientID !== clientID, - ); - /* eslint no-param-reassign: ["error", { "props": false }] */ - objectState.descriptions = []; - objectState.save().then(() => { - this.setState({ - trackedShapes: filteredStates, - }); - }); - fetchAnnotations(); - }} - /> - - ) : ( - - { - objectState.descriptions = [`Trackable (${activeTracker.name})`]; - objectState.save().then(() => { - this.setState({ - trackedShapes: [ - ...trackedShapes, - { - clientID, - serverlessState: null, - shapePoints: objectState.points, - trackerModel: activeTracker, - }, - ], - }); - }); - fetchAnnotations(); - }} - /> - - )} - , - targetElement, - ); - } - - return null; - }) - .filter((portal: ReactPortal | null) => portal !== null); this.setState({ - portals: portals as ReactPortal[], + portals: this.collectTrackerPortals(), }); } @@ -326,6 +296,7 @@ export class ToolsControlComponent extends React.PureComponent { public componentWillUnmount(): void { const { canvasInstance } = this.props; + onRemoveAnnotations(null); canvasInstance.html().removeEventListener('canvas.interacted', this.interactionListener); canvasInstance.html().removeEventListener('canvas.canceled', this.cancelListener); } @@ -538,6 +509,81 @@ export class ToolsControlComponent extends React.PureComponent { } }; + private collectTrackerPortals(): React.ReactPortal[] { + const { states, fetchAnnotations } = this.props; + const { trackedShapes, activeTracker } = this.state; + + const trackedClientIDs = trackedShapes.map((trackedShape: TrackedShape) => trackedShape.clientID); + const portals = !activeTracker ? + [] : + states + .filter((objectState) => objectState.objectType === 'track' && objectState.shapeType === 'rectangle') + .map((objectState: any): React.ReactPortal | null => { + const { clientID } = objectState; + const selectorID = `#cvat-objects-sidebar-state-item-${clientID}`; + let targetElement = window.document.querySelector( + `${selectorID} .cvat-object-item-button-prev-keyframe`, + ) as HTMLElement; + + const isTracked = trackedClientIDs.includes(clientID); + if (targetElement) { + targetElement = targetElement.parentElement?.parentElement as HTMLElement; + return ReactDOM.createPortal( + + {isTracked ? ( + + { + const filteredStates = trackedShapes.filter( + (trackedShape: TrackedShape) => + trackedShape.clientID !== clientID, + ); + /* eslint no-param-reassign: ["error", { "props": false }] */ + objectState.descriptions = []; + objectState.save().then(() => { + this.setState({ + trackedShapes: filteredStates, + }); + }); + fetchAnnotations(); + }} + /> + + ) : ( + + { + objectState.descriptions = [`Trackable (${activeTracker.name})`]; + objectState.save().then(() => { + this.setState({ + trackedShapes: [ + ...trackedShapes, + { + clientID, + serverlessState: null, + shapePoints: objectState.points, + trackerModel: activeTracker, + }, + ], + }); + }); + fetchAnnotations(); + }} + /> + + )} + , + targetElement, + ); + } + + return null; + }) + .filter((portal: ReactPortal | null) => portal !== null); + + return portals as React.ReactPortal[]; + } + private async checkTrackedStates(prevProps: Props): Promise { const { frame, diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index d574d76ae2f5..ef144ca1bf7d 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -989,7 +989,9 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { } case AnnotationActionTypes.FETCH_ANNOTATIONS_SUCCESS: { const { activatedStateID } = state.annotations; - const { states, minZ, maxZ } = action.payload; + const { + states, history, minZ, maxZ, + } = action.payload; return { ...state, @@ -997,6 +999,7 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { ...state.annotations, activatedStateID: updateActivatedStateID(states, activatedStateID), states, + history, zLayer: { min: minZ, max: maxZ,