Merge remote-tracking branch 'origin/develop' into release-1.0.0
@ -0,0 +1,39 @@
|
||||
# https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners
|
||||
|
||||
# These owners will be the default owners for everything in
|
||||
# the repo. Unless a later match takes precedence, they will
|
||||
# be requested for review when someone opens a pull request.
|
||||
* @nmanovic
|
||||
|
||||
# Order is important; the last matching pattern takes the most
|
||||
# precedence. When someone opens a pull request that only
|
||||
# modifies components below, only the list of owners and not
|
||||
# the global owner(s) will be requested for a review.
|
||||
|
||||
# Component: Server
|
||||
/cvat/ @nmanovic
|
||||
|
||||
# Component: CVAT UI
|
||||
/cvat-ui/ @bsekachev
|
||||
/cvat-data/ @azhavoro
|
||||
/cvat-canvas/ @bsekachev
|
||||
/cvat-core/ @bsekachev
|
||||
|
||||
# Component: Datumaro
|
||||
/datumaro/ @zhiltsov-max
|
||||
/cvat/apps/dataset_manager/ @zhiltsov-max
|
||||
|
||||
# Advanced components (e.g. OpenVINO)
|
||||
/components/ @azhavoro
|
||||
|
||||
# Infrastructure
|
||||
Dockerfile* @azhavoro
|
||||
docker-compose* @azhavoro
|
||||
.* @azhavoro
|
||||
*.conf @azhavoro
|
||||
*.sh @azhavoro
|
||||
/cvat_proxy/ @azhavoro
|
||||
/tests/ @azhavoro
|
||||
/utils/ @azhavoro
|
||||
/LICENSE @nmanovic
|
||||
/.github/ @nmanovic
|
||||
@ -0,0 +1,53 @@
|
||||
<!---
|
||||
Copyright (C) 2020 Intel Corporation
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
-->
|
||||
|
||||
### My actions before raising this issue
|
||||
- [ ] Read/searched [the docs](https://github.com/opencv/cvat/tree/master#documentation)
|
||||
- [ ] Searched [past issues](/issues)
|
||||
|
||||
<!--- Provide a general summary of the issue in the Title above -->
|
||||
|
||||
### Expected Behaviour
|
||||
<!--- If you're describing a bug, tell us what should happen. If you're
|
||||
suggesting a change/improvement, tell us how it should work -->
|
||||
|
||||
### Current Behaviour
|
||||
<!--- If describing a bug, tell us what happens instead of the expected
|
||||
behavior. If suggesting a change/improvement, explain the difference from
|
||||
current behavior -->
|
||||
|
||||
### Possible Solution
|
||||
<!--- Not obligatory, but suggest a fix/reason for the bug, or ideas how
|
||||
to implement the addition or change -->
|
||||
|
||||
### Steps to Reproduce (for bugs)
|
||||
<!--- Provide a link to a live example, or an unambiguous set of steps to
|
||||
reproduce this bug. Include code to reproduce, if relevant -->
|
||||
1.
|
||||
1.
|
||||
1.
|
||||
1.
|
||||
|
||||
### Context
|
||||
<!--- How has this issue affected you? What are you trying to accomplish?
|
||||
Providing context helps us come up with a solution that is most useful in
|
||||
the real world -->
|
||||
|
||||
### Your Environment
|
||||
<!--- Include as many relevant details about the environment you experienced
|
||||
the bug in -->
|
||||
- Git hash commit (`git log -1`):
|
||||
- Docker version `docker version` (e.g. Docker 17.0.05):
|
||||
- Are you using Docker Swarm or Kubernetes?
|
||||
- Operating System and version (e.g. Linux, Windows, MacOS):
|
||||
- Code example or link to GitHub repo or gist to reproduce problem:
|
||||
- Other diagnostic information / logs:
|
||||
<details>
|
||||
<summary>Logs from `cvat` container</summary>
|
||||
</details>
|
||||
|
||||
### Next steps
|
||||
You may [join our Gitter](https://gitter.im/opencv-cvat/public) channel for community support.
|
||||
@ -0,0 +1,46 @@
|
||||
<!---
|
||||
Copyright (C) 2020 Intel Corporation
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
-->
|
||||
|
||||
<!-- Provide a general summary of your changes in the Title above -->
|
||||
|
||||
### Motivation and context
|
||||
<!-- Why is this change required? What problem does it solve? If it fixes an open
|
||||
issue, please link to the issue here. Describe your changes in detail, add
|
||||
screenshots. -->
|
||||
|
||||
### How has this been tested?
|
||||
<!-- Please describe in detail how you tested your changes.
|
||||
Include details of your testing environment, and the tests you ran to
|
||||
see how your change affects other areas of the code, etc. -->
|
||||
|
||||
### Checklist
|
||||
<!-- Go over all the following points, and put an `x` in all the boxes that apply.
|
||||
If an item isn't applicable by a reason then ~~explicitly strikethrough~~ the whole
|
||||
line. If you don't do that github will show incorrect process for the pull request.
|
||||
If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
|
||||
|
||||
- [ ] I have raised an issue to propose this change ([required](https://github.com/opencv/cvat/issues))
|
||||
- [ ] My issue has received approval from the maintainers
|
||||
- [ ] I've read the [CONTRIBUTION](https://github.com/opencv/cvat/blob/develop/CONTRIBUTING.md) guide
|
||||
- [ ] I have added description of my changes into [CHANGELOG](https://github.com/opencv/cvat/blob/develop/CHANGELOG.md) file
|
||||
- [ ] I have updated the [documentation](
|
||||
https://github.com/opencv/cvat/blob/develop/README.md#documentation) accordingly
|
||||
- [ ] I have added tests to cover my changes
|
||||
- [ ] I have linked related issues ([read github docs](
|
||||
https://help.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword))
|
||||
|
||||
### License
|
||||
|
||||
- [ ] I submit _my code changes_ under the same [MIT License](
|
||||
https://github.com/opencv/cvat/blob/develop/LICENSE) that covers the project.
|
||||
Feel free to contact the maintainers if that's a concern.
|
||||
- [ ] I have updated the license header for each file (see an example below)
|
||||
|
||||
```python
|
||||
# Copyright (C) 2020 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
```
|
||||
@ -1,38 +0,0 @@
|
||||
# Core support team
|
||||
- **[Nikita Manovich](https://github.com/nmanovic)**
|
||||
|
||||
* Project lead
|
||||
* Developer
|
||||
* Author and maintainer
|
||||
|
||||
- **[Boris Sekachev](https://github.com/bsekachev)**
|
||||
|
||||
* Primary developer
|
||||
* Author and maintainer
|
||||
|
||||
- **[Andrey Zhavoronkov](https://github.com/azhavoro)**
|
||||
|
||||
* Developer
|
||||
* Author and maintainer
|
||||
|
||||
# Contributors
|
||||
|
||||
- **[Victor Salimonov](https://github.com/VikTorSalimonov)**
|
||||
|
||||
* Documentation, screencasts
|
||||
|
||||
- **[Dmitry Sidnev](https://github.com/DmitriySidnev)**
|
||||
|
||||
* [convert_to_coco.py](utils/coco) - an utility for converting annotation from CVAT to COCO data annotation format
|
||||
|
||||
- **[Sebastián Yonekura](https://github.com/syonekura)**
|
||||
|
||||
* [convert_to_voc.py](utils/voc) - an utility for converting CVAT XML to PASCAL VOC data annotation format.
|
||||
|
||||
- **[ITLab Team](https://github.com/itlab-vision/cvat):**
|
||||
**[Vasily Danilin](https://github.com/DanVev)**,
|
||||
**[Eugene Shashkin](https://github.com/EvgenyShashkin)**,
|
||||
**[Dmitry Silenko](https://github.com/DimaSilenko)**,
|
||||
**[Alina Bykovskaya](https://github.com/alinaut)**,
|
||||
**[Yanina Koltushkina](https://github.com/YaniKolt)**
|
||||
* Integrating CI tools as Travis CI, Codacy and Coveralls.io
|
||||
@ -0,0 +1,301 @@
|
||||
// Copyright (C) 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import * as SVG from 'svg.js';
|
||||
|
||||
import consts from './consts';
|
||||
import { Geometry } from './canvasModel';
|
||||
|
||||
interface TransformedShape {
|
||||
points: string;
|
||||
color: string;
|
||||
}
|
||||
|
||||
export interface AutoborderHandler {
|
||||
autoborder(enabled: boolean, currentShape?: SVG.Shape, ignoreCurrent?: boolean): void;
|
||||
transform(geometry: Geometry): void;
|
||||
updateObjects(): void;
|
||||
}
|
||||
|
||||
export class AutoborderHandlerImpl implements AutoborderHandler {
|
||||
private currentShape: SVG.Shape | null;
|
||||
private ignoreCurrent: boolean;
|
||||
private frameContent: SVGSVGElement;
|
||||
private enabled: boolean;
|
||||
private scale: number;
|
||||
private groups: SVGGElement[];
|
||||
private auxiliaryGroupID: number | null;
|
||||
private auxiliaryClicks: number[];
|
||||
private listeners: Record<number, Record<number, {
|
||||
click: (event: MouseEvent) => void;
|
||||
dblclick: (event: MouseEvent) => void;
|
||||
}>>;
|
||||
|
||||
public constructor(frameContent: SVGSVGElement) {
|
||||
this.frameContent = frameContent;
|
||||
this.ignoreCurrent = false;
|
||||
this.currentShape = null;
|
||||
this.enabled = false;
|
||||
this.scale = 1;
|
||||
this.groups = [];
|
||||
this.auxiliaryGroupID = null;
|
||||
this.auxiliaryClicks = [];
|
||||
this.listeners = {};
|
||||
}
|
||||
|
||||
private removeMarkers(): void {
|
||||
this.groups.forEach((group: SVGGElement): void => {
|
||||
const groupID = group.dataset.groupId;
|
||||
Array.from(group.children)
|
||||
.forEach((circle: SVGCircleElement, pointID: number): void => {
|
||||
circle.removeEventListener('click', this.listeners[+groupID][pointID].click);
|
||||
circle.removeEventListener('dblclick', this.listeners[+groupID][pointID].click);
|
||||
circle.remove();
|
||||
});
|
||||
|
||||
group.remove();
|
||||
});
|
||||
|
||||
this.groups = [];
|
||||
this.auxiliaryGroupID = null;
|
||||
this.auxiliaryClicks = [];
|
||||
this.listeners = {};
|
||||
}
|
||||
|
||||
private release(): void {
|
||||
this.removeMarkers();
|
||||
this.enabled = false;
|
||||
this.currentShape = null;
|
||||
}
|
||||
|
||||
private addPointToCurrentShape(x: number, y: number): void {
|
||||
const array: number[][] = (this.currentShape as any).array().valueOf();
|
||||
array.pop();
|
||||
|
||||
// need to append twice (specific of the library)
|
||||
array.push([x, y]);
|
||||
array.push([x, y]);
|
||||
|
||||
const paintHandler = this.currentShape.remember('_paintHandler');
|
||||
paintHandler.drawCircles();
|
||||
paintHandler.set.members.forEach((el: SVG.Circle): void => {
|
||||
el.attr('stroke-width', 1 / this.scale).attr('r', 2.5 / this.scale);
|
||||
});
|
||||
(this.currentShape as any).plot(array);
|
||||
}
|
||||
|
||||
private resetAuxiliaryShape(): void {
|
||||
if (this.auxiliaryGroupID !== null) {
|
||||
while (this.auxiliaryClicks.length > 0) {
|
||||
const resetID = this.auxiliaryClicks.pop();
|
||||
this.groups[this.auxiliaryGroupID]
|
||||
.children[resetID].classList.remove('cvat_canvas_autoborder_point_direction');
|
||||
}
|
||||
}
|
||||
|
||||
this.auxiliaryClicks = [];
|
||||
this.auxiliaryGroupID = null;
|
||||
}
|
||||
|
||||
// convert each shape to group of clicable points
|
||||
// save all groups
|
||||
private drawMarkers(transformedShapes: TransformedShape[]): void {
|
||||
const svgNamespace = 'http://www.w3.org/2000/svg';
|
||||
|
||||
this.groups = transformedShapes
|
||||
.map((shape: TransformedShape, groupID: number): SVGGElement => {
|
||||
const group = document.createElementNS(svgNamespace, 'g');
|
||||
group.setAttribute('data-group-id', `${groupID}`);
|
||||
|
||||
this.listeners[groupID] = this.listeners[groupID] || {};
|
||||
const circles = shape.points.split(/\s/).map((
|
||||
point: string, pointID: number, points: string[],
|
||||
): SVGCircleElement => {
|
||||
const [x, y] = point.split(',');
|
||||
|
||||
const circle = document.createElementNS(svgNamespace, 'circle');
|
||||
circle.classList.add('cvat_canvas_autoborder_point');
|
||||
circle.setAttribute('fill', shape.color);
|
||||
circle.setAttribute('stroke', 'black');
|
||||
circle.setAttribute('stroke-width', `${consts.POINTS_STROKE_WIDTH / this.scale}`);
|
||||
circle.setAttribute('cx', x);
|
||||
circle.setAttribute('cy', y);
|
||||
circle.setAttribute('r', `${consts.BASE_POINT_SIZE / this.scale}`);
|
||||
|
||||
const click = (event: MouseEvent): void => {
|
||||
event.stopPropagation();
|
||||
|
||||
// another shape was clicked
|
||||
if (this.auxiliaryGroupID !== null
|
||||
&& this.auxiliaryGroupID !== groupID
|
||||
) {
|
||||
this.resetAuxiliaryShape();
|
||||
}
|
||||
|
||||
this.auxiliaryGroupID = groupID;
|
||||
// up clicked group for convenience
|
||||
this.frameContent.appendChild(group);
|
||||
|
||||
if (this.auxiliaryClicks[1] === pointID) {
|
||||
// the second point was clicked twice
|
||||
this.addPointToCurrentShape(+x, +y);
|
||||
this.resetAuxiliaryShape();
|
||||
return;
|
||||
}
|
||||
|
||||
// the first point can not be clicked twice
|
||||
// just ignore such a click if it is
|
||||
if (this.auxiliaryClicks[0] !== pointID) {
|
||||
this.auxiliaryClicks.push(pointID);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// it is the first click
|
||||
if (this.auxiliaryClicks.length === 1) {
|
||||
const handler = this.currentShape.remember('_paintHandler');
|
||||
// draw and remove initial point just to initialize data structures
|
||||
if (!handler || !handler.startPoint) {
|
||||
(this.currentShape as any).draw('point', event);
|
||||
(this.currentShape as any).draw('undo');
|
||||
}
|
||||
|
||||
this.addPointToCurrentShape(+x, +y);
|
||||
// is is the second click
|
||||
} else if (this.auxiliaryClicks.length === 2) {
|
||||
circle.classList.add('cvat_canvas_autoborder_point_direction');
|
||||
// it is the third click
|
||||
} else {
|
||||
// sign defines bypass direction
|
||||
const landmarks = this.auxiliaryClicks;
|
||||
const sign = Math.sign(landmarks[2] - landmarks[0])
|
||||
* Math.sign(landmarks[1] - landmarks[0])
|
||||
* Math.sign(landmarks[2] - landmarks[1]);
|
||||
|
||||
// go via a polygon and get vertexes
|
||||
// the first vertex has been already drawn
|
||||
const way = [];
|
||||
for (let i = landmarks[0] + sign; ; i += sign) {
|
||||
if (i < 0) {
|
||||
i = points.length - 1;
|
||||
} else if (i === points.length) {
|
||||
i = 0;
|
||||
}
|
||||
|
||||
way.push(points[i]);
|
||||
|
||||
if (i === this.auxiliaryClicks[this.auxiliaryClicks.length - 1]) {
|
||||
// put the last element twice
|
||||
// specific of svg.draw.js
|
||||
// way.push(points[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// remove the latest cursor position from drawing array
|
||||
for (const wayPoint of way) {
|
||||
const [_x, _y] = wayPoint.split(',')
|
||||
.map((coordinate: string): number => +coordinate);
|
||||
this.addPointToCurrentShape(_x, _y);
|
||||
}
|
||||
|
||||
this.resetAuxiliaryShape();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const dblclick = (event: MouseEvent): void => {
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
this.listeners[groupID][pointID] = {
|
||||
click,
|
||||
dblclick,
|
||||
};
|
||||
|
||||
circle.addEventListener('mousedown', this.listeners[groupID][pointID].click);
|
||||
circle.addEventListener('dblclick', this.listeners[groupID][pointID].click);
|
||||
return circle;
|
||||
});
|
||||
|
||||
group.append(...circles);
|
||||
return group;
|
||||
});
|
||||
|
||||
this.frameContent.append(...this.groups);
|
||||
}
|
||||
|
||||
public updateObjects(): void {
|
||||
if (!this.enabled) return;
|
||||
this.removeMarkers();
|
||||
|
||||
const currentClientID = this.currentShape.node.dataset.originClientId;
|
||||
const shapes = Array.from(this.frameContent.getElementsByClassName('cvat_canvas_shape'));
|
||||
const transformedShapes = shapes.map((shape: HTMLElement): TransformedShape | null => {
|
||||
const color = shape.getAttribute('fill');
|
||||
const clientID = shape.getAttribute('clientID');
|
||||
|
||||
if (color === null || clientID === null) return null;
|
||||
if (+clientID === +currentClientID) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let points = '';
|
||||
if (shape.tagName === 'polyline' || shape.tagName === 'polygon') {
|
||||
points = shape.getAttribute('points');
|
||||
} else if (shape.tagName === 'rect') {
|
||||
const x = +shape.getAttribute('x');
|
||||
const y = +shape.getAttribute('y');
|
||||
const width = +shape.getAttribute('width');
|
||||
const height = +shape.getAttribute('height');
|
||||
|
||||
if (Number.isNaN(x) || Number.isNaN(y) || Number.isNaN(x) || Number.isNaN(x)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
points = `${x},${y} ${x + width},${y} ${x + width},${y + height} ${x},${y + height}`;
|
||||
} else if (shape.tagName === 'g') {
|
||||
const polylineID = shape.dataset.polylineId;
|
||||
const polyline = this.frameContent.getElementById(polylineID);
|
||||
if (polyline && polyline.getAttribute('points')) {
|
||||
points = polyline.getAttribute('points');
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
color,
|
||||
points: points.trim(),
|
||||
};
|
||||
}).filter((state: TransformedShape | null): boolean => state !== null);
|
||||
|
||||
this.drawMarkers(transformedShapes);
|
||||
}
|
||||
|
||||
public autoborder(
|
||||
enabled: boolean,
|
||||
currentShape?: SVG.Shape,
|
||||
ignoreCurrent: boolean = false,
|
||||
): void {
|
||||
if (enabled && !this.enabled && currentShape) {
|
||||
this.enabled = true;
|
||||
this.currentShape = currentShape;
|
||||
this.ignoreCurrent = ignoreCurrent;
|
||||
this.updateObjects();
|
||||
} else {
|
||||
this.release();
|
||||
}
|
||||
}
|
||||
|
||||
public transform(geometry: Geometry): void {
|
||||
this.scale = geometry.scale;
|
||||
this.groups.forEach((group: SVGGElement): void => {
|
||||
Array.from(group.children).forEach((circle: SVGCircleElement): void => {
|
||||
circle.setAttribute('r', `${consts.BASE_POINT_SIZE / this.scale}`);
|
||||
circle.setAttribute('stroke-width', `${consts.BASE_STROKE_WIDTH / this.scale}`);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,90 @@
|
||||
// Copyright (C) 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox';
|
||||
import Tooltip from 'antd/lib/tooltip';
|
||||
|
||||
import { Canvas } from 'cvat-canvas';
|
||||
import { CombinedState } from 'reducers/interfaces';
|
||||
import { activate as activatePlugin, deactivate as deactivatePlugin } from 'utils/dextr-utils';
|
||||
|
||||
|
||||
interface StateToProps {
|
||||
pluginEnabled: boolean;
|
||||
canvasInstance: Canvas;
|
||||
}
|
||||
|
||||
interface DispatchToProps {
|
||||
activate(canvasInstance: Canvas): void;
|
||||
deactivate(canvasInstance: Canvas): void;
|
||||
}
|
||||
|
||||
function mapStateToProps(state: CombinedState): StateToProps {
|
||||
const {
|
||||
plugins: {
|
||||
list,
|
||||
},
|
||||
annotation: {
|
||||
canvas: {
|
||||
instance: canvasInstance,
|
||||
},
|
||||
},
|
||||
} = state;
|
||||
|
||||
return {
|
||||
canvasInstance,
|
||||
pluginEnabled: list.DEXTR_SEGMENTATION,
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(): DispatchToProps {
|
||||
return {
|
||||
activate(canvasInstance: Canvas): void {
|
||||
activatePlugin(canvasInstance);
|
||||
},
|
||||
deactivate(canvasInstance: Canvas): void {
|
||||
deactivatePlugin(canvasInstance);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function DEXTRPlugin(props: StateToProps & DispatchToProps): JSX.Element | null {
|
||||
const {
|
||||
pluginEnabled,
|
||||
canvasInstance,
|
||||
activate,
|
||||
deactivate,
|
||||
} = props;
|
||||
const [pluginActivated, setActivated] = useState(false);
|
||||
|
||||
return (
|
||||
pluginEnabled ? (
|
||||
<Tooltip title='Make AI polygon from at least 4 extreme points using deep extreme cut'>
|
||||
<Checkbox
|
||||
style={{ marginTop: 5 }}
|
||||
checked={pluginActivated}
|
||||
onChange={(event: CheckboxChangeEvent): void => {
|
||||
setActivated(event.target.checked);
|
||||
if (event.target.checked) {
|
||||
activate(canvasInstance);
|
||||
} else {
|
||||
deactivate(canvasInstance);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Make AI polygon
|
||||
</Checkbox>
|
||||
</Tooltip>
|
||||
) : null
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
)(DEXTRPlugin);
|
||||
|
||||
// TODO: Add dialog window with cancel button
|
||||
@ -0,0 +1,256 @@
|
||||
// Copyright (C) 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import getCore from 'cvat-core';
|
||||
import { Canvas } from 'cvat-canvas';
|
||||
import { ShapeType, RQStatus } from 'reducers/interfaces';
|
||||
|
||||
const core = getCore();
|
||||
const baseURL = core.config.backendAPI.slice(0, -7);
|
||||
|
||||
interface DEXTRPlugin {
|
||||
name: string;
|
||||
description: string;
|
||||
cvat: {
|
||||
classes: {
|
||||
Job: {
|
||||
prototype: {
|
||||
annotations: {
|
||||
put: {
|
||||
enter(self: any, objects: any[]): Promise<void>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
data: {
|
||||
canceled: boolean;
|
||||
enabled: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
interface Point {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
const antModalRoot = document.createElement('div');
|
||||
const antModalMask = document.createElement('div');
|
||||
antModalMask.classList.add('ant-modal-mask');
|
||||
const antModalWrap = document.createElement('div');
|
||||
antModalWrap.classList.add('ant-modal-wrap');
|
||||
antModalWrap.setAttribute('role', 'dialog');
|
||||
const antModal = document.createElement('div');
|
||||
antModal.classList.add('ant-modal');
|
||||
antModal.style.width = '300px';
|
||||
antModal.style.top = '40%';
|
||||
antModal.setAttribute('role', 'document');
|
||||
const antModalContent = document.createElement('div');
|
||||
antModalContent.classList.add('ant-modal-content');
|
||||
const antModalBody = document.createElement('div');
|
||||
antModalBody.classList.add('ant-modal-body');
|
||||
antModalBody.style.textAlign = 'center';
|
||||
const antModalSpan = document.createElement('span');
|
||||
antModalSpan.innerText = 'Segmentation request is being processed';
|
||||
antModalSpan.style.display = 'block';
|
||||
const antModalButton = document.createElement('button');
|
||||
antModalButton.disabled = true;
|
||||
antModalButton.classList.add('ant-btn', 'ant-btn-primary');
|
||||
antModalButton.style.width = '100px';
|
||||
antModalButton.style.margin = '10px auto';
|
||||
const antModalButtonSpan = document.createElement('span');
|
||||
antModalButtonSpan.innerText = 'Cancel';
|
||||
|
||||
antModalBody.append(antModalSpan, antModalButton);
|
||||
antModalButton.append(antModalButtonSpan);
|
||||
antModalContent.append(antModalBody);
|
||||
antModal.append(antModalContent);
|
||||
antModalWrap.append(antModal);
|
||||
antModalRoot.append(antModalMask, antModalWrap);
|
||||
|
||||
|
||||
function serverRequest(
|
||||
plugin: DEXTRPlugin,
|
||||
jid: number,
|
||||
frame: number,
|
||||
points: number[],
|
||||
): Promise<number[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reducer = (acc: Point[], _: number, index: number, array: number[]): Point[] => {
|
||||
if (!(index % 2)) { // 0, 2, 4
|
||||
acc.push({
|
||||
x: array[index],
|
||||
y: array[index + 1],
|
||||
});
|
||||
}
|
||||
|
||||
return acc;
|
||||
};
|
||||
|
||||
const reducedPoints = points.reduce(reducer, []);
|
||||
core.server.request(
|
||||
`${baseURL}/dextr/create/${jid}`, {
|
||||
method: 'POST',
|
||||
data: JSON.stringify({
|
||||
frame,
|
||||
points: reducedPoints,
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
},
|
||||
).then(() => {
|
||||
const timeoutCallback = (): void => {
|
||||
core.server.request(
|
||||
`${baseURL}/dextr/check/${jid}`, {
|
||||
method: 'GET',
|
||||
},
|
||||
).then((response: any) => {
|
||||
const { status } = response;
|
||||
if (status === RQStatus.finished) {
|
||||
resolve(response.result.split(/\s|,/).map((coord: string) => +coord));
|
||||
} else if (status === RQStatus.failed) {
|
||||
reject(new Error(response.stderr));
|
||||
} else if (status === RQStatus.unknown) {
|
||||
reject(new Error('Unknown DEXTR status has been received'));
|
||||
} else {
|
||||
if (status === RQStatus.queued) {
|
||||
antModalButton.disabled = false;
|
||||
}
|
||||
if (!plugin.data.canceled) {
|
||||
setTimeout(timeoutCallback, 1000);
|
||||
} else {
|
||||
core.server.request(
|
||||
`${baseURL}/dextr/cancel/${jid}`, {
|
||||
method: 'GET',
|
||||
},
|
||||
).then(() => {
|
||||
resolve(points);
|
||||
}).catch((error: Error) => {
|
||||
reject(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
}).catch((error: Error) => {
|
||||
reject(error);
|
||||
});
|
||||
};
|
||||
|
||||
setTimeout(timeoutCallback, 1000);
|
||||
}).catch((error: Error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
|
||||
// start checking
|
||||
}
|
||||
|
||||
const plugin: DEXTRPlugin = {
|
||||
name: 'Deep extreme cut',
|
||||
description: 'Plugin allows to get a polygon from extreme points using AI',
|
||||
cvat: {
|
||||
classes: {
|
||||
Job: {
|
||||
prototype: {
|
||||
annotations: {
|
||||
put: {
|
||||
async enter(self: DEXTRPlugin, objects: any[]): Promise<void> {
|
||||
try {
|
||||
if (self.data.enabled) {
|
||||
document.body.append(antModalRoot);
|
||||
const promises: Record<number, Promise<number[]>> = {};
|
||||
for (let i = 0; i < objects.length; i++) {
|
||||
if (objects[i].points.length >= 8) {
|
||||
promises[i] = serverRequest(
|
||||
self,
|
||||
(this as any).id,
|
||||
objects[i].frame,
|
||||
objects[i].points,
|
||||
);
|
||||
} else {
|
||||
promises[i] = new Promise((resolve) => {
|
||||
resolve(objects[i].points);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const transformed = await Promise
|
||||
.all(Object.values(promises));
|
||||
for (let i = 0; i < objects.length; i++) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
objects[i] = new core.classes.ObjectState({
|
||||
frame: objects[i].frame,
|
||||
objectType: objects[i].objectType,
|
||||
label: objects[i].label,
|
||||
shapeType: ShapeType.POLYGON,
|
||||
points: transformed[i],
|
||||
occluded: objects[i].occluded,
|
||||
zOrder: objects[i].zOrder,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
} catch (error) {
|
||||
throw new core.exceptions.PluginError(error.toString());
|
||||
} finally {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
self.data.canceled = false;
|
||||
antModalButton.disabled = true;
|
||||
if (antModalRoot.parentElement === document.body) {
|
||||
document.body.removeChild(antModalRoot);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
data: {
|
||||
canceled: false,
|
||||
enabled: false,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
antModalButton.onclick = () => {
|
||||
plugin.data.canceled = true;
|
||||
};
|
||||
|
||||
export function activate(canvasInstance: Canvas): void {
|
||||
if (!plugin.data.enabled) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
canvasInstance.draw = (drawData: any): void => {
|
||||
if (drawData.enabled && drawData.shapeType === ShapeType.POLYGON
|
||||
&& (typeof (drawData.numberOfPoints) === 'undefined' || drawData.numberOfPoints >= 4)
|
||||
&& (typeof (drawData.initialState) === 'undefined')
|
||||
) {
|
||||
const patchedData = { ...drawData };
|
||||
patchedData.shapeType = ShapeType.POINTS;
|
||||
patchedData.crosshair = true;
|
||||
Object.getPrototypeOf(canvasInstance)
|
||||
.draw.call(canvasInstance, patchedData);
|
||||
} else {
|
||||
Object.getPrototypeOf(canvasInstance)
|
||||
.draw.call(canvasInstance, drawData);
|
||||
}
|
||||
};
|
||||
plugin.data.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
export function deactivate(canvasInstance: Canvas): void {
|
||||
if (plugin.data.enabled) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
canvasInstance.draw = Object.getPrototypeOf(canvasInstance).draw;
|
||||
plugin.data.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
export function registerDEXTRPlugin(): void {
|
||||
core.plugins.register(plugin);
|
||||
}
|
||||
|
Before Width: | Height: | Size: 261 KiB |
|
After Width: | Height: | Size: 116 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 105 KiB |
|
After Width: | Height: | Size: 159 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 12 KiB |