Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Semi-automatic tools enhancements (Client-side points minimizer) #3450

Merged
merged 18 commits into from
Jul 28, 2021
Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Filter `is_active` for user list (<https://github.com/openvinotoolkit/cvat/pull/3235>)
- Ability to export/import tasks (<https://github.com/openvinotoolkit/cvat/pull/3056>)
- Explicit "Done" button when drawing any polyshapes (<https://github.com/openvinotoolkit/cvat/pull/3417>)
- 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
2 changes: 1 addition & 1 deletion cvat-ui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion cvat-ui/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cvat-ui",
"version": "1.21.0",
"version": "1.21.1",
"description": "CVAT single-page application",
"main": "src/index.tsx",
"scripts": {
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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the threshold be negative?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If approxPolyAccuracy == 13, it will be -1/7

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, approxPolyMaxDistance is an integer >= 1 and <= 8 in this piece of code.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if approxPolyAccuracy == 13, then approxPolyMaxDistance == 0 and the condition in line 35 is false

} 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