lost-sia 2.0.1-alpha3 → 2.0.1-alpha6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +36 -24
- package/src/AnnoExampleViewer.jsx +2 -3
- package/src/Annotation/logic/Annotation.ts +39 -0
- package/src/Annotation/logic/AnnotationUtils.ts +30 -0
- package/src/Annotation/ui/AnnotationComponent.tsx +265 -0
- package/src/Annotation/ui/atoms/AnnoBar.tsx +132 -0
- package/src/Annotation/ui/atoms/DaviIcon.tsx +57 -0
- package/src/Annotation/ui/atoms/Edge.tsx +57 -0
- package/src/Annotation/ui/atoms/Node.tsx +126 -0
- package/src/Annotation/ui/atoms/PolygonArea.tsx +75 -0
- package/src/Annotation/ui/tools/BBox.tsx +330 -0
- package/src/Annotation/ui/tools/Line.tsx +220 -0
- package/src/Annotation/ui/tools/Point.tsx +50 -0
- package/src/Annotation/ui/tools/Polygon.tsx +233 -0
- package/src/Canvas/Canvas.tsx +869 -0
- package/src/Canvas/LabelInput.tsx +74 -0
- package/src/InfoBoxes/AnnoDetails.jsx +1 -2
- package/src/InfoBoxes/AnnoStats.jsx +0 -1
- package/src/InfoBoxes/InfoBox.jsx +2 -2
- package/src/InfoBoxes/InfoBoxArea.jsx +1 -4
- package/src/InfoBoxes/LabelInfo.jsx +4 -12
- package/src/SIAFilterButton.jsx +12 -13
- package/src/SIASettingButton.jsx +9 -10
- package/src/Sia.jsx +2 -0
- package/src/Sia2.tsx +392 -0
- package/src/Toolbar/NavigationButtons.tsx +21 -0
- package/src/Toolbar/Toolbar.tsx +76 -0
- package/src/Toolbar/ToolbarItem.jsx +30 -0
- package/src/Toolbar/ToolbarItems/AccessibilityTools.tsx +69 -0
- package/src/Toolbar/ToolbarItems/AnnoToolSelector.tsx +88 -0
- package/src/Toolbar/ToolbarItems/ImageToolItems/ImageLabel.tsx +62 -0
- package/src/Toolbar/ToolbarItems/ImageToolItems/TagLabel.tsx +56 -0
- package/src/Toolbar/ToolbarItems/ImageTools.tsx +63 -0
- package/src/Toolbar/ToolbarItems/Instructions.tsx +124 -0
- package/src/Toolbar/ToolbarItems/InstructionsModal.tsx +20 -0
- package/src/filterTools.js +1 -1
- package/src/index.ts +14 -0
- package/src/models/AllowedTools.tsx +9 -0
- package/src/models/AnnotationMode.tsx +12 -0
- package/src/models/AnnotationSettings.tsx +9 -0
- package/src/models/AnnotationStatus.tsx +9 -0
- package/src/models/AnnotationTool.tsx +8 -0
- package/src/models/CanvasAction.tsx +29 -0
- package/src/models/Direction.tsx +8 -0
- package/src/models/EditorModes.tsx +12 -0
- package/src/models/ExternalAnnotation.ts +15 -0
- package/src/models/KeyAction.tsx +21 -0
- package/src/models/Label.tsx +8 -0
- package/src/models/UiConfig.tsx +6 -0
- package/src/models/index.js +8 -0
- package/src/stories/Canvas/Canvas.stories.tsx +156 -0
- package/src/stories/Canvas/CanvasOffset.tsx +58 -0
- package/src/stories/Canvas/CanvasWithOffset.stories.tsx +156 -0
- package/src/stories/FilterDropdown.stories.ts +23 -0
- package/src/stories/{Sia.stories.jsx → SIA/SIA.stories.tsx} +25 -14
- package/src/stories/SIA2/DemoWrapper.stories.tsx +167 -0
- package/src/stories/SIA2/DemoWrapper.tsx +54 -0
- package/src/stories/SIA2/Sia2.stories.tsx +62 -0
- package/src/stories/Toolbar/ImageTools/ImageLabel.stories.tsx +32 -0
- package/src/stories/Toolbar/ImageTools/TagLabel.stories.tsx +37 -0
- package/src/stories/Toolbar/Instructions.stories.tsx +22 -0
- package/src/stories/Toolbar/Toolbar.stories.tsx +101 -0
- package/src/stories/main.scss +6 -0
- package/src/stories/siaDummyData2.ts +265 -0
- package/src/styles/style.scss +30 -0
- package/src/types/notificationType.js +5 -5
- package/src/types.tsx +11 -0
- package/src/utils/KeyMapper.ts +93 -0
- package/src/utils/annoConversion2.ts +145 -0
- package/src/utils/color.ts +61 -0
- package/src/utils/mouse2.ts +35 -0
- package/src/utils/transform2.ts +343 -0
- package/src/utils/windowViewport2.ts +50 -0
- package/src/index.js +0 -13
- package/src/stories/Configure.mdx +0 -369
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lost-sia",
|
|
3
|
-
"version": "2.0.1-
|
|
3
|
+
"version": "2.0.1-alpha6",
|
|
4
4
|
"description": "Single Image Annotation Tool",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "l3p-cv/lost-sia",
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"module": "dist/index.es.js",
|
|
9
9
|
"jsnext:main": "dist/index.es.js",
|
|
10
10
|
"type": "module",
|
|
11
|
+
"types": "dist/index.d.ts",
|
|
11
12
|
"keywords": [
|
|
12
13
|
"lost",
|
|
13
14
|
"annotation",
|
|
@@ -20,32 +21,48 @@
|
|
|
20
21
|
},
|
|
21
22
|
"exports": {
|
|
22
23
|
".": {
|
|
23
|
-
"
|
|
24
|
-
|
|
24
|
+
"development": {
|
|
25
|
+
"types": "./src/types.ts",
|
|
26
|
+
"import": "./src/index.ts"
|
|
27
|
+
},
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"import": "./dist/index.js",
|
|
30
|
+
"require": "./dist/index.js"
|
|
25
31
|
},
|
|
26
32
|
"./utils": {
|
|
27
|
-
"
|
|
28
|
-
"
|
|
33
|
+
"development": "./src/utils/index.js",
|
|
34
|
+
"types": "./dist/utils/index.d.ts",
|
|
35
|
+
"import": "./dist/utils/index.js",
|
|
36
|
+
"require": "./dist/utils/index.js"
|
|
37
|
+
},
|
|
38
|
+
"./models": {
|
|
39
|
+
"development": "./src/models/index.js",
|
|
40
|
+
"types": "./dist/models/index.d.ts",
|
|
41
|
+
"import": "./dist/models/index.js",
|
|
42
|
+
"require": "./dist/models/index.js"
|
|
29
43
|
},
|
|
30
44
|
"./styles/variables": "./src/styles/_variables.scss",
|
|
31
45
|
"./styles/coreui": "./src/styles/coreui.scss"
|
|
32
46
|
},
|
|
33
47
|
"files": [
|
|
48
|
+
"dist",
|
|
34
49
|
"src",
|
|
35
50
|
"styles"
|
|
36
51
|
],
|
|
52
|
+
"style": "dist/main.css",
|
|
37
53
|
"scripts": {
|
|
38
54
|
"build": "vite build",
|
|
39
55
|
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
|
40
56
|
"preview": "vite preview",
|
|
41
57
|
"publishpkg": "npm publish",
|
|
42
58
|
"test": "vitest run",
|
|
59
|
+
"start": "storybook dev -p 6006",
|
|
43
60
|
"storybook": "storybook dev -p 6006",
|
|
44
61
|
"build-storybook": "storybook build",
|
|
45
62
|
"format": "prettier --write src/"
|
|
46
63
|
},
|
|
47
64
|
"dependencies": {
|
|
48
|
-
"@coreui/react": "^5.
|
|
65
|
+
"@coreui/react": "^5.7.0",
|
|
49
66
|
"@fortawesome/free-regular-svg-icons": "^6.7.2",
|
|
50
67
|
"@fortawesome/free-solid-svg-icons": "^6.7.2",
|
|
51
68
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
|
@@ -55,7 +72,8 @@
|
|
|
55
72
|
"react-draggable": "^4.4.6",
|
|
56
73
|
"sass": "^1.89.2",
|
|
57
74
|
"semantic-ui-css": "2.5.0",
|
|
58
|
-
"semantic-ui-react": "^2.0.3"
|
|
75
|
+
"semantic-ui-react": "^2.0.3",
|
|
76
|
+
"vite-plugin-dts": "^4.5.4"
|
|
59
77
|
},
|
|
60
78
|
"peerDependencies": {
|
|
61
79
|
"lodash": "^4.17.15",
|
|
@@ -64,16 +82,11 @@
|
|
|
64
82
|
"react-dom": "^18.0.0"
|
|
65
83
|
},
|
|
66
84
|
"devDependencies": {
|
|
67
|
-
"@
|
|
68
|
-
"@storybook/addon-
|
|
69
|
-
"@storybook/
|
|
70
|
-
"@storybook/
|
|
71
|
-
"@storybook/
|
|
72
|
-
"@storybook/addon-onboarding": "^8.1.5",
|
|
73
|
-
"@storybook/blocks": "^8.1.5",
|
|
74
|
-
"@storybook/react": "^8.1.5",
|
|
75
|
-
"@storybook/react-vite": "^8.1.5",
|
|
76
|
-
"@storybook/test": "^8.1.5",
|
|
85
|
+
"@microsoft/api-extractor": "^7.52.13",
|
|
86
|
+
"@storybook/addon-links": "^9.0.12",
|
|
87
|
+
"@storybook/react": "^9.0.12",
|
|
88
|
+
"@storybook/react-vite": "^9.0.12",
|
|
89
|
+
"@storybook/test": "9.0.0-alpha.2",
|
|
77
90
|
"@vitejs/plugin-react": "^4.3.0",
|
|
78
91
|
"cross-env": "^7.0.3",
|
|
79
92
|
"eslint": "^8.0.1",
|
|
@@ -81,18 +94,17 @@
|
|
|
81
94
|
"eslint-config-standard-react": "^13.0.0",
|
|
82
95
|
"eslint-plugin-import": "^2.29.1",
|
|
83
96
|
"eslint-plugin-node": "^11.1.0",
|
|
84
|
-
"eslint-plugin-promise": "^6.
|
|
97
|
+
"eslint-plugin-promise": "^6.6.0",
|
|
85
98
|
"eslint-plugin-react": "^7.34.2",
|
|
86
99
|
"eslint-plugin-standard": "^5.0.0",
|
|
87
|
-
"
|
|
88
|
-
"glob": "^10.4.1",
|
|
100
|
+
"glob": "^10.4.5",
|
|
89
101
|
"prettier": "^3.3.3",
|
|
90
|
-
"prop-types": "^15.8.1",
|
|
91
102
|
"react-redux": "^9.1.2",
|
|
92
103
|
"redux": "^5.0.1",
|
|
93
|
-
"storybook": "^
|
|
94
|
-
"
|
|
95
|
-
"
|
|
104
|
+
"storybook": "^9.0.12",
|
|
105
|
+
"unplugin-dts": "^1.0.0-beta.0",
|
|
106
|
+
"vite": "^5.4.19",
|
|
107
|
+
"vitest": "^1.6.1"
|
|
96
108
|
},
|
|
97
109
|
"eslintConfig": {
|
|
98
110
|
"extends": [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Prompt from "./Prompt";
|
|
2
2
|
import React from "react";
|
|
3
|
-
import { Card,
|
|
3
|
+
import { Card, Image } from "semantic-ui-react";
|
|
4
4
|
|
|
5
5
|
const LabelExampleViewer = (props) => {
|
|
6
6
|
const requestExample = (e) => {
|
|
@@ -14,9 +14,8 @@ const LabelExampleViewer = (props) => {
|
|
|
14
14
|
if (!props.exampleImg) return null;
|
|
15
15
|
const description = (
|
|
16
16
|
<div>
|
|
17
|
-
{/* <Divider horizontal> Description </Divider> */}
|
|
18
17
|
{props.lbl.description}
|
|
19
|
-
<
|
|
18
|
+
<h4>Comment:</h4>
|
|
20
19
|
{props.exampleImg.anno ? props.exampleImg.anno.comment : "no comment"}
|
|
21
20
|
</div>
|
|
22
21
|
);
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import AnnotationMode from "../../models/AnnotationMode";
|
|
2
|
+
import AnnotationStatus from "../../models/AnnotationStatus";
|
|
3
|
+
import AnnotationTool from "../../models/AnnotationTool";
|
|
4
|
+
import { Point } from "../../types";
|
|
5
|
+
|
|
6
|
+
class Annotation {
|
|
7
|
+
internalId: number;
|
|
8
|
+
externalId?: string;
|
|
9
|
+
annoTime?: number;
|
|
10
|
+
coordinates: Point[];
|
|
11
|
+
labelIds?: number[];
|
|
12
|
+
mode: AnnotationMode; // do we even need this globally? - only really used inside AnnotationComponent
|
|
13
|
+
selectedNode: number;
|
|
14
|
+
status?: AnnotationStatus;
|
|
15
|
+
type: AnnotationTool;
|
|
16
|
+
timestamp?: DOMHighResTimeStamp;
|
|
17
|
+
|
|
18
|
+
constructor(
|
|
19
|
+
internalId: number,
|
|
20
|
+
type: AnnotationTool,
|
|
21
|
+
coordinates: Point[],
|
|
22
|
+
mode: AnnotationMode = AnnotationMode.CREATE,
|
|
23
|
+
status: AnnotationStatus = AnnotationStatus.CREATING,
|
|
24
|
+
externalId: string = "",
|
|
25
|
+
) {
|
|
26
|
+
this.internalId = internalId;
|
|
27
|
+
this.externalId = externalId;
|
|
28
|
+
this.labelIds = [];
|
|
29
|
+
this.type = type;
|
|
30
|
+
this.mode = mode;
|
|
31
|
+
this.status = status;
|
|
32
|
+
this.coordinates = coordinates;
|
|
33
|
+
this.selectedNode = 1;
|
|
34
|
+
this.timestamp = performance.now();
|
|
35
|
+
this.annoTime = 0.0;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export default Annotation;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Point } from "../../types";
|
|
2
|
+
import Annotation from "./Annotation";
|
|
3
|
+
|
|
4
|
+
const addNode = (annotation: Annotation, point: Point) => {
|
|
5
|
+
const _annotation = { ...annotation };
|
|
6
|
+
_annotation.coordinates.push(point);
|
|
7
|
+
return _annotation;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const startAnnotimeMeasure = (annotation: Annotation) => {
|
|
11
|
+
const _annotation = { ...annotation };
|
|
12
|
+
_annotation.timestamp = performance.now();
|
|
13
|
+
return _annotation;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const stopAnnotimeMeasure = (annotation: Annotation): [Annotation, number] => {
|
|
17
|
+
const _annotation = { ...annotation };
|
|
18
|
+
const now = performance.now();
|
|
19
|
+
const duration = (now - _annotation.timestamp) / 1000;
|
|
20
|
+
_annotation.annoTime += duration;
|
|
21
|
+
_annotation.timestamp = now;
|
|
22
|
+
|
|
23
|
+
return [_annotation, duration];
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default {
|
|
27
|
+
addNode,
|
|
28
|
+
startAnnotimeMeasure,
|
|
29
|
+
stopAnnotimeMeasure,
|
|
30
|
+
};
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import AnnotationTool from "../../models/AnnotationTool";
|
|
2
|
+
import Label from "../../models/Label";
|
|
3
|
+
import Annotation from "../logic/Annotation";
|
|
4
|
+
import * as colorUtils from "../../utils/color";
|
|
5
|
+
import PointTool from "./tools/Point";
|
|
6
|
+
import Line from "./tools/Line";
|
|
7
|
+
import AnnoBar from "./atoms/AnnoBar";
|
|
8
|
+
import CanvasAction from "../../models/CanvasAction";
|
|
9
|
+
import BBox from "./tools/BBox";
|
|
10
|
+
import Polygon from "./tools/Polygon";
|
|
11
|
+
import { useEffect, useRef, useState } from "react";
|
|
12
|
+
import { Point } from "../../types";
|
|
13
|
+
import AnnotationMode from "../../models/AnnotationMode";
|
|
14
|
+
import AnnotationSettings from "../../models/AnnotationSettings";
|
|
15
|
+
|
|
16
|
+
type AnnotationComponentProps = {
|
|
17
|
+
scaledAnnotation: Annotation;
|
|
18
|
+
annotationSettings: AnnotationSettings;
|
|
19
|
+
possibleLabels: Label[];
|
|
20
|
+
svgScale: number;
|
|
21
|
+
pageToStageOffset: Point;
|
|
22
|
+
strokeWidth: number;
|
|
23
|
+
nodeRadius: number;
|
|
24
|
+
isSelected: boolean;
|
|
25
|
+
isDisabled?: boolean;
|
|
26
|
+
onFinishAnnoCreate: (fullyCreatedAnnotation: Annotation) => void;
|
|
27
|
+
onLabelIconClicked: (markerPosition: Point) => void;
|
|
28
|
+
onAction?: (annotation: Annotation, canvasAction: CanvasAction) => void;
|
|
29
|
+
onAnnoChanged?: (annotation: Annotation) => void;
|
|
30
|
+
onAnnotationModeChange?: (annotationMode: AnnotationMode) => void;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const AnnotationComponent = ({
|
|
34
|
+
scaledAnnotation,
|
|
35
|
+
annotationSettings,
|
|
36
|
+
possibleLabels,
|
|
37
|
+
svgScale,
|
|
38
|
+
pageToStageOffset,
|
|
39
|
+
strokeWidth,
|
|
40
|
+
nodeRadius,
|
|
41
|
+
isSelected,
|
|
42
|
+
isDisabled = false,
|
|
43
|
+
onFinishAnnoCreate,
|
|
44
|
+
onLabelIconClicked,
|
|
45
|
+
onAction = (_, __) => {},
|
|
46
|
+
onAnnoChanged = (_) => {},
|
|
47
|
+
onAnnotationModeChange = (_) => {},
|
|
48
|
+
}: AnnotationComponentProps) => {
|
|
49
|
+
const [coordinates, setCoordinates] = useState<Point[]>(
|
|
50
|
+
scaledAnnotation.coordinates,
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const [annotationMode, setAnnotationMode] = useState<AnnotationMode>(
|
|
54
|
+
scaledAnnotation.mode,
|
|
55
|
+
);
|
|
56
|
+
const [isDragging, setIsDragging] = useState<boolean>(false);
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* during user editing of the annotation, multiple events are fired by the children
|
|
60
|
+
* onMoving for updating the data
|
|
61
|
+
* onMoved for telling its parents (us) that the user has finished moving
|
|
62
|
+
* onMoved has no access to up-to-date coordinates, because the state is always one render step behind the setState
|
|
63
|
+
* since both events are fired during the same render, onMoved would give away old coorinates
|
|
64
|
+
* use this reference as a workaround to get the up-to-date coordinates even in this edge-case
|
|
65
|
+
*/
|
|
66
|
+
const coordinatesRef = useRef<Point[]>(coordinates);
|
|
67
|
+
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
coordinatesRef.current = coordinates;
|
|
70
|
+
}, [coordinates]);
|
|
71
|
+
|
|
72
|
+
const finishAnnoCreate = () => {
|
|
73
|
+
setAnnotationMode(AnnotationMode.VIEW);
|
|
74
|
+
|
|
75
|
+
const newAnnotation = {
|
|
76
|
+
...scaledAnnotation,
|
|
77
|
+
coordinates: coordinatesRef.current,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
onFinishAnnoCreate(newAnnotation);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const getLabel = (labelId: number): Label | undefined => {
|
|
84
|
+
return possibleLabels.find((label: Label) => {
|
|
85
|
+
return label.id === labelId;
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const getColor = () => {
|
|
90
|
+
if (!scaledAnnotation.labelIds || scaledAnnotation.labelIds.length == 0)
|
|
91
|
+
return colorUtils.getDefaultColor();
|
|
92
|
+
|
|
93
|
+
const label = getLabel(scaledAnnotation.labelIds[0]);
|
|
94
|
+
|
|
95
|
+
if (label === undefined || label.color === undefined)
|
|
96
|
+
return colorUtils.getDefaultColor();
|
|
97
|
+
|
|
98
|
+
return label.color;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const color = getColor();
|
|
102
|
+
const annotationStyle = {
|
|
103
|
+
stroke: color,
|
|
104
|
+
fill: color,
|
|
105
|
+
strokeWidth: strokeWidth / svgScale,
|
|
106
|
+
r: nodeRadius / svgScale,
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const changeAnnoCoords = (newCoordinates: Point[]) => {
|
|
110
|
+
setCoordinates(newCoordinates);
|
|
111
|
+
|
|
112
|
+
let newCoordinatesWithoutMouse = newCoordinates;
|
|
113
|
+
|
|
114
|
+
// while adding/moving the annotation, the mouse cursor is the last point of the coordinates
|
|
115
|
+
// remove it before saving the annotation
|
|
116
|
+
if ([AnnotationMode.ADD, AnnotationMode.MOVE].includes(annotationMode)) {
|
|
117
|
+
// last point is mouse - remove it before export
|
|
118
|
+
newCoordinatesWithoutMouse = newCoordinates.slice(0, -1);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
onAnnoChanged({
|
|
122
|
+
...scaledAnnotation,
|
|
123
|
+
coordinates: newCoordinatesWithoutMouse,
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const onMoving = (newCoords: Point[]) => {
|
|
128
|
+
if (annotationMode !== AnnotationMode.CREATE)
|
|
129
|
+
setAnnotationMode(AnnotationMode.MOVE);
|
|
130
|
+
|
|
131
|
+
setCoordinates(newCoords);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const onMoved = () => {
|
|
135
|
+
setAnnotationMode(AnnotationMode.VIEW);
|
|
136
|
+
|
|
137
|
+
// moving finished - send event to canvas
|
|
138
|
+
onAnnoChanged({
|
|
139
|
+
...scaledAnnotation,
|
|
140
|
+
coordinates: coordinatesRef.current,
|
|
141
|
+
});
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
useEffect(() => {
|
|
145
|
+
onAnnotationModeChange(annotationMode);
|
|
146
|
+
}, [annotationMode]);
|
|
147
|
+
|
|
148
|
+
// apply coordinate changes from sia (e.g. out of image fixes)
|
|
149
|
+
// ignore outside changes while creating annotation
|
|
150
|
+
useEffect(() => {
|
|
151
|
+
if (annotationMode === AnnotationMode.CREATE) return;
|
|
152
|
+
setCoordinates(scaledAnnotation.coordinates);
|
|
153
|
+
}, [scaledAnnotation]);
|
|
154
|
+
|
|
155
|
+
const renderAnno = () => {
|
|
156
|
+
switch (scaledAnnotation.type) {
|
|
157
|
+
case AnnotationTool.Point:
|
|
158
|
+
return (
|
|
159
|
+
<PointTool
|
|
160
|
+
isSelected={isSelected}
|
|
161
|
+
annotationSettings={annotationSettings}
|
|
162
|
+
coordinates={coordinates[0]}
|
|
163
|
+
pageToStageOffset={pageToStageOffset}
|
|
164
|
+
svgScale={svgScale}
|
|
165
|
+
style={annotationStyle}
|
|
166
|
+
onMoving={(newPoint: Point) => {
|
|
167
|
+
setAnnotationMode(AnnotationMode.MOVE);
|
|
168
|
+
setCoordinates([newPoint]);
|
|
169
|
+
}}
|
|
170
|
+
onMoved={onMoved}
|
|
171
|
+
onIsDraggingStateChanged={setIsDragging}
|
|
172
|
+
/>
|
|
173
|
+
);
|
|
174
|
+
case AnnotationTool.Line:
|
|
175
|
+
return (
|
|
176
|
+
<Line
|
|
177
|
+
annotationSettings={annotationSettings}
|
|
178
|
+
coordinates={coordinates}
|
|
179
|
+
isSelected={isSelected}
|
|
180
|
+
pageToStageOffset={pageToStageOffset}
|
|
181
|
+
annotationMode={annotationMode}
|
|
182
|
+
setAnnotationMode={setAnnotationMode}
|
|
183
|
+
svgScale={svgScale}
|
|
184
|
+
style={annotationStyle}
|
|
185
|
+
onAddNode={changeAnnoCoords}
|
|
186
|
+
onDeleteNode={changeAnnoCoords}
|
|
187
|
+
onMoving={onMoving}
|
|
188
|
+
onMoved={onMoved}
|
|
189
|
+
onIsDraggingStateChanged={setIsDragging}
|
|
190
|
+
onFinishAnnoCreate={finishAnnoCreate}
|
|
191
|
+
/>
|
|
192
|
+
);
|
|
193
|
+
case AnnotationTool.BBox:
|
|
194
|
+
return (
|
|
195
|
+
<BBox
|
|
196
|
+
annotationMode={annotationMode}
|
|
197
|
+
annotationSettings={annotationSettings}
|
|
198
|
+
startCoords={coordinates[0]}
|
|
199
|
+
endCoords={coordinates[1]}
|
|
200
|
+
isSelected={isSelected}
|
|
201
|
+
pageToStageOffset={pageToStageOffset}
|
|
202
|
+
style={annotationStyle}
|
|
203
|
+
svgScale={svgScale}
|
|
204
|
+
onDeleteNode={() => {
|
|
205
|
+
console.log("TODO");
|
|
206
|
+
}}
|
|
207
|
+
onIsDraggingStateChanged={setIsDragging}
|
|
208
|
+
onFinishAnnoCreate={finishAnnoCreate}
|
|
209
|
+
onMoving={onMoving}
|
|
210
|
+
onMoved={onMoved}
|
|
211
|
+
/>
|
|
212
|
+
);
|
|
213
|
+
case AnnotationTool.Polygon:
|
|
214
|
+
return (
|
|
215
|
+
<Polygon
|
|
216
|
+
annotationSettings={annotationSettings}
|
|
217
|
+
coordinates={coordinates}
|
|
218
|
+
isSelected={isSelected}
|
|
219
|
+
isDisabled={isDisabled}
|
|
220
|
+
pageToStageOffset={pageToStageOffset}
|
|
221
|
+
annotationMode={annotationMode}
|
|
222
|
+
setAnnotationMode={setAnnotationMode}
|
|
223
|
+
svgScale={svgScale}
|
|
224
|
+
style={annotationStyle}
|
|
225
|
+
onAddNode={changeAnnoCoords}
|
|
226
|
+
onDeleteNode={changeAnnoCoords}
|
|
227
|
+
onMoving={onMoving}
|
|
228
|
+
onMoved={onMoved}
|
|
229
|
+
onIsDraggingStateChanged={setIsDragging}
|
|
230
|
+
onFinishAnnoCreate={finishAnnoCreate}
|
|
231
|
+
/>
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
return (
|
|
237
|
+
<g
|
|
238
|
+
// visibility={this.state.visibility}
|
|
239
|
+
// onClick={(e) => this.onClick(e)}
|
|
240
|
+
// onMouseDown={(e) => this.onMouseDown(e)}
|
|
241
|
+
// onContextMenu={(e) => this.onContextMenu(e)}
|
|
242
|
+
onClick={(e) => {
|
|
243
|
+
e.stopPropagation();
|
|
244
|
+
onAction(scaledAnnotation, CanvasAction.ANNO_SELECTED);
|
|
245
|
+
}}
|
|
246
|
+
>
|
|
247
|
+
{!isDragging && annotationMode !== AnnotationMode.CREATE && (
|
|
248
|
+
<AnnoBar
|
|
249
|
+
annotationCoordinates={coordinates}
|
|
250
|
+
canLabel={annotationSettings.canLabel}
|
|
251
|
+
labels={possibleLabels}
|
|
252
|
+
color={color}
|
|
253
|
+
isSelected={isSelected}
|
|
254
|
+
selectedLabelIds={scaledAnnotation.labelIds}
|
|
255
|
+
style={annotationStyle}
|
|
256
|
+
svgScale={svgScale}
|
|
257
|
+
onLabelIconClicked={onLabelIconClicked}
|
|
258
|
+
/>
|
|
259
|
+
)}
|
|
260
|
+
{renderAnno()}
|
|
261
|
+
</g>
|
|
262
|
+
);
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
export default AnnotationComponent;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { CSSProperties, useEffect, useRef, useState } from "react";
|
|
2
|
+
import Label from "../../../models/Label";
|
|
3
|
+
import { Point } from "../../../types";
|
|
4
|
+
import transform from "../../../utils/transform2";
|
|
5
|
+
import DaviIcon from "./DaviIcon";
|
|
6
|
+
|
|
7
|
+
const DEFAULT_FONT_SIZE = 10;
|
|
8
|
+
const DEFAULT_RECT_HEIGHT = 15;
|
|
9
|
+
const TEXT_PADDING = 3;
|
|
10
|
+
|
|
11
|
+
type AnnoBarProps = {
|
|
12
|
+
annotationCoordinates: Point[];
|
|
13
|
+
canLabel: boolean;
|
|
14
|
+
color: string;
|
|
15
|
+
labels: Label[];
|
|
16
|
+
selectedLabelIds: number[];
|
|
17
|
+
isSelected: boolean;
|
|
18
|
+
svgScale: number;
|
|
19
|
+
style: CSSProperties;
|
|
20
|
+
onLabelIconClicked: (markerPosition: Point) => void;
|
|
21
|
+
};
|
|
22
|
+
const AnnoBar = ({
|
|
23
|
+
annotationCoordinates,
|
|
24
|
+
canLabel,
|
|
25
|
+
color,
|
|
26
|
+
labels,
|
|
27
|
+
selectedLabelIds = [],
|
|
28
|
+
isSelected,
|
|
29
|
+
svgScale,
|
|
30
|
+
style,
|
|
31
|
+
onLabelIconClicked,
|
|
32
|
+
}: AnnoBarProps) => {
|
|
33
|
+
const [barPosition, setBarPosition] = useState<Point>({ x: 0, y: 0 });
|
|
34
|
+
const [barWidth, setBarWidth] = useState<number>(0);
|
|
35
|
+
const [barHeight, setBarHeight] = useState<number>(0);
|
|
36
|
+
const [fontSize, setFontSize] = useState<number>(0);
|
|
37
|
+
const [labelText, setLabelText] = useState<string>("");
|
|
38
|
+
|
|
39
|
+
const textRef = useRef(null);
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
setLabelText(getLabelText());
|
|
43
|
+
}, [selectedLabelIds]);
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
// get the most top left point from the annotation
|
|
47
|
+
const topPoints: Point[] = transform.getTopPoint(annotationCoordinates);
|
|
48
|
+
const topLeftPoint: Point = transform.getMostLeftPoints(topPoints)[0];
|
|
49
|
+
|
|
50
|
+
const newBarPosition: Point = {
|
|
51
|
+
x: topLeftPoint.x + 7 / svgScale,
|
|
52
|
+
y: topLeftPoint.y - 10 / svgScale,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
setBarPosition(newBarPosition);
|
|
56
|
+
|
|
57
|
+
// calculate scaled font size
|
|
58
|
+
const newFontSize = Math.ceil(DEFAULT_FONT_SIZE / svgScale);
|
|
59
|
+
setFontSize(newFontSize);
|
|
60
|
+
|
|
61
|
+
setBarHeight(DEFAULT_RECT_HEIGHT / svgScale);
|
|
62
|
+
}, [svgScale]);
|
|
63
|
+
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
if (textRef === undefined) return;
|
|
66
|
+
|
|
67
|
+
// calculate size of box around label text
|
|
68
|
+
const textElement: DOMRect = textRef.current.getBoundingClientRect();
|
|
69
|
+
const textWidth: number = textElement.width;
|
|
70
|
+
const rectWidth = (textWidth + TEXT_PADDING) / svgScale;
|
|
71
|
+
|
|
72
|
+
setBarWidth(rectWidth);
|
|
73
|
+
}, [textRef, labelText, fontSize]);
|
|
74
|
+
|
|
75
|
+
const getLabelText = () => {
|
|
76
|
+
const selectedLabels: Label[] = labels.filter((label: Label) =>
|
|
77
|
+
selectedLabelIds.includes(label.id),
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const labelText = selectedLabels.map((l) => l.name).join(", ");
|
|
81
|
+
|
|
82
|
+
return labelText.length ? labelText : "no label";
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<g>
|
|
87
|
+
{isSelected && canLabel && (
|
|
88
|
+
<DaviIcon
|
|
89
|
+
x={barPosition.x - 33 / svgScale}
|
|
90
|
+
y={barPosition.y - 30 / svgScale}
|
|
91
|
+
color={color}
|
|
92
|
+
size={60 / svgScale}
|
|
93
|
+
onClick={() => onLabelIconClicked(barPosition)}
|
|
94
|
+
/>
|
|
95
|
+
)}
|
|
96
|
+
|
|
97
|
+
<rect
|
|
98
|
+
x={barPosition.x}
|
|
99
|
+
y={barPosition.y - 6 / (svgScale * 1.2)}
|
|
100
|
+
width={barWidth}
|
|
101
|
+
height={barHeight}
|
|
102
|
+
rx={5 / svgScale}
|
|
103
|
+
opacity="0.5"
|
|
104
|
+
style={style}
|
|
105
|
+
/>
|
|
106
|
+
<text
|
|
107
|
+
x={barPosition.x + 1 / svgScale}
|
|
108
|
+
y={barPosition.y + 6 / svgScale}
|
|
109
|
+
fill="white"
|
|
110
|
+
textAnchor="start"
|
|
111
|
+
alignmentBaseline="central"
|
|
112
|
+
ref={textRef}
|
|
113
|
+
fontSize={`${fontSize}pt`}
|
|
114
|
+
>
|
|
115
|
+
{labelText}
|
|
116
|
+
</text>
|
|
117
|
+
{/* This second rect is to prevent text from getting marked */}
|
|
118
|
+
<rect
|
|
119
|
+
x={barPosition.x}
|
|
120
|
+
y={barPosition.y - 6 / (svgScale * 1.2)}
|
|
121
|
+
width={barWidth}
|
|
122
|
+
height={DEFAULT_RECT_HEIGHT}
|
|
123
|
+
rx={5 / svgScale}
|
|
124
|
+
opacity="0.01"
|
|
125
|
+
style={style}
|
|
126
|
+
onContextMenu={(e) => e.preventDefault()}
|
|
127
|
+
/>
|
|
128
|
+
</g>
|
|
129
|
+
);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export default AnnoBar;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
type DaviIconProps = {
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
color: string;
|
|
5
|
+
size: number;
|
|
6
|
+
onClick?: () => void;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const DaviIcon = ({
|
|
10
|
+
x,
|
|
11
|
+
y,
|
|
12
|
+
color,
|
|
13
|
+
size = 60,
|
|
14
|
+
onClick = () => {},
|
|
15
|
+
}: DaviIconProps) => {
|
|
16
|
+
const scale: number = size / 1400;
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<g
|
|
20
|
+
transform={`translate(${x} ${y}) scale(${scale})`}
|
|
21
|
+
fill={color}
|
|
22
|
+
onClick={onClick}
|
|
23
|
+
>
|
|
24
|
+
<path
|
|
25
|
+
id="Maps"
|
|
26
|
+
d="M620.561,817.217c-1.568-3.62-3.771-7.101-4.611-10.885
|
|
27
|
+
c-24.452-109.811-74.341-207.569-139.215-298.675c-27.507-38.628-55.814-77.404-77.438-119.371
|
|
28
|
+
C324.363,242.85,402.696,58.71,574.209,26.508c145.509-27.32,282.953,75.871,296.543,222.773
|
|
29
|
+
c4.659,50.356-7.471,97.96-32.152,141.022c-27.812,48.526-58.75,95.364-90.073,141.758
|
|
30
|
+
c-50.917,75.411-91.062,155.558-113.421,244.091c-2.438,9.652-3.936,19.543-6.271,29.227c-0.992,4.104-3.023,7.961-4.584,11.93
|
|
31
|
+
C623.021,817.277,621.789,817.247,620.561,817.217z"
|
|
32
|
+
/>
|
|
33
|
+
<path
|
|
34
|
+
id="Text"
|
|
35
|
+
fillRule="evenodd"
|
|
36
|
+
clipRule="evenodd"
|
|
37
|
+
fill="#FFFFFF"
|
|
38
|
+
d="M724.709,250.898
|
|
39
|
+
c-0.055-3.974,0.047-7.949,0.033-11.923c-0.007-1.228-1.54-2.767-2.76-2.777c-2.428-0.021-4.862-0.015-7.29-0.015
|
|
40
|
+
c-22.466,0.001-44.934,0.005-67.404,0.009c-2.475,0.001-3.658,1.154-3.658,3.588c-0.001,47.365-0.001,94.73-0.001,142.096
|
|
41
|
+
c0,13.526,0,27.05,0.001,40.577c0,2.305,1.274,3.584,3.562,3.59c6.623,0.004,9.256-0.07,19.873,0.004
|
|
42
|
+
c10.618,0.072,14.146,8.543,14.146,14.189c-0.002,5.645-4.233,13.055-14.299,13.367c-13.135,0.061-26.277-0.02-39.414-0.014
|
|
43
|
+
c-14.575,0.004-29.146,0.004-43.722,0.02c-4.191-0.084-13.717-2.789-13.717-13.492s6.233-13.643,13.132-14.117
|
|
44
|
+
c4.979,0.037,13.802,0.057,20.701,0.049c1.695,0,3.11-1.537,3.11-3.361c0.003-13.912-0.004-27.823-0.004-41.736
|
|
45
|
+
c0-30.252,0-60.503,0.002-90.754c0.002-17.03,0.005-33.649,0.002-50.678c0-1.991-1.38-3.336-3.396-3.336
|
|
46
|
+
c-24.79,0-49.577,0-74.362,0c-2.133,0-3.38,1.22-3.383,3.308c-0.006,3.754-0.011,7.509,0.005,11.262
|
|
47
|
+
c-0.023,6.339-3.067,13.143-12.456,13.143c-9.389,0-14.337-4.647-14.915-13.23c-0.128-4.305,0.004-8.612,0.004-12.918
|
|
48
|
+
c0-8.392,0-16.781,0-25.173c0-0.321,0.228-5.156,4.091-9.277c3.864-4.122,8.014-3.822,8.445-3.822
|
|
49
|
+
c25.835,0.003,51.672,0.002,77.507,0.002c46.813,0.001,140.443,0.001,140.443,0.001s6.153-0.088,10.926-0.052
|
|
50
|
+
c0.3-0.008,5.713,0.363,9.178,3.994c3.46,3.631,3.046,8.407,3.046,8.635c-0.007,12.862,0.079,25.725-0.003,38.587
|
|
51
|
+
c-0.074,8.051-6.938,12.819-13.703,12.847C731.482,263.519,724.956,259.059,724.709,250.898z"
|
|
52
|
+
/>
|
|
53
|
+
</g>
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export default DaviIcon;
|