Skip to content

Commit

Permalink
React UI: Improved rotation feature (#1206)
Browse files Browse the repository at this point in the history
Co-authored-by: Boris Sekachev <40690378+bsekachev@users.noreply.github.com>
ActiveChooN and bsekachev authored Feb 28, 2020

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent da69a40 commit adb66b5
Showing 12 changed files with 79 additions and 49 deletions.
9 changes: 2 additions & 7 deletions cvat-canvas/README.md
Original file line number Diff line number Diff line change
@@ -32,11 +32,6 @@ Canvas itself handles:
### API Methods

```ts
enum Rotation {
ANTICLOCKWISE90,
CLOCKWISE90,
}

enum RectDrawingMethod {
CLASSIC = 'By 2 points',
EXTREME_POINTS = 'By 4 points'
@@ -79,7 +74,7 @@ Canvas itself handles:
setZLayer(zLayer: number | null): void;
setup(frameData: any, objectStates: any[]): void;
activate(clientID: number, attributeID?: number): void;
rotate(rotation: Rotation, remember?: boolean): void;
rotate(frameAngle: number): void;
focus(clientID: number, padding?: number): void;
fit(): void;
grid(stepX: number, stepY: number): void;
@@ -147,7 +142,7 @@ Standard JS events are used.
canvas.fitCanvas();

// Next you can use its API methods. For example:
canvas.rotate(window.Canvas.Rotation.CLOCKWISE90);
canvas.rotate(270);
canvas.draw({
enabled: true,
shapeType: 'rectangle',
8 changes: 3 additions & 5 deletions cvat-canvas/src/typescript/canvas.ts
Original file line number Diff line number Diff line change
@@ -3,7 +3,6 @@
// SPDX-License-Identifier: MIT

import {
Rotation,
DrawData,
MergeData,
SplitData,
@@ -37,7 +36,7 @@ interface Canvas {
setZLayer(zLayer: number | null): void;
setup(frameData: any, objectStates: any[]): void;
activate(clientID: number | null, attributeID?: number): void;
rotate(rotation: Rotation, remember?: boolean): void;
rotate(rotationAngle: number): void;
focus(clientID: number, padding?: number): void;
fit(): void;
grid(stepX: number, stepY: number): void;
@@ -97,8 +96,8 @@ class CanvasImpl implements Canvas {
this.model.activate(clientID, attributeID);
}

public rotate(rotation: Rotation, remember: boolean = false): void {
this.model.rotate(rotation, remember);
public rotate(rotationAngle: number): void {
this.model.rotate(rotationAngle);
}

public focus(clientID: number, padding: number = 0): void {
@@ -140,7 +139,6 @@ class CanvasImpl implements Canvas {

export {
CanvasImpl as Canvas,
Rotation,
CanvasVersion,
RectDrawingMethod,
};
26 changes: 5 additions & 21 deletions cvat-canvas/src/typescript/canvasModel.ts
Original file line number Diff line number Diff line change
@@ -72,11 +72,6 @@ export enum FrameZoom {
MAX = 10,
}

export enum Rotation {
ANTICLOCKWISE90,
CLOCKWISE90,
}

export enum UpdateReasons {
IMAGE_CHANGED = 'image_changed',
IMAGE_ZOOMED = 'image_zoomed',
@@ -135,7 +130,7 @@ export interface CanvasModel {

setup(frameData: any, objectStates: any[]): void;
activate(clientID: number | null, attributeID: number | null): void;
rotate(rotation: Rotation, remember: boolean): void;
rotate(rotationAngle: number): void;
focus(clientID: number, padding: number): void;
fit(): void;
grid(stepX: number, stepY: number): void;
@@ -166,7 +161,6 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
gridSize: Size;
left: number;
objects: any[];
rememberAngle: boolean;
scale: number;
top: number;
zLayer: number | null;
@@ -208,7 +202,6 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
},
left: 0,
objects: [],
rememberAngle: false,
scale: 1,
top: 0,
zLayer: null,
@@ -323,10 +316,6 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
return;
}

if (!this.data.rememberAngle) {
this.data.angle = 0;
}

this.data.imageSize = {
height: (frameData.height as number),
width: (frameData.width as number),
@@ -355,16 +344,11 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
this.notify(UpdateReasons.SHAPE_ACTIVATED);
}

public rotate(rotation: Rotation, remember: boolean = false): void {
if (rotation === Rotation.CLOCKWISE90) {
this.data.angle += 90;
} else {
this.data.angle -= 90;
public rotate(rotationAngle: number): void {
if (this.data.angle !== rotationAngle) {
this.data.angle = (360 + Math.floor((rotationAngle) / 90) * 90) % 360;
this.fit();
}

this.data.angle %= 360;
this.data.rememberAngle = remember;
this.fit();
}

public focus(clientID: number, padding: number): void {
23 changes: 23 additions & 0 deletions cvat-ui/src/actions/annotation-actions.ts
Original file line number Diff line number Diff line change
@@ -17,6 +17,7 @@ import {
ObjectType,
Task,
FrameSpeed,
Rotation,
} from 'reducers/interfaces';

import getCore from 'cvat-core';
@@ -129,6 +130,7 @@ export enum AnnotationActionTypes {
CHANGE_ANNOTATIONS_FILTERS = 'CHANGE_ANNOTATIONS_FILTERS',
FETCH_ANNOTATIONS_SUCCESS = 'FETCH_ANNOTATIONS_SUCCESS',
FETCH_ANNOTATIONS_FAILED = 'FETCH_ANNOTATIONS_FAILED',
ROTATE_FRAME = 'ROTATE_FRAME',
SWITCH_Z_LAYER = 'SWITCH_Z_LAYER',
ADD_Z_LAYER = 'ADD_Z_LAYER',
}
@@ -666,6 +668,27 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
};
}


export function rotateCurrentFrame(rotation: Rotation): AnyAction {
const state: CombinedState = getStore().getState();
const { number: frameNumber } = state.annotation.player.frame;
const { startFrame } = state.annotation.job.instance;
const { frameAngles } = state.annotation.player;
const { rotateAll } = state.settings.player;

const frameAngle = (frameAngles[frameNumber - startFrame]
+ (rotation === Rotation.CLOCKWISE90 ? 90 : 270)) % 360;

return {
type: AnnotationActionTypes.ROTATE_FRAME,
payload: {
offset: frameNumber - state.annotation.job.instance.startFrame,
angle: frameAngle,
rotateAll,
},
};
}

export function dragCanvas(enabled: boolean): AnyAction {
return {
type: AnnotationActionTypes.DRAG_CANVAS,
Original file line number Diff line number Diff line change
@@ -37,6 +37,7 @@ interface Props {
selectedStatesID: number[];
annotations: any[];
frameData: any;
frameAngle: number;
frame: number;
opacity: number;
colorBy: ColorBy;
@@ -101,6 +102,7 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
selectedOpacity,
blackBorders,
frameData,
frameAngle,
annotations,
canvasInstance,
sidebarCollapsed,
@@ -149,6 +151,10 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
canvasInstance.setZLayer(curZLayer);
}

if (prevProps.frameAngle !== frameAngle) {
canvasInstance.rotate(frameAngle);
}

this.activateOnCanvas();
}

@@ -305,11 +311,13 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
const {
annotations,
frameData,
frameAngle,
canvasInstance,
} = this.props;

if (frameData !== null) {
canvasInstance.setup(frameData, annotations);
canvasInstance.rotate(frameAngle);
}
}

Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@ import {

import {
ActiveControl,
Rotation
} from 'reducers/interfaces';

import {
@@ -22,9 +23,9 @@ import {
Canvas,
} from 'cvat-canvas';

import RotateControl from './rotate-control';
import CursorControl from './cursor-control';
import MoveControl from './move-control';
import RotateControl from './rotate-control';
import FitControl from './fit-control';
import ResizeControl from './resize-control';
import DrawRectangleControl from './draw-rectangle-control';
@@ -37,23 +38,23 @@ import SplitControl from './split-control';

interface Props {
canvasInstance: Canvas;
rotateAll: boolean;
activeControl: ActiveControl;

mergeObjects(enabled: boolean): void;
groupObjects(enabled: boolean): void;
splitTrack(enabled: boolean): void;
rotateFrame(rotation: Rotation): void;
}

export default function ControlsSideBarComponent(props: Props): JSX.Element {
const {
canvasInstance,
activeControl,
rotateAll,

mergeObjects,
groupObjects,
splitTrack,
rotateFrame,
} = props;

return (
@@ -64,7 +65,7 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element {
>
<CursorControl canvasInstance={canvasInstance} activeControl={activeControl} />
<MoveControl canvasInstance={canvasInstance} activeControl={activeControl} />
<RotateControl canvasInstance={canvasInstance} rotateAll={rotateAll} />
<RotateControl rotateFrame={rotateFrame} />

<hr />

Original file line number Diff line number Diff line change
@@ -16,18 +16,15 @@ import {

import {
Rotation,
Canvas,
} from 'cvat-canvas';
} from 'reducers/interfaces';

interface Props {
canvasInstance: Canvas;
rotateAll: boolean;
rotateFrame(rotation: Rotation): void;
}

function RotateControl(props: Props): JSX.Element {
const {
rotateAll,
canvasInstance,
rotateFrame,
} = props;

return (
@@ -39,16 +36,14 @@ function RotateControl(props: Props): JSX.Element {
<Tooltip title='Rotate the image anticlockwise' placement='topRight'>
<Icon
className='cvat-rotate-canvas-controls-left'
onClick={(): void => canvasInstance
.rotate(Rotation.ANTICLOCKWISE90, rotateAll)}
onClick={(): void => rotateFrame(Rotation.ANTICLOCKWISE90)}
component={RotateIcon}
/>
</Tooltip>
<Tooltip title='Rotate the image clockwise' placement='topRight'>
<Icon
className='cvat-rotate-canvas-controls-right'
onClick={(): void => canvasInstance
.rotate(Rotation.CLOCKWISE90, rotateAll)}
onClick={(): void => rotateFrame(Rotation.CLOCKWISE90)}
component={RotateIcon}
/>
</Tooltip>
Original file line number Diff line number Diff line change
@@ -45,6 +45,7 @@ interface StateToProps {
selectedStatesID: number[];
annotations: any[];
frameData: any;
frameAngle: number;
frame: number;
opacity: number;
colorBy: ColorBy;
@@ -105,6 +106,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
data: frameData,
number: frame,
},
frameAngles,
},
annotations: {
states: annotations,
@@ -143,6 +145,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
canvasInstance,
jobInstance,
frameData,
frameAngle: frameAngles[frame - jobInstance.startFrame],
frame,
activatedStateID,
selectedStatesID,
Original file line number Diff line number Diff line change
@@ -11,11 +11,13 @@ import {
mergeObjects,
groupObjects,
splitTrack,
rotateCurrentFrame,
} from 'actions/annotation-actions';
import ControlsSideBarComponent from 'components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar';
import {
ActiveControl,
CombinedState,
Rotation,
} from 'reducers/interfaces';

interface StateToProps {
@@ -28,6 +30,7 @@ interface DispatchToProps {
mergeObjects(enabled: boolean): void;
groupObjects(enabled: boolean): void;
splitTrack(enabled: boolean): void;
rotateFrame(angle: Rotation): void;
}

function mapStateToProps(state: CombinedState): StateToProps {
@@ -63,6 +66,9 @@ function dispatchToProps(dispatch: any): DispatchToProps {
splitTrack(enabled: boolean): void {
dispatch(splitTrack(enabled));
},
rotateFrame(rotation: Rotation): void {
dispatch(rotateCurrentFrame(rotation));
},
};
}

2 changes: 0 additions & 2 deletions cvat-ui/src/cvat-canvas.ts
Original file line number Diff line number Diff line change
@@ -4,14 +4,12 @@

import {
Canvas,
Rotation,
CanvasVersion,
RectDrawingMethod,
} from '../../cvat-canvas/src/typescript/canvas';

export {
Canvas,
Rotation,
CanvasVersion,
RectDrawingMethod,
};
13 changes: 13 additions & 0 deletions cvat-ui/src/reducers/annotation-reducer.ts
Original file line number Diff line number Diff line change
@@ -44,6 +44,7 @@ const defaultState: AnnotationState = {
changeTime: null,
},
playing: false,
frameAngles: [],
},
drawing: {
activeShapeType: ShapeType.RECTANGLE,
@@ -138,6 +139,7 @@ export default (state = defaultState, action: AnyAction): AnnotationState => {
number,
data,
},
frameAngles: Array(job.stopFrame - job.startFrame + 1).fill(0),
},
drawing: {
...state.drawing,
@@ -233,6 +235,17 @@ export default (state = defaultState, action: AnyAction): AnnotationState => {
},
};
}
case AnnotationActionTypes.ROTATE_FRAME: {
const { offset, angle, rotateAll } = action.payload;
return {
...state,
player: {
...state.player,
frameAngles: state.player.frameAngles.map((_angle: number, idx: number) => (
rotateAll || offset === idx ? angle : _angle)),
},
};
}
case AnnotationActionTypes.SAVE_ANNOTATIONS: {
return {
...state,
6 changes: 6 additions & 0 deletions cvat-ui/src/reducers/interfaces.ts
Original file line number Diff line number Diff line change
@@ -275,6 +275,11 @@ export enum ContextMenuType {
CANVAS_SHAPE = 'canvas_shape',
}

export enum Rotation {
ANTICLOCKWISE90,
CLOCKWISE90,
}

export interface AnnotationState {
activities: {
loads: {
@@ -308,6 +313,7 @@ export interface AnnotationState {
changeTime: number | null;
};
playing: boolean;
frameAngles: number[];
};
drawing: {
activeShapeType: ShapeType;

0 comments on commit adb66b5

Please sign in to comment.