Skip to content

Commit 134e6f6

Browse files
committed
point distance support, settings fix, pixel size output
1 parent 6127fea commit 134e6f6

10 files changed

Lines changed: 340 additions & 92 deletions

File tree

.storybook/config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { action } from "@storybook/addon-actions"
77
import SettingsProvider from "../src/SettingsProvider"
88

99
addDecorator(storyFn => <Theme>{storyFn()}</Theme>)
10-
addDecorator(storyFn => <SettingsProvider>{storyFn()}</SettingsProvider>)
10+
// addDecorator(storyFn => <SettingsProvider>{storyFn()}</SettingsProvider>)
1111

1212
function loadStories() {
1313
require("../src/stories")

src/Annotator/index.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type {
99
MainLayoutState,
1010
Action
1111
} from "../MainLayout/types"
12+
import SettingsProvider from "../SettingsProvider"
1213
import reducer from "./reducer"
1314

1415
type Props = {
@@ -22,13 +23,17 @@ type Props = {
2223
showTags?: boolean,
2324
selectedImage?: string,
2425
images: Array<Image>,
26+
showPointDistances?: boolean,
27+
pointDistancePrecision?: number,
2528
onExit: MainLayoutState => any
2629
}
2730

2831
export default ({
2932
images,
3033
allowedArea,
3134
selectedImage = images.length > 0 ? images[0].src : undefined,
35+
showPointDistances,
36+
pointDistancePrecision,
3237
showTags = true,
3338
enabledTools = ["select", "create-point", "create-box", "create-polygon"],
3439
regionTagList = [],
@@ -42,6 +47,8 @@ export default ({
4247
showTags,
4348
allowedArea,
4449
selectedImage,
50+
showPointDistances,
51+
pointDistancePrecision,
4552
selectedTool: "select",
4653
mode: null,
4754
taskDescription,
@@ -68,5 +75,9 @@ export default ({
6875
}
6976
}
7077

71-
return <MainLayout debug state={state} dispatch={dispatch} />
78+
return (
79+
<SettingsProvider>
80+
<MainLayout debug state={state} dispatch={dispatch} />
81+
</SettingsProvider>
82+
)
7283
}

src/Annotator/reducer.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,12 @@ export default (state: MainLayoutState, action: Action) => {
125125
case "SELECT_IMAGE": {
126126
return setNewImage(action.image.src)
127127
}
128+
case "IMAGE_LOADED": {
129+
return setIn(state, ["images", currentImageIndex, "pixelSize"], {
130+
w: action.image.width,
131+
h: action.image.height
132+
})
133+
}
128134
case "CHANGE_REGION": {
129135
const regionIndex = getRegionIndex(action.region)
130136
if (regionIndex === null) return state
@@ -279,15 +285,15 @@ export default (state: MainLayoutState, action: Action) => {
279285
xFree === 0
280286
? ow
281287
: xFree === -1
282-
? ow + (ox - dx)
283-
: Math.max(0, ow + (x - ox - ow))
288+
? ow + (ox - dx)
289+
: Math.max(0, ow + (x - ox - ow))
284290
const dy = yFree === 0 ? oy : yFree === -1 ? Math.min(oy + oh, y) : oy
285291
const dh =
286292
yFree === 0
287293
? oh
288294
: yFree === -1
289-
? oh + (oy - dy)
290-
: Math.max(0, oh + (y - oy - oh))
295+
? oh + (oy - dy)
296+
: Math.max(0, oh + (y - oy - oh))
291297

292298
// determine if we should switch the freedom
293299
if (dw <= 0.001) {

src/HighlightBox/index.js

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// @flow
2+
3+
import React from "react"
4+
import classnames from "classnames"
5+
import { makeStyles } from "@material-ui/styles"
6+
7+
const useStyles = makeStyles({
8+
"@keyframes borderDance": {
9+
from: { strokeDashoffset: 0 },
10+
to: { strokeDashoffset: 100 }
11+
},
12+
highlightBox: {
13+
transition: "opacity 500ms",
14+
"&:not(.highlighted)": {
15+
opacity: 0
16+
},
17+
"&:not(.highlighted):hover": {
18+
opacity: 0.6
19+
},
20+
"& path": {
21+
vectorEffect: "non-scaling-stroke",
22+
strokeWidth: 2,
23+
stroke: "#FFF",
24+
fill: "none",
25+
strokeDasharray: 5,
26+
animationName: "$borderDance",
27+
animationDuration: "4s",
28+
animationTimingFunction: "linear",
29+
animationIterationCount: "infinite",
30+
animationPlayState: "running"
31+
}
32+
}
33+
})
34+
35+
export default ({
36+
mouseEvents,
37+
dragWithPrimary,
38+
zoomWithPrimary,
39+
createWithPrimary,
40+
onBeginMovePoint,
41+
onSelectRegion,
42+
region: r,
43+
pbox
44+
}: {
45+
mouseEvents: any,
46+
dragWithPrimary: boolean,
47+
zoomWithPrimary: boolean,
48+
createWithPrimary: boolean,
49+
onBeginMovePoint: Function,
50+
onSelectRegion: Function,
51+
region: any,
52+
pbox: { x: number, y: number, w: number, h: number }
53+
}) => {
54+
const classes = useStyles()
55+
return (
56+
<svg
57+
key={r.id}
58+
className={classnames(classes.highlightBox, {
59+
highlighted: r.highlighted
60+
})}
61+
{...mouseEvents}
62+
{...(!zoomWithPrimary && !dragWithPrimary
63+
? {
64+
onMouseDown: e => {
65+
if (
66+
!r.locked &&
67+
r.type === "point" &&
68+
r.highlighted &&
69+
e.button === 0
70+
) {
71+
return onBeginMovePoint(r)
72+
}
73+
if (e.button === 0 && !createWithPrimary) return onSelectRegion(r)
74+
mouseEvents.onMouseDown(e)
75+
}
76+
}
77+
: {})}
78+
style={{
79+
...(r.highlighted
80+
? {
81+
pointerEvents: r.type !== "point" ? "none" : undefined,
82+
cursor: "grab"
83+
}
84+
: {
85+
cursor: !(zoomWithPrimary || dragWithPrimary || createWithPrimary)
86+
? "pointer"
87+
: undefined,
88+
pointerEvents:
89+
zoomWithPrimary ||
90+
dragWithPrimary ||
91+
(createWithPrimary && !r.highlighted)
92+
? "none"
93+
: undefined
94+
}),
95+
position: "absolute",
96+
left: pbox.x - 5,
97+
top: pbox.y - 5,
98+
width: pbox.w + 10,
99+
height: pbox.h + 10
100+
}}
101+
>
102+
<path
103+
d={`M5,5 L${pbox.w + 5},5 L${pbox.w + 5},${pbox.h + 5} L5,${pbox.h +
104+
5} Z`}
105+
/>
106+
</svg>
107+
)
108+
}

src/ImageCanvas/index.js

Lines changed: 78 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import classnames from "classnames"
1717
import RegionLabel from "../RegionLabel"
1818
import LockIcon from "@material-ui/icons/Lock"
1919
import Paper from "@material-ui/core/Paper"
20+
import HighlightBox from "../HighlightBox"
2021
// import excludePatternSrc from "./xpattern.png"
2122
import excludePatternSrc from "./xpattern.js"
2223

@@ -38,7 +39,10 @@ type Props = {
3839
zoomWithPrimary?: boolean,
3940
createWithPrimary?: boolean,
4041
showTags?: boolean,
42+
realSize?: { width: number, height: number, unitName: string },
4143
showCrosshairs?: boolean,
44+
showPointDistances?: boolean,
45+
pointDistancePrecision?: number,
4246
regionClsList?: Array<string>,
4347
regionTagList?: Array<string>,
4448
allowedArea?: { x: number, y: number, w: number, h: number },
@@ -51,26 +55,31 @@ type Props = {
5155
onBeginMovePolygonPoint: (Polygon, index: number) => any,
5256
onAddPolygonPoint: (Polygon, point: [number, number], index: number) => any,
5357
onSelectRegion: Region => any,
54-
onBeginMovePoint: Point => any
58+
onBeginMovePoint: Point => any,
59+
onImageLoaded: ({ width: number, height: number }) => any
5560
}
5661

5762
const getDefaultMat = () => Matrix.from(1, 0, 0, 1, -10, -10)
5863

5964
export default ({
6065
regions,
6166
imageSrc,
67+
realSize,
6268
showTags,
6369
onMouseMove = p => null,
6470
onMouseDown = p => null,
6571
onMouseUp = p => null,
6672
dragWithPrimary = false,
6773
zoomWithPrimary = false,
6874
createWithPrimary = false,
75+
pointDistancePrecision = 0,
6976
regionClsList,
7077
regionTagList,
7178
showCrosshairs,
79+
showPointDistances,
7280
allowedArea,
7381

82+
onImageLoaded,
7483
onChangeRegion,
7584
onBeginRegionEdit,
7685
onCloseRegionEdit,
@@ -128,6 +137,10 @@ export default ({
128137
image.current = new Image()
129138
image.current.onload = () => {
130139
changeImageLoaded(true)
140+
onImageLoaded({
141+
width: image.current.naturalWidth,
142+
height: image.current.naturalHeight
143+
})
131144
}
132145
image.current.src = imageSrc
133146
}
@@ -504,62 +517,16 @@ export default ({
504517
const { iw, ih } = layoutParams.current
505518
return (
506519
<Fragment>
507-
<svg
508-
key={r.id}
509-
className={classnames(classes.highlightBox, {
510-
highlighted: r.highlighted
511-
})}
512-
{...mouseEvents}
513-
{...(!zoomWithPrimary && !dragWithPrimary
514-
? {
515-
onMouseDown: e => {
516-
if (
517-
!r.locked &&
518-
r.type === "point" &&
519-
r.highlighted &&
520-
e.button === 0
521-
) {
522-
return onBeginMovePoint(r)
523-
}
524-
if (e.button === 0 && !createWithPrimary)
525-
return onSelectRegion(r)
526-
mouseEvents.onMouseDown(e)
527-
}
528-
}
529-
: {})}
530-
style={{
531-
...(r.highlighted
532-
? {
533-
pointerEvents: r.type !== "point" ? "none" : undefined,
534-
cursor: "grab"
535-
}
536-
: {
537-
cursor: !(
538-
zoomWithPrimary ||
539-
dragWithPrimary ||
540-
createWithPrimary
541-
)
542-
? "pointer"
543-
: undefined,
544-
pointerEvents:
545-
zoomWithPrimary ||
546-
dragWithPrimary ||
547-
(createWithPrimary && !r.highlighted)
548-
? "none"
549-
: undefined
550-
}),
551-
position: "absolute",
552-
left: pbox.x - 5,
553-
top: pbox.y - 5,
554-
width: pbox.w + 10,
555-
height: pbox.h + 10
556-
}}
557-
>
558-
<path
559-
d={`M5,5 L${pbox.w + 5},5 L${pbox.w + 5},${pbox.h +
560-
5} L5,${pbox.h + 5} Z`}
561-
/>
562-
</svg>
520+
<HighlightBox
521+
region={r}
522+
mouseEvents={mouseEvents}
523+
dragWithPrimary={dragWithPrimary}
524+
createWithPrimary={createWithPrimary}
525+
zoomWithPrimary={zoomWithPrimary}
526+
onBeginMovePoint={onBeginMovePoint}
527+
onSelectRegion={onSelectRegion}
528+
pbox={pbox}
529+
/>
563530
{r.type === "box" &&
564531
!dragWithPrimary &&
565532
!zoomWithPrimary &&
@@ -749,6 +716,60 @@ export default ({
749716
}}
750717
/>
751718
)}
719+
{showPointDistances && (
720+
<svg
721+
className={classes.pointDistanceIndicator}
722+
style={{
723+
pointerEvents: "none",
724+
position: "absolute",
725+
left: 0,
726+
top: 0,
727+
width: "100%",
728+
height: "100%"
729+
}}
730+
>
731+
{regions.filter(r1 => r1.type === "point").flatMap((r1, i1) =>
732+
regions
733+
.filter((r2, i2) => i2 > i1)
734+
.filter(r2 => r2.type === "point")
735+
.map(r2 => {
736+
const pr1 = projectRegionBox(r1)
737+
const pr2 = projectRegionBox(r2)
738+
const prm = {
739+
x: (pr1.x + pr1.w / 2 + pr2.x + pr2.w / 2) / 2,
740+
y: (pr1.y + pr1.h / 2 + pr2.y + pr2.h / 2) / 2
741+
}
742+
let displayDistance
743+
if (realSize) {
744+
const { w, h, unitName } = realSize
745+
displayDistance =
746+
Math.sqrt(
747+
Math.pow(r1.x * w - r2.x * w, 2) +
748+
Math.pow(r1.y * h - r2.y * h, 2)
749+
).toFixed(pointDistancePrecision) + unitName
750+
} else {
751+
displayDistance =
752+
(
753+
Math.sqrt(
754+
Math.pow(r1.x - r2.x, 2) + Math.pow(r1.y - r2.y, 2)
755+
) * 100
756+
).toFixed(pointDistancePrecision) + "%"
757+
}
758+
return (
759+
<Fragment>
760+
<path
761+
d={`M${pr1.x + pr1.w / 2},${pr1.y + pr1.h / 2} L${pr2.x +
762+
pr2.w / 2},${pr2.y + pr2.h / 2}`}
763+
/>
764+
<text x={prm.x} y={prm.y}>
765+
{displayDistance}
766+
</text>
767+
</Fragment>
768+
)
769+
})
770+
)}
771+
</svg>
772+
)}
752773
<canvas {...mouseEvents} className={classes.canvas} ref={canvasEl} />
753774
<div className={classes.zoomIndicator}>
754775
{((1 / mat.a) * 100).toFixed(0)}%

0 commit comments

Comments
 (0)