Skip to content

Commit

Permalink
Semi-automatic tools enhancements (Client-side points minimizer) (#3450)
Browse files Browse the repository at this point in the history
* First stage for points minimizer

* Fixed issue with correct opencv initialization status

* Displaying points during interaction

* Added releasing memory

* Initial version for on-the-fly optimization

* Redesigned accuracy

* Updated version & changelog

* Fixed opencv scissors

* Clean up some intermediate state

* Fixed scss

* Redesigned slider a bit

* Added errored shape

* Keep slider hidden while didn't recieve first points

* Adjusted settings slider

* Updated label

* A couple of fixes for trackers & detectors

* Updated default value
  • Loading branch information
Boris Sekachev authored Jul 28, 2021
1 parent 7e7a5b9 commit 0ea4897
Show file tree
Hide file tree
Showing 15 changed files with 423 additions and 49 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add a tutorial for semi-automatic/automatic annotation (<https://github.com/openvinotoolkit/cvat/pull/3124>)
- Explicit "Done" button when drawing any polyshapes (<https://github.com/openvinotoolkit/cvat/pull/3417>)
- Histogram equalization with OpenCV javascript (<https://github.com/openvinotoolkit/cvat/pull/3447>)
- Client-side polyshapes approximation when using semi-automatic interactors & scissors (<https://github.com/openvinotoolkit/cvat/pull/3450>)

### Changed

Expand Down
9 changes: 7 additions & 2 deletions cvat-canvas/src/scss/canvas.scss
Original file line number Diff line number Diff line change
Expand Up @@ -161,11 +161,16 @@ polyline.cvat_canvas_shape_splitting {

.cvat_canvas_removable_interaction_point {
cursor:
url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAxMCAxMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEgMUw5IDlNMSA5TDkgMSIgc3Ryb2tlPSJibGFjayIvPgo8L3N2Zz4K')
10 10,
url(
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAxMCAxMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEgMUw5IDlNMSA5TDkgMSIgc3Ryb2tlPSJibGFjayIvPgo8L3N2Zz4K'
) 10 10,
auto;
}

.cvat_canvas_interact_intermediate_shape_point {
pointer-events: none;
}

.svg_select_boundingRect {
opacity: 0;
pointer-events: none;
Expand Down
47 changes: 47 additions & 0 deletions cvat-canvas/src/typescript/interactionHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ export class InteractionHandlerImpl implements InteractionHandler {

private release(): void {
if (this.drawnIntermediateShape) {
this.selectize(false, this.drawnIntermediateShape);
this.drawnIntermediateShape.remove();
this.drawnIntermediateShape = null;
}
Expand Down Expand Up @@ -270,28 +271,65 @@ export class InteractionHandlerImpl implements InteractionHandler {
private updateIntermediateShape(): void {
const { intermediateShape, geometry } = this;
if (this.drawnIntermediateShape) {
this.selectize(false, this.drawnIntermediateShape);
this.drawnIntermediateShape.remove();
}

if (!intermediateShape) return;
const { shapeType, points } = intermediateShape;
if (shapeType === 'polygon') {
const erroredShape = shapeType === 'polygon' && points.length < 3 * 2;
this.drawnIntermediateShape = this.canvas
.polygon(stringifyPoints(translateToCanvas(geometry.offset, points)))
.attr({
'color-rendering': 'optimizeQuality',
'shape-rendering': 'geometricprecision',
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
stroke: erroredShape ? 'red' : 'black',
fill: 'none',
})
.addClass('cvat_canvas_interact_intermediate_shape');
this.selectize(true, this.drawnIntermediateShape, erroredShape);
} else {
throw new Error(
`Shape type "${shapeType}" was not implemented at interactionHandler::updateIntermediateShape`,
);
}
}

private selectize(value: boolean, shape: SVG.Element, erroredShape = false): void {
const self = this;

if (value) {
(shape as any).selectize(value, {
deepSelect: true,
pointSize: consts.BASE_POINT_SIZE / self.geometry.scale,
rotationPoint: false,
classPoints: 'cvat_canvas_interact_intermediate_shape_point',
pointType(cx: number, cy: number): SVG.Circle {
return this.nested
.circle(this.options.pointSize)
.stroke(erroredShape ? 'red' : 'black')
.fill('black')
.center(cx, cy)
.attr({
'fill-opacity': 1,
'stroke-width': consts.POINTS_STROKE_WIDTH / self.geometry.scale,
});
},
});
} else {
(shape as any).selectize(false, {
deepSelect: true,
});
}

const handler = shape.remember('_selectHandler');
if (handler && handler.nested) {
handler.nested.fill(shape.attr('fill'));
}
}

public constructor(
onInteraction: (
shapes: InteractionResult[] | null,
Expand Down Expand Up @@ -398,6 +436,15 @@ export class InteractionHandlerImpl implements InteractionHandler {
shape.attr('stroke-width', consts.BASE_STROKE_WIDTH / this.geometry.scale);
}
}

for (const element of window.document.getElementsByClassName('cvat_canvas_interact_intermediate_shape_point')) {
element.setAttribute('stroke-width', `${consts.POINTS_STROKE_WIDTH / (2 * this.geometry.scale)}`);
element.setAttribute('r', `${consts.BASE_POINT_SIZE / this.geometry.scale}`);
}

if (this.drawnIntermediateShape) {
this.drawnIntermediateShape.stroke({ width: consts.BASE_STROKE_WIDTH / this.geometry.scale });
}
}

public interact(interactionData: InteractionData): void {
Expand Down
10 changes: 10 additions & 0 deletions cvat-ui/src/actions/settings-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export enum SettingsActionTypes {
SWITCH_AUTO_SAVE = 'SWITCH_AUTO_SAVE',
CHANGE_AUTO_SAVE_INTERVAL = 'CHANGE_AUTO_SAVE_INTERVAL',
CHANGE_AAM_ZOOM_MARGIN = 'CHANGE_AAM_ZOOM_MARGIN',
CHANGE_DEFAULT_APPROX_POLY_THRESHOLD = 'CHANGE_DEFAULT_APPROX_POLY_THRESHOLD',
SWITCH_AUTOMATIC_BORDERING = 'SWITCH_AUTOMATIC_BORDERING',
SWITCH_INTELLIGENT_POLYGON_CROP = 'SWITCH_INTELLIGENT_POLYGON_CROP',
SWITCH_SHOWNIG_INTERPOLATED_TRACKS = 'SWITCH_SHOWNIG_INTERPOLATED_TRACKS',
Expand Down Expand Up @@ -270,6 +271,15 @@ export function switchSettingsDialog(show?: boolean): AnyAction {
};
}

export function changeDefaultApproxPolyAccuracy(approxPolyAccuracy: number): AnyAction {
return {
type: SettingsActionTypes.CHANGE_DEFAULT_APPROX_POLY_THRESHOLD,
payload: {
approxPolyAccuracy,
},
};
}

export function setSettings(settings: Partial<SettingsState>): AnyAction {
return {
type: SettingsActionTypes.SET_SETTINGS,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright (C) 2021 Intel Corporation
//
// SPDX-License-Identifier: MIT

import React, { CSSProperties } from 'react';
import ReactDOM from 'react-dom';
import Text from 'antd/lib/typography/Text';
import Slider from 'antd/lib/slider';
import { Col, Row } from 'antd/lib/grid';

interface Props {
approxPolyAccuracy: number;
onChange(value: number): void;
}

export const MAX_ACCURACY = 13;

export const marks: Record<number, { style: CSSProperties; label: JSX.Element }> = {};
marks[0] = {
style: {
color: '#1890ff',
},
label: <strong>less</strong>,
};
marks[MAX_ACCURACY] = {
style: {
color: '#61c200',
},
label: <strong>more</strong>,
};

export function thresholdFromAccuracy(approxPolyAccuracy: number): number {
const approxPolyMaxDistance = MAX_ACCURACY - approxPolyAccuracy;
let threshold = 0;
if (approxPolyMaxDistance > 0) {
if (approxPolyMaxDistance <= 8) {
// −2.75x+7y+1=0 linear made from two points (1; 0.25) and (8; 3)
threshold = (2.75 * approxPolyMaxDistance - 1) / 7;
} else {
// 4 for 9, 8 for 10, 16 for 11, 32 for 12, 64 for 13
threshold = 2 ** (approxPolyMaxDistance - 7);
}
}

return threshold;
}

function ApproximationAccuracy(props: Props): React.ReactPortal | null {
const { approxPolyAccuracy, onChange } = props;
const target = window.document.getElementsByClassName('cvat-canvas-container')[0];

return target ?
ReactDOM.createPortal(
<Row align='middle' className='cvat-approx-poly-threshold-wrapper'>
<Col span={5}>
<Text>Points: </Text>
</Col>
<Col offset={1} span={18}>
<Slider
value={approxPolyAccuracy}
min={0}
max={MAX_ACCURACY}
step={1}
dots
tooltipVisible={false}
onChange={onChange}
marks={marks}
/>
</Col>
</Row>,
target,
) :
null;
}

export default React.memo(ApproximationAccuracy);
Loading

0 comments on commit 0ea4897

Please sign in to comment.