@searpent/react-image-annotate 2.0.75 → 2.0.76
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/package.json +1 -1
- 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/exampleImages.js +48 -0
- package/src/Annotator/examplePhotos.js +7603 -0
- package/src/Annotator/index.js +380 -0
- package/src/Annotator/index.story.js +877 -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/{Annotator → src/Annotator}/reducers/fix-twisted.js +5 -3
- package/src/Annotator/reducers/general-reducer.js +1228 -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 +112 -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/Editor/annotation-plugin/annotation.js +536 -0
- package/src/Editor/index.js +50 -0
- package/src/Editor/readOnly.js +21 -0
- package/{Editor → src/Editor}/tools.js +3 -2
- package/src/Errorer/index.js +13 -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/GroupSelectorSidebarBox/index.js +48 -0
- package/src/GroupsEditorSidebarBox/index.js +108 -0
- package/src/HelpSidebarBox/index.js +43 -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 +515 -0
- package/src/ImageCanvas/index.story.js +314 -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/{ImageCanvas → src/ImageCanvas}/styles.js +8 -12
- 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/Locker/index.js +13 -0
- package/src/MainLayout/RightSidebarItemsWrapper.js +21 -0
- package/src/MainLayout/icon-dictionary.js +79 -0
- package/src/MainLayout/index.js +564 -0
- package/src/MainLayout/index.story.js +240 -0
- package/{MainLayout → src/MainLayout}/styles.js +7 -6
- package/src/MainLayout/types.js +171 -0
- package/src/MainLayout/use-implied-video-regions.js +17 -0
- package/src/MetadataEditorSidebarBox/index.js +160 -0
- package/src/PageSelector/index.js +159 -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 +236 -0
- package/{RegionLabel → src/RegionLabel}/styles.js +15 -12
- package/src/RegionSelectAndTransformBoxes/index.js +236 -0
- package/src/RegionSelectorSidebarBox/index.js +220 -0
- package/{RegionSelectorSidebarBox → src/RegionSelectorSidebarBox}/styles.js +14 -13
- package/src/RegionShapes/index.js +254 -0
- package/src/RegionTags/index.js +136 -0
- package/src/SettingsDialog/index.js +58 -0
- package/src/SettingsProvider/index.js +57 -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-colors.js +75 -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/{hooks → src/hooks}/xpattern.js +1 -1
- 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/blocks-to-article.js +61 -0
- package/{utils → src/utils}/blocks-to-article.test.js +8 -5
- package/{utils → src/utils}/default-locked-until.js +1 -2
- package/{utils → src/utils}/filter-only-unique.js +1 -1
- 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/photosToImages.js +67 -0
- package/src/utils/regions-groups.js +19 -0
- package/src/utils/regions-to-blocks.js +16 -0
- package/src/utils/saveable-actions-enum.js +5 -0
- package/src/utils/set-in-local-storage.js +6 -0
- package/src/utils/sleep.js +3 -0
- package/src/utils/uuid-to-hash.js +5 -0
- package/Annotator/exampleImages.js +0 -41
- package/Annotator/examplePhotos.js +0 -6980
- package/Annotator/index.js +0 -417
- package/Annotator/reducers/combine-reducers.js +0 -14
- package/Annotator/reducers/convert-expanding-line-to-polygon.js +0 -73
- package/Annotator/reducers/general-reducer.js +0 -1430
- package/Annotator/reducers/get-active-image.js +0 -27
- package/Annotator/reducers/get-implied-video-regions.js +0 -180
- package/Annotator/reducers/history-handler.js +0 -38
- package/Annotator/reducers/image-reducer.js +0 -20
- package/Annotator/reducers/video-reducer.js +0 -88
- package/ClassSelectionMenu/index.js +0 -140
- package/Crosshairs/index.js +0 -53
- package/DebugSidebarBox/index.js +0 -20
- package/DemoSite/Editor.js +0 -194
- package/DemoSite/ErrorBoundaryDialog.js +0 -64
- package/DemoSite/index.js +0 -40
- package/Editor/annotation-plugin/annotation.js +0 -647
- package/Editor/index.js +0 -93
- package/Editor/readOnly.js +0 -73
- package/Errorer/index.js +0 -11
- package/FullImageSegmentationAnnotator/index.js +0 -7
- package/GroupSelectorSidebarBox/index.js +0 -63
- package/GroupsEditorSidebarBox/index.js +0 -138
- package/HelpSidebarBox/index.js +0 -58
- package/HighlightBox/index.js +0 -102
- package/HistorySidebarBox/index.js +0 -71
- package/ImageCanvas/index.js +0 -441
- package/ImageCanvas/region-tools.js +0 -165
- package/ImageCanvas/use-mouse.js +0 -180
- package/ImageCanvas/use-project-box.js +0 -27
- package/ImageCanvas/use-wasd-mode.js +0 -62
- package/ImageMask/index.js +0 -133
- package/ImageMask/load-image.js +0 -25
- package/ImageSelectorSidebarBox/index.js +0 -60
- package/KeyframeTimeline/get-time-string.js +0 -27
- package/KeyframeTimeline/index.js +0 -233
- package/KeyframesSelectorSidebarBox/index.js +0 -93
- package/LandingPage/index.js +0 -159
- package/Locker/index.js +0 -11
- package/MainLayout/RightSidebarItemsWrapper.js +0 -19
- package/MainLayout/icon-dictionary.js +0 -104
- package/MainLayout/index.js +0 -526
- package/MainLayout/types.js +0 -0
- package/MainLayout/use-implied-video-regions.js +0 -13
- package/MetadataEditorSidebarBox/index.js +0 -231
- package/PageSelector/index.js +0 -180
- package/PointDistances/index.js +0 -73
- package/PreventScrollToParents/index.js +0 -51
- package/RegionLabel/index.js +0 -232
- package/RegionSelectAndTransformBoxes/index.js +0 -169
- package/RegionSelectorSidebarBox/index.js +0 -254
- package/RegionShapes/index.js +0 -294
- package/RegionTags/index.js +0 -144
- package/SettingsDialog/index.js +0 -52
- package/SettingsProvider/index.js +0 -60
- package/Shortcuts/ShortcutField.js +0 -46
- package/Shortcuts/index.js +0 -133
- package/ShortcutsManager/index.js +0 -155
- package/Sidebar/index.js +0 -69
- package/SidebarBoxContainer/index.js +0 -93
- package/SmallToolButton/index.js +0 -42
- package/TagsSidebarBox/index.js +0 -105
- package/TaskDescriptionSidebarBox/index.js +0 -58
- package/Theme/index.js +0 -30
- package/VideoOrImageCanvasBackground/index.js +0 -151
- package/colors.js +0 -14
- package/hooks/use-colors.js +0 -97
- package/hooks/use-event-callback.js +0 -10
- package/hooks/use-exclude-pattern.js +0 -24
- package/hooks/use-load-image.js +0 -26
- package/hooks/use-window-size.js +0 -46
- package/index.js +0 -3
- package/lib.js +0 -3
- package/stories.js +0 -5
- package/utils/blocks-to-article.js +0 -60
- package/utils/get-from-local-storage.js +0 -7
- package/utils/get-hotkey-help-text.js +0 -9
- package/utils/get-landmarks-with-transform.js +0 -40
- package/utils/photosToImages.js +0 -85
- package/utils/regions-groups.js +0 -28
- package/utils/regions-to-blocks.js +0 -18
- package/utils/saveable-actions-enum.js +0 -3
- package/utils/set-in-local-storage.js +0 -3
- package/utils/sleep.js +0 -7
- package/utils/uuid-to-hash.js +0 -5
- /package/{Editor → src/Editor}/annotation-plugin/annotation.css +0 -0
- /package/{Errorer → src/Errorer}/errorer.css +0 -0
- /package/{Locker → src/Locker}/locker.css +0 -0
- /package/{PageSelector → src/PageSelector}/page-selector.css +0 -0
- /package/{utils → src/utils}/next-group-id.js +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { getIn } from "seamless-immutable"
|
|
2
|
+
|
|
3
|
+
export default (state) => {
|
|
4
|
+
let currentImageIndex = null,
|
|
5
|
+
pathToActiveImage,
|
|
6
|
+
activeImage
|
|
7
|
+
if (state.annotationType === "image") {
|
|
8
|
+
currentImageIndex = state.selectedImage
|
|
9
|
+
if (currentImageIndex === -1) {
|
|
10
|
+
currentImageIndex = null
|
|
11
|
+
activeImage = null
|
|
12
|
+
} else {
|
|
13
|
+
pathToActiveImage = ["images", currentImageIndex]
|
|
14
|
+
activeImage = getIn(state, pathToActiveImage)
|
|
15
|
+
}
|
|
16
|
+
} else if (state.annotationType === "video") {
|
|
17
|
+
pathToActiveImage = ["keyframes", state.currentVideoTime || 0]
|
|
18
|
+
activeImage = getIn(state, pathToActiveImage) || null
|
|
19
|
+
}
|
|
20
|
+
return { currentImageIndex, pathToActiveImage, activeImage }
|
|
21
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import type { Region } from "../../ImageCanvas/region-tools.js"
|
|
4
|
+
|
|
5
|
+
const emptyArr = []
|
|
6
|
+
|
|
7
|
+
export default (
|
|
8
|
+
keyframes: { [string | number]: { regions: Array<Region> } },
|
|
9
|
+
time: number
|
|
10
|
+
) => {
|
|
11
|
+
if (keyframes[time || 0]) {
|
|
12
|
+
return keyframes[time || 0].regions
|
|
13
|
+
}
|
|
14
|
+
// Get surrounding video keyframes
|
|
15
|
+
const keyframeTimes = Object.keys(keyframes)
|
|
16
|
+
.map((a) => parseInt(a))
|
|
17
|
+
.filter((a) => !isNaN(a))
|
|
18
|
+
if (keyframeTimes.length === 0) return emptyArr
|
|
19
|
+
keyframeTimes.sort((a, b) => a - b)
|
|
20
|
+
let nextKeyframeTimeIndex = keyframeTimes.findIndex((kt) => kt >= time)
|
|
21
|
+
if (nextKeyframeTimeIndex === -1) {
|
|
22
|
+
return (
|
|
23
|
+
keyframes[keyframeTimes[keyframeTimes.length - 1]].regions || emptyArr
|
|
24
|
+
)
|
|
25
|
+
} else if (nextKeyframeTimeIndex === 0) {
|
|
26
|
+
return emptyArr
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const t1 = keyframeTimes[nextKeyframeTimeIndex - 1]
|
|
30
|
+
const prevKeyframe = keyframes[t1]
|
|
31
|
+
const t2 = keyframeTimes[nextKeyframeTimeIndex]
|
|
32
|
+
const nextKeyframe = keyframes[t2]
|
|
33
|
+
|
|
34
|
+
const [prevRegionMap, nextRegionMap] = [{}, {}]
|
|
35
|
+
for (const region of prevKeyframe.regions) prevRegionMap[region.id] = region
|
|
36
|
+
for (const region of nextKeyframe.regions) nextRegionMap[region.id] = region
|
|
37
|
+
|
|
38
|
+
const impliedRegions = []
|
|
39
|
+
|
|
40
|
+
// Weighted time coefficients for linear transition
|
|
41
|
+
const w1 = (t2 - time) / (t2 - t1)
|
|
42
|
+
const w2 = 1 - w1
|
|
43
|
+
|
|
44
|
+
for (const regionId in prevRegionMap) {
|
|
45
|
+
const [prev, next] = [prevRegionMap[regionId], nextRegionMap[regionId]]
|
|
46
|
+
if (!next) {
|
|
47
|
+
impliedRegions.push({
|
|
48
|
+
...prev,
|
|
49
|
+
highlighted: false,
|
|
50
|
+
editingLabels: false,
|
|
51
|
+
})
|
|
52
|
+
continue
|
|
53
|
+
}
|
|
54
|
+
switch (prev.type) {
|
|
55
|
+
case "point": {
|
|
56
|
+
impliedRegions.push({
|
|
57
|
+
...prev,
|
|
58
|
+
highlighted: false,
|
|
59
|
+
editingLabels: false,
|
|
60
|
+
x: prev.x * w1 + next.x * w2,
|
|
61
|
+
y: prev.y * w1 + next.y * w2,
|
|
62
|
+
})
|
|
63
|
+
break
|
|
64
|
+
}
|
|
65
|
+
case "box": {
|
|
66
|
+
impliedRegions.push({
|
|
67
|
+
...prev,
|
|
68
|
+
highlighted: false,
|
|
69
|
+
editingLabels: false,
|
|
70
|
+
x: prev.x * w1 + next.x * w2,
|
|
71
|
+
y: prev.y * w1 + next.y * w2,
|
|
72
|
+
w: prev.w * w1 + next.w * w2,
|
|
73
|
+
h: prev.h * w1 + next.h * w2,
|
|
74
|
+
})
|
|
75
|
+
break
|
|
76
|
+
}
|
|
77
|
+
case "polygon": {
|
|
78
|
+
if (next.points.length === prev.points.length) {
|
|
79
|
+
impliedRegions.push({
|
|
80
|
+
...prev,
|
|
81
|
+
highlighted: false,
|
|
82
|
+
editingLabels: false,
|
|
83
|
+
points: prev.points.map((pp, i) => [
|
|
84
|
+
pp[0] * w1 + next.points[i][0] * w2,
|
|
85
|
+
pp[1] * w1 + next.points[i][1] * w2,
|
|
86
|
+
]),
|
|
87
|
+
})
|
|
88
|
+
} else {
|
|
89
|
+
impliedRegions.push(prev)
|
|
90
|
+
}
|
|
91
|
+
break
|
|
92
|
+
}
|
|
93
|
+
case "keypoints": {
|
|
94
|
+
const newPoints = {}
|
|
95
|
+
for (const [pointId, prevPoint] of Object.entries(prev.points)) {
|
|
96
|
+
newPoints[pointId] = {
|
|
97
|
+
x: prevPoint.x * w1 + next.points[pointId].x * w2,
|
|
98
|
+
y: prevPoint.y * w1 + next.points[pointId].y * w2,
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
impliedRegions.push({
|
|
102
|
+
...prev,
|
|
103
|
+
highlighted: false,
|
|
104
|
+
editingLabels: false,
|
|
105
|
+
points: newPoints,
|
|
106
|
+
})
|
|
107
|
+
break
|
|
108
|
+
}
|
|
109
|
+
default:
|
|
110
|
+
break
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return impliedRegions
|
|
115
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import type { MainLayoutState, Action } from "../../MainLayout/types"
|
|
4
|
+
import { setIn, updateIn, asMutable, without } from "seamless-immutable"
|
|
5
|
+
import moment from "moment"
|
|
6
|
+
|
|
7
|
+
const typesToSaveWithHistory = {
|
|
8
|
+
BEGIN_BOX_TRANSFORM: "Transform/Move Box",
|
|
9
|
+
BEGIN_MOVE_POINT: "Move Point",
|
|
10
|
+
DELETE_REGION: "Delete Region",
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const saveToHistory = (state: MainLayoutState, name: string) =>
|
|
14
|
+
updateIn(state, ["history"], (h) =>
|
|
15
|
+
[
|
|
16
|
+
{
|
|
17
|
+
time: moment().toDate(),
|
|
18
|
+
state: without(state, "history"),
|
|
19
|
+
name,
|
|
20
|
+
},
|
|
21
|
+
].concat((h || []).slice(0, 9))
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
export default (reducer) => {
|
|
25
|
+
return (state: MainLayoutState, action: Action) => {
|
|
26
|
+
const prevState = state
|
|
27
|
+
const nextState = reducer(state, action)
|
|
28
|
+
|
|
29
|
+
if (action.type === "RESTORE_HISTORY") {
|
|
30
|
+
if (state.history.length > 0) {
|
|
31
|
+
return setIn(
|
|
32
|
+
nextState.history[0].state,
|
|
33
|
+
["history"],
|
|
34
|
+
nextState.history.slice(1)
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
if (
|
|
39
|
+
prevState !== nextState &&
|
|
40
|
+
Object.keys(typesToSaveWithHistory).includes(action.type)
|
|
41
|
+
) {
|
|
42
|
+
return setIn(
|
|
43
|
+
nextState,
|
|
44
|
+
["history"],
|
|
45
|
+
[
|
|
46
|
+
{
|
|
47
|
+
time: moment().toDate(),
|
|
48
|
+
state: without(prevState, "history"),
|
|
49
|
+
name: typesToSaveWithHistory[action.type] || action.type,
|
|
50
|
+
},
|
|
51
|
+
]
|
|
52
|
+
.concat(nextState.history || [])
|
|
53
|
+
.slice(0, 9)
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return nextState
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
MainLayoutImageAnnotationState,
|
|
5
|
+
Action,
|
|
6
|
+
} from "../../MainLayout/types"
|
|
7
|
+
import { setIn } from "seamless-immutable"
|
|
8
|
+
import getActiveImage from "./get-active-image"
|
|
9
|
+
|
|
10
|
+
export default (state: MainLayoutImageAnnotationState, action: Action) => {
|
|
11
|
+
const { currentImageIndex, pathToActiveImage, activeImage } =
|
|
12
|
+
getActiveImage(state)
|
|
13
|
+
|
|
14
|
+
switch (action.type) {
|
|
15
|
+
case "IMAGE_OR_VIDEO_LOADED": {
|
|
16
|
+
return setIn(state, ["images", currentImageIndex, "pixelSize"], {
|
|
17
|
+
w: action.metadata.naturalWidth,
|
|
18
|
+
h: action.metadata.naturalHeight,
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return state
|
|
23
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
MainLayoutVideoAnnotationState,
|
|
5
|
+
Action,
|
|
6
|
+
} from "../../MainLayout/types"
|
|
7
|
+
import { setIn, without } from "seamless-immutable"
|
|
8
|
+
import getImpliedVideoRegions from "./get-implied-video-regions"
|
|
9
|
+
import { saveToHistory } from "./history-handler.js"
|
|
10
|
+
|
|
11
|
+
export default (state: MainLayoutVideoAnnotationState, action: Action) => {
|
|
12
|
+
const copyImpliedRegions = () => {
|
|
13
|
+
return setIn(
|
|
14
|
+
saveToHistory(state, "Add Keyframe"),
|
|
15
|
+
["keyframes", state.currentVideoTime || 0],
|
|
16
|
+
{
|
|
17
|
+
regions: getImpliedVideoRegions(
|
|
18
|
+
state.keyframes,
|
|
19
|
+
state.currentVideoTime
|
|
20
|
+
),
|
|
21
|
+
}
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
switch (action.type) {
|
|
26
|
+
case "IMAGE_OR_VIDEO_LOADED": {
|
|
27
|
+
const { duration } = action.metadata
|
|
28
|
+
if (typeof duration === "number") {
|
|
29
|
+
return setIn(state, ["videoDuration"], duration * 1000)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
case "HEADER_BUTTON_CLICKED": {
|
|
33
|
+
switch (action.buttonName.toLowerCase()) {
|
|
34
|
+
case "play":
|
|
35
|
+
return setIn(state, ["videoPlaying"], true)
|
|
36
|
+
case "pause":
|
|
37
|
+
return setIn(state, ["videoPlaying"], false)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
case "CHANGE_VIDEO_TIME": {
|
|
41
|
+
return setIn(state, ["currentVideoTime"], action.newTime)
|
|
42
|
+
}
|
|
43
|
+
case "CHANGE_VIDEO_PLAYING": {
|
|
44
|
+
return setIn(state, ["videoPlaying"], action.isPlaying)
|
|
45
|
+
}
|
|
46
|
+
case "DELETE_KEYFRAME": {
|
|
47
|
+
return setIn(state, ["keyframes"], without(state.keyframes, action.time))
|
|
48
|
+
}
|
|
49
|
+
default:
|
|
50
|
+
break
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Before the user modifies regions, copy the interpolated regions over to a
|
|
54
|
+
// new keyframe
|
|
55
|
+
if (!state.keyframes[state.currentVideoTime]) {
|
|
56
|
+
switch (action.type) {
|
|
57
|
+
case "BEGIN_BOX_TRANSFORM":
|
|
58
|
+
case "BEGIN_MOVE_POINT":
|
|
59
|
+
case "BEGIN_MOVE_KEYPOINT":
|
|
60
|
+
case "BEGIN_MOVE_POLYGON_POINT":
|
|
61
|
+
case "ADD_POLYGON_POINT":
|
|
62
|
+
case "SELECT_REGION":
|
|
63
|
+
case "CHANGE_REGION":
|
|
64
|
+
case "DELETE_REGION":
|
|
65
|
+
case "OPEN_REGION_EDITOR":
|
|
66
|
+
return copyImpliedRegions()
|
|
67
|
+
case "MOUSE_DOWN": {
|
|
68
|
+
switch (state.selectedTool) {
|
|
69
|
+
case "create-point":
|
|
70
|
+
case "create-polygon":
|
|
71
|
+
case "create-box":
|
|
72
|
+
case "create-keypoints":
|
|
73
|
+
return copyImpliedRegions()
|
|
74
|
+
default:
|
|
75
|
+
break
|
|
76
|
+
}
|
|
77
|
+
break
|
|
78
|
+
}
|
|
79
|
+
default:
|
|
80
|
+
break
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return state
|
|
85
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import React from "react"
|
|
4
|
+
|
|
5
|
+
import { storiesOf } from "@storybook/react"
|
|
6
|
+
import { action } from "@storybook/addon-actions"
|
|
7
|
+
import Annotator from "./"
|
|
8
|
+
|
|
9
|
+
storiesOf("Annotator(video)", module).add("Video Annotator", () => {
|
|
10
|
+
const props = {
|
|
11
|
+
regionClsList: ["valid", "invalid"],
|
|
12
|
+
enabledTools: ["select", "create-box", "create-polygon", "create-point"],
|
|
13
|
+
keyframes: {
|
|
14
|
+
0: {
|
|
15
|
+
regions: [
|
|
16
|
+
{
|
|
17
|
+
id: "910517662556203",
|
|
18
|
+
cls: "valid",
|
|
19
|
+
color: "#f44336",
|
|
20
|
+
type: "box",
|
|
21
|
+
x: 0.12195121951219515,
|
|
22
|
+
y: 0.28726287262872624,
|
|
23
|
+
w: 0.2606707317073171,
|
|
24
|
+
h: 0.4769647696476965,
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
2656: {
|
|
29
|
+
regions: [
|
|
30
|
+
{
|
|
31
|
+
id: "910517662556203",
|
|
32
|
+
cls: "valid",
|
|
33
|
+
color: "#f44336",
|
|
34
|
+
type: "box",
|
|
35
|
+
x: 0.13109756097560976,
|
|
36
|
+
y: 0.08672086720867206,
|
|
37
|
+
w: 0.3445121951219512,
|
|
38
|
+
h: 0.7913279132791328,
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
onExit: action("onExit"),
|
|
44
|
+
taskDescription: "",
|
|
45
|
+
videoName: "",
|
|
46
|
+
videoTime: 0,
|
|
47
|
+
videoSrc:
|
|
48
|
+
"https://s3.amazonaws.com/asset.workaround.online/SampleVideo_1280x720_1mb.mp4",
|
|
49
|
+
}
|
|
50
|
+
return <Annotator {...props} />
|
|
51
|
+
})
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import React, { useEffect } from "react"
|
|
2
|
+
import { styled } from "@mui/material/styles"
|
|
3
|
+
import { createTheme, ThemeProvider } from "@mui/material/styles"
|
|
4
|
+
import Box from "@mui/material/Box"
|
|
5
|
+
import * as muiColors from "@mui/material/colors"
|
|
6
|
+
import SidebarBoxContainer from "../SidebarBoxContainer"
|
|
7
|
+
import BallotIcon from "@mui/icons-material/Ballot"
|
|
8
|
+
import capitalize from "lodash/capitalize"
|
|
9
|
+
import classnames from "classnames"
|
|
10
|
+
import useColors from "../hooks/use-colors"
|
|
11
|
+
|
|
12
|
+
const theme = createTheme()
|
|
13
|
+
const LabelContainer = styled("div")(({ theme }) => ({
|
|
14
|
+
display: "flex",
|
|
15
|
+
paddingTop: 4,
|
|
16
|
+
paddingBottom: 4,
|
|
17
|
+
paddingLeft: 16,
|
|
18
|
+
paddingRight: 16,
|
|
19
|
+
alignItems: "center",
|
|
20
|
+
cursor: "pointer",
|
|
21
|
+
opacity: 0.7,
|
|
22
|
+
backgroundColor: "#fff",
|
|
23
|
+
"&:hover": {
|
|
24
|
+
opacity: 1,
|
|
25
|
+
},
|
|
26
|
+
"&.selected": {
|
|
27
|
+
opacity: 1,
|
|
28
|
+
fontWeight: "bold",
|
|
29
|
+
},
|
|
30
|
+
}))
|
|
31
|
+
const Circle = styled("div")(({ theme }) => ({
|
|
32
|
+
width: 12,
|
|
33
|
+
height: 12,
|
|
34
|
+
borderRadius: 12,
|
|
35
|
+
marginRight: 8,
|
|
36
|
+
}))
|
|
37
|
+
const Label = styled("div")(({ theme }) => ({
|
|
38
|
+
fontSize: 11,
|
|
39
|
+
}))
|
|
40
|
+
const DashSep = styled("div")(({ theme }) => ({
|
|
41
|
+
flexGrow: 1,
|
|
42
|
+
borderBottom: `2px dotted ${muiColors.grey[300]}`,
|
|
43
|
+
marginLeft: 8,
|
|
44
|
+
marginRight: 8,
|
|
45
|
+
}))
|
|
46
|
+
const Number = styled("div")(({ theme }) => ({
|
|
47
|
+
fontSize: 11,
|
|
48
|
+
textAlign: "center",
|
|
49
|
+
minWidth: 14,
|
|
50
|
+
paddingTop: 2,
|
|
51
|
+
paddingBottom: 2,
|
|
52
|
+
fontWeight: "bold",
|
|
53
|
+
color: muiColors.grey[700],
|
|
54
|
+
}))
|
|
55
|
+
|
|
56
|
+
export const ClassSelectionMenu = ({
|
|
57
|
+
selectedCls,
|
|
58
|
+
regionClsList,
|
|
59
|
+
onSelectCls,
|
|
60
|
+
}) => {
|
|
61
|
+
const { clsColor } = useColors()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
const keyMapping = {}
|
|
66
|
+
for (let i = 0; i < 9 && i < regionClsList.length; i++) {
|
|
67
|
+
keyMapping[i + 1] = () => onSelectCls(regionClsList[i])
|
|
68
|
+
}
|
|
69
|
+
const onKeyDown = (e) => {
|
|
70
|
+
if (keyMapping[e.key]) {
|
|
71
|
+
keyMapping[e.key]()
|
|
72
|
+
e.preventDefault()
|
|
73
|
+
e.stopPropagation()
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
window.addEventListener("keydown", onKeyDown)
|
|
77
|
+
return () => window.removeEventListener("keydown", onKeyDown)
|
|
78
|
+
}, [regionClsList, selectedCls])
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<ThemeProvider theme={theme}>
|
|
82
|
+
<SidebarBoxContainer
|
|
83
|
+
title="Classifications"
|
|
84
|
+
subTitle=""
|
|
85
|
+
icon={<BallotIcon style={{ color: muiColors.grey[700] }} />}
|
|
86
|
+
expandedByDefault
|
|
87
|
+
>
|
|
88
|
+
{regionClsList.map((label, index) => (
|
|
89
|
+
<LabelContainer
|
|
90
|
+
key={label}
|
|
91
|
+
className={classnames({ selected: label === selectedCls })}
|
|
92
|
+
onClick={() => onSelectCls(label)}
|
|
93
|
+
>
|
|
94
|
+
<Circle
|
|
95
|
+
style={{ backgroundColor: clsColor(label) }}
|
|
96
|
+
/>
|
|
97
|
+
<Label className={classnames({ selected: label === selectedCls })}>
|
|
98
|
+
{capitalize(label)}
|
|
99
|
+
</Label>
|
|
100
|
+
<DashSep />
|
|
101
|
+
<Number className={classnames({ selected: label === selectedCls })}>
|
|
102
|
+
{index < 9 ? `Key [${index + 1}]` : ""}
|
|
103
|
+
</Number>
|
|
104
|
+
</LabelContainer>
|
|
105
|
+
))}
|
|
106
|
+
<Box pb={2} />
|
|
107
|
+
</SidebarBoxContainer>
|
|
108
|
+
</ThemeProvider>
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export default ClassSelectionMenu
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import React, { Fragment, useEffect, useState } from "react"
|
|
4
|
+
|
|
5
|
+
export const Crosshairs = ({
|
|
6
|
+
mousePosition,
|
|
7
|
+
x,
|
|
8
|
+
y,
|
|
9
|
+
}: {
|
|
10
|
+
mousePosition: { current: { x: number, y: number } },
|
|
11
|
+
x?: number,
|
|
12
|
+
y?: number,
|
|
13
|
+
}) => {
|
|
14
|
+
const [forceRenderState, changeForceRenderState] = useState()
|
|
15
|
+
|
|
16
|
+
if (mousePosition) {
|
|
17
|
+
x = mousePosition.current.x
|
|
18
|
+
y = mousePosition.current.y
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (!mousePosition) return
|
|
23
|
+
const interval = setInterval(() => {
|
|
24
|
+
if (x !== mousePosition.current.x || y !== mousePosition.current.y) {
|
|
25
|
+
changeForceRenderState([
|
|
26
|
+
mousePosition.current.x,
|
|
27
|
+
mousePosition.current.y,
|
|
28
|
+
])
|
|
29
|
+
}
|
|
30
|
+
}, 10)
|
|
31
|
+
return () => clearInterval(interval)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<Fragment>
|
|
36
|
+
<div
|
|
37
|
+
style={{
|
|
38
|
+
position: "absolute",
|
|
39
|
+
height: "100%",
|
|
40
|
+
width: 1,
|
|
41
|
+
zIndex: 10,
|
|
42
|
+
backgroundColor: "#f00",
|
|
43
|
+
left: x,
|
|
44
|
+
pointerEvents: "none",
|
|
45
|
+
top: 0,
|
|
46
|
+
}}
|
|
47
|
+
/>
|
|
48
|
+
<div
|
|
49
|
+
style={{
|
|
50
|
+
position: "absolute",
|
|
51
|
+
width: "100%",
|
|
52
|
+
zIndex: 10,
|
|
53
|
+
height: 1,
|
|
54
|
+
backgroundColor: "#f00",
|
|
55
|
+
top: y,
|
|
56
|
+
pointerEvents: "none",
|
|
57
|
+
left: 0,
|
|
58
|
+
}}
|
|
59
|
+
/>
|
|
60
|
+
</Fragment>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export default Crosshairs
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import React from "react"
|
|
4
|
+
import SidebarBoxContainer from "../SidebarBoxContainer"
|
|
5
|
+
|
|
6
|
+
export const DebugSidebarBox = ({ state, lastAction }: any) => {
|
|
7
|
+
const image = (state.images || [])[state.selectedImage]
|
|
8
|
+
const region = image
|
|
9
|
+
? (image.regions || []).filter((r) => r.highlighted)
|
|
10
|
+
: null
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<SidebarBoxContainer title="Debug" icon={<span />} expandedByDefault>
|
|
14
|
+
<div style={{ padding: 4 }}>
|
|
15
|
+
<div>
|
|
16
|
+
<b>region</b>:
|
|
17
|
+
</div>
|
|
18
|
+
<pre>{JSON.stringify(region, null, " ")}</pre>
|
|
19
|
+
<div>
|
|
20
|
+
<b>lastAction</b>:
|
|
21
|
+
</div>
|
|
22
|
+
<pre>{JSON.stringify(lastAction, null, " ")}</pre>
|
|
23
|
+
<div>
|
|
24
|
+
<b>mode</b>:
|
|
25
|
+
</div>
|
|
26
|
+
<pre>{JSON.stringify(state.mode, null, " ")}</pre>
|
|
27
|
+
<div>
|
|
28
|
+
<b>frame:</b>
|
|
29
|
+
</div>
|
|
30
|
+
<pre>{JSON.stringify(state.selectedImageFrameTime, null, " ")}</pre>
|
|
31
|
+
</div>
|
|
32
|
+
</SidebarBoxContainer>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default DebugSidebarBox
|