|
|
|
|
@ -4,6 +4,7 @@
|
|
|
|
|
|
|
|
|
|
import React from 'react';
|
|
|
|
|
import { connect } from 'react-redux';
|
|
|
|
|
import { GlobalHotKeys, KeyMap } from 'react-hotkeys';
|
|
|
|
|
|
|
|
|
|
import { SelectValue } from 'antd/lib/select';
|
|
|
|
|
|
|
|
|
|
@ -11,13 +12,18 @@ import ObjectsListComponent from 'components/annotation-page/standard-workspace/
|
|
|
|
|
import {
|
|
|
|
|
updateAnnotationsAsync,
|
|
|
|
|
fetchAnnotationsAsync,
|
|
|
|
|
removeObjectAsync,
|
|
|
|
|
changeFrameAsync,
|
|
|
|
|
changeAnnotationsFilters as changeAnnotationsFiltersAction,
|
|
|
|
|
collapseObjectItems,
|
|
|
|
|
copyShape as copyShapeAction,
|
|
|
|
|
propagateObject as propagateObjectAction,
|
|
|
|
|
} from 'actions/annotation-actions';
|
|
|
|
|
|
|
|
|
|
import {
|
|
|
|
|
CombinedState,
|
|
|
|
|
StatesOrdering,
|
|
|
|
|
ObjectType,
|
|
|
|
|
} from 'reducers/interfaces';
|
|
|
|
|
|
|
|
|
|
interface StateToProps {
|
|
|
|
|
@ -29,6 +35,9 @@ interface StateToProps {
|
|
|
|
|
statesCollapsed: boolean;
|
|
|
|
|
objectStates: any[];
|
|
|
|
|
annotationsFilters: string[];
|
|
|
|
|
activatedStateID: number | null;
|
|
|
|
|
minZLayer: number;
|
|
|
|
|
maxZLayer: number;
|
|
|
|
|
annotationsFiltersHistory: string[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -36,6 +45,10 @@ interface DispatchToProps {
|
|
|
|
|
updateAnnotations(sessionInstance: any, frameNumber: number, states: any[]): void;
|
|
|
|
|
changeAnnotationsFilters(sessionInstance: any, filters: string[]): void;
|
|
|
|
|
collapseStates(states: any[], value: boolean): void;
|
|
|
|
|
removeObject: (sessionInstance: any, objectState: any, force: boolean) => void;
|
|
|
|
|
copyShape: (objectState: any) => void;
|
|
|
|
|
propagateObject: (objectState: any) => void;
|
|
|
|
|
changeFrame(frame: number): void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function mapStateToProps(state: CombinedState): StateToProps {
|
|
|
|
|
@ -46,6 +59,11 @@ function mapStateToProps(state: CombinedState): StateToProps {
|
|
|
|
|
filters: annotationsFilters,
|
|
|
|
|
filtersHistory: annotationsFiltersHistory,
|
|
|
|
|
collapsed,
|
|
|
|
|
activatedStateID,
|
|
|
|
|
zLayer: {
|
|
|
|
|
min: minZLayer,
|
|
|
|
|
max: maxZLayer,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
job: {
|
|
|
|
|
instance: jobInstance,
|
|
|
|
|
@ -64,9 +82,11 @@ function mapStateToProps(state: CombinedState): StateToProps {
|
|
|
|
|
let statesCollapsed = true;
|
|
|
|
|
|
|
|
|
|
objectStates.forEach((objectState: any) => {
|
|
|
|
|
const { clientID } = objectState;
|
|
|
|
|
statesHidden = statesHidden && objectState.hidden;
|
|
|
|
|
statesLocked = statesLocked && objectState.lock;
|
|
|
|
|
const { clientID, lock } = objectState;
|
|
|
|
|
if (!lock) {
|
|
|
|
|
statesHidden = statesHidden && objectState.hidden;
|
|
|
|
|
statesLocked = statesLocked && objectState.lock;
|
|
|
|
|
}
|
|
|
|
|
const stateCollapsed = clientID in collapsed ? collapsed[clientID] : true;
|
|
|
|
|
statesCollapsed = statesCollapsed && stateCollapsed;
|
|
|
|
|
});
|
|
|
|
|
@ -80,6 +100,9 @@ function mapStateToProps(state: CombinedState): StateToProps {
|
|
|
|
|
frameNumber,
|
|
|
|
|
jobInstance,
|
|
|
|
|
annotationsFilters,
|
|
|
|
|
activatedStateID,
|
|
|
|
|
minZLayer,
|
|
|
|
|
maxZLayer,
|
|
|
|
|
annotationsFiltersHistory,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
@ -99,6 +122,18 @@ function mapDispatchToProps(dispatch: any): DispatchToProps {
|
|
|
|
|
dispatch(changeAnnotationsFiltersAction(filters));
|
|
|
|
|
dispatch(fetchAnnotationsAsync(sessionInstance));
|
|
|
|
|
},
|
|
|
|
|
removeObject(sessionInstance: any, objectState: any, force: boolean): void {
|
|
|
|
|
dispatch(removeObjectAsync(sessionInstance, objectState, force));
|
|
|
|
|
},
|
|
|
|
|
copyShape(objectState: any): void {
|
|
|
|
|
dispatch(copyShapeAction(objectState));
|
|
|
|
|
},
|
|
|
|
|
propagateObject(objectState: any): void {
|
|
|
|
|
dispatch(propagateObjectAction(objectState));
|
|
|
|
|
},
|
|
|
|
|
changeFrame(frame: number): void {
|
|
|
|
|
dispatch(changeFrameAsync(frame));
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -226,6 +261,19 @@ class ObjectsListContainer extends React.PureComponent<Props, State> {
|
|
|
|
|
public render(): JSX.Element {
|
|
|
|
|
const {
|
|
|
|
|
annotationsFilters,
|
|
|
|
|
statesHidden,
|
|
|
|
|
statesLocked,
|
|
|
|
|
activatedStateID,
|
|
|
|
|
objectStates,
|
|
|
|
|
frameNumber,
|
|
|
|
|
jobInstance,
|
|
|
|
|
updateAnnotations,
|
|
|
|
|
removeObject,
|
|
|
|
|
copyShape,
|
|
|
|
|
propagateObject,
|
|
|
|
|
changeFrame,
|
|
|
|
|
maxZLayer,
|
|
|
|
|
minZLayer,
|
|
|
|
|
annotationsFiltersHistory,
|
|
|
|
|
} = this.props;
|
|
|
|
|
const {
|
|
|
|
|
@ -233,22 +281,241 @@ class ObjectsListContainer extends React.PureComponent<Props, State> {
|
|
|
|
|
statesOrdering,
|
|
|
|
|
} = this.state;
|
|
|
|
|
|
|
|
|
|
const keyMap = {
|
|
|
|
|
SWITCH_ALL_LOCK: {
|
|
|
|
|
name: 'Lock/unlock all objects',
|
|
|
|
|
description: 'Change locked state for all objects in the side bar',
|
|
|
|
|
sequence: 't+l',
|
|
|
|
|
action: 'keydown',
|
|
|
|
|
},
|
|
|
|
|
SWITCH_LOCK: {
|
|
|
|
|
name: 'Lock/unlock an object',
|
|
|
|
|
description: 'Change locked state for an active object',
|
|
|
|
|
sequence: 'l',
|
|
|
|
|
action: 'keydown',
|
|
|
|
|
},
|
|
|
|
|
SWITCH_ALL_HIDDEN: {
|
|
|
|
|
name: 'Hide/show all objects',
|
|
|
|
|
description: 'Change hidden state for objects in the side bar',
|
|
|
|
|
sequence: 't+h',
|
|
|
|
|
action: 'keydown',
|
|
|
|
|
},
|
|
|
|
|
SWITCH_HIDDEN: {
|
|
|
|
|
name: 'Hide/show an object',
|
|
|
|
|
description: 'Change hidden state for an active object',
|
|
|
|
|
sequence: 'h',
|
|
|
|
|
action: 'keydown',
|
|
|
|
|
},
|
|
|
|
|
SWITCH_OCCLUDED: {
|
|
|
|
|
name: 'Switch occluded',
|
|
|
|
|
description: 'Change occluded property for an active object',
|
|
|
|
|
sequences: ['q', '/'],
|
|
|
|
|
action: 'keydown',
|
|
|
|
|
},
|
|
|
|
|
SWITCH_KEYFRAME: {
|
|
|
|
|
name: 'Switch keyframe',
|
|
|
|
|
description: 'Change keyframe property for an active track',
|
|
|
|
|
sequence: 'k',
|
|
|
|
|
action: 'keydown',
|
|
|
|
|
},
|
|
|
|
|
SWITCH_OUTSIDE: {
|
|
|
|
|
name: 'Switch outside',
|
|
|
|
|
description: 'Change outside property for an active track',
|
|
|
|
|
sequence: 'o',
|
|
|
|
|
action: 'keydown',
|
|
|
|
|
},
|
|
|
|
|
DELETE_OBJECT: {
|
|
|
|
|
name: 'Delete object',
|
|
|
|
|
description: 'Delete an active object. Use shift to force delete of locked objects',
|
|
|
|
|
sequences: ['del', 'shift+del'],
|
|
|
|
|
action: 'keydown',
|
|
|
|
|
},
|
|
|
|
|
TO_BACKGROUND: {
|
|
|
|
|
name: 'To background',
|
|
|
|
|
description: 'Put an active object "farther" from the user (decrease z axis value)',
|
|
|
|
|
sequences: ['-', '_'],
|
|
|
|
|
action: 'keydown',
|
|
|
|
|
},
|
|
|
|
|
TO_FOREGROUND: {
|
|
|
|
|
name: 'To foreground',
|
|
|
|
|
description: 'Put an active object "closer" to the user (increase z axis value)',
|
|
|
|
|
sequences: ['+', '='],
|
|
|
|
|
action: 'keydown',
|
|
|
|
|
},
|
|
|
|
|
COPY_SHAPE: {
|
|
|
|
|
name: 'Copy shape',
|
|
|
|
|
description: 'Copy shape to CVAT internal clipboard',
|
|
|
|
|
sequence: 'ctrl+c',
|
|
|
|
|
action: 'keydown',
|
|
|
|
|
},
|
|
|
|
|
PROPAGATE_OBJECT: {
|
|
|
|
|
name: 'Propagate object',
|
|
|
|
|
description: 'Make a copy of the object on the following frames',
|
|
|
|
|
sequence: 'ctrl+b',
|
|
|
|
|
action: 'keydown',
|
|
|
|
|
},
|
|
|
|
|
NEXT_KEY_FRAME: {
|
|
|
|
|
name: 'Next keyframe',
|
|
|
|
|
description: 'Go to the next keyframe of an active track',
|
|
|
|
|
sequence: 'r',
|
|
|
|
|
action: 'keydown',
|
|
|
|
|
},
|
|
|
|
|
PREV_KEY_FRAME: {
|
|
|
|
|
name: 'Previous keyframe',
|
|
|
|
|
description: 'Go to the previous keyframe of an active track',
|
|
|
|
|
sequence: 'e',
|
|
|
|
|
action: 'keydown',
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const preventDefault = (event: KeyboardEvent | undefined): void => {
|
|
|
|
|
if (event) {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const activatedStated = (): any | null => {
|
|
|
|
|
if (activatedStateID !== null) {
|
|
|
|
|
const [state] = objectStates
|
|
|
|
|
.filter((objectState: any): boolean => (
|
|
|
|
|
objectState.clientID === activatedStateID
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
return state || null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handlers = {
|
|
|
|
|
SWITCH_ALL_LOCK: (event: KeyboardEvent | undefined) => {
|
|
|
|
|
preventDefault(event);
|
|
|
|
|
this.lockAllStates(!statesLocked);
|
|
|
|
|
},
|
|
|
|
|
SWITCH_LOCK: (event: KeyboardEvent | undefined) => {
|
|
|
|
|
preventDefault(event);
|
|
|
|
|
const state = activatedStated();
|
|
|
|
|
if (state) {
|
|
|
|
|
state.lock = !state.lock;
|
|
|
|
|
updateAnnotations(jobInstance, frameNumber, [state]);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
SWITCH_ALL_HIDDEN: (event: KeyboardEvent | undefined) => {
|
|
|
|
|
preventDefault(event);
|
|
|
|
|
this.hideAllStates(!statesHidden);
|
|
|
|
|
},
|
|
|
|
|
SWITCH_HIDDEN: (event: KeyboardEvent | undefined) => {
|
|
|
|
|
preventDefault(event);
|
|
|
|
|
const state = activatedStated();
|
|
|
|
|
if (state) {
|
|
|
|
|
state.hidden = !state.hidden;
|
|
|
|
|
updateAnnotations(jobInstance, frameNumber, [state]);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
SWITCH_OCCLUDED: (event: KeyboardEvent | undefined) => {
|
|
|
|
|
preventDefault(event);
|
|
|
|
|
const state = activatedStated();
|
|
|
|
|
if (state && state.objectType !== ObjectType.TAG) {
|
|
|
|
|
state.occluded = !state.occluded;
|
|
|
|
|
updateAnnotations(jobInstance, frameNumber, [state]);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
SWITCH_KEYFRAME: (event: KeyboardEvent | undefined) => {
|
|
|
|
|
preventDefault(event);
|
|
|
|
|
const state = activatedStated();
|
|
|
|
|
if (state && state.objectType === ObjectType.TRACK) {
|
|
|
|
|
state.keyframe = !state.keyframe;
|
|
|
|
|
updateAnnotations(jobInstance, frameNumber, [state]);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
SWITCH_OUTSIDE: (event: KeyboardEvent | undefined) => {
|
|
|
|
|
preventDefault(event);
|
|
|
|
|
const state = activatedStated();
|
|
|
|
|
if (state && state.objectType === ObjectType.TRACK) {
|
|
|
|
|
state.outside = !state.outside;
|
|
|
|
|
updateAnnotations(jobInstance, frameNumber, [state]);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
DELETE_OBJECT: (event: KeyboardEvent | undefined) => {
|
|
|
|
|
preventDefault(event);
|
|
|
|
|
const state = activatedStated();
|
|
|
|
|
if (state) {
|
|
|
|
|
removeObject(jobInstance, state, event ? event.shiftKey : false);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
TO_BACKGROUND: (event: KeyboardEvent | undefined) => {
|
|
|
|
|
preventDefault(event);
|
|
|
|
|
const state = activatedStated();
|
|
|
|
|
if (state && state.objectType !== ObjectType.TAG) {
|
|
|
|
|
state.zOrder = minZLayer - 1;
|
|
|
|
|
updateAnnotations(jobInstance, frameNumber, [state]);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
TO_FOREGROUND: (event: KeyboardEvent | undefined) => {
|
|
|
|
|
preventDefault(event);
|
|
|
|
|
const state = activatedStated();
|
|
|
|
|
if (state && state.objectType !== ObjectType.TAG) {
|
|
|
|
|
state.zOrder = maxZLayer + 1;
|
|
|
|
|
updateAnnotations(jobInstance, frameNumber, [state]);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
COPY_SHAPE: (event: KeyboardEvent | undefined) => {
|
|
|
|
|
preventDefault(event);
|
|
|
|
|
const state = activatedStated();
|
|
|
|
|
if (state && state.objectType !== ObjectType.TAG) {
|
|
|
|
|
copyShape(state);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
PROPAGATE_OBJECT: (event: KeyboardEvent | undefined) => {
|
|
|
|
|
preventDefault(event);
|
|
|
|
|
const state = activatedStated();
|
|
|
|
|
if (state && state.objectType !== ObjectType.TAG) {
|
|
|
|
|
propagateObject(state);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
NEXT_KEY_FRAME: (event: KeyboardEvent | undefined) => {
|
|
|
|
|
preventDefault(event);
|
|
|
|
|
const state = activatedStated();
|
|
|
|
|
if (state && state.objectType === ObjectType.TRACK) {
|
|
|
|
|
const frame = typeof (state.keyframes.next) === 'number'
|
|
|
|
|
? state.keyframes.next : null;
|
|
|
|
|
if (frame !== null) {
|
|
|
|
|
changeFrame(frame);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
PREV_KEY_FRAME: (event: KeyboardEvent | undefined) => {
|
|
|
|
|
preventDefault(event);
|
|
|
|
|
const state = activatedStated();
|
|
|
|
|
if (state && state.objectType === ObjectType.TRACK) {
|
|
|
|
|
const frame = typeof (state.keyframes.prev) === 'number'
|
|
|
|
|
? state.keyframes.prev : null;
|
|
|
|
|
if (frame !== null) {
|
|
|
|
|
changeFrame(frame);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<ObjectsListComponent
|
|
|
|
|
{...this.props}
|
|
|
|
|
statesOrdering={statesOrdering}
|
|
|
|
|
sortedStatesID={sortedStatesID}
|
|
|
|
|
annotationsFilters={annotationsFilters}
|
|
|
|
|
annotationsFiltersHistory={annotationsFiltersHistory}
|
|
|
|
|
changeStatesOrdering={this.onChangeStatesOrdering}
|
|
|
|
|
changeAnnotationsFilters={this.onChangeAnnotationsFilters}
|
|
|
|
|
lockAllStates={this.onLockAllStates}
|
|
|
|
|
unlockAllStates={this.onUnlockAllStates}
|
|
|
|
|
collapseAllStates={this.onCollapseAllStates}
|
|
|
|
|
expandAllStates={this.onExpandAllStates}
|
|
|
|
|
hideAllStates={this.onHideAllStates}
|
|
|
|
|
showAllStates={this.onShowAllStates}
|
|
|
|
|
/>
|
|
|
|
|
<>
|
|
|
|
|
<GlobalHotKeys keyMap={keyMap as any as KeyMap} handlers={handlers} allowChanges />
|
|
|
|
|
<ObjectsListComponent
|
|
|
|
|
{...this.props}
|
|
|
|
|
statesOrdering={statesOrdering}
|
|
|
|
|
sortedStatesID={sortedStatesID}
|
|
|
|
|
annotationsFilters={annotationsFilters}
|
|
|
|
|
changeStatesOrdering={this.onChangeStatesOrdering}
|
|
|
|
|
changeAnnotationsFilters={this.onChangeAnnotationsFilters}
|
|
|
|
|
annotationsFiltersHistory={annotationsFiltersHistory}
|
|
|
|
|
lockAllStates={this.onLockAllStates}
|
|
|
|
|
unlockAllStates={this.onUnlockAllStates}
|
|
|
|
|
collapseAllStates={this.onCollapseAllStates}
|
|
|
|
|
expandAllStates={this.onExpandAllStates}
|
|
|
|
|
hideAllStates={this.onHideAllStates}
|
|
|
|
|
showAllStates={this.onShowAllStates}
|
|
|
|
|
/>
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|