@searpent/react-image-annotate 2.0.0
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/.babelrc +6 -0
- package/.env +1 -0
- package/.flowconfig +2 -0
- package/.github/workflows/release-on-master.yml +32 -0
- package/.github/workflows/test.yml +16 -0
- package/.prettierrc +3 -0
- package/.releaserc.js +18 -0
- package/.storybook/addons.js +2 -0
- package/.storybook/config.js +16 -0
- package/LICENSE +21 -0
- package/README.md +101 -0
- package/package.json +93 -0
- package/public/favicon.ico +0 -0
- package/public/index.html +38 -0
- package/src/Annotator/bike-pic.png +0 -0
- package/src/Annotator/bike-pic2.png +0 -0
- package/src/Annotator/dab-keyframes.story.json +1 -0
- package/src/Annotator/index.js +206 -0
- package/src/Annotator/index.story.js +727 -0
- package/src/Annotator/poses.story.js +150 -0
- package/src/Annotator/reducers/combine-reducers.js +7 -0
- package/src/Annotator/reducers/convert-expanding-line-to-polygon.js +53 -0
- package/src/Annotator/reducers/fix-twisted.js +6 -0
- package/src/Annotator/reducers/general-reducer.js +914 -0
- package/src/Annotator/reducers/get-active-image.js +21 -0
- package/src/Annotator/reducers/get-implied-video-regions.js +115 -0
- package/src/Annotator/reducers/history-handler.js +60 -0
- package/src/Annotator/reducers/image-reducer.js +23 -0
- package/src/Annotator/reducers/video-reducer.js +85 -0
- package/src/Annotator/video.story.js +51 -0
- package/src/ClassSelectionMenu/index.js +108 -0
- package/src/Crosshairs/index.js +64 -0
- package/src/DebugSidebarBox/index.js +36 -0
- package/src/DemoSite/Editor.js +235 -0
- package/src/DemoSite/ErrorBoundaryDialog.js +34 -0
- package/src/DemoSite/index.js +41 -0
- package/src/DemoSite/index.story.js +10 -0
- package/src/DemoSite/simple-segmentation-example.json +572 -0
- package/src/FullImageSegmentationAnnotator/hard1.story.jpg +0 -0
- package/src/FullImageSegmentationAnnotator/hard2.story.jpg +0 -0
- package/src/FullImageSegmentationAnnotator/hard3.story.jpg +0 -0
- package/src/FullImageSegmentationAnnotator/index.js +7 -0
- package/src/FullImageSegmentationAnnotator/index.story.js +177 -0
- package/src/FullImageSegmentationAnnotator/orange.story.png +0 -0
- package/src/HighlightBox/index.js +143 -0
- package/src/HistorySidebarBox/index.js +78 -0
- package/src/ImageCanvas/dancing-man.story.jpg +0 -0
- package/src/ImageCanvas/index.js +488 -0
- package/src/ImageCanvas/index.story.js +214 -0
- package/src/ImageCanvas/mouse_mask.story.png +0 -0
- package/src/ImageCanvas/region-tools.js +171 -0
- package/src/ImageCanvas/seves_desk.story.jpg +0 -0
- package/src/ImageCanvas/styles.js +27 -0
- package/src/ImageCanvas/use-mouse.js +168 -0
- package/src/ImageCanvas/use-project-box.js +23 -0
- package/src/ImageCanvas/use-wasd-mode.js +50 -0
- package/src/ImageMask/index.js +127 -0
- package/src/ImageMask/load-image.js +32 -0
- package/src/ImageSelectorSidebarBox/index.js +54 -0
- package/src/KeyframeTimeline/get-time-string.js +25 -0
- package/src/KeyframeTimeline/index.js +223 -0
- package/src/KeyframesSelectorSidebarBox/index.js +93 -0
- package/src/LandingPage/content.md +57 -0
- package/src/LandingPage/github-markdown.css +964 -0
- package/src/LandingPage/index.js +147 -0
- package/src/MainLayout/icon-dictionary.js +79 -0
- package/src/MainLayout/index.js +420 -0
- package/src/MainLayout/index.story.js +240 -0
- package/src/MainLayout/styles.js +26 -0
- package/src/MainLayout/types.js +156 -0
- package/src/MainLayout/use-implied-video-regions.js +17 -0
- package/src/PointDistances/index.js +90 -0
- package/src/PreventScrollToParents/index.js +48 -0
- package/src/PreventScrollToParents/index.story.js +23 -0
- package/src/RegionLabel/index.js +201 -0
- package/src/RegionLabel/styles.js +51 -0
- package/src/RegionSelectAndTransformBoxes/index.js +234 -0
- package/src/RegionSelectorSidebarBox/index.js +216 -0
- package/src/RegionSelectorSidebarBox/styles.js +54 -0
- package/src/RegionShapes/index.js +236 -0
- package/src/RegionTags/index.js +130 -0
- package/src/SettingsDialog/index.js +58 -0
- package/src/SettingsProvider/index.js +44 -0
- package/src/Shortcuts/ShortcutField.js +44 -0
- package/src/Shortcuts/index.js +129 -0
- package/src/ShortcutsManager/index.js +162 -0
- package/src/Sidebar/index.js +117 -0
- package/src/SidebarBoxContainer/index.js +93 -0
- package/src/SmallToolButton/index.js +57 -0
- package/src/TagsSidebarBox/index.js +93 -0
- package/src/TaskDescriptionSidebarBox/index.js +43 -0
- package/src/Theme/index.js +36 -0
- package/src/VideoOrImageCanvasBackground/index.js +170 -0
- package/src/colors.js +32 -0
- package/src/hooks/use-event-callback.js +11 -0
- package/src/hooks/use-exclude-pattern.js +27 -0
- package/src/hooks/use-load-image.js +21 -0
- package/src/hooks/use-window-size.js +46 -0
- package/src/hooks/xpattern.js +1 -0
- package/src/hooks/xpattern.png +0 -0
- package/src/index.js +18 -0
- package/src/lib.js +7 -0
- package/src/screenshot.png +0 -0
- package/src/site.css +5 -0
- package/src/stories.js +2 -0
- package/src/utils/get-from-local-storage.js +7 -0
- package/src/utils/get-hotkey-help-text.js +11 -0
- package/src/utils/get-landmarks-with-transform.js +23 -0
- package/src/utils/set-in-local-storage.js +6 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
// @flow weak
|
|
2
|
+
|
|
3
|
+
import React, { useMemo, useState, useEffect } from "react"
|
|
4
|
+
import { styled } from "@mui/material/styles"
|
|
5
|
+
import { createTheme, ThemeProvider } from "@mui/material/styles"
|
|
6
|
+
import range from "lodash/range"
|
|
7
|
+
import * as colors from "@mui/material/colors"
|
|
8
|
+
import useMeasure from "react-use-measure"
|
|
9
|
+
import useEventCallback from "use-event-callback"
|
|
10
|
+
import { useRafState } from "react-use"
|
|
11
|
+
import getTimeString from "./get-time-string"
|
|
12
|
+
|
|
13
|
+
const theme = createTheme()
|
|
14
|
+
|
|
15
|
+
const Container = styled("div")(({ theme }) => ({
|
|
16
|
+
position: "relative",
|
|
17
|
+
display: "flex",
|
|
18
|
+
flexGrow: 1,
|
|
19
|
+
minWidth: 240,
|
|
20
|
+
height: 64,
|
|
21
|
+
marginLeft: 16,
|
|
22
|
+
marginRight: 16,
|
|
23
|
+
}))
|
|
24
|
+
|
|
25
|
+
const Tick = styled("div")(({ theme }) => ({
|
|
26
|
+
position: "absolute",
|
|
27
|
+
width: 2,
|
|
28
|
+
marginLeft: -1,
|
|
29
|
+
height: "100%",
|
|
30
|
+
backgroundColor: colors.grey[300],
|
|
31
|
+
bottom: 0,
|
|
32
|
+
}))
|
|
33
|
+
const TickText = styled("div")(({ theme }) => ({
|
|
34
|
+
position: "absolute",
|
|
35
|
+
userSelect: "none",
|
|
36
|
+
fontSize: 10,
|
|
37
|
+
color: colors.grey[600],
|
|
38
|
+
fontWeight: "bold",
|
|
39
|
+
bottom: 0,
|
|
40
|
+
}))
|
|
41
|
+
|
|
42
|
+
const PositionCursor = styled("div")(({ theme }) => ({
|
|
43
|
+
position: "absolute",
|
|
44
|
+
bottom: "calc(50% + 6px)",
|
|
45
|
+
fontSize: 10,
|
|
46
|
+
fontWeight: "bold",
|
|
47
|
+
color: "#fff",
|
|
48
|
+
display: "grid",
|
|
49
|
+
placeItems: "center",
|
|
50
|
+
width: 48,
|
|
51
|
+
marginLeft: -24,
|
|
52
|
+
borderRadius: 2,
|
|
53
|
+
height: 24,
|
|
54
|
+
backgroundColor: colors.blue[500],
|
|
55
|
+
userSelect: "none",
|
|
56
|
+
fontVariantNumeric: "tabular-nums",
|
|
57
|
+
|
|
58
|
+
"&::before": {
|
|
59
|
+
position: "absolute",
|
|
60
|
+
bottom: -6,
|
|
61
|
+
left: 24 - 8,
|
|
62
|
+
content: '""',
|
|
63
|
+
width: 0,
|
|
64
|
+
height: 0,
|
|
65
|
+
borderTop: `8px solid ${colors.blue[500]}`,
|
|
66
|
+
borderLeft: "8px solid transparent",
|
|
67
|
+
borderRight: "8px solid transparent",
|
|
68
|
+
},
|
|
69
|
+
}))
|
|
70
|
+
|
|
71
|
+
const KeyframeMarker = styled("div")(({ theme }) => ({
|
|
72
|
+
position: "absolute",
|
|
73
|
+
bottom: 8,
|
|
74
|
+
cursor: "pointer",
|
|
75
|
+
opacity: 0.75,
|
|
76
|
+
fontSize: 10,
|
|
77
|
+
fontWeight: "bold",
|
|
78
|
+
color: "#fff",
|
|
79
|
+
display: "grid",
|
|
80
|
+
placeItems: "center",
|
|
81
|
+
width: 16,
|
|
82
|
+
marginLeft: 0,
|
|
83
|
+
borderTopLeftRadius: 2,
|
|
84
|
+
borderTopRightRadius: 2,
|
|
85
|
+
height: 12,
|
|
86
|
+
marginLeft: -8,
|
|
87
|
+
backgroundColor: colors.red[500],
|
|
88
|
+
userSelect: "none",
|
|
89
|
+
fontVariantNumeric: "tabular-nums",
|
|
90
|
+
|
|
91
|
+
"&::before": {
|
|
92
|
+
position: "absolute",
|
|
93
|
+
bottom: -8,
|
|
94
|
+
left: 0,
|
|
95
|
+
content: '""',
|
|
96
|
+
width: 0,
|
|
97
|
+
height: 0,
|
|
98
|
+
borderTop: `8px solid ${colors.red[500]}`,
|
|
99
|
+
borderLeft: "8px solid transparent",
|
|
100
|
+
borderRight: "8px solid transparent",
|
|
101
|
+
},
|
|
102
|
+
}))
|
|
103
|
+
|
|
104
|
+
const min = 60000
|
|
105
|
+
const displayIntervalPairs = [
|
|
106
|
+
[50, 250],
|
|
107
|
+
[100, 500],
|
|
108
|
+
[250, 1000],
|
|
109
|
+
[1000, 5000],
|
|
110
|
+
[5000, 30000],
|
|
111
|
+
[10000, min],
|
|
112
|
+
[30000, min * 2],
|
|
113
|
+
[min, min * 5],
|
|
114
|
+
[min * 5, min * 30],
|
|
115
|
+
[min * 10, min * 60],
|
|
116
|
+
[min * 30, min * 60 * 3],
|
|
117
|
+
[min * 60, min * 60 * 5],
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
const getMajorInterval = (duration) => {
|
|
121
|
+
for (const [minor, major] of displayIntervalPairs) {
|
|
122
|
+
if (duration / major < 6 && duration / major > 2) {
|
|
123
|
+
return [minor, major]
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return [duration / 4, duration]
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export default ({
|
|
130
|
+
currentTime = 0,
|
|
131
|
+
duration,
|
|
132
|
+
onChangeCurrentTime,
|
|
133
|
+
keyframes,
|
|
134
|
+
}) => {
|
|
135
|
+
const [ref, bounds] = useMeasure()
|
|
136
|
+
const [instantCurrentTime, changeInstantCurrentTime] = useState(currentTime)
|
|
137
|
+
const [draggingTime, changeDraggingTime] = useRafState(false)
|
|
138
|
+
const keyframeTimes = Object.keys(keyframes || {})
|
|
139
|
+
.map((t) => parseInt(t))
|
|
140
|
+
.filter((t) => !isNaN(t))
|
|
141
|
+
.sort((a, b) => a - b)
|
|
142
|
+
|
|
143
|
+
useEffect(() => {
|
|
144
|
+
if (currentTime !== instantCurrentTime) {
|
|
145
|
+
changeInstantCurrentTime(currentTime)
|
|
146
|
+
}
|
|
147
|
+
}, [currentTime])
|
|
148
|
+
|
|
149
|
+
const [minorInterval, majorInterval] = useMemo(
|
|
150
|
+
() => getMajorInterval(duration),
|
|
151
|
+
[duration]
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
const onMouseMove = useEventCallback((e) => {
|
|
155
|
+
if (draggingTime) {
|
|
156
|
+
const px = (e.clientX - bounds.left) / bounds.width
|
|
157
|
+
changeInstantCurrentTime(
|
|
158
|
+
Math.min(duration, Math.max(0, Math.floor(px * duration)))
|
|
159
|
+
)
|
|
160
|
+
}
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
const onMouseUp = useEventCallback((e) => {
|
|
164
|
+
changeDraggingTime(false)
|
|
165
|
+
const px = (e.clientX - bounds.left) / bounds.width
|
|
166
|
+
const newTime = Math.min(duration, Math.max(0, Math.floor(px * duration)))
|
|
167
|
+
changeInstantCurrentTime(newTime)
|
|
168
|
+
onChangeCurrentTime(newTime)
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
// TODO skeleton
|
|
172
|
+
if (!duration) return null
|
|
173
|
+
|
|
174
|
+
return (
|
|
175
|
+
<ThemeProvider theme={theme}>
|
|
176
|
+
<Container onMouseMove={onMouseMove} onMouseUp={onMouseUp} ref={ref}>
|
|
177
|
+
{range(0, duration, majorInterval).map((a) => (
|
|
178
|
+
<>
|
|
179
|
+
<Tick
|
|
180
|
+
key={a}
|
|
181
|
+
style={{ left: (a / duration) * bounds.width, height: "50%" }}
|
|
182
|
+
/>
|
|
183
|
+
<TickText
|
|
184
|
+
style={{
|
|
185
|
+
left: (a / duration) * bounds.width + 8,
|
|
186
|
+
bottom: "calc(50% - 12px)",
|
|
187
|
+
}}
|
|
188
|
+
>
|
|
189
|
+
{getTimeString(a)}
|
|
190
|
+
</TickText>
|
|
191
|
+
</>
|
|
192
|
+
))}
|
|
193
|
+
{range(0, duration, minorInterval)
|
|
194
|
+
.filter((a) => !Number.isInteger(a / majorInterval))
|
|
195
|
+
.map((a) => (
|
|
196
|
+
<Tick
|
|
197
|
+
key={a}
|
|
198
|
+
style={{
|
|
199
|
+
left: (a / duration) * bounds.width,
|
|
200
|
+
height: "25%",
|
|
201
|
+
}}
|
|
202
|
+
/>
|
|
203
|
+
))}
|
|
204
|
+
{keyframeTimes.map((kt) => (
|
|
205
|
+
<KeyframeMarker
|
|
206
|
+
onClick={() => onChangeCurrentTime(kt)}
|
|
207
|
+
key={kt}
|
|
208
|
+
style={{ left: (kt / duration) * bounds.width }}
|
|
209
|
+
/>
|
|
210
|
+
))}
|
|
211
|
+
<PositionCursor
|
|
212
|
+
onMouseDown={(e) => changeDraggingTime(true)}
|
|
213
|
+
style={{
|
|
214
|
+
cursor: draggingTime ? "grabbing" : "grab",
|
|
215
|
+
left: (instantCurrentTime / duration) * bounds.width,
|
|
216
|
+
}}
|
|
217
|
+
>
|
|
218
|
+
{getTimeString(instantCurrentTime)}
|
|
219
|
+
</PositionCursor>
|
|
220
|
+
</Container>
|
|
221
|
+
</ThemeProvider>
|
|
222
|
+
)
|
|
223
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// @flow weak
|
|
2
|
+
|
|
3
|
+
import React from "react"
|
|
4
|
+
import AddLocationIcon from "@mui/icons-material/AddLocation"
|
|
5
|
+
import SidebarBoxContainer from "../SidebarBoxContainer"
|
|
6
|
+
import * as colors from "@mui/material/colors"
|
|
7
|
+
import getTimeString from "../KeyframeTimeline/get-time-string.js"
|
|
8
|
+
import TrashIcon from "@mui/icons-material/Delete"
|
|
9
|
+
import { styled } from "@mui/material/styles"
|
|
10
|
+
import { createTheme, ThemeProvider } from "@mui/material/styles"
|
|
11
|
+
|
|
12
|
+
const theme = createTheme()
|
|
13
|
+
const KeyframeRow = styled("div")(({ theme }) => ({
|
|
14
|
+
cursor: "pointer",
|
|
15
|
+
display: "flex",
|
|
16
|
+
alignItems: "center",
|
|
17
|
+
padding: 8,
|
|
18
|
+
fontSize: 14,
|
|
19
|
+
color: colors.grey[700],
|
|
20
|
+
"&.current": {
|
|
21
|
+
backgroundColor: colors.blue[100],
|
|
22
|
+
},
|
|
23
|
+
"&:hover": {
|
|
24
|
+
backgroundColor: colors.grey[100],
|
|
25
|
+
},
|
|
26
|
+
"& .time": {
|
|
27
|
+
flexGrow: 1,
|
|
28
|
+
fontWeight: "bold",
|
|
29
|
+
"& .regionCount": {
|
|
30
|
+
marginLeft: 8,
|
|
31
|
+
fontWeight: "normal",
|
|
32
|
+
color: colors.grey[500],
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
"& .trash": {
|
|
36
|
+
"& .icon": {
|
|
37
|
+
fontSize: 18,
|
|
38
|
+
color: colors.grey[600],
|
|
39
|
+
transition: "transform 80ms",
|
|
40
|
+
"&:hover": {
|
|
41
|
+
color: colors.grey[800],
|
|
42
|
+
transform: "scale(1.25,1.25)",
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
}))
|
|
47
|
+
|
|
48
|
+
const KeyframesSelectorSidebarBox = ({
|
|
49
|
+
currentVideoTime,
|
|
50
|
+
keyframes,
|
|
51
|
+
onChangeVideoTime,
|
|
52
|
+
onDeleteKeyframe,
|
|
53
|
+
}) => {
|
|
54
|
+
const keyframeTimes = Object.keys(keyframes).map((t) => parseInt(t))
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<ThemeProvider theme={theme}>
|
|
58
|
+
<SidebarBoxContainer
|
|
59
|
+
title="Keyframes"
|
|
60
|
+
subTitle=""
|
|
61
|
+
icon={<AddLocationIcon style={{ color: colors.grey[700] }} />}
|
|
62
|
+
expandedByDefault
|
|
63
|
+
>
|
|
64
|
+
{keyframeTimes.map((t) => (
|
|
65
|
+
<KeyframeRow
|
|
66
|
+
fullWidth
|
|
67
|
+
key={t}
|
|
68
|
+
className={currentVideoTime === t ? "current" : ""}
|
|
69
|
+
onClick={() => onChangeVideoTime(t)}
|
|
70
|
+
>
|
|
71
|
+
<div className="time">
|
|
72
|
+
{getTimeString(t, 2)}
|
|
73
|
+
<span className="regionCount">
|
|
74
|
+
({(keyframes[t]?.regions || []).length})
|
|
75
|
+
</span>
|
|
76
|
+
</div>
|
|
77
|
+
<div className="trash">
|
|
78
|
+
<TrashIcon
|
|
79
|
+
onClick={(e) => {
|
|
80
|
+
onDeleteKeyframe(t)
|
|
81
|
+
e.stopPropagation()
|
|
82
|
+
}}
|
|
83
|
+
className="icon"
|
|
84
|
+
/>
|
|
85
|
+
</div>
|
|
86
|
+
</KeyframeRow>
|
|
87
|
+
))}
|
|
88
|
+
</SidebarBoxContainer>
|
|
89
|
+
</ThemeProvider>
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export default KeyframesSelectorSidebarBox
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Features
|
|
2
|
+
|
|
3
|
+
- Simple input/output format
|
|
4
|
+
- Bounding Box, Point and Polygon Annotation
|
|
5
|
+
- Zooming, Scaling, Panning
|
|
6
|
+
- Multiple Images
|
|
7
|
+
- Cursor Crosshair
|
|
8
|
+
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
# Usage
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install react-image-annotate
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Basic Example
|
|
20
|
+
|
|
21
|
+
```javascript
|
|
22
|
+
import ReactImageAnnotate from "react-image-annotate"
|
|
23
|
+
|
|
24
|
+
const App = () => (
|
|
25
|
+
<ReactImageAnnotate
|
|
26
|
+
selectedImage="https://example.com/image1.png"
|
|
27
|
+
taskDescription="# Draw region around each animal."
|
|
28
|
+
images={[{ src: "https://example.com/image1.png", name: "Image 1" }]}
|
|
29
|
+
regionClsList={["Dog", "Cat"]}
|
|
30
|
+
enabledTools={["create-box"]}
|
|
31
|
+
/>
|
|
32
|
+
)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
# Props
|
|
36
|
+
|
|
37
|
+
All of the following properties can be defined on the `ReactImageAnnotate` component...
|
|
38
|
+
|
|
39
|
+
| Prop | Type (\* = required) | Description | Default |
|
|
40
|
+
| ------------------------ | ------------------------------------------------ | --------------------------------------------------------------------------------------- | ------------- |
|
|
41
|
+
| `taskDescription` | \*`string` | Markdown description for what to do in the image. | |
|
|
42
|
+
| `allowedArea` | `{ x: number, y: number, w: number, h: number }` | Area that is available for annotation. | Entire image. |
|
|
43
|
+
| `regionTagList` | `Array<string>` | Allowed "tags" (mutually inclusive classifications) for regions. | |
|
|
44
|
+
| `regionClsList` | `Array<string>` | Allowed "classes" (mutually exclusive classifications) for regions. | |
|
|
45
|
+
| `imageTagList` | `Array<string>` | Allowed tags for entire image. | |
|
|
46
|
+
| `imageClsList` | `Array<string>` | Allowed classes for entire image. | |
|
|
47
|
+
| `enabledTools` | `Array<string>` | Tools allowed to be used. e.g. "select", "create-point", "create-box", "create-polygon" | Everything. |
|
|
48
|
+
| `showTags` | `boolean` | Show tags and allow tags on regions. | `true` |
|
|
49
|
+
| `selectedImage` | `string` | URL of initially selected image. | |
|
|
50
|
+
| `images` | `Array<Image>` | Array of images to load into annotator | |
|
|
51
|
+
| `showPointDistances` | `boolean` | Show distances between points. | `false` |
|
|
52
|
+
| `pointDistancePrecision` | `number` | Precision on displayed points (e.g. 3 => 0.123) | |
|
|
53
|
+
| `onExit` | `MainLayoutState => any` | Called when "Save" is called. | |
|
|
54
|
+
|
|
55
|
+
# Sponsors
|
|
56
|
+
|
|
57
|
+
[](https://wao.ai)
|