You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
881 lines
35 KiB
TypeScript
881 lines
35 KiB
TypeScript
// Copyright (C) 2021-2022 Intel Corporation
|
|
//
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
import React from 'react';
|
|
import { connect } from 'react-redux';
|
|
import { Row, Col } from 'antd/lib/grid';
|
|
import Popover from 'antd/lib/popover';
|
|
import Icon, { AreaChartOutlined, ScissorOutlined } from '@ant-design/icons';
|
|
import Text from 'antd/lib/typography/Text';
|
|
import Tabs from 'antd/lib/tabs';
|
|
import Button from 'antd/lib/button';
|
|
import Progress from 'antd/lib/progress';
|
|
import Select from 'antd/lib/select';
|
|
import notification from 'antd/lib/notification';
|
|
import message from 'antd/lib/message';
|
|
|
|
import { OpenCVIcon } from 'icons';
|
|
import { Canvas, convertShapesForInteractor } from 'cvat-canvas-wrapper';
|
|
import getCore from 'cvat-core-wrapper';
|
|
import openCVWrapper from 'utils/opencv-wrapper/opencv-wrapper';
|
|
import { IntelligentScissors } from 'utils/opencv-wrapper/intelligent-scissors';
|
|
import {
|
|
CombinedState, ActiveControl, OpenCVTool, ObjectType, ShapeType, ToolsBlockerState,
|
|
} from 'reducers/interfaces';
|
|
import {
|
|
interactWithCanvas,
|
|
fetchAnnotationsAsync,
|
|
updateAnnotationsAsync,
|
|
createAnnotationsAsync,
|
|
changeFrameAsync,
|
|
switchNavigationBlocked as switchNavigationBlockedAction,
|
|
} from 'actions/annotation-actions';
|
|
import LabelSelector from 'components/label-selector/label-selector';
|
|
import CVATTooltip from 'components/common/cvat-tooltip';
|
|
import ApproximationAccuracy, {
|
|
thresholdFromAccuracy,
|
|
} from 'components/annotation-page/standard-workspace/controls-side-bar/approximation-accuracy';
|
|
import { ImageProcessing, OpenCVTracker, TrackerModel } from 'utils/opencv-wrapper/opencv-interfaces';
|
|
import { switchToolsBlockerState } from 'actions/settings-actions';
|
|
import withVisibilityHandling from './handle-popover-visibility';
|
|
|
|
interface Props {
|
|
labels: any[];
|
|
canvasInstance: Canvas;
|
|
jobInstance: any;
|
|
isActivated: boolean;
|
|
states: any[];
|
|
frame: number;
|
|
curZOrder: number;
|
|
defaultApproxPolyAccuracy: number;
|
|
frameData: any;
|
|
toolsBlockerState: ToolsBlockerState;
|
|
activeControl: ActiveControl;
|
|
}
|
|
|
|
interface DispatchToProps {
|
|
onInteractionStart(activeInteractor: OpenCVTool, activeLabelID: number): void;
|
|
updateAnnotations(statesToUpdate: any[]): void;
|
|
createAnnotations(sessionInstance: any, frame: number, statesToCreate: any[]): void;
|
|
fetchAnnotations(): void;
|
|
changeFrame(toFrame: number, fillBuffer?: boolean, frameStep?: number, forceUpdate?: boolean):void;
|
|
onSwitchToolsBlockerState(toolsBlockerState: ToolsBlockerState):void;
|
|
switchNavigationBlocked(navigationBlocked: boolean): void;
|
|
}
|
|
|
|
interface TrackedShape {
|
|
clientID: number;
|
|
shapePoints: number[];
|
|
trackerModel: TrackerModel;
|
|
}
|
|
|
|
interface State {
|
|
libraryInitialized: boolean;
|
|
initializationError: boolean;
|
|
initializationProgress: number;
|
|
activeLabelID: number;
|
|
approxPolyAccuracy: number;
|
|
activeImageModifiers: ImageModifier[];
|
|
mode: 'interaction' | 'tracking';
|
|
trackedShapes: TrackedShape[];
|
|
activeTracker: OpenCVTracker | null;
|
|
trackers: OpenCVTracker[]
|
|
}
|
|
|
|
interface ImageModifier {
|
|
modifier: ImageProcessing,
|
|
alias: string
|
|
}
|
|
|
|
const core = getCore();
|
|
const CustomPopover = withVisibilityHandling(Popover, 'opencv-control');
|
|
|
|
function mapStateToProps(state: CombinedState): Props {
|
|
const {
|
|
annotation: {
|
|
annotations: {
|
|
states,
|
|
zLayer: { cur: curZOrder },
|
|
},
|
|
job: { instance: jobInstance, labels },
|
|
canvas: { activeControl, instance: canvasInstance },
|
|
player: {
|
|
frame: { number: frame, data: frameData },
|
|
},
|
|
},
|
|
settings: {
|
|
workspace: { defaultApproxPolyAccuracy, toolsBlockerState },
|
|
},
|
|
} = state;
|
|
|
|
return {
|
|
isActivated: activeControl === ActiveControl.OPENCV_TOOLS,
|
|
activeControl,
|
|
canvasInstance: canvasInstance as Canvas,
|
|
defaultApproxPolyAccuracy,
|
|
jobInstance,
|
|
curZOrder,
|
|
labels,
|
|
states,
|
|
frame,
|
|
frameData,
|
|
toolsBlockerState,
|
|
};
|
|
}
|
|
|
|
const mapDispatchToProps = {
|
|
onInteractionStart: interactWithCanvas,
|
|
updateAnnotations: updateAnnotationsAsync,
|
|
fetchAnnotations: fetchAnnotationsAsync,
|
|
createAnnotations: createAnnotationsAsync,
|
|
changeFrame: changeFrameAsync,
|
|
onSwitchToolsBlockerState: switchToolsBlockerState,
|
|
switchNavigationBlocked: switchNavigationBlockedAction,
|
|
};
|
|
|
|
class OpenCVControlComponent extends React.PureComponent<Props & DispatchToProps, State> {
|
|
private activeTool: IntelligentScissors | null;
|
|
private latestPoints: number[];
|
|
private canvasForceUpdateWasEnabled: boolean;
|
|
|
|
public constructor(props: Props & DispatchToProps) {
|
|
super(props);
|
|
const { labels, defaultApproxPolyAccuracy } = props;
|
|
this.activeTool = null;
|
|
this.latestPoints = [];
|
|
this.canvasForceUpdateWasEnabled = false;
|
|
|
|
this.state = {
|
|
libraryInitialized: openCVWrapper.isInitialized,
|
|
initializationError: false,
|
|
initializationProgress: -1,
|
|
approxPolyAccuracy: defaultApproxPolyAccuracy,
|
|
activeLabelID: labels.length ? labels[0].id : null,
|
|
activeImageModifiers: [],
|
|
mode: 'interaction',
|
|
trackedShapes: [],
|
|
trackers: openCVWrapper.isInitialized ? Object.values(openCVWrapper.tracking) : [],
|
|
activeTracker: openCVWrapper.isInitialized ? Object.values(openCVWrapper.tracking)[0] : null,
|
|
};
|
|
}
|
|
|
|
public componentDidMount(): void {
|
|
const { canvasInstance } = this.props;
|
|
canvasInstance.html().addEventListener('canvas.interacted', this.interactionListener);
|
|
canvasInstance.html().addEventListener('canvas.setup', this.runImageModifier);
|
|
}
|
|
|
|
public componentDidUpdate(prevProps: Props, prevState: State): void {
|
|
const { approxPolyAccuracy } = this.state;
|
|
const {
|
|
isActivated, defaultApproxPolyAccuracy, canvasInstance, toolsBlockerState,
|
|
} = this.props;
|
|
|
|
if (!prevProps.isActivated && isActivated) {
|
|
// reset flags & states before using a tool
|
|
this.latestPoints = [];
|
|
this.setState({
|
|
approxPolyAccuracy: defaultApproxPolyAccuracy,
|
|
});
|
|
if (this.activeTool) {
|
|
this.activeTool.switchBlockMode(toolsBlockerState.algorithmsLocked);
|
|
this.activeTool.reset();
|
|
}
|
|
}
|
|
|
|
if (prevState.approxPolyAccuracy !== approxPolyAccuracy) {
|
|
if (isActivated) {
|
|
const approx = openCVWrapper.contours.approxPoly(
|
|
this.latestPoints,
|
|
thresholdFromAccuracy(approxPolyAccuracy),
|
|
);
|
|
canvasInstance.interact({
|
|
enabled: true,
|
|
intermediateShape: {
|
|
shapeType: ShapeType.POLYGON,
|
|
points: approx.flat(),
|
|
},
|
|
});
|
|
}
|
|
}
|
|
if (prevProps.toolsBlockerState.algorithmsLocked !== toolsBlockerState.algorithmsLocked &&
|
|
!!this.activeTool?.switchBlockMode) {
|
|
this.activeTool.switchBlockMode(toolsBlockerState.algorithmsLocked);
|
|
}
|
|
this.checkTrackedStates(prevProps);
|
|
}
|
|
|
|
public componentWillUnmount(): void {
|
|
const { canvasInstance } = this.props;
|
|
canvasInstance.html().removeEventListener('canvas.interacted', this.interactionListener);
|
|
canvasInstance.html().removeEventListener('canvas.setup', this.runImageModifier);
|
|
}
|
|
|
|
private interactionListener = async (e: Event): Promise<void> => {
|
|
const { mode } = this.state;
|
|
|
|
if (mode === 'interaction') {
|
|
await this.onInteraction(e);
|
|
}
|
|
|
|
if (mode === 'tracking') {
|
|
await this.onTracking(e);
|
|
}
|
|
};
|
|
|
|
private onInteraction = async (e: Event): Promise<void> => {
|
|
const { approxPolyAccuracy } = this.state;
|
|
const {
|
|
createAnnotations, isActivated, jobInstance, frame, labels, curZOrder, canvasInstance, toolsBlockerState,
|
|
} = this.props;
|
|
const { activeLabelID } = this.state;
|
|
if (!isActivated || !this.activeTool) {
|
|
return;
|
|
}
|
|
|
|
const {
|
|
shapesUpdated, isDone, threshold, shapes,
|
|
} = (e as CustomEvent).detail;
|
|
const pressedPoints = convertShapesForInteractor(shapes, 0).flat();
|
|
try {
|
|
if (shapesUpdated) {
|
|
this.latestPoints = await this.runCVAlgorithm(pressedPoints,
|
|
toolsBlockerState.algorithmsLocked ? 0 : threshold);
|
|
let points = [];
|
|
if (toolsBlockerState.algorithmsLocked && this.latestPoints.length > 2) {
|
|
// disable approximation for lastest two points to disable fickering
|
|
const [x, y] = this.latestPoints.slice(-2);
|
|
this.latestPoints.splice(this.latestPoints.length - 2, 2);
|
|
points = openCVWrapper.contours.approxPoly(
|
|
this.latestPoints,
|
|
thresholdFromAccuracy(approxPolyAccuracy),
|
|
false,
|
|
);
|
|
points.push([x, y]);
|
|
} else {
|
|
points = openCVWrapper.contours.approxPoly(
|
|
this.latestPoints,
|
|
thresholdFromAccuracy(approxPolyAccuracy),
|
|
false,
|
|
);
|
|
}
|
|
canvasInstance.interact({
|
|
enabled: true,
|
|
intermediateShape: {
|
|
shapeType: ShapeType.POLYGON,
|
|
points: points.flat(),
|
|
},
|
|
});
|
|
}
|
|
|
|
if (isDone) {
|
|
// need to recalculate without the latest sliding point
|
|
const finalPoints = await this.runCVAlgorithm(pressedPoints,
|
|
toolsBlockerState.algorithmsLocked ? 0 : threshold);
|
|
const finalObject = new core.classes.ObjectState({
|
|
frame,
|
|
objectType: ObjectType.SHAPE,
|
|
shapeType: ShapeType.POLYGON,
|
|
label: labels.filter((label: any) => label.id === activeLabelID)[0],
|
|
points: openCVWrapper.contours
|
|
.approxPoly(finalPoints, thresholdFromAccuracy(approxPolyAccuracy))
|
|
.flat(),
|
|
occluded: false,
|
|
zOrder: curZOrder,
|
|
});
|
|
createAnnotations(jobInstance, frame, [finalObject]);
|
|
}
|
|
} catch (error) {
|
|
notification.error({
|
|
description: error.toString(),
|
|
message: 'OpenCV.js processing error occured',
|
|
className: 'cvat-notification-notice-opencv-processing-error',
|
|
});
|
|
}
|
|
};
|
|
|
|
private onTracking = async (e: Event): Promise<void> => {
|
|
const {
|
|
isActivated, jobInstance, frame, curZOrder, fetchAnnotations,
|
|
} = this.props;
|
|
|
|
if (!isActivated) {
|
|
return;
|
|
}
|
|
|
|
const { activeLabelID, trackedShapes, activeTracker } = this.state;
|
|
const [label] = jobInstance.labels.filter((_label: any): boolean => _label.id === activeLabelID);
|
|
|
|
const { isDone, shapesUpdated } = (e as CustomEvent).detail;
|
|
if (!isDone || !shapesUpdated || !activeTracker) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const { points } = (e as CustomEvent).detail.shapes[0];
|
|
const imageData = this.getCanvasImageData();
|
|
const trackerModel = activeTracker.model();
|
|
trackerModel.init(imageData, points);
|
|
const state = new core.classes.ObjectState({
|
|
shapeType: ShapeType.RECTANGLE,
|
|
objectType: ObjectType.TRACK,
|
|
zOrder: curZOrder,
|
|
label,
|
|
points,
|
|
frame,
|
|
occluded: false,
|
|
attributes: {},
|
|
descriptions: [`Trackable (${activeTracker.name})`],
|
|
});
|
|
const [clientID] = await jobInstance.annotations.put([state]);
|
|
this.setState({
|
|
trackedShapes: [
|
|
...trackedShapes,
|
|
{
|
|
clientID,
|
|
trackerModel,
|
|
shapePoints: points,
|
|
},
|
|
],
|
|
});
|
|
|
|
// update annotations on a canvas
|
|
fetchAnnotations();
|
|
} catch (err) {
|
|
notification.error({
|
|
description: err.toString(),
|
|
message: 'Tracking error occured',
|
|
});
|
|
}
|
|
};
|
|
|
|
private getCanvasImageData = ():ImageData => {
|
|
const canvas: HTMLCanvasElement | null = window.document.getElementById('cvat_canvas_background') as
|
|
| HTMLCanvasElement
|
|
| null;
|
|
if (!canvas) {
|
|
throw new Error('Element #cvat_canvas_background was not found');
|
|
}
|
|
const { width, height } = canvas;
|
|
const context = canvas.getContext('2d');
|
|
if (!context) {
|
|
throw new Error('Canvas context is empty');
|
|
}
|
|
return context.getImageData(0, 0, width, height);
|
|
};
|
|
|
|
private onChangeToolsBlockerState = (event:string):void => {
|
|
const {
|
|
isActivated, toolsBlockerState, onSwitchToolsBlockerState, canvasInstance,
|
|
} = this.props;
|
|
if (isActivated && event === 'keyup') {
|
|
onSwitchToolsBlockerState({ algorithmsLocked: !toolsBlockerState.algorithmsLocked });
|
|
canvasInstance.interact({
|
|
enabled: true,
|
|
crosshair: toolsBlockerState.algorithmsLocked,
|
|
enableThreshold: toolsBlockerState.algorithmsLocked,
|
|
onChangeToolsBlockerState: this.onChangeToolsBlockerState,
|
|
});
|
|
}
|
|
};
|
|
|
|
private runImageModifier = async ():Promise<void> => {
|
|
const { activeImageModifiers } = this.state;
|
|
const {
|
|
frameData, states, curZOrder, canvasInstance, frame,
|
|
} = this.props;
|
|
|
|
try {
|
|
if (activeImageModifiers.length !== 0 && activeImageModifiers[0].modifier.currentProcessedImage !== frame) {
|
|
this.enableCanvasForceUpdate();
|
|
const imageData = this.getCanvasImageData();
|
|
const newImageData = activeImageModifiers
|
|
.reduce((oldImageData, activeImageModifier) => activeImageModifier
|
|
.modifier.processImage(oldImageData, frame), imageData);
|
|
const imageBitmap = await createImageBitmap(newImageData);
|
|
frameData.imageData = imageBitmap;
|
|
canvasInstance.setup(frameData, states, curZOrder);
|
|
}
|
|
} catch (error) {
|
|
notification.error({
|
|
description: error.toString(),
|
|
message: 'OpenCV.js processing error occured',
|
|
className: 'cvat-notification-notice-opencv-processing-error',
|
|
});
|
|
} finally {
|
|
this.disableCanvasForceUpdate();
|
|
}
|
|
};
|
|
|
|
private applyTracking = (imageData: ImageData, shape: TrackedShape,
|
|
objectState: any): Promise<void> => new Promise((resolve, reject) => {
|
|
setTimeout(() => {
|
|
try {
|
|
const stateIsRelevant =
|
|
objectState.points.length === shape.shapePoints.length &&
|
|
objectState.points.every(
|
|
(coord: number, index: number) => coord === shape.shapePoints[index],
|
|
);
|
|
if (!stateIsRelevant) {
|
|
shape.trackerModel.reinit(objectState.points);
|
|
shape.shapePoints = objectState.points;
|
|
}
|
|
const { updated, points } = shape.trackerModel.update(imageData);
|
|
if (updated) {
|
|
objectState.points = points;
|
|
objectState.save().then(() => {
|
|
shape.shapePoints = points;
|
|
}).catch((error) => {
|
|
reject(error);
|
|
});
|
|
}
|
|
resolve();
|
|
} catch (error) {
|
|
reject(error);
|
|
}
|
|
});
|
|
});
|
|
|
|
private setActiveTracker = (value: string): void => {
|
|
const { trackers } = this.state;
|
|
this.setState({
|
|
activeTracker: trackers.filter((tracker: OpenCVTracker) => tracker.name === value)[0],
|
|
});
|
|
};
|
|
|
|
private checkTrackedStates(prevProps: Props): void {
|
|
const {
|
|
frame,
|
|
states: objectStates,
|
|
fetchAnnotations,
|
|
switchNavigationBlocked,
|
|
} = this.props;
|
|
const { trackedShapes } = this.state;
|
|
if (prevProps.frame !== frame && trackedShapes.length) {
|
|
type AccumulatorType = {
|
|
[index: string]: TrackedShape[];
|
|
};
|
|
const trackingData = trackedShapes.reduce<AccumulatorType>(
|
|
(acc: AccumulatorType, trackedShape: TrackedShape): AccumulatorType => {
|
|
const [clientState] = objectStates.filter(
|
|
(_state: any): boolean => _state.clientID === trackedShape.clientID,
|
|
);
|
|
if (
|
|
!clientState ||
|
|
clientState.keyframes.prev !== frame - 1 ||
|
|
clientState.keyframes.last >= frame
|
|
) {
|
|
return acc;
|
|
}
|
|
|
|
const { name: trackerName } = trackedShape.trackerModel;
|
|
if (!acc[trackerName]) {
|
|
acc[trackerName] = [];
|
|
}
|
|
acc[trackerName].push(trackedShape);
|
|
return acc;
|
|
}, {},
|
|
);
|
|
|
|
if (Object.keys(trackingData).length === 0) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
switchNavigationBlocked(true);
|
|
for (const trackerID of Object.keys(trackingData)) {
|
|
const numOfObjects = trackingData[trackerID].length;
|
|
const hideMessage = message.loading(
|
|
`${trackerID}: ${numOfObjects} ${
|
|
numOfObjects > 1 ? 'objects are' : 'object is'
|
|
} being tracked..`,
|
|
0,
|
|
);
|
|
const imageData = this.getCanvasImageData();
|
|
for (const shape of trackingData[trackerID]) {
|
|
const [objectState] = objectStates.filter(
|
|
(_state: any): boolean => _state.clientID === shape.clientID,
|
|
);
|
|
|
|
this.applyTracking(imageData, shape, objectState)
|
|
.catch((error) => {
|
|
notification.error({
|
|
message: 'Tracking error',
|
|
description: error.toString(),
|
|
});
|
|
});
|
|
}
|
|
setTimeout(() => {
|
|
if (hideMessage) hideMessage();
|
|
});
|
|
}
|
|
} finally {
|
|
setTimeout(() => {
|
|
fetchAnnotations();
|
|
switchNavigationBlocked(false);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
private async runCVAlgorithm(pressedPoints: number[], threshold: number): Promise<number[]> {
|
|
// Getting image data
|
|
const canvas: HTMLCanvasElement | undefined = window.document.getElementById('cvat_canvas_background') as
|
|
| HTMLCanvasElement
|
|
| undefined;
|
|
if (!canvas) {
|
|
throw new Error('Element #cvat_canvas_background was not found');
|
|
}
|
|
if (!this.activeTool || pressedPoints.length === 0) return [];
|
|
|
|
const { width, height } = canvas;
|
|
const context = canvas.getContext('2d');
|
|
if (!context) {
|
|
throw new Error('Canvas context is empty');
|
|
}
|
|
let imageData;
|
|
const [x, y] = pressedPoints.slice(-2);
|
|
const startX = Math.round(Math.max(0, x - threshold));
|
|
const startY = Math.round(Math.max(0, y - threshold));
|
|
if (threshold !== 0) {
|
|
const segmentWidth = Math.min(2 * threshold, width - startX);
|
|
const segmentHeight = Math.min(2 * threshold, height - startY);
|
|
imageData = context.getImageData(startX, startY, segmentWidth, segmentHeight);
|
|
} else {
|
|
imageData = context.getImageData(0, 0, width, height);
|
|
}
|
|
// Handling via OpenCV.js
|
|
const points = await this.activeTool.run(pressedPoints, imageData, startX, startY);
|
|
return points;
|
|
}
|
|
|
|
private imageModifier(alias: string): ImageProcessing | null {
|
|
const { activeImageModifiers } = this.state;
|
|
return activeImageModifiers.find((imageModifier) => imageModifier.alias === alias)?.modifier || null;
|
|
}
|
|
|
|
private disableImageModifier(alias: string):void {
|
|
const { activeImageModifiers } = this.state;
|
|
const index = activeImageModifiers.findIndex((imageModifier) => imageModifier.alias === alias);
|
|
if (index !== -1) {
|
|
activeImageModifiers.splice(index, 1);
|
|
this.setState({
|
|
activeImageModifiers: [...activeImageModifiers],
|
|
});
|
|
}
|
|
}
|
|
|
|
private enableImageModifier(modifier: ImageProcessing, alias: string): void {
|
|
this.setState((prev: State) => ({
|
|
...prev,
|
|
activeImageModifiers: [...prev.activeImageModifiers, { modifier, alias }],
|
|
}), () => {
|
|
this.runImageModifier();
|
|
});
|
|
}
|
|
|
|
private enableCanvasForceUpdate():void {
|
|
const { canvasInstance } = this.props;
|
|
canvasInstance.configure({ forceFrameUpdate: true });
|
|
this.canvasForceUpdateWasEnabled = true;
|
|
}
|
|
|
|
private disableCanvasForceUpdate():void {
|
|
if (this.canvasForceUpdateWasEnabled) {
|
|
const { canvasInstance } = this.props;
|
|
canvasInstance.configure({ forceFrameUpdate: false });
|
|
this.canvasForceUpdateWasEnabled = false;
|
|
}
|
|
}
|
|
|
|
private renderDrawingContent(): JSX.Element {
|
|
const { activeLabelID } = this.state;
|
|
const { labels, canvasInstance, onInteractionStart } = this.props;
|
|
|
|
return (
|
|
<>
|
|
<Row justify='center'>
|
|
<Col span={24}>
|
|
<LabelSelector
|
|
style={{ width: '100%' }}
|
|
labels={labels}
|
|
value={activeLabelID}
|
|
onChange={(label: any) => this.setState({ activeLabelID: label.id })}
|
|
/>
|
|
</Col>
|
|
</Row>
|
|
<Row justify='start' className='cvat-opencv-drawing-tools'>
|
|
<Col>
|
|
<CVATTooltip title='Intelligent scissors' className='cvat-opencv-drawing-tool'>
|
|
<Button
|
|
onClick={() => {
|
|
this.setState({ mode: 'interaction' });
|
|
this.activeTool = openCVWrapper.segmentation
|
|
.intelligentScissorsFactory(this.onChangeToolsBlockerState);
|
|
canvasInstance.cancel();
|
|
onInteractionStart(this.activeTool, activeLabelID);
|
|
canvasInstance.interact({
|
|
enabled: true,
|
|
...this.activeTool.params.canvas,
|
|
});
|
|
}}
|
|
>
|
|
<ScissorOutlined />
|
|
</Button>
|
|
</CVATTooltip>
|
|
</Col>
|
|
</Row>
|
|
</>
|
|
);
|
|
}
|
|
|
|
private renderImageContent():JSX.Element {
|
|
return (
|
|
<Row justify='start'>
|
|
<Col>
|
|
<CVATTooltip title='Histogram equalization' className='cvat-opencv-image-tool'>
|
|
<Button
|
|
className={this.imageModifier('histogram') ? 'cvat-opencv-image-tool-active' : ''}
|
|
onClick={(e: React.MouseEvent<HTMLElement>) => {
|
|
const modifier = this.imageModifier('histogram');
|
|
if (!modifier) {
|
|
this.enableImageModifier(openCVWrapper.imgproc.hist(), 'histogram');
|
|
} else {
|
|
const button = e.target as HTMLElement;
|
|
button.blur();
|
|
this.disableImageModifier('histogram');
|
|
const { changeFrame } = this.props;
|
|
const { frame } = this.props;
|
|
this.enableCanvasForceUpdate();
|
|
changeFrame(frame, false, 1, true);
|
|
}
|
|
}}
|
|
>
|
|
<AreaChartOutlined />
|
|
</Button>
|
|
</CVATTooltip>
|
|
</Col>
|
|
</Row>
|
|
);
|
|
}
|
|
|
|
private renderTrackingContent(): JSX.Element {
|
|
const { activeLabelID, trackers, activeTracker } = this.state;
|
|
const {
|
|
labels, canvasInstance, onInteractionStart, frame, jobInstance,
|
|
} = this.props;
|
|
if (!trackers.length) {
|
|
return (
|
|
<Row justify='center' align='middle' className='cvat-opencv-tracker-content'>
|
|
<Col>
|
|
<Text type='warning' className='cvat-text-color'>
|
|
No available trackers found
|
|
</Text>
|
|
</Col>
|
|
</Row>
|
|
);
|
|
}
|
|
return (
|
|
<>
|
|
<Row justify='start'>
|
|
<Col>
|
|
<Text className='cvat-text-color'>Label</Text>
|
|
</Col>
|
|
</Row>
|
|
<Row justify='center'>
|
|
<Col span={24}>
|
|
<LabelSelector
|
|
className='cvat-opencv-tracker-select'
|
|
labels={labels}
|
|
value={activeLabelID}
|
|
onChange={(value: any) => this.setState({ activeLabelID: value.id })}
|
|
/>
|
|
</Col>
|
|
</Row>
|
|
<Row justify='start'>
|
|
<Col>
|
|
<Text className='cvat-text-color'>Tracker</Text>
|
|
</Col>
|
|
</Row>
|
|
<Row align='middle' justify='center'>
|
|
<Col span={24}>
|
|
<Select
|
|
className='cvat-opencv-tracker-select'
|
|
defaultValue={trackers[0].name}
|
|
onChange={this.setActiveTracker}
|
|
>
|
|
{trackers.map(
|
|
(tracker: OpenCVTracker): JSX.Element => (
|
|
<Select.Option value={tracker.name} title={tracker.description} key={tracker.name}>
|
|
{tracker.name}
|
|
</Select.Option>
|
|
),
|
|
)}
|
|
</Select>
|
|
</Col>
|
|
</Row>
|
|
<Row align='middle' justify='end'>
|
|
<Col>
|
|
<Button
|
|
type='primary'
|
|
className='cvat-tools-track-button'
|
|
disabled={!activeTracker || frame === jobInstance.stopFrame}
|
|
onClick={() => {
|
|
this.setState({ mode: 'tracking' });
|
|
|
|
if (activeTracker) {
|
|
canvasInstance.cancel();
|
|
canvasInstance.interact({
|
|
shapeType: 'rectangle',
|
|
enabled: true,
|
|
});
|
|
|
|
onInteractionStart(activeTracker as OpenCVTracker, activeLabelID);
|
|
const { onSwitchToolsBlockerState } = this.props;
|
|
onSwitchToolsBlockerState({ buttonVisible: false });
|
|
}
|
|
}}
|
|
>
|
|
Track
|
|
</Button>
|
|
</Col>
|
|
</Row>
|
|
</>
|
|
);
|
|
}
|
|
|
|
private renderContent(): JSX.Element {
|
|
const { libraryInitialized, initializationProgress, initializationError } = this.state;
|
|
|
|
return (
|
|
<div className='cvat-opencv-control-popover-content'>
|
|
<Row justify='start'>
|
|
<Col>
|
|
<Text className='cvat-text-color' strong>
|
|
OpenCV
|
|
</Text>
|
|
</Col>
|
|
</Row>
|
|
{libraryInitialized ? (
|
|
<Tabs tabBarGutter={8}>
|
|
<Tabs.TabPane key='drawing' tab='Drawing' className='cvat-opencv-control-tabpane'>
|
|
{this.renderDrawingContent()}
|
|
</Tabs.TabPane>
|
|
<Tabs.TabPane key='image' tab='Image' className='cvat-opencv-control-tabpane'>
|
|
{this.renderImageContent()}
|
|
</Tabs.TabPane>
|
|
<Tabs.TabPane key='tracking' tab='Tracking' className='cvat-opencv-control-tabpane'>
|
|
{this.renderTrackingContent()}
|
|
</Tabs.TabPane>
|
|
</Tabs>
|
|
) : (
|
|
<>
|
|
<Row justify='start' align='middle'>
|
|
<Col span={initializationProgress >= 0 ? 17 : 24}>
|
|
<Button
|
|
disabled={initializationProgress !== -1}
|
|
className='cvat-opencv-initialization-button'
|
|
onClick={async () => {
|
|
try {
|
|
this.setState({
|
|
initializationError: false,
|
|
initializationProgress: 0,
|
|
});
|
|
await openCVWrapper.initialize((progress: number) => {
|
|
this.setState({ initializationProgress: progress });
|
|
});
|
|
const trackers = Object.values(openCVWrapper.tracking);
|
|
this.setState({
|
|
libraryInitialized: true,
|
|
activeTracker: trackers[0],
|
|
trackers,
|
|
});
|
|
} catch (error) {
|
|
notification.error({
|
|
description: error.toString(),
|
|
message: 'Could not initialize OpenCV library',
|
|
});
|
|
this.setState({
|
|
initializationError: true,
|
|
initializationProgress: -1,
|
|
});
|
|
}
|
|
}}
|
|
>
|
|
Load OpenCV
|
|
</Button>
|
|
</Col>
|
|
{initializationProgress >= 0 && (
|
|
<Col span={6} offset={1}>
|
|
<Progress
|
|
width={8 * 5}
|
|
percent={initializationProgress}
|
|
type='circle'
|
|
status={initializationError ? 'exception' : undefined}
|
|
/>
|
|
</Col>
|
|
)}
|
|
</Row>
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
public render(): JSX.Element {
|
|
const { isActivated, canvasInstance, labels } = this.props;
|
|
const { libraryInitialized, approxPolyAccuracy, mode } = this.state;
|
|
const dynamcPopoverPros = isActivated ?
|
|
{
|
|
overlayStyle: {
|
|
display: 'none',
|
|
},
|
|
} :
|
|
{};
|
|
|
|
const dynamicIconProps = isActivated ?
|
|
{
|
|
className: 'cvat-opencv-control cvat-active-canvas-control',
|
|
onClick: (): void => {
|
|
canvasInstance.interact({ enabled: false });
|
|
},
|
|
} :
|
|
{
|
|
className: 'cvat-tools-control',
|
|
};
|
|
|
|
return !labels.length ? (
|
|
<Icon className='cvat-opencv-control cvat-disabled-canvas-control' component={OpenCVIcon} />
|
|
) : (
|
|
<>
|
|
<CustomPopover
|
|
{...dynamcPopoverPros}
|
|
placement='right'
|
|
overlayClassName='cvat-opencv-control-popover'
|
|
content={this.renderContent()}
|
|
afterVisibleChange={() => {
|
|
if (libraryInitialized !== openCVWrapper.isInitialized) {
|
|
this.setState({
|
|
libraryInitialized: openCVWrapper.isInitialized,
|
|
});
|
|
}
|
|
}}
|
|
>
|
|
<Icon {...dynamicIconProps} component={OpenCVIcon} />
|
|
</CustomPopover>
|
|
{isActivated && mode !== 'tracking' ? (
|
|
<ApproximationAccuracy
|
|
approxPolyAccuracy={approxPolyAccuracy}
|
|
onChange={(value: number) => {
|
|
this.setState({ approxPolyAccuracy: value });
|
|
}}
|
|
/>
|
|
) : null}
|
|
</>
|
|
);
|
|
}
|
|
}
|
|
|
|
export default connect(mapStateToProps, mapDispatchToProps)(OpenCVControlComponent);
|