@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,235 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import React, { useState } from "react"
|
|
4
|
+
import Button from "@mui/material/Button"
|
|
5
|
+
import { makeStyles } from "@mui/styles"
|
|
6
|
+
import { createTheme, ThemeProvider } from "@mui/material/styles"
|
|
7
|
+
import Select from "react-select"
|
|
8
|
+
import Code from "react-syntax-highlighter"
|
|
9
|
+
import Dialog from "@mui/material/Dialog"
|
|
10
|
+
import DialogTitle from "@mui/material/DialogTitle"
|
|
11
|
+
import DialogContent from "@mui/material/DialogContent"
|
|
12
|
+
import DialogActions from "@mui/material/DialogActions"
|
|
13
|
+
import MonacoEditor from "react-monaco-editor"
|
|
14
|
+
|
|
15
|
+
const theme = createTheme()
|
|
16
|
+
const useStyles = makeStyles((theme) => ({
|
|
17
|
+
editBar: {
|
|
18
|
+
padding: 10,
|
|
19
|
+
borderBottom: "1px solid #ccc",
|
|
20
|
+
backgroundColor: "#f8f8f8",
|
|
21
|
+
display: "flex",
|
|
22
|
+
alignItems: "center",
|
|
23
|
+
"& .button": { margin: 5 },
|
|
24
|
+
},
|
|
25
|
+
select: { width: 240, fontSize: 14 },
|
|
26
|
+
contentArea: {
|
|
27
|
+
padding: 10,
|
|
28
|
+
},
|
|
29
|
+
specificationArea: {
|
|
30
|
+
padding: 10,
|
|
31
|
+
},
|
|
32
|
+
}))
|
|
33
|
+
|
|
34
|
+
const loadSavedInput = () => {
|
|
35
|
+
try {
|
|
36
|
+
return JSON.parse(window.localStorage.getItem("customInput") || "{}")
|
|
37
|
+
} catch (e) {
|
|
38
|
+
return {}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const examples = {
|
|
43
|
+
"Simple Bounding Box": () => ({
|
|
44
|
+
taskDescription:
|
|
45
|
+
"Annotate each image according to this _markdown_ specification.",
|
|
46
|
+
// regionTagList: [],
|
|
47
|
+
// regionClsList: ["hotdog"],
|
|
48
|
+
regionTagList: ["has-bun"],
|
|
49
|
+
regionClsList: ["hotdog", "not-hotdog"],
|
|
50
|
+
enabledTools: ["select", "create-box"],
|
|
51
|
+
// showTags: true,
|
|
52
|
+
images: [
|
|
53
|
+
{
|
|
54
|
+
src: "https://images.unsplash.com/photo-1496905583330-eb54c7e5915a?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=634&q=80",
|
|
55
|
+
name: "hot-dogs-1",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
src: "https://www.bianchi.com/wp-content/uploads/2019/07/YPB17I555K.jpg",
|
|
59
|
+
name: "bianchi-oltre-xr4",
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
allowComments: true,
|
|
63
|
+
}),
|
|
64
|
+
"Simple Segmentation": () => ({
|
|
65
|
+
taskDescription:
|
|
66
|
+
"Annotate each image according to this _markdown_ specification.",
|
|
67
|
+
regionClsList: ["car", "truck"],
|
|
68
|
+
enabledTools: ["select", "create-polygon"],
|
|
69
|
+
images: [
|
|
70
|
+
{
|
|
71
|
+
src: "https://images.unsplash.com/photo-1561518776-e76a5e48f731?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=750&q=80",
|
|
72
|
+
name: "car-image-1",
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
}),
|
|
76
|
+
Custom: () => loadSavedInput(),
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const Editor = ({ onOpenAnnotator, lastOutput }: any) => {
|
|
80
|
+
const c = useStyles()
|
|
81
|
+
const [currentError, changeCurrentError] = useState()
|
|
82
|
+
const [selectedExample, changeSelectedExample] = useState(
|
|
83
|
+
window.localStorage.getItem("customInput")
|
|
84
|
+
? "Custom"
|
|
85
|
+
: "Simple Bounding Box"
|
|
86
|
+
)
|
|
87
|
+
const [outputDialogOpen, changeOutputOpen] = useState(false)
|
|
88
|
+
const [currentJSONValue, changeCurrentJSONValue] = useState(
|
|
89
|
+
JSON.stringify(examples[selectedExample](), null, " ")
|
|
90
|
+
)
|
|
91
|
+
return (
|
|
92
|
+
<ThemeProvider theme={theme}>
|
|
93
|
+
<div>
|
|
94
|
+
<div className={c.editBar}>
|
|
95
|
+
<h3>React Image Annotate</h3>
|
|
96
|
+
<div style={{ flexGrow: 1 }} />
|
|
97
|
+
<div>
|
|
98
|
+
<div style={{ display: "inline-flex" }}>
|
|
99
|
+
<Select
|
|
100
|
+
className={c.select}
|
|
101
|
+
value={{ label: selectedExample, value: selectedExample }}
|
|
102
|
+
options={Object.keys(examples).map((s) => ({
|
|
103
|
+
label: s,
|
|
104
|
+
value: s,
|
|
105
|
+
}))}
|
|
106
|
+
onChange={(selectedOption) => {
|
|
107
|
+
changeSelectedExample(selectedOption.value)
|
|
108
|
+
|
|
109
|
+
changeCurrentJSONValue(
|
|
110
|
+
JSON.stringify(
|
|
111
|
+
selectedOption.value === "Custom"
|
|
112
|
+
? loadSavedInput()
|
|
113
|
+
: examples[selectedOption.value](),
|
|
114
|
+
null,
|
|
115
|
+
" "
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
}}
|
|
119
|
+
/>
|
|
120
|
+
</div>
|
|
121
|
+
<Button
|
|
122
|
+
className="button"
|
|
123
|
+
disabled={!lastOutput}
|
|
124
|
+
onClick={() => changeOutputOpen(true)}
|
|
125
|
+
>
|
|
126
|
+
View Output
|
|
127
|
+
</Button>
|
|
128
|
+
<Button
|
|
129
|
+
className="button"
|
|
130
|
+
variant="outlined"
|
|
131
|
+
disabled={Boolean(currentError)}
|
|
132
|
+
onClick={() => {
|
|
133
|
+
onOpenAnnotator(
|
|
134
|
+
selectedExample === "Custom"
|
|
135
|
+
? loadSavedInput()
|
|
136
|
+
: examples[selectedExample]
|
|
137
|
+
)
|
|
138
|
+
}}
|
|
139
|
+
>
|
|
140
|
+
Open Annotator
|
|
141
|
+
</Button>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
<div
|
|
145
|
+
className={c.contentArea}
|
|
146
|
+
style={
|
|
147
|
+
currentError
|
|
148
|
+
? { border: "2px solid #f00" }
|
|
149
|
+
: { border: "2px solid #fff" }
|
|
150
|
+
}
|
|
151
|
+
>
|
|
152
|
+
<div>
|
|
153
|
+
<MonacoEditor
|
|
154
|
+
value={currentJSONValue}
|
|
155
|
+
language="javascript"
|
|
156
|
+
onChange={(code) => {
|
|
157
|
+
try {
|
|
158
|
+
window.localStorage.setItem(
|
|
159
|
+
"customInput",
|
|
160
|
+
JSON.stringify(JSON.parse(code))
|
|
161
|
+
)
|
|
162
|
+
changeCurrentError(null)
|
|
163
|
+
} catch (e) {
|
|
164
|
+
changeCurrentError(e.toString())
|
|
165
|
+
}
|
|
166
|
+
changeCurrentJSONValue(code)
|
|
167
|
+
}}
|
|
168
|
+
width="100%"
|
|
169
|
+
height="550px"
|
|
170
|
+
/>
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
<div className={c.specificationArea}>
|
|
174
|
+
<h2>React Image Annotate Format</h2>
|
|
175
|
+
<Code language="javascript">{`
|
|
176
|
+
{
|
|
177
|
+
taskDescription?: string, // markdown
|
|
178
|
+
regionTagList?: Array<string>,
|
|
179
|
+
regionClsList?: Array<string>,
|
|
180
|
+
imageTagList?: Array<string>,
|
|
181
|
+
imageClsList?: Array<string>,
|
|
182
|
+
// all tools are enabled by default
|
|
183
|
+
enabledTools?: Array< "select" | "create-point" | "create-box" | "create-polygon" | "create-line">,
|
|
184
|
+
selectedImage?: string, // initial selected image
|
|
185
|
+
images: Array<{
|
|
186
|
+
src: string,
|
|
187
|
+
thumbnailSrc?: string, // use this if you are using high-res images
|
|
188
|
+
name: string,
|
|
189
|
+
regions?: Array<{
|
|
190
|
+
id: string | number,
|
|
191
|
+
cls?: string,
|
|
192
|
+
color?: string,
|
|
193
|
+
tags?: Array<string>,
|
|
194
|
+
|
|
195
|
+
// Point
|
|
196
|
+
type: "point",
|
|
197
|
+
x: number, // [0-1] % of image width
|
|
198
|
+
y: number, // [0-1] % of image height
|
|
199
|
+
|
|
200
|
+
// Bounding Box
|
|
201
|
+
type: "box",
|
|
202
|
+
x: number, // [0-1] % of image width
|
|
203
|
+
y: number, // [0-1] % of image height
|
|
204
|
+
w: number, // [0-1] % of image width
|
|
205
|
+
h: number, // [0-1] % of image height
|
|
206
|
+
|
|
207
|
+
// Polygon
|
|
208
|
+
type: "polygon",
|
|
209
|
+
open?: boolean, // should last and first points be connected, default: true
|
|
210
|
+
points: Array<[number, number]> // [0-1] % of image width/height
|
|
211
|
+
}>
|
|
212
|
+
}>,
|
|
213
|
+
}
|
|
214
|
+
`}</Code>
|
|
215
|
+
</div>
|
|
216
|
+
<Dialog fullScreen open={outputDialogOpen}>
|
|
217
|
+
<DialogTitle>React Image Annotate Output</DialogTitle>
|
|
218
|
+
<DialogContent style={{ minWidth: 400 }}>
|
|
219
|
+
<MonacoEditor
|
|
220
|
+
value={JSON.stringify(lastOutput, null, " ")}
|
|
221
|
+
language="javascript"
|
|
222
|
+
width="100%"
|
|
223
|
+
height="550px"
|
|
224
|
+
/>
|
|
225
|
+
</DialogContent>
|
|
226
|
+
<DialogActions>
|
|
227
|
+
<Button onClick={() => changeOutputOpen(false)}>Close</Button>
|
|
228
|
+
</DialogActions>
|
|
229
|
+
</Dialog>
|
|
230
|
+
</div>
|
|
231
|
+
</ThemeProvider>
|
|
232
|
+
)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export default Editor
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import React, { Component } from "react"
|
|
4
|
+
import Dialog from "@mui/material/Dialog"
|
|
5
|
+
import Button from "@mui/material/Button"
|
|
6
|
+
import DialogTitle from "@mui/material/DialogTitle"
|
|
7
|
+
import DialogContent from "@mui/material/DialogContent"
|
|
8
|
+
import DialogActions from "@mui/material/DialogActions"
|
|
9
|
+
|
|
10
|
+
export default class ErrorBoundaryDialog extends Component {
|
|
11
|
+
state = { hasError: false, err: "" }
|
|
12
|
+
componentDidCatch(err, info) {
|
|
13
|
+
this.setState({
|
|
14
|
+
hasError: true,
|
|
15
|
+
err: err.toString() + "\n\n" + info.componentStack,
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
render() {
|
|
19
|
+
if (this.state.hasError) {
|
|
20
|
+
return (
|
|
21
|
+
<Dialog open={this.state.hasError} onClose={this.props.onClose}>
|
|
22
|
+
<DialogTitle>Error Loading Annotator</DialogTitle>
|
|
23
|
+
<DialogContent>
|
|
24
|
+
<pre>{this.state.err}</pre>
|
|
25
|
+
</DialogContent>
|
|
26
|
+
<DialogActions>
|
|
27
|
+
<Button onClick={this.props.onClose}>Close</Button>
|
|
28
|
+
</DialogActions>
|
|
29
|
+
</Dialog>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
return this.props.children
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import React, { useState } from "react"
|
|
3
|
+
import ReactDOM from "react-dom"
|
|
4
|
+
import Editor, { examples } from "./Editor"
|
|
5
|
+
import Annotator from "../Annotator"
|
|
6
|
+
import ErrorBoundaryDialog from "./ErrorBoundaryDialog.js"
|
|
7
|
+
|
|
8
|
+
export default () => {
|
|
9
|
+
const [annotatorOpen, changeAnnotatorOpen] = useState(false)
|
|
10
|
+
const [annotatorProps, changeAnnotatorProps] = useState(examples["Custom"]())
|
|
11
|
+
const [lastOutput, changeLastOutput] = useState()
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<div>
|
|
15
|
+
{annotatorOpen ? (
|
|
16
|
+
<ErrorBoundaryDialog
|
|
17
|
+
onClose={() => {
|
|
18
|
+
changeAnnotatorOpen(false)
|
|
19
|
+
}}
|
|
20
|
+
>
|
|
21
|
+
<Annotator
|
|
22
|
+
{...(annotatorProps: any)}
|
|
23
|
+
onExit={(output) => {
|
|
24
|
+
delete (output: any)["lastAction"]
|
|
25
|
+
changeLastOutput(output)
|
|
26
|
+
changeAnnotatorOpen(false)
|
|
27
|
+
}}
|
|
28
|
+
/>
|
|
29
|
+
</ErrorBoundaryDialog>
|
|
30
|
+
) : (
|
|
31
|
+
<Editor
|
|
32
|
+
lastOutput={lastOutput}
|
|
33
|
+
onOpenAnnotator={(props) => {
|
|
34
|
+
changeAnnotatorProps(props)
|
|
35
|
+
changeAnnotatorOpen(true)
|
|
36
|
+
}}
|
|
37
|
+
/>
|
|
38
|
+
)}
|
|
39
|
+
</div>
|
|
40
|
+
)
|
|
41
|
+
}
|