lost-sia 2.0.1-alpha12 → 2.0.1-alpha13
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/dist/Annotation/ui/atoms/AnnoBar.js +1 -1
- package/dist/Annotation/ui/atoms/Edge.js +1 -1
- package/dist/Annotation/ui/atoms/Node.js +1 -1
- package/dist/Annotation/ui/tools/BBox.js +1 -1
- package/dist/Annotation/ui/tools/Line.js +1 -1
- package/dist/Annotation/ui/tools/Polygon.js +1 -1
- package/dist/Canvas/Canvas.d.ts +2 -1
- package/dist/Canvas/Canvas.js +1 -1
- package/dist/Sia.js +1 -1
- package/dist/index.d.ts +0 -9
- package/dist/index.js +1 -1
- package/dist/models/KeyAction.d.ts +2 -1
- package/dist/models/KeyAction.js +1 -1
- package/dist/stories/Canvas/Canvas.stories.d.ts +2 -1
- package/dist/utils/KeyMapper.js +1 -1
- package/dist/utils/mouse.js +1 -1
- package/dist/utils/siaIcons.js +5 -5
- package/dist/utils/transform.js +1 -1
- package/dist/utils/windowViewport.d.ts +22 -0
- package/dist/utils/windowViewport.js +1 -1
- package/package.json +9 -8
- package/src/Annotation/ui/atoms/AnnoBar.tsx +46 -46
- package/src/Annotation/ui/atoms/Edge.tsx +23 -23
- package/src/Annotation/ui/atoms/Node.tsx +51 -51
- package/src/Annotation/ui/tools/BBox.tsx +129 -145
- package/src/Annotation/ui/tools/Line.tsx +81 -87
- package/src/Annotation/ui/tools/Polygon.tsx +92 -95
- package/src/Canvas/Canvas.tsx +369 -397
- package/src/InfoBoxes/AnnoStats.jsx +1 -1
- package/src/InfoBoxes/InfoBoxArea.jsx +32 -34
- package/src/{Sia2.tsx → Sia.tsx} +156 -175
- package/src/index.ts +6 -17
- package/src/models/KeyAction.ts +1 -0
- package/src/stories/AnnotationTools.stories.tsx +21 -21
- package/src/stories/Canvas/Canvas.stories.tsx +31 -26
- package/src/stories/Canvas/CanvasOffset.tsx +23 -25
- package/src/stories/MinimalSia.stories.tsx +22 -22
- package/src/stories/SIA2/DemoWrapper.tsx +20 -24
- package/src/stories/SIA2/Sia2.stories.tsx +24 -24
- package/src/utils/KeyMapper.ts +3 -0
- package/src/utils/siaIcons.jsx +5 -7
- package/src/utils/windowViewport.ts +34 -0
- package/dist/AnnoExampleViewer.js +0 -1
- package/dist/AnnoLabelInput.js +0 -1
- package/dist/AnnoToolBar.js +0 -19
- package/dist/Annotation/AnnoBar.js +0 -1
- package/dist/Annotation/Annotation.js +0 -1
- package/dist/Annotation/BBox.js +0 -1
- package/dist/Annotation/Edge.js +0 -1
- package/dist/Annotation/InfSelectionArea.js +0 -1
- package/dist/Annotation/Line.js +0 -1
- package/dist/Annotation/Node.js +0 -1
- package/dist/Annotation/Point.js +0 -1
- package/dist/Annotation/Polygon.js +0 -1
- package/dist/Canvas.js +0 -1
- package/dist/ImgBar.js +0 -1
- package/dist/InfoBoxes/AnnoDetails.js +0 -1
- package/dist/InfoBoxes/AnnoStats.js +0 -1
- package/dist/InfoBoxes/InfoBox.js +0 -1
- package/dist/InfoBoxes/InfoBoxArea.js +0 -1
- package/dist/InfoBoxes/LabelInfo.js +0 -1
- package/dist/LabelInput.js +0 -1
- package/dist/Prompt.js +0 -1
- package/dist/SIAFilterButton.js +0 -1
- package/dist/SIASettingButton.js +0 -1
- package/dist/Sia2.js +0 -1
- package/dist/SiaPopup.js +0 -1
- package/dist/ToolBar.js +0 -1
- package/dist/ToolbarItem.js +0 -1
- package/dist/_virtual/_commonjsHelpers.js +0 -1
- package/dist/_virtual/lodash.js +0 -1
- package/dist/assets/Annotation/Annotation-Cd5Ua5TG.css +0 -1
- package/dist/assets/Toolbar-Cp1xyYeH.css +0 -1
- package/dist/filterTools.js +0 -1
- package/dist/siaDummyData.js +0 -7
- package/dist/stories/Canvas/CanvasOffset.js +0 -1
- package/dist/stories/FilterDropdown.stories.js +0 -1
- package/dist/stories/SIA/SIA.stories.d.ts +0 -36
- package/dist/stories/SIA2/DemoWrapper.js +0 -1
- package/dist/stories/Toolbar/ImageTools/ImageLabel.stories.d.ts +0 -13
- package/dist/stories/siaDummyData.js +0 -1
- package/dist/stories/siaDummyData2.js +0 -7
- package/dist/types/annoStatus.js +0 -1
- package/dist/types/canvasActions.js +0 -1
- package/dist/types/cursorstyles.js +0 -1
- package/dist/types/modes.js +0 -1
- package/dist/types/notificationType.js +0 -1
- package/dist/types/toolbarEvents.js +0 -1
- package/dist/types/tools.js +0 -1
- package/dist/utils/annoConversion.js +0 -1
- package/dist/utils/annoConversion2.d.ts +0 -27
- package/dist/utils/annoConversion2.js +0 -1
- package/dist/utils/colorlut.js +0 -1
- package/dist/utils/constraints.js +0 -1
- package/dist/utils/hist.js +0 -1
- package/dist/utils/keyActions.js +0 -1
- package/dist/utils/mouse2.js +0 -1
- package/dist/utils/transform2.js +0 -1
- package/src/AnnoLabelInput.jsx +0 -109
- package/src/AnnoToolBar.jsx +0 -153
- package/src/Annotation/AnnoBar.jsx +0 -154
- package/src/Annotation/Annotation.jsx +0 -395
- package/src/Annotation/Annotation.scss +0 -47
- package/src/Annotation/BBox.jsx +0 -299
- package/src/Annotation/Edge.jsx +0 -92
- package/src/Annotation/InfSelectionArea.jsx +0 -72
- package/src/Annotation/Line.jsx +0 -68
- package/src/Annotation/Node.jsx +0 -282
- package/src/Annotation/Point.jsx +0 -200
- package/src/Annotation/Polygon.jsx +0 -404
- package/src/Canvas.jsx +0 -2194
- package/src/ImgBar.jsx +0 -131
- package/src/LabelInput.jsx +0 -238
- package/src/Prompt.jsx +0 -45
- package/src/SIAFilterButton.jsx +0 -186
- package/src/Sia.jsx +0 -478
- package/src/SiaPopup.jsx +0 -15
- package/src/ToolBar.jsx +0 -463
- package/src/Toolbar/ToolbarItem.jsx +0 -30
- package/src/Toolbar.css +0 -13
- package/src/ToolbarItem.jsx +0 -31
- package/src/filterTools.js +0 -5
- package/src/stories/SIA/SIA.stories.tsx +0 -64
- package/src/stories/Toolbar/ImageTools/ImageLabel.stories.tsx +0 -31
- package/src/stories/lost.js +0 -54
- package/src/stories/store.js +0 -18
- package/src/test.js +0 -7
- package/src/types/annoStatus.js +0 -4
- package/src/types/canvasActions.js +0 -58
- package/src/types/cursorstyles.js +0 -3
- package/src/types/modes.js +0 -9
- package/src/types/notificationType.js +0 -11
- package/src/types/toolbarEvents.js +0 -35
- package/src/types/tools.js +0 -17
- package/src/utils/annoConversion.js +0 -145
- package/src/utils/annoConversion2.ts +0 -145
- package/src/utils/colorlut.js +0 -68
- package/src/utils/constraints.js +0 -81
- package/src/utils/mouse.js +0 -14
- package/src/utils/transform.js +0 -336
- package/src/utils/windowViewport.js +0 -34
- /package/dist/{Sia2.d.ts → Sia.d.ts} +0 -0
- /package/dist/utils/{mouse2.d.ts → mouse.d.ts} +0 -0
- /package/dist/utils/{transform2.d.ts → transform.d.ts} +0 -0
- /package/src/utils/{mouse2.ts → mouse.ts} +0 -0
- /package/src/utils/{transform2.ts → transform.ts} +0 -0
package/src/Canvas/Canvas.tsx
CHANGED
|
@@ -5,64 +5,63 @@ import React, {
|
|
|
5
5
|
useRef,
|
|
6
6
|
useState,
|
|
7
7
|
WheelEvent,
|
|
8
|
-
} from
|
|
9
|
-
import AnnotationTool from
|
|
10
|
-
import EditorModes from
|
|
11
|
-
import KeyMapper from
|
|
12
|
-
import KeyAction from
|
|
13
|
-
import Annotation from
|
|
14
|
-
import CanvasAction from
|
|
15
|
-
import AnnotationComponent from
|
|
16
|
-
import { AnnotationSettings, Label, UiConfig } from
|
|
8
|
+
} from 'react'
|
|
9
|
+
import AnnotationTool from '../models/AnnotationTool'
|
|
10
|
+
import EditorModes from '../models/EditorModes'
|
|
11
|
+
import KeyMapper from '../utils/KeyMapper'
|
|
12
|
+
import KeyAction from '../models/KeyAction'
|
|
13
|
+
import Annotation from '../Annotation/logic/Annotation'
|
|
14
|
+
import CanvasAction from '../models/CanvasAction'
|
|
15
|
+
import AnnotationComponent from '../Annotation/ui/AnnotationComponent'
|
|
16
|
+
import { AnnotationSettings, Label, UiConfig } from '../types'
|
|
17
17
|
import {
|
|
18
18
|
Point,
|
|
19
19
|
PolygonOperationResult,
|
|
20
20
|
SIANotification,
|
|
21
21
|
ToolCoordinates,
|
|
22
22
|
Vector2,
|
|
23
|
-
} from
|
|
24
|
-
import
|
|
25
|
-
import AnnotationMode from
|
|
26
|
-
import LabelInput from
|
|
27
|
-
import { FontAwesomeIcon } from
|
|
28
|
-
import { faBan } from
|
|
29
|
-
import { IconProp } from
|
|
30
|
-
import AnnotationStatus from
|
|
31
|
-
import
|
|
32
|
-
import NotificationType from
|
|
33
|
-
import TimeUtils from
|
|
23
|
+
} from '../types'
|
|
24
|
+
import mouse from '../utils/mouse'
|
|
25
|
+
import AnnotationMode from '../models/AnnotationMode'
|
|
26
|
+
import LabelInput from './LabelInput'
|
|
27
|
+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
|
28
|
+
import { faBan } from '@fortawesome/free-solid-svg-icons'
|
|
29
|
+
import { IconProp } from '@fortawesome/fontawesome-svg-core'
|
|
30
|
+
import AnnotationStatus from '../models/AnnotationStatus'
|
|
31
|
+
import transform from '../utils/transform'
|
|
32
|
+
import NotificationType from '../models/NotificationType'
|
|
33
|
+
import TimeUtils from '../utils/TimeUtils'
|
|
34
|
+
import windowViewport from '../utils/windowViewport'
|
|
34
35
|
|
|
35
36
|
type CanvasProps = {
|
|
36
|
-
annotations?: Annotation[]
|
|
37
|
-
annotationSettings: AnnotationSettings
|
|
38
|
-
defaultLabelId?: number
|
|
39
|
-
image: string
|
|
40
|
-
isFullscreen?: boolean
|
|
41
|
-
isImageJunk?: boolean
|
|
42
|
-
isPolygonSelectionMode?: boolean
|
|
43
|
-
selectedAnnotation: Annotation | undefined
|
|
44
|
-
selectedAnnoTool: AnnotationTool
|
|
45
|
-
toolbarHeight?: number
|
|
46
|
-
polygonOperationResult?: PolygonOperationResult
|
|
47
|
-
possibleLabels: Label[]
|
|
48
|
-
preventScrolling?: boolean
|
|
49
|
-
uiConfig: UiConfig
|
|
37
|
+
annotations?: Annotation[]
|
|
38
|
+
annotationSettings: AnnotationSettings
|
|
39
|
+
defaultLabelId?: number
|
|
40
|
+
image: string
|
|
41
|
+
isFullscreen?: boolean
|
|
42
|
+
isImageJunk?: boolean
|
|
43
|
+
isPolygonSelectionMode?: boolean
|
|
44
|
+
selectedAnnotation: Annotation | undefined
|
|
45
|
+
selectedAnnoTool: AnnotationTool
|
|
46
|
+
toolbarHeight?: number
|
|
47
|
+
polygonOperationResult?: PolygonOperationResult
|
|
48
|
+
possibleLabels: Label[]
|
|
49
|
+
preventScrolling?: boolean
|
|
50
|
+
uiConfig: UiConfig
|
|
50
51
|
// onKeyDown?: (e) => void | undefined;
|
|
51
52
|
// onKeyUp?: (e) => void | undefined;
|
|
52
|
-
onAnnoCreated: (createdAnno: Annotation) => void
|
|
53
|
-
onAnnoCreationFinished: (
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
) => void;
|
|
57
|
-
onAnnoChanged: (changedAnno: Annotation) => void;
|
|
58
|
-
onAnnoEditing: (annoToEdit: Annotation) => void;
|
|
53
|
+
onAnnoCreated: (createdAnno: Annotation) => void
|
|
54
|
+
onAnnoCreationFinished: (createdAnno: Annotation, hasAnnotationExisted: boolean) => void
|
|
55
|
+
onAnnoChanged: (changedAnno: Annotation) => void
|
|
56
|
+
onAnnoEditing: (annoToEdit: Annotation) => void
|
|
59
57
|
// onAnnoDeleted: (deletedAnno: Annotation, allAnnos: Annotation[]) => void;
|
|
60
|
-
onNotification?: (notification: SIANotification) => void
|
|
61
|
-
onRequestNewAnnoId: () => number
|
|
62
|
-
onSelectAnnotation: (annotation?: Annotation) => void
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
58
|
+
onNotification?: (notification: SIANotification) => void
|
|
59
|
+
onRequestNewAnnoId: () => number
|
|
60
|
+
onSelectAnnotation: (annotation?: Annotation) => void
|
|
61
|
+
onSetIsImageJunk: (newJunkState: boolean) => void
|
|
62
|
+
onSetSelectedTool: (tool: AnnotationTool) => void
|
|
63
|
+
onShouldDeleteAnno: (internalAnnoId: number) => void
|
|
64
|
+
}
|
|
66
65
|
|
|
67
66
|
const Canvas = ({
|
|
68
67
|
annotations = [],
|
|
@@ -88,148 +87,137 @@ const Canvas = ({
|
|
|
88
87
|
onNotification = (_) => {},
|
|
89
88
|
onRequestNewAnnoId,
|
|
90
89
|
onSelectAnnotation,
|
|
90
|
+
onSetIsImageJunk,
|
|
91
91
|
onSetSelectedTool = (_) => {},
|
|
92
92
|
onShouldDeleteAnno,
|
|
93
93
|
}: CanvasProps) => {
|
|
94
|
-
const [editorMode, setEditorMode] = useState<EditorModes>(EditorModes.VIEW)
|
|
95
|
-
const [annoTimestamp, setAnnoTimestamp] = useState<number | undefined>()
|
|
94
|
+
const [editorMode, setEditorMode] = useState<EditorModes>(EditorModes.VIEW)
|
|
95
|
+
const [annoTimestamp, setAnnoTimestamp] = useState<number | undefined>()
|
|
96
96
|
|
|
97
97
|
// remember which label was added last
|
|
98
|
-
const [currentLabelId, setCurrentLabelId] = useState<number | undefined>(
|
|
99
|
-
defaultLabelId,
|
|
100
|
-
);
|
|
98
|
+
const [currentLabelId, setCurrentLabelId] = useState<number | undefined>(defaultLabelId)
|
|
101
99
|
|
|
102
100
|
// vector from the top left of the DOM document to the top left of the stage
|
|
103
101
|
// (events are emitting page coordinates, so we need this to convert them)
|
|
104
102
|
const [pageToCanvasOffset, setPageToCanvasOffset] = useState<Point>({
|
|
105
103
|
x: -1,
|
|
106
104
|
y: -1,
|
|
107
|
-
})
|
|
105
|
+
})
|
|
108
106
|
|
|
109
107
|
// space to add to the x translation to center the image
|
|
110
|
-
const [imageCenteringSpace, setImageCenteringSpace] = useState<number>(0)
|
|
108
|
+
const [imageCenteringSpace, setImageCenteringSpace] = useState<number>(0)
|
|
111
109
|
|
|
112
110
|
// the stage can be horizontally centered - apply offset on x axis
|
|
113
111
|
const pageToStageOffset: Vector2 = {
|
|
114
112
|
// x: pageToCanvasOffset.x + imageCenteringSpace,
|
|
115
113
|
x: pageToCanvasOffset.x,
|
|
116
114
|
y: pageToCanvasOffset.y,
|
|
117
|
-
}
|
|
115
|
+
}
|
|
118
116
|
|
|
119
117
|
// default (unscaled) image and canvas sizes (for stage calculation)
|
|
120
118
|
// invalid default value, so that the image uses its default values at first
|
|
121
|
-
const [imgSize, setImgSize] = useState<Vector2>({ x: -1, y: -1 })
|
|
122
|
-
const [canvasSize, setCanvasSize] = useState<Vector2>({ x: -1, y: -1 })
|
|
119
|
+
const [imgSize, setImgSize] = useState<Vector2>({ x: -1, y: -1 })
|
|
120
|
+
const [canvasSize, setCanvasSize] = useState<Vector2>({ x: -1, y: -1 })
|
|
123
121
|
|
|
124
122
|
// largest possible annotation size fitting the whole image
|
|
125
|
-
const [stageSize, setStageSize] = useState<Vector2>({ x: -1, y: -1 })
|
|
123
|
+
const [stageSize, setStageSize] = useState<Vector2>({ x: -1, y: -1 })
|
|
126
124
|
|
|
127
|
-
const [svgScale, setSvgScale] = useState<number>(1.0)
|
|
128
|
-
const [svgTranslation, setSvgTranslation] = useState<Vector2>({ x: 0, y: 0 })
|
|
125
|
+
const [svgScale, setSvgScale] = useState<number>(1.0)
|
|
126
|
+
const [svgTranslation, setSvgTranslation] = useState<Vector2>({ x: 0, y: 0 })
|
|
129
127
|
const centeredSvgTranslation: Vector2 = {
|
|
130
128
|
x: svgTranslation.x + imageCenteringSpace,
|
|
131
129
|
y: svgTranslation.y,
|
|
132
|
-
}
|
|
130
|
+
}
|
|
133
131
|
|
|
134
132
|
// label input will be opened if a position is set here
|
|
135
|
-
const [labelInputPosition, setLabelInputPosition] = useState<Point>()
|
|
136
|
-
const [isLabelInputVisible, setIsLabelInputVisible] =
|
|
137
|
-
useState<boolean>(false);
|
|
133
|
+
const [labelInputPosition, setLabelInputPosition] = useState<Point>()
|
|
134
|
+
const [isLabelInputVisible, setIsLabelInputVisible] = useState<boolean>(false)
|
|
138
135
|
|
|
139
136
|
// available canvas area - all possible space for creating a canvas
|
|
140
|
-
const canvasRef = useRef(null)
|
|
137
|
+
const canvasRef = useRef(null)
|
|
141
138
|
|
|
142
139
|
// used to get the size of the dynamically loaded image
|
|
143
|
-
const imageRef = useRef(null)
|
|
140
|
+
const imageRef = useRef(null)
|
|
144
141
|
|
|
145
142
|
// returns the aspect ratio of the largest image size fitting onto the canvas without changing the aspect ratio
|
|
146
143
|
const getFittedImageScale = (imgSize: Vector2, svgSize: Vector2): number => {
|
|
147
|
-
if (
|
|
148
|
-
imgSize.x === 0 ||
|
|
149
|
-
imgSize.y === 0 ||
|
|
150
|
-
svgSize.x === 0 ||
|
|
151
|
-
svgSize.y === 0
|
|
152
|
-
)
|
|
153
|
-
return 0;
|
|
144
|
+
if (imgSize.x === 0 || imgSize.y === 0 || svgSize.x === 0 || svgSize.y === 0) return 0
|
|
154
145
|
|
|
155
|
-
const scaleX = svgSize.x / imgSize.x
|
|
156
|
-
const scaleY = svgSize.y / imgSize.y
|
|
146
|
+
const scaleX = svgSize.x / imgSize.x
|
|
147
|
+
const scaleY = svgSize.y / imgSize.y
|
|
157
148
|
|
|
158
|
-
return Math.min(scaleX, scaleY)
|
|
159
|
-
}
|
|
149
|
+
return Math.min(scaleX, scaleY)
|
|
150
|
+
}
|
|
160
151
|
|
|
161
152
|
// factor to convert coordinates from an (untransformed) image into the stage
|
|
162
|
-
const imageToStageFactor = getFittedImageScale(imgSize, canvasSize)
|
|
153
|
+
const imageToStageFactor = getFittedImageScale(imgSize, canvasSize)
|
|
163
154
|
|
|
164
155
|
// store + update the vector between start of the page and start of the (translated) image to be able to do transformations
|
|
165
156
|
const calculatePageToCanvasOffset = () => {
|
|
166
|
-
if (imageRef?.current === null) return { x: 0, y: 0 }
|
|
157
|
+
if (imageRef?.current === null) return { x: 0, y: 0 }
|
|
167
158
|
|
|
168
159
|
// if image can and should be centered
|
|
169
|
-
const resizedImageWidth: number = imgSize.x * imageToStageFactor
|
|
160
|
+
const resizedImageWidth: number = imgSize.x * imageToStageFactor
|
|
170
161
|
if (uiConfig.imageCentered && canvasSize.x > resizedImageWidth) {
|
|
171
162
|
// image is at (0,0) - get the blanc space at the right side of the image
|
|
172
|
-
const remainingSpace: number = canvasSize.x - resizedImageWidth
|
|
163
|
+
const remainingSpace: number = canvasSize.x - resizedImageWidth
|
|
173
164
|
|
|
174
165
|
// divide remaining space to be equal to the left and right side
|
|
175
|
-
const spaceToLeft: number = remainingSpace / 2
|
|
166
|
+
const spaceToLeft: number = remainingSpace / 2
|
|
176
167
|
|
|
177
168
|
// page to stage offset marks the top left point of the stage
|
|
178
169
|
// add the space to the translation to center the image
|
|
179
|
-
setImageCenteringSpace(spaceToLeft)
|
|
170
|
+
setImageCenteringSpace(spaceToLeft)
|
|
180
171
|
} else {
|
|
181
|
-
setImageCenteringSpace(0)
|
|
172
|
+
setImageCenteringSpace(0)
|
|
182
173
|
}
|
|
183
174
|
|
|
184
175
|
// get image starting position in window coordinates
|
|
185
|
-
const { top, left } = canvasRef.current.getBoundingClientRect()
|
|
176
|
+
const { top, left } = canvasRef.current.getBoundingClientRect()
|
|
186
177
|
|
|
187
178
|
// top and left are in window coordinates
|
|
188
179
|
// we need to convert them to page coordinates
|
|
189
180
|
const pageOffset: Point = {
|
|
190
181
|
x: left + window.scrollX,
|
|
191
182
|
y: top + window.scrollY,
|
|
192
|
-
}
|
|
183
|
+
}
|
|
193
184
|
|
|
194
|
-
setPageToCanvasOffset(pageOffset)
|
|
195
|
-
}
|
|
185
|
+
setPageToCanvasOffset(pageOffset)
|
|
186
|
+
}
|
|
196
187
|
|
|
197
188
|
// const pageToStageOffset = calculatePageToStageOffset();
|
|
198
189
|
|
|
199
|
-
const keyMapper = new KeyMapper((keyAction: KeyAction) =>
|
|
200
|
-
handleKeyAction(keyAction),
|
|
201
|
-
);
|
|
190
|
+
const keyMapper = new KeyMapper((keyAction: KeyAction) => handleKeyAction(keyAction))
|
|
202
191
|
|
|
203
192
|
const createNewAnnotation = (antiScaledMouseStagePosition: Point) => {
|
|
204
193
|
// switch to editing mode
|
|
205
|
-
setEditorMode(EditorModes.CREATE)
|
|
194
|
+
setEditorMode(EditorModes.CREATE)
|
|
206
195
|
|
|
207
196
|
// we get the mouse position in the stage coordinate system
|
|
208
197
|
// to use it as annotation coordinates, convert it into a list in the percentage coordinates
|
|
209
|
-
const percentagedInitialCoords =
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
);
|
|
198
|
+
const percentagedInitialCoords = transform.convertStageCoordinatesToPercentaged(
|
|
199
|
+
[antiScaledMouseStagePosition],
|
|
200
|
+
imageToStageFactor,
|
|
201
|
+
imgSize,
|
|
202
|
+
)
|
|
215
203
|
|
|
216
204
|
// bbox always require 2 points - add first coordinate again
|
|
217
205
|
if (selectedAnnoTool === AnnotationTool.BBox)
|
|
218
|
-
percentagedInitialCoords.push(percentagedInitialCoords[0])
|
|
206
|
+
percentagedInitialCoords.push(percentagedInitialCoords[0])
|
|
219
207
|
|
|
220
|
-
const newAnnotationInternalId: number = onRequestNewAnnoId()
|
|
208
|
+
const newAnnotationInternalId: number = onRequestNewAnnoId()
|
|
221
209
|
const newAnnotation = new Annotation(
|
|
222
210
|
newAnnotationInternalId,
|
|
223
211
|
selectedAnnoTool,
|
|
224
212
|
percentagedInitialCoords,
|
|
225
|
-
)
|
|
213
|
+
)
|
|
226
214
|
|
|
227
|
-
setAnnoTimestamp(performance.now())
|
|
215
|
+
setAnnoTimestamp(performance.now())
|
|
228
216
|
|
|
229
217
|
// automatically select the last used label
|
|
230
|
-
if (currentLabelId !== undefined) newAnnotation.labelIds = [currentLabelId]
|
|
218
|
+
if (currentLabelId !== undefined) newAnnotation.labelIds = [currentLabelId]
|
|
231
219
|
|
|
232
|
-
onAnnoCreated(newAnnotation)
|
|
220
|
+
onAnnoCreated(newAnnotation)
|
|
233
221
|
|
|
234
222
|
// points are created in only one frame
|
|
235
223
|
// (no size / shape has to be defined)
|
|
@@ -241,468 +229,474 @@ const Canvas = ({
|
|
|
241
229
|
...newAnnotation,
|
|
242
230
|
coordinates: [antiScaledMouseStagePosition],
|
|
243
231
|
annoTime: 0, // its literally one frame
|
|
244
|
-
}
|
|
245
|
-
onFinishCreateAnno(newPointAnnotation)
|
|
232
|
+
}
|
|
233
|
+
onFinishCreateAnno(newPointAnnotation)
|
|
246
234
|
}
|
|
247
|
-
}
|
|
235
|
+
}
|
|
248
236
|
|
|
249
237
|
// @TODO
|
|
250
238
|
const editSelectedAnnotation = () => {
|
|
251
239
|
if (
|
|
252
240
|
selectedAnnotation &&
|
|
253
|
-
![AnnotationTool.Line, AnnotationTool.Polygon].includes(
|
|
254
|
-
selectedAnnotation!.type,
|
|
255
|
-
)
|
|
241
|
+
![AnnotationTool.Line, AnnotationTool.Polygon].includes(selectedAnnotation!.type)
|
|
256
242
|
)
|
|
257
|
-
return
|
|
243
|
+
return
|
|
258
244
|
|
|
259
245
|
// selectedAnnotation!.mode = AnnotationMode.CREATE;
|
|
260
246
|
|
|
261
247
|
const newSelectedAnnotation: Annotation | undefined = annotations.find(
|
|
262
248
|
(annotation: Annotation) =>
|
|
263
249
|
annotation.internalId === selectedAnnotation?.internalId,
|
|
264
|
-
)
|
|
250
|
+
)
|
|
265
251
|
|
|
266
|
-
if (newSelectedAnnotation === undefined) return
|
|
252
|
+
if (newSelectedAnnotation === undefined) return
|
|
267
253
|
|
|
268
|
-
newSelectedAnnotation.mode = AnnotationMode.CREATE
|
|
269
|
-
newSelectedAnnotation.status = AnnotationStatus.CREATING
|
|
270
|
-
newSelectedAnnotation.selectedNode =
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
onSetSelectedTool(newSelectedAnnotation.type);
|
|
254
|
+
newSelectedAnnotation.mode = AnnotationMode.CREATE
|
|
255
|
+
newSelectedAnnotation.status = AnnotationStatus.CREATING
|
|
256
|
+
newSelectedAnnotation.selectedNode = newSelectedAnnotation.coordinates.length - 1
|
|
257
|
+
setEditorMode(EditorModes.ADD)
|
|
258
|
+
onSetSelectedTool(newSelectedAnnotation.type)
|
|
274
259
|
|
|
275
260
|
// replace annotation in list
|
|
276
261
|
// const selectedAnnotationId: number = annotations.findIndex((annotation: Annotation) => annotation.internalId === selectedAnnotation?.internalId)
|
|
277
262
|
// const newAnnotations = [...annotations]
|
|
278
263
|
// newAnnotations[selectedAnnotationId] = newSelectedAnnotation
|
|
279
|
-
onAnnoEditing(newSelectedAnnotation)
|
|
264
|
+
onAnnoEditing(newSelectedAnnotation)
|
|
280
265
|
// onShouldDeleteAnno(selectedAnnotation!.internalId);
|
|
281
|
-
}
|
|
266
|
+
}
|
|
282
267
|
|
|
283
268
|
const traverseAnnos = () => {
|
|
284
|
-
const currentId = selectedAnnotation ? selectedAnnotation.internalId : 0
|
|
269
|
+
const currentId = selectedAnnotation ? selectedAnnotation.internalId : 0
|
|
285
270
|
|
|
286
271
|
// get the first annotation with an higher internal id
|
|
287
272
|
const nextAnno: Annotation | undefined = annotations.find(
|
|
288
273
|
(annotation: Annotation) => annotation.internalId > currentId,
|
|
289
|
-
)
|
|
274
|
+
)
|
|
290
275
|
|
|
291
|
-
if (nextAnno) return onSelectAnnotation(nextAnno)
|
|
276
|
+
if (nextAnno) return onSelectAnnotation(nextAnno)
|
|
292
277
|
|
|
293
278
|
// fallback: there was no anno with an higher id -> just use first anno
|
|
294
|
-
if (annotations.length > 0) return onSelectAnnotation(annotations[0])
|
|
295
|
-
}
|
|
279
|
+
if (annotations.length > 0) return onSelectAnnotation(annotations[0])
|
|
280
|
+
}
|
|
296
281
|
|
|
297
282
|
const traverseAnnosBackwards = () => {
|
|
298
|
-
const currentId = selectedAnnotation ? selectedAnnotation.internalId : 0
|
|
283
|
+
const currentId = selectedAnnotation ? selectedAnnotation.internalId : 0
|
|
299
284
|
|
|
300
285
|
// copy list (dont mutate original annotations)
|
|
301
|
-
const newAnnotations: Annotation[] = [...annotations]
|
|
286
|
+
const newAnnotations: Annotation[] = [...annotations]
|
|
302
287
|
|
|
303
288
|
// sort by internalId descending
|
|
304
|
-
newAnnotations.sort((a, b) => b.internalId - a.internalId)
|
|
289
|
+
newAnnotations.sort((a, b) => b.internalId - a.internalId)
|
|
305
290
|
|
|
306
291
|
// get the first annotation with an lower internal id than current
|
|
307
292
|
const nextAnno: Annotation | undefined = newAnnotations.find(
|
|
308
293
|
(annotation: Annotation) => annotation.internalId < currentId,
|
|
309
|
-
)
|
|
294
|
+
)
|
|
310
295
|
|
|
311
|
-
if (nextAnno) return onSelectAnnotation(nextAnno)
|
|
296
|
+
if (nextAnno) return onSelectAnnotation(nextAnno)
|
|
312
297
|
|
|
313
298
|
// fallback: there was no anno with an higher id -> just use first anno
|
|
314
299
|
if (annotations.length > 0)
|
|
315
|
-
return onSelectAnnotation(annotations[annotations.length - 1])
|
|
316
|
-
}
|
|
300
|
+
return onSelectAnnotation(annotations[annotations.length - 1])
|
|
301
|
+
}
|
|
317
302
|
|
|
318
303
|
const copyCurrentAnnotation = () => {
|
|
319
304
|
// set a copy of the annotation object to the clipboard in localstorage
|
|
320
305
|
// if (selectedAnnotation) setAnnotationClipboard({ ...selectedAnnotation });
|
|
321
306
|
if (selectedAnnotation) {
|
|
322
|
-
const serializedAnnotation: string = JSON.stringify(selectedAnnotation)
|
|
323
|
-
localStorage.setItem(
|
|
307
|
+
const serializedAnnotation: string = JSON.stringify(selectedAnnotation)
|
|
308
|
+
localStorage.setItem('lostAnnotationClipboard', serializedAnnotation)
|
|
324
309
|
|
|
325
310
|
const copySuccessNotification: SIANotification = {
|
|
326
|
-
title:
|
|
327
|
-
message:
|
|
311
|
+
title: 'Success',
|
|
312
|
+
message: 'Annotation copied',
|
|
328
313
|
type: NotificationType.SUCCESS,
|
|
329
|
-
}
|
|
314
|
+
}
|
|
330
315
|
|
|
331
|
-
onNotification(copySuccessNotification)
|
|
316
|
+
onNotification(copySuccessNotification)
|
|
332
317
|
}
|
|
333
|
-
}
|
|
318
|
+
}
|
|
334
319
|
|
|
335
320
|
const pasteAnnotation = () => {
|
|
336
321
|
const clipboardAnnotation: string | null = localStorage.getItem(
|
|
337
|
-
|
|
338
|
-
)
|
|
339
|
-
if (clipboardAnnotation == undefined) return
|
|
322
|
+
'lostAnnotationClipboard',
|
|
323
|
+
)
|
|
324
|
+
if (clipboardAnnotation == undefined) return
|
|
340
325
|
|
|
341
|
-
const annotationToPaste: Annotation = JSON.parse(clipboardAnnotation)
|
|
326
|
+
const annotationToPaste: Annotation = JSON.parse(clipboardAnnotation)
|
|
342
327
|
|
|
343
328
|
// get a new temporary id
|
|
344
|
-
annotationToPaste.internalId = onRequestNewAnnoId()
|
|
345
|
-
annotationToPaste.externalId =
|
|
329
|
+
annotationToPaste.internalId = onRequestNewAnnoId()
|
|
330
|
+
annotationToPaste.externalId = ''
|
|
346
331
|
|
|
347
|
-
onAnnoCreationFinished(annotationToPaste, true)
|
|
332
|
+
onAnnoCreationFinished(annotationToPaste, true)
|
|
348
333
|
|
|
349
|
-
onSelectAnnotation(annotationToPaste)
|
|
350
|
-
}
|
|
334
|
+
onSelectAnnotation(annotationToPaste)
|
|
335
|
+
}
|
|
351
336
|
|
|
352
337
|
const handleKeyAction = (keyAction: KeyAction) => {
|
|
353
338
|
switch (keyAction) {
|
|
354
339
|
case KeyAction.EDIT_LABEL:
|
|
355
|
-
if (selectedAnnotation) setIsLabelInputVisible(true)
|
|
356
|
-
break
|
|
340
|
+
if (selectedAnnotation) setIsLabelInputVisible(true)
|
|
341
|
+
break
|
|
357
342
|
case KeyAction.DELETE_ANNO:
|
|
358
|
-
if (selectedAnnotation)
|
|
359
|
-
|
|
360
|
-
break;
|
|
343
|
+
if (selectedAnnotation) onShouldDeleteAnno(selectedAnnotation.internalId)
|
|
344
|
+
break
|
|
361
345
|
// case KeyAction.TOGGLE_ANNO_COMMENT_INPUT:
|
|
362
346
|
// console.log("KeyAction TODO: TOGGLE_ANNO_COMMENT_INPUT");
|
|
363
347
|
// break;
|
|
364
348
|
case KeyAction.DELETE_ANNO_IN_CREATION:
|
|
365
349
|
// remove an unfinished annotation
|
|
366
350
|
if (editorMode === EditorModes.CREATE) {
|
|
367
|
-
onShouldDeleteAnno(selectedAnnotation!.internalId)
|
|
368
|
-
setEditorMode(EditorModes.VIEW)
|
|
351
|
+
onShouldDeleteAnno(selectedAnnotation!.internalId)
|
|
352
|
+
setEditorMode(EditorModes.VIEW)
|
|
369
353
|
}
|
|
370
|
-
break
|
|
354
|
+
break
|
|
371
355
|
case KeyAction.ENTER_ANNO_ADD_MODE:
|
|
372
|
-
console.log(
|
|
373
|
-
break
|
|
356
|
+
console.log('KeyAction TODO: ENTER_ANNO_ADD_MODE')
|
|
357
|
+
break
|
|
374
358
|
case KeyAction.LEAVE_ANNO_ADD_MODE:
|
|
375
|
-
console.log(
|
|
376
|
-
break
|
|
359
|
+
console.log('KeyAction TODO: LEAVE_ANNO_ADD_MODE')
|
|
360
|
+
break
|
|
377
361
|
case KeyAction.UNDO:
|
|
378
|
-
console.log(
|
|
379
|
-
break
|
|
362
|
+
console.log('KeyAction TODO: UNDO')
|
|
363
|
+
break
|
|
380
364
|
case KeyAction.REDO:
|
|
381
|
-
console.log(
|
|
382
|
-
break
|
|
365
|
+
console.log('KeyAction TODO: REDO')
|
|
366
|
+
break
|
|
383
367
|
case KeyAction.TRAVERSE_ANNOS:
|
|
384
|
-
traverseAnnos()
|
|
385
|
-
break
|
|
368
|
+
traverseAnnos()
|
|
369
|
+
break
|
|
386
370
|
case KeyAction.TRAVERSE_ANNOS_BACKWARDS:
|
|
387
|
-
traverseAnnosBackwards()
|
|
388
|
-
break
|
|
371
|
+
traverseAnnosBackwards()
|
|
372
|
+
break
|
|
389
373
|
case KeyAction.CAM_MOVE_LEFT:
|
|
390
|
-
moveCamera(20 * svgScale, 0)
|
|
391
|
-
break
|
|
374
|
+
moveCamera(20 * svgScale, 0)
|
|
375
|
+
break
|
|
392
376
|
case KeyAction.CAM_MOVE_RIGHT:
|
|
393
|
-
moveCamera(-20 * svgScale, 0)
|
|
394
|
-
break
|
|
377
|
+
moveCamera(-20 * svgScale, 0)
|
|
378
|
+
break
|
|
395
379
|
case KeyAction.CAM_MOVE_UP:
|
|
396
|
-
moveCamera(0, 20 * svgScale)
|
|
397
|
-
break
|
|
380
|
+
moveCamera(0, 20 * svgScale)
|
|
381
|
+
break
|
|
398
382
|
case KeyAction.CAM_MOVE_DOWN:
|
|
399
|
-
moveCamera(0, -20 * svgScale)
|
|
400
|
-
break
|
|
383
|
+
moveCamera(0, -20 * svgScale)
|
|
384
|
+
break
|
|
401
385
|
case KeyAction.CAM_MOVE_STOP:
|
|
402
|
-
console.log(
|
|
403
|
-
break
|
|
386
|
+
console.log('KeyAction TODO: CAM_MOVE_STOP')
|
|
387
|
+
break
|
|
404
388
|
case KeyAction.COPY_ANNOTATION:
|
|
405
|
-
copyCurrentAnnotation()
|
|
406
|
-
break
|
|
389
|
+
copyCurrentAnnotation()
|
|
390
|
+
break
|
|
407
391
|
case KeyAction.PASTE_ANNOTATION:
|
|
408
|
-
pasteAnnotation()
|
|
409
|
-
break
|
|
392
|
+
pasteAnnotation()
|
|
393
|
+
break
|
|
410
394
|
case KeyAction.RECREATE_ANNO:
|
|
411
|
-
console.log(
|
|
412
|
-
editSelectedAnnotation()
|
|
413
|
-
break
|
|
395
|
+
console.log('KeyAction TODO: RECREATE_ANNO')
|
|
396
|
+
editSelectedAnnotation()
|
|
397
|
+
break
|
|
398
|
+
case KeyAction.TOGGLE_IMAGE_JUNK:
|
|
399
|
+
// dont junk image while creating annotation
|
|
400
|
+
if (editorMode === EditorModes.ADD || editorMode === EditorModes.CREATE) return
|
|
401
|
+
onSetIsImageJunk(!isImageJunk)
|
|
402
|
+
break
|
|
414
403
|
default:
|
|
415
|
-
console.log(
|
|
416
|
-
break
|
|
404
|
+
console.log('Unknown KeyAction', keyAction)
|
|
405
|
+
break
|
|
417
406
|
}
|
|
418
|
-
}
|
|
407
|
+
}
|
|
419
408
|
|
|
420
409
|
const moveCamera = (movementX: number, movementY: number) => {
|
|
421
|
-
let newTransX = svgTranslation.x + movementX / svgScale
|
|
422
|
-
let newTransY = svgTranslation.y + movementY / svgScale
|
|
410
|
+
let newTransX = svgTranslation.x + movementX / svgScale
|
|
411
|
+
let newTransY = svgTranslation.y + movementY / svgScale
|
|
423
412
|
|
|
424
|
-
|
|
425
|
-
const
|
|
426
|
-
const
|
|
427
|
-
const
|
|
428
|
-
const maxTransY = canvasSize.y * 0.75;
|
|
413
|
+
const vXMin = canvasSize.x * 0.45
|
|
414
|
+
const vXMax = canvasSize.x * 0.55
|
|
415
|
+
const yXMin = canvasSize.y * 0.45
|
|
416
|
+
const yXMax = canvasSize.y * 0.55
|
|
429
417
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
418
|
+
const topLeftPoint: Point = { x: 0, y: 0 }
|
|
419
|
+
const vLeft = windowViewport.getViewportCoordinates(
|
|
420
|
+
svgTranslation,
|
|
421
|
+
canvasSize,
|
|
422
|
+
svgScale,
|
|
423
|
+
topLeftPoint,
|
|
424
|
+
)
|
|
425
|
+
const vRight = windowViewport.getViewportCoordinates(
|
|
426
|
+
svgTranslation,
|
|
427
|
+
canvasSize,
|
|
428
|
+
svgScale,
|
|
429
|
+
canvasSize,
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
if (vLeft.vX >= vXMin) newTransX = svgTranslation.x - 5
|
|
433
|
+
else if (vRight.vX <= vXMax) newTransX = svgTranslation.x + 5
|
|
434
|
+
else if (vLeft.vY >= yXMin) newTransY = svgTranslation.y - 5
|
|
435
|
+
else if (vRight.vY <= yXMax) newTransY = svgTranslation.y + 5
|
|
435
436
|
|
|
436
|
-
setSvgTranslation({ x: newTransX, y: newTransY })
|
|
437
|
-
}
|
|
437
|
+
setSvgTranslation({ x: newTransX, y: newTransY })
|
|
438
|
+
}
|
|
438
439
|
|
|
439
440
|
const calculateScaledAnnotations = (_annotations: Annotation[]) => {
|
|
440
|
-
if (
|
|
441
|
-
|
|
442
|
-
stageSize.y <= 0 ||
|
|
443
|
-
imgSize.x <= 0 ||
|
|
444
|
-
imgSize.y <= 0
|
|
445
|
-
)
|
|
446
|
-
return [];
|
|
441
|
+
if (stageSize.x <= 0 || stageSize.y <= 0 || imgSize.x <= 0 || imgSize.y <= 0)
|
|
442
|
+
return []
|
|
447
443
|
|
|
448
444
|
const newScaledAnnotations = annotations.map((annotation: Annotation) => ({
|
|
449
445
|
...annotation,
|
|
450
|
-
coordinates:
|
|
446
|
+
coordinates: transform.convertPercentagedCoordinatesToStage(
|
|
451
447
|
annotation.coordinates,
|
|
452
448
|
imgSize,
|
|
453
449
|
stageSize,
|
|
454
450
|
),
|
|
455
|
-
}))
|
|
451
|
+
}))
|
|
456
452
|
|
|
457
|
-
return newScaledAnnotations
|
|
458
|
-
}
|
|
453
|
+
return newScaledAnnotations
|
|
454
|
+
}
|
|
459
455
|
|
|
460
|
-
const scaledAnnotations = calculateScaledAnnotations(annotations)
|
|
456
|
+
const scaledAnnotations = calculateScaledAnnotations(annotations)
|
|
461
457
|
|
|
462
458
|
const resetCanvas = () => {
|
|
463
|
-
setEditorMode(EditorModes.VIEW)
|
|
459
|
+
setEditorMode(EditorModes.VIEW)
|
|
464
460
|
|
|
465
461
|
// largest possible annotation size fitting the whole image
|
|
466
|
-
setStageSize({ x: -1, y: -1 })
|
|
462
|
+
setStageSize({ x: -1, y: -1 })
|
|
467
463
|
|
|
468
464
|
if (imageRef.current !== null) {
|
|
469
|
-
const { width, height } = imageRef.current.getBoundingClientRect()
|
|
470
|
-
setImgSize({ x: width, y: height })
|
|
465
|
+
const { width, height } = imageRef.current.getBoundingClientRect()
|
|
466
|
+
setImgSize({ x: width, y: height })
|
|
471
467
|
}
|
|
472
468
|
|
|
473
|
-
setSvgScale(1.0)
|
|
474
|
-
setSvgTranslation({ x: 0, y: 0 })
|
|
469
|
+
setSvgScale(1.0)
|
|
470
|
+
setSvgTranslation({ x: 0, y: 0 })
|
|
475
471
|
|
|
476
|
-
setLabelInputPosition(undefined)
|
|
477
|
-
setIsLabelInputVisible(false)
|
|
478
|
-
}
|
|
472
|
+
setLabelInputPosition(undefined)
|
|
473
|
+
setIsLabelInputVisible(false)
|
|
474
|
+
}
|
|
479
475
|
|
|
480
476
|
// image changed after init -> reset everything
|
|
481
477
|
useEffect(() => {
|
|
482
478
|
if (canvasRef?.current !== undefined) {
|
|
483
|
-
const { width, height } = canvasRef!.current!.getBoundingClientRect()
|
|
479
|
+
const { width, height } = canvasRef!.current!.getBoundingClientRect()
|
|
484
480
|
|
|
485
481
|
// for whatever reason the ref adds the toolbars height to the available space, leading to a container size reaching outside the bottom
|
|
486
482
|
// remove its height here manually
|
|
487
|
-
const heightWithoutToolbar: number = height - toolbarHeight
|
|
483
|
+
const heightWithoutToolbar: number = height - toolbarHeight
|
|
488
484
|
|
|
489
|
-
setCanvasSize({ x: width, y: heightWithoutToolbar })
|
|
485
|
+
setCanvasSize({ x: width, y: heightWithoutToolbar })
|
|
490
486
|
|
|
491
487
|
// listen for size changes on div element
|
|
492
488
|
const resizeObserver = new ResizeObserver(() => {
|
|
493
|
-
const { width, height } = canvasRef!.current!.getBoundingClientRect()
|
|
494
|
-
const heightWithoutToolbar: number = height - toolbarHeight
|
|
489
|
+
const { width, height } = canvasRef!.current!.getBoundingClientRect()
|
|
490
|
+
const heightWithoutToolbar: number = height - toolbarHeight
|
|
495
491
|
|
|
496
|
-
setCanvasSize({ x: width, y: heightWithoutToolbar })
|
|
497
|
-
})
|
|
498
|
-
resizeObserver.observe(canvasRef!.current!)
|
|
492
|
+
setCanvasSize({ x: width, y: heightWithoutToolbar })
|
|
493
|
+
})
|
|
494
|
+
resizeObserver.observe(canvasRef!.current!)
|
|
499
495
|
|
|
500
496
|
// cleanup
|
|
501
|
-
return () => resizeObserver.disconnect()
|
|
497
|
+
return () => resizeObserver.disconnect()
|
|
502
498
|
}
|
|
503
499
|
|
|
504
|
-
resetCanvas()
|
|
505
|
-
}, [image, isFullscreen])
|
|
500
|
+
resetCanvas()
|
|
501
|
+
}, [image, isFullscreen])
|
|
506
502
|
|
|
507
503
|
useEffect(() => {
|
|
508
|
-
calculatePageToCanvasOffset()
|
|
509
|
-
}, [imageRef, svgTranslation, canvasSize])
|
|
504
|
+
calculatePageToCanvasOffset()
|
|
505
|
+
}, [imageRef, svgTranslation, canvasSize])
|
|
510
506
|
|
|
511
507
|
// notify component about available size
|
|
512
508
|
useEffect(() => {
|
|
513
|
-
if (canvasRef.current === null) return
|
|
514
|
-
const { width, height } = canvasRef.current.getBoundingClientRect()
|
|
509
|
+
if (canvasRef.current === null) return
|
|
510
|
+
const { width, height } = canvasRef.current.getBoundingClientRect()
|
|
515
511
|
|
|
516
512
|
// for whatever reason the ref adds the toolbars height to the available space, leading to a container size reaching outside the bottom
|
|
517
513
|
// remove its height here manually
|
|
518
|
-
const heightWithoutToolbar: number = height - toolbarHeight
|
|
514
|
+
const heightWithoutToolbar: number = height - toolbarHeight
|
|
519
515
|
|
|
520
|
-
setCanvasSize({ x: width, y: heightWithoutToolbar })
|
|
521
|
-
}, [canvasRef])
|
|
516
|
+
setCanvasSize({ x: width, y: heightWithoutToolbar })
|
|
517
|
+
}, [canvasRef])
|
|
522
518
|
|
|
523
519
|
// notify component about default image size
|
|
524
520
|
useEffect(() => {
|
|
525
|
-
if (imageRef.current === null) return
|
|
521
|
+
if (imageRef.current === null) return
|
|
526
522
|
|
|
527
|
-
const { width, height } = imageRef.current.getBoundingClientRect()
|
|
523
|
+
const { width, height } = imageRef.current.getBoundingClientRect()
|
|
528
524
|
|
|
529
|
-
setImgSize({ x: width, y: height })
|
|
525
|
+
setImgSize({ x: width, y: height })
|
|
530
526
|
|
|
531
527
|
// listen for size changes on div element
|
|
532
528
|
const imgResizeObserver = new ResizeObserver(() => {
|
|
533
|
-
const { width, height } = imageRef!.current!.getBoundingClientRect()
|
|
529
|
+
const { width, height } = imageRef!.current!.getBoundingClientRect()
|
|
534
530
|
|
|
535
|
-
setImgSize({ x: width, y: height })
|
|
536
|
-
})
|
|
537
|
-
imgResizeObserver.observe(imageRef!.current!)
|
|
531
|
+
setImgSize({ x: width, y: height })
|
|
532
|
+
})
|
|
533
|
+
imgResizeObserver.observe(imageRef!.current!)
|
|
538
534
|
|
|
539
|
-
return () => imgResizeObserver.disconnect()
|
|
540
|
-
}, [imageRef])
|
|
535
|
+
return () => imgResizeObserver.disconnect()
|
|
536
|
+
}, [imageRef])
|
|
541
537
|
|
|
542
538
|
useEffect(() => {
|
|
543
|
-
if (imageToStageFactor === 0) return
|
|
539
|
+
if (imageToStageFactor === 0) return
|
|
544
540
|
|
|
545
541
|
const newStageSize: Vector2 = {
|
|
546
542
|
x: imgSize.x * imageToStageFactor,
|
|
547
543
|
y: imgSize.y * imageToStageFactor,
|
|
548
|
-
}
|
|
544
|
+
}
|
|
549
545
|
|
|
550
|
-
setStageSize(newStageSize)
|
|
551
|
-
}, [imageToStageFactor, imgSize])
|
|
546
|
+
setStageSize(newStageSize)
|
|
547
|
+
}, [imageToStageFactor, imgSize])
|
|
552
548
|
|
|
553
549
|
useEffect(() => {
|
|
554
|
-
if (!isPolygonSelectionMode) return
|
|
555
|
-
if (polygonOperationResult.polygonsToCreate === undefined) return
|
|
550
|
+
if (!isPolygonSelectionMode) return
|
|
551
|
+
if (polygonOperationResult.polygonsToCreate === undefined) return
|
|
556
552
|
|
|
557
553
|
// create all polygons calculated from the outside world
|
|
558
554
|
polygonOperationResult.polygonsToCreate.forEach(
|
|
559
555
|
(polygonToCreate: ToolCoordinates) => {
|
|
560
|
-
const newAnnotationInternalId: number = onRequestNewAnnoId()
|
|
556
|
+
const newAnnotationInternalId: number = onRequestNewAnnoId()
|
|
561
557
|
const newAnnotation: Annotation = new Annotation(
|
|
562
558
|
newAnnotationInternalId,
|
|
563
559
|
polygonToCreate.type,
|
|
564
|
-
|
|
560
|
+
transform.convertPercentagedCoordinatesToStage(
|
|
565
561
|
polygonToCreate.coordinates,
|
|
566
562
|
imgSize,
|
|
567
563
|
stageSize,
|
|
568
564
|
),
|
|
569
565
|
AnnotationMode.VIEW,
|
|
570
566
|
AnnotationStatus.CREATED,
|
|
571
|
-
)
|
|
567
|
+
)
|
|
572
568
|
|
|
573
|
-
onFinishCreateAnno(newAnnotation)
|
|
569
|
+
onFinishCreateAnno(newAnnotation)
|
|
574
570
|
},
|
|
575
|
-
)
|
|
576
|
-
}, [polygonOperationResult])
|
|
571
|
+
)
|
|
572
|
+
}, [polygonOperationResult])
|
|
577
573
|
|
|
578
574
|
const onFinishCreateAnno = (fullyCreatedAnnotation: Annotation) => {
|
|
579
|
-
setEditorMode(EditorModes.VIEW)
|
|
575
|
+
setEditorMode(EditorModes.VIEW)
|
|
580
576
|
|
|
581
577
|
const newAnnotation: Annotation = {
|
|
582
578
|
...fullyCreatedAnnotation,
|
|
583
579
|
mode: AnnotationMode.VIEW,
|
|
584
|
-
}
|
|
580
|
+
}
|
|
585
581
|
|
|
586
582
|
// handle annoTime (not for points though - they are created in only one frame)
|
|
587
583
|
if (fullyCreatedAnnotation.type !== AnnotationTool.Point) {
|
|
588
584
|
const annoEditDuration: number = TimeUtils.getRoundedDuration(
|
|
589
585
|
annoTimestamp,
|
|
590
586
|
performance.now(),
|
|
591
|
-
)
|
|
592
|
-
newAnnotation.annoTime = annoEditDuration
|
|
587
|
+
)
|
|
588
|
+
newAnnotation.annoTime = annoEditDuration
|
|
593
589
|
}
|
|
594
590
|
|
|
595
591
|
// convert the coordinates from our local scaled sytem into the percentaged one
|
|
596
|
-
const percentagedCoordinates =
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
newAnnotation.coordinates = percentagedCoordinates;
|
|
592
|
+
const percentagedCoordinates = transform.convertStageCoordinatesToPercentaged(
|
|
593
|
+
fullyCreatedAnnotation.coordinates,
|
|
594
|
+
imageToStageFactor,
|
|
595
|
+
imgSize,
|
|
596
|
+
)
|
|
597
|
+
newAnnotation.coordinates = percentagedCoordinates
|
|
603
598
|
|
|
604
|
-
onAnnoChanged(newAnnotation)
|
|
599
|
+
onAnnoChanged(newAnnotation)
|
|
605
600
|
|
|
606
601
|
// inform annotation that we just created it
|
|
607
602
|
const hasAnnoJustBeenCreated: boolean =
|
|
608
|
-
selectedAnnoTool === AnnotationTool.Point || isPolygonSelectionMode
|
|
603
|
+
selectedAnnoTool === AnnotationTool.Point || isPolygonSelectionMode
|
|
609
604
|
|
|
610
|
-
onAnnoCreationFinished(newAnnotation, hasAnnoJustBeenCreated)
|
|
611
|
-
}
|
|
605
|
+
onAnnoCreationFinished(newAnnotation, hasAnnoJustBeenCreated)
|
|
606
|
+
}
|
|
612
607
|
|
|
613
608
|
const onKeyDown = (e: KeyboardEvent) => {
|
|
614
|
-
e.preventDefault()
|
|
609
|
+
e.preventDefault()
|
|
615
610
|
|
|
616
|
-
keyMapper.keyDown(e.key, e.shiftKey, e.ctrlKey)
|
|
617
|
-
}
|
|
611
|
+
keyMapper.keyDown(e.key, e.shiftKey, e.ctrlKey)
|
|
612
|
+
}
|
|
618
613
|
|
|
619
614
|
const onKeyUp = (e: KeyboardEvent) => {
|
|
620
|
-
e.preventDefault()
|
|
615
|
+
e.preventDefault()
|
|
621
616
|
// @TODO implement keyMapper
|
|
622
617
|
// this.keyMapper.keyUp(e.key);
|
|
623
618
|
// if (propsOnKeyUp) propsOnKeyUp(e);
|
|
624
|
-
}
|
|
619
|
+
}
|
|
625
620
|
|
|
626
|
-
const onMouseDown = (e: React.MouseEvent<
|
|
621
|
+
const onMouseDown = (e: React.MouseEvent<SVGElement, MouseEvent>) => {
|
|
627
622
|
if (e.button === 0) {
|
|
628
623
|
// left click
|
|
629
624
|
} else if (e.button === 1) {
|
|
630
625
|
// click on mouse wheel
|
|
631
|
-
setEditorMode(EditorModes.CAMERA_MOVE)
|
|
626
|
+
setEditorMode(EditorModes.CAMERA_MOVE)
|
|
632
627
|
} else if (e.button === 2) {
|
|
633
628
|
// check if annotation creation allowed in settings
|
|
634
|
-
if (!annotationSettings!.canCreate) return
|
|
629
|
+
if (!annotationSettings!.canCreate) return
|
|
635
630
|
|
|
636
|
-
if (editorMode === EditorModes.ADD) return
|
|
631
|
+
if (editorMode === EditorModes.ADD || editorMode === EditorModes.CREATE) return
|
|
637
632
|
|
|
638
633
|
// right click -> start new annotation
|
|
639
634
|
// clicks during annotation creation will be handled inside the AnnotationComponent
|
|
640
635
|
const antiScaledMouseStageMovedPosition: Point =
|
|
641
|
-
|
|
636
|
+
mouse.getAntiScaledMouseStagePosition(
|
|
642
637
|
e,
|
|
643
638
|
pageToStageOffset,
|
|
644
639
|
svgScale,
|
|
645
640
|
svgTranslation,
|
|
646
|
-
)
|
|
641
|
+
)
|
|
647
642
|
|
|
648
643
|
// remove translation when image was horizontally centered
|
|
649
644
|
const antiScaledMouseStagePosition: Point = {
|
|
650
645
|
x: antiScaledMouseStageMovedPosition.x - imageCenteringSpace,
|
|
651
646
|
y: antiScaledMouseStageMovedPosition.y,
|
|
652
|
-
}
|
|
647
|
+
}
|
|
653
648
|
|
|
654
|
-
createNewAnnotation(antiScaledMouseStagePosition)
|
|
649
|
+
createNewAnnotation(antiScaledMouseStagePosition)
|
|
655
650
|
}
|
|
656
|
-
}
|
|
651
|
+
}
|
|
657
652
|
|
|
658
653
|
const onMouseOver = () => {
|
|
659
654
|
// this.svg.current.focus();
|
|
660
655
|
//Prevent scrolling on svg
|
|
661
656
|
if (preventScrolling) {
|
|
662
|
-
document.body.style.overflow =
|
|
657
|
+
document.body.style.overflow = 'hidden'
|
|
663
658
|
}
|
|
664
|
-
}
|
|
659
|
+
}
|
|
665
660
|
|
|
666
661
|
const onMouseUp = (e) => {
|
|
667
662
|
switch (e.button) {
|
|
668
663
|
case 1:
|
|
669
|
-
setEditorMode(EditorModes.VIEW)
|
|
670
|
-
break
|
|
664
|
+
setEditorMode(EditorModes.VIEW)
|
|
665
|
+
break
|
|
671
666
|
default:
|
|
672
|
-
break
|
|
667
|
+
break
|
|
673
668
|
}
|
|
674
|
-
}
|
|
669
|
+
}
|
|
675
670
|
|
|
676
671
|
const onMouseMove = (movementX: number, movementY: number) => {
|
|
677
672
|
if (editorMode === EditorModes.CAMERA_MOVE) {
|
|
678
|
-
moveCamera(movementX, movementY)
|
|
673
|
+
moveCamera(movementX, movementY)
|
|
679
674
|
}
|
|
680
|
-
}
|
|
675
|
+
}
|
|
681
676
|
|
|
682
677
|
const onMouseLeave = () => {
|
|
683
678
|
if (preventScrolling) {
|
|
684
|
-
document.body.style.overflow =
|
|
679
|
+
document.body.style.overflow = ''
|
|
685
680
|
}
|
|
686
|
-
}
|
|
681
|
+
}
|
|
687
682
|
|
|
688
683
|
const onWheel = (e: WheelEvent) => {
|
|
689
|
-
const scaleFactor = 1.25
|
|
690
|
-
const scrollDirection = e.deltaY < 0 ? 1 : -1
|
|
684
|
+
const scaleFactor = 1.25
|
|
685
|
+
const scrollDirection = e.deltaY < 0 ? 1 : -1
|
|
691
686
|
|
|
692
687
|
// calculate scaling based on scroll wheel direction
|
|
693
|
-
const newScale =
|
|
694
|
-
scrollDirection > 0 ? svgScale * scaleFactor : svgScale / scaleFactor;
|
|
688
|
+
const newScale = scrollDirection > 0 ? svgScale * scaleFactor : svgScale / scaleFactor
|
|
695
689
|
|
|
696
690
|
// zoom in/out without affecting the pixel the mouse is at
|
|
697
|
-
const mousePositionInStage: Point =
|
|
691
|
+
const mousePositionInStage: Point = mouse.getAntiScaledMouseStagePosition(
|
|
698
692
|
e,
|
|
699
693
|
pageToStageOffset,
|
|
700
694
|
svgScale,
|
|
701
695
|
svgTranslation,
|
|
702
|
-
)
|
|
696
|
+
)
|
|
703
697
|
|
|
704
698
|
// set translation around mouse pixel
|
|
705
|
-
const scaleChangeFactor: number = svgScale / newScale
|
|
699
|
+
const scaleChangeFactor: number = svgScale / newScale
|
|
706
700
|
const newTranslation: Vector2 = {
|
|
707
701
|
x:
|
|
708
702
|
scaleChangeFactor * (mousePositionInStage.x + svgTranslation.x) -
|
|
@@ -710,94 +704,91 @@ const Canvas = ({
|
|
|
710
704
|
y:
|
|
711
705
|
scaleChangeFactor * (mousePositionInStage.y + svgTranslation.y) -
|
|
712
706
|
mousePositionInStage.y,
|
|
713
|
-
}
|
|
707
|
+
}
|
|
714
708
|
|
|
715
709
|
// contstrain zoom
|
|
716
710
|
if (newScale < 1.0) {
|
|
717
|
-
setSvgScale(1)
|
|
711
|
+
setSvgScale(1)
|
|
718
712
|
if (svgTranslation.x != 0 || svgTranslation.y != 0)
|
|
719
|
-
setSvgTranslation({ x: 0, y: 0 })
|
|
713
|
+
setSvgTranslation({ x: 0, y: 0 })
|
|
720
714
|
} else if (newScale > 200) {
|
|
721
|
-
setSvgScale(200)
|
|
722
|
-
setSvgTranslation(newTranslation)
|
|
715
|
+
setSvgScale(200)
|
|
716
|
+
setSvgTranslation(newTranslation)
|
|
723
717
|
} else {
|
|
724
|
-
setSvgScale(newScale)
|
|
725
|
-
setSvgTranslation(newTranslation)
|
|
718
|
+
setSvgScale(newScale)
|
|
719
|
+
setSvgTranslation(newTranslation)
|
|
726
720
|
}
|
|
727
|
-
}
|
|
721
|
+
}
|
|
728
722
|
|
|
729
723
|
const onAnnoAction = (annotation: Annotation, canvasAction: CanvasAction) => {
|
|
730
724
|
if (canvasAction !== CanvasAction.ANNO_SELECTED) {
|
|
731
|
-
console.log(
|
|
732
|
-
return
|
|
725
|
+
console.log('Unknown Canvas Action:', canvasAction)
|
|
726
|
+
return
|
|
733
727
|
}
|
|
734
728
|
|
|
735
729
|
const percentagedAnnotation = {
|
|
736
730
|
...annotation,
|
|
737
|
-
coordinates:
|
|
731
|
+
coordinates: transform.convertStageCoordinatesToPercentaged(
|
|
738
732
|
[...annotation.coordinates],
|
|
739
733
|
imageToStageFactor,
|
|
740
734
|
imgSize,
|
|
741
735
|
),
|
|
742
|
-
}
|
|
736
|
+
}
|
|
743
737
|
|
|
744
|
-
onSelectAnnotation(percentagedAnnotation)
|
|
738
|
+
onSelectAnnotation(percentagedAnnotation)
|
|
745
739
|
|
|
746
740
|
// get top left point of annotation
|
|
747
|
-
const leftPoints: Point[] =
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
const topLeftPoint: Point = transform2.getTopPoint(leftPoints)[0];
|
|
751
|
-
const pageTopLeftPoint: Point = transform2.convertStageToPage(
|
|
741
|
+
const leftPoints: Point[] = transform.getMostLeftPoints(annotation.coordinates)
|
|
742
|
+
const topLeftPoint: Point = transform.getTopPoint(leftPoints)[0]
|
|
743
|
+
const pageTopLeftPoint: Point = transform.convertStageToPage(
|
|
752
744
|
topLeftPoint,
|
|
753
745
|
pageToStageOffset,
|
|
754
746
|
svgScale,
|
|
755
747
|
svgTranslation,
|
|
756
|
-
)
|
|
748
|
+
)
|
|
757
749
|
|
|
758
|
-
setLabelInputPosition(pageTopLeftPoint)
|
|
759
|
-
}
|
|
750
|
+
setLabelInputPosition(pageTopLeftPoint)
|
|
751
|
+
}
|
|
760
752
|
|
|
761
753
|
const handleOnAnnoChanged = (annotation: Annotation) => {
|
|
762
|
-
const percentagedCoordinates =
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
);
|
|
754
|
+
const percentagedCoordinates = transform.convertStageCoordinatesToPercentaged(
|
|
755
|
+
annotation.coordinates,
|
|
756
|
+
imageToStageFactor,
|
|
757
|
+
imgSize,
|
|
758
|
+
)
|
|
768
759
|
|
|
769
760
|
const newAnnotation = {
|
|
770
761
|
...annotation,
|
|
771
762
|
coordinates: percentagedCoordinates,
|
|
772
|
-
}
|
|
763
|
+
}
|
|
773
764
|
|
|
774
765
|
// mark loaded annotations as changed (they wont be saved otherwise)
|
|
775
766
|
if (newAnnotation.status === AnnotationStatus.LOADED)
|
|
776
|
-
newAnnotation.status = AnnotationStatus.CHANGED
|
|
767
|
+
newAnnotation.status = AnnotationStatus.CHANGED
|
|
777
768
|
|
|
778
769
|
// send event to parent component
|
|
779
|
-
onAnnoChanged(newAnnotation)
|
|
780
|
-
}
|
|
770
|
+
onAnnoChanged(newAnnotation)
|
|
771
|
+
}
|
|
781
772
|
|
|
782
773
|
const renderAnnotations = (): ReactElement => {
|
|
783
774
|
// hide all annotations when image is moved
|
|
784
|
-
if (editorMode === EditorModes.CAMERA_MOVE) return
|
|
775
|
+
if (editorMode === EditorModes.CAMERA_MOVE) return <></>
|
|
785
776
|
|
|
786
777
|
const editorModesOtherAnnosShouldBeHiddenIn = [
|
|
787
778
|
EditorModes.CREATE,
|
|
788
779
|
EditorModes.ADD,
|
|
789
780
|
EditorModes.MOVE,
|
|
790
|
-
]
|
|
781
|
+
]
|
|
791
782
|
|
|
792
783
|
const shouldHideOtherAnnos: boolean =
|
|
793
|
-
editorModesOtherAnnosShouldBeHiddenIn.includes(editorMode)
|
|
784
|
+
editorModesOtherAnnosShouldBeHiddenIn.includes(editorMode)
|
|
794
785
|
|
|
795
786
|
// draw the annotation using the AnnotationComponent and the scaled coordinates
|
|
796
787
|
const annos: ReactElement[] = scaledAnnotations.map(
|
|
797
788
|
(scaledAnnotation: Annotation): ReactElement => {
|
|
798
789
|
// only show selected anno in specific editor modes
|
|
799
790
|
const isAnnoSelected: boolean =
|
|
800
|
-
scaledAnnotation.internalId === selectedAnnotation?.internalId
|
|
791
|
+
scaledAnnotation.internalId === selectedAnnotation?.internalId
|
|
801
792
|
|
|
802
793
|
if (shouldHideOtherAnnos && !isAnnoSelected)
|
|
803
794
|
return (
|
|
@@ -805,7 +796,7 @@ const Canvas = ({
|
|
|
805
796
|
// we still need to provide a key, otherwise we got 10 nothings and react cannot differ between them (it wants to)
|
|
806
797
|
// use an empty svg g element because <></> cannot have a key
|
|
807
798
|
<g key={`annotationComponent_${scaledAnnotation.internalId}`} />
|
|
808
|
-
)
|
|
799
|
+
)
|
|
809
800
|
|
|
810
801
|
return (
|
|
811
802
|
<AnnotationComponent
|
|
@@ -828,22 +819,21 @@ const Canvas = ({
|
|
|
828
819
|
onAction={onAnnoAction}
|
|
829
820
|
onAnnoChanged={handleOnAnnoChanged}
|
|
830
821
|
onAnnotationModeChange={(annotationMode: AnnotationMode) => {
|
|
831
|
-
if (annotationMode === AnnotationMode.MOVE)
|
|
832
|
-
setEditorMode(EditorModes.MOVE);
|
|
822
|
+
if (annotationMode === AnnotationMode.MOVE) setEditorMode(EditorModes.MOVE)
|
|
833
823
|
if (
|
|
834
824
|
editorMode === EditorModes.MOVE &&
|
|
835
825
|
annotationMode === AnnotationMode.VIEW
|
|
836
826
|
)
|
|
837
|
-
setEditorMode(EditorModes.VIEW)
|
|
827
|
+
setEditorMode(EditorModes.VIEW)
|
|
838
828
|
}}
|
|
839
829
|
onNotification={onNotification}
|
|
840
830
|
/>
|
|
841
|
-
)
|
|
831
|
+
)
|
|
842
832
|
},
|
|
843
|
-
)
|
|
833
|
+
)
|
|
844
834
|
|
|
845
|
-
return <g>{annos}</g
|
|
846
|
-
}
|
|
835
|
+
return <g>{annos}</g>
|
|
836
|
+
}
|
|
847
837
|
|
|
848
838
|
const renderInfiniteSelectionArea = (): ReactElement => {
|
|
849
839
|
// block changing annotations while label selector is open
|
|
@@ -852,36 +842,36 @@ const Canvas = ({
|
|
|
852
842
|
<circle
|
|
853
843
|
cx={stageSize.x / 2}
|
|
854
844
|
cy={stageSize.y / 2}
|
|
855
|
-
r={
|
|
845
|
+
r={'100%'}
|
|
856
846
|
style={{ opacity: 0 }}
|
|
857
847
|
onContextMenu={(e) => e.preventDefault()}
|
|
858
848
|
onClick={() => {
|
|
859
|
-
setIsLabelInputVisible(false)
|
|
849
|
+
setIsLabelInputVisible(false)
|
|
860
850
|
}}
|
|
861
851
|
/>
|
|
862
|
-
)
|
|
863
|
-
}
|
|
852
|
+
)
|
|
853
|
+
}
|
|
864
854
|
|
|
865
855
|
// calculate the center of the canvas in page coordinates
|
|
866
856
|
const junkTextStart: Vector2 = {
|
|
867
857
|
x: pageToCanvasOffset.x + canvasSize.x / 2,
|
|
868
858
|
y: pageToCanvasOffset.y + canvasSize.y / 2,
|
|
869
|
-
}
|
|
859
|
+
}
|
|
870
860
|
|
|
871
861
|
return (
|
|
872
862
|
<div
|
|
873
863
|
ref={canvasRef}
|
|
874
864
|
style={{
|
|
875
|
-
width:
|
|
876
|
-
height:
|
|
865
|
+
width: '100%',
|
|
866
|
+
height: '100%',
|
|
877
867
|
}}
|
|
878
868
|
>
|
|
879
869
|
<div
|
|
880
870
|
style={{
|
|
881
|
-
position:
|
|
871
|
+
position: 'absolute',
|
|
882
872
|
left: labelInputPosition?.x !== undefined ? labelInputPosition.x : 0,
|
|
883
873
|
top: labelInputPosition?.y !== undefined ? labelInputPosition.y : 0,
|
|
884
|
-
display: labelInputPosition?.y !== undefined ?
|
|
874
|
+
display: labelInputPosition?.y !== undefined ? 'inherit' : 'none',
|
|
885
875
|
zIndex: isLabelInputVisible ? 7000 : -1,
|
|
886
876
|
}}
|
|
887
877
|
>
|
|
@@ -893,17 +883,16 @@ const Canvas = ({
|
|
|
893
883
|
isMultilabel={annotationSettings!.canHaveMultipleLabels}
|
|
894
884
|
onLabelSelect={(selectedLabelIds: number[]) => {
|
|
895
885
|
// close the input popup
|
|
896
|
-
setIsLabelInputVisible(false)
|
|
886
|
+
setIsLabelInputVisible(false)
|
|
897
887
|
|
|
898
888
|
// inform parent which label was chosen
|
|
899
889
|
if (selectedLabelIds.length > 0) {
|
|
900
890
|
const newLabelIds: number[] = selectedLabelIds.filter(
|
|
901
|
-
(labelId: number) =>
|
|
902
|
-
|
|
903
|
-
);
|
|
891
|
+
(labelId: number) => !selectedAnnotation!.labelIds!.includes(labelId),
|
|
892
|
+
)
|
|
904
893
|
|
|
905
894
|
if (newLabelIds.length > 0) {
|
|
906
|
-
setCurrentLabelId(newLabelIds[0])
|
|
895
|
+
setCurrentLabelId(newLabelIds[0])
|
|
907
896
|
}
|
|
908
897
|
}
|
|
909
898
|
|
|
@@ -911,22 +900,22 @@ const Canvas = ({
|
|
|
911
900
|
const newAnnotationStatus: AnnotationStatus =
|
|
912
901
|
selectedAnnotation.status === AnnotationStatus.LOADED
|
|
913
902
|
? AnnotationStatus.CHANGED
|
|
914
|
-
: selectedAnnotation.status
|
|
903
|
+
: selectedAnnotation.status
|
|
915
904
|
|
|
916
905
|
// selectedAnnotation comes from SIA and is therefore in the percentaged system
|
|
917
906
|
// convert it first
|
|
918
907
|
// also update the new labels
|
|
919
908
|
const updatedAnno: Annotation = {
|
|
920
909
|
...selectedAnnotation,
|
|
921
|
-
coordinates:
|
|
910
|
+
coordinates: transform.convertPercentagedCoordinatesToStage(
|
|
922
911
|
selectedAnnotation!.coordinates,
|
|
923
912
|
imgSize,
|
|
924
913
|
stageSize,
|
|
925
914
|
),
|
|
926
915
|
labelIds: [...selectedLabelIds],
|
|
927
916
|
status: newAnnotationStatus,
|
|
928
|
-
}
|
|
929
|
-
handleOnAnnoChanged(updatedAnno)
|
|
917
|
+
}
|
|
918
|
+
handleOnAnnoChanged(updatedAnno)
|
|
930
919
|
}}
|
|
931
920
|
/>
|
|
932
921
|
</div>
|
|
@@ -934,12 +923,12 @@ const Canvas = ({
|
|
|
934
923
|
{isImageJunk && (
|
|
935
924
|
<div
|
|
936
925
|
style={{
|
|
937
|
-
position:
|
|
926
|
+
position: 'absolute',
|
|
938
927
|
left: junkTextStart.x,
|
|
939
928
|
top: junkTextStart.y,
|
|
940
|
-
transform:
|
|
941
|
-
textAlign:
|
|
942
|
-
color:
|
|
929
|
+
transform: 'translate(-50%, -50%)',
|
|
930
|
+
textAlign: 'center',
|
|
931
|
+
color: 'white',
|
|
943
932
|
}}
|
|
944
933
|
>
|
|
945
934
|
<FontAwesomeIcon
|
|
@@ -952,29 +941,13 @@ const Canvas = ({
|
|
|
952
941
|
)}
|
|
953
942
|
|
|
954
943
|
<svg
|
|
955
|
-
// ref={svgRef}
|
|
956
|
-
// width={svgSize[0] > 0 ? svgSize[0] : "100%"}
|
|
957
|
-
// height={svgSize[1] > 0 ? svgSize[1] : "100%"}
|
|
958
944
|
width="100%"
|
|
959
945
|
height="100%"
|
|
960
|
-
// width={
|
|
961
|
-
// this.props.fixedImageSize
|
|
962
|
-
// ? this.props.fixedImageSize
|
|
963
|
-
// : this.state.svg.width
|
|
964
|
-
// }
|
|
965
|
-
// height={
|
|
966
|
-
// this.props.fixedImageSize
|
|
967
|
-
// ? this.props.fixedImageSize
|
|
968
|
-
// : this.state.svg.height
|
|
969
|
-
// }
|
|
970
946
|
onKeyDown={onKeyDown}
|
|
971
947
|
onKeyUp={onKeyUp}
|
|
972
948
|
onMouseMove={(e) => onMouseMove(e.movementX, e.movementY)}
|
|
973
|
-
// onMouseMove={(e) => this.handleSvgMouseMove(e)}
|
|
974
949
|
tabIndex={0}
|
|
975
|
-
|
|
976
|
-
// height="100%"
|
|
977
|
-
// style={{ position: "absolute" }}
|
|
950
|
+
onMouseDown={(e) => onMouseDown(e)}
|
|
978
951
|
>
|
|
979
952
|
<g
|
|
980
953
|
transform={`scale(${svgScale}) translate(${centeredSvgTranslation.x}, ${centeredSvgTranslation.y})`}
|
|
@@ -986,12 +959,11 @@ const Canvas = ({
|
|
|
986
959
|
onMouseMove={(e) => onMouseMove(e.movementX, e.movementY)}
|
|
987
960
|
onClick={() => {
|
|
988
961
|
// clicked onto canvas => clear selected anno
|
|
989
|
-
onSelectAnnotation(undefined)
|
|
962
|
+
onSelectAnnotation(undefined)
|
|
990
963
|
}}
|
|
991
964
|
>
|
|
992
965
|
<image
|
|
993
966
|
onContextMenu={(e) => e.preventDefault()}
|
|
994
|
-
onMouseDown={(e) => onMouseDown(e)}
|
|
995
967
|
href={image}
|
|
996
968
|
ref={imageRef}
|
|
997
969
|
// undefined -> use default (unscaled) size of image
|
|
@@ -1012,13 +984,13 @@ const Canvas = ({
|
|
|
1012
984
|
style={{ opacity: 0.8 }}
|
|
1013
985
|
onContextMenu={(e) => e.preventDefault()}
|
|
1014
986
|
onClick={() => {
|
|
1015
|
-
setLabelInputPosition(undefined)
|
|
987
|
+
setLabelInputPosition(undefined)
|
|
1016
988
|
}}
|
|
1017
989
|
/>
|
|
1018
990
|
)}
|
|
1019
991
|
</svg>
|
|
1020
992
|
</div>
|
|
1021
|
-
)
|
|
1022
|
-
}
|
|
993
|
+
)
|
|
994
|
+
}
|
|
1023
995
|
|
|
1024
|
-
export default Canvas
|
|
996
|
+
export default Canvas
|