lost-sia 1.2.0 → 2.0.0-alpha0
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/LICENSE +21 -0
- package/README.md +0 -0
- package/package.json +60 -47
- package/src/AnnoExampleViewer.jsx +57 -0
- package/src/AnnoLabelInput.jsx +99 -0
- package/src/AnnoToolBar.jsx +132 -0
- package/src/Annotation/AnnoBar.jsx +148 -0
- package/src/Annotation/Annotation.jsx +358 -0
- package/src/Annotation/Annotation.scss +47 -0
- package/src/Annotation/BBox.jsx +291 -0
- package/src/Annotation/Edge.jsx +82 -0
- package/src/Annotation/InfSelectionArea.jsx +71 -0
- package/src/Annotation/Line.jsx +60 -0
- package/src/Annotation/Node.jsx +278 -0
- package/src/Annotation/Point.jsx +196 -0
- package/src/Annotation/Polygon.jsx +361 -0
- package/src/Canvas.jsx +1839 -0
- package/src/ImgBar.jsx +123 -0
- package/src/InfoBoxes/AnnoDetails.jsx +166 -0
- package/src/InfoBoxes/AnnoStats.jsx +104 -0
- package/src/InfoBoxes/InfoBox.jsx +78 -0
- package/src/InfoBoxes/InfoBoxArea.jsx +155 -0
- package/src/InfoBoxes/LabelInfo.jsx +95 -0
- package/src/LabelInput.jsx +224 -0
- package/src/Prompt.jsx +46 -0
- package/src/SIA.scss +10 -0
- package/src/SIAFilterButton.jsx +178 -0
- package/src/SIASettingButton.jsx +122 -0
- package/src/Sia.jsx +485 -0
- package/src/SiaPopup.jsx +10 -0
- package/src/ToolBar.jsx +399 -0
- package/src/Toolbar.css +13 -0
- package/src/ToolbarItem.jsx +25 -0
- package/src/filterTools.js +3 -0
- package/src/index.js +17 -0
- package/src/siaDummyData.js +265 -0
- package/src/test.js +7 -0
- package/src/types/annoStatus.js +4 -0
- package/src/types/canvasActions.js +57 -0
- package/src/types/cursorstyles.js +3 -0
- package/src/types/modes.js +8 -0
- package/src/types/notificationType.js +4 -0
- package/src/types/toolbarEvents.js +33 -0
- package/src/types/tools.js +11 -0
- package/src/utils/annoConversion.js +115 -0
- package/src/utils/colorlut.js +67 -0
- package/src/utils/constraints.js +75 -0
- package/src/utils/hist.js +67 -0
- package/src/utils/keyActions.js +107 -0
- package/src/utils/mouse.js +14 -0
- package/src/utils/siaIcons.jsx +95 -0
- package/src/utils/transform.js +318 -0
- package/src/utils/uiConfig.js +57 -0
- package/src/utils/windowViewport.js +35 -0
- package/CHANGELOG.md +0 -163
- package/dist/index.css +0 -24823
- package/dist/index.es.js +0 -10431
- package/dist/index.es.js.map +0 -1
- package/dist/index.js +0 -10446
- package/dist/index.js.map +0 -1
package/src/Canvas.jsx
ADDED
|
@@ -0,0 +1,1839 @@
|
|
|
1
|
+
import React, { Component } from 'react'
|
|
2
|
+
import _, { transform } from 'lodash'
|
|
3
|
+
import Annotation from './Annotation/Annotation'
|
|
4
|
+
import AnnoLabelInput from './AnnoLabelInput'
|
|
5
|
+
import ImgBar from './ImgBar'
|
|
6
|
+
import Prompt from './Prompt'
|
|
7
|
+
import LabelInput from './LabelInput'
|
|
8
|
+
import AnnoToolBar from './AnnoToolBar'
|
|
9
|
+
|
|
10
|
+
import * as annoConversion from './utils/annoConversion'
|
|
11
|
+
import * as keyActions from './utils/keyActions'
|
|
12
|
+
import KeyMapper from './utils/keyActions'
|
|
13
|
+
import * as TOOLS from './types/tools'
|
|
14
|
+
import * as modes from './types/modes'
|
|
15
|
+
import UndoRedo from './utils/hist'
|
|
16
|
+
import * as transformAnnos from './utils/transform'
|
|
17
|
+
import * as annoStatus from './types/annoStatus'
|
|
18
|
+
import * as canvasActions from './types/canvasActions'
|
|
19
|
+
import { Loader, Dimmer, Icon, Header, Button, Form, TextArea } from 'semantic-ui-react';
|
|
20
|
+
import * as mouse from './utils/mouse';
|
|
21
|
+
import * as colorlut from './utils/colorlut'
|
|
22
|
+
import * as notificationType from './types/notificationType'
|
|
23
|
+
import * as wv from './utils/windowViewport'
|
|
24
|
+
|
|
25
|
+
import './SIA.scss'
|
|
26
|
+
import InfoBoxes from './InfoBoxes/InfoBoxArea'
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* SIA Canvas element that handles annotations within an image
|
|
31
|
+
*
|
|
32
|
+
* @param {React.Ref} container - A react ref to a div that defines the
|
|
33
|
+
* space where this Canvas lives in.
|
|
34
|
+
* @param {object} annos - A json object containing all annotation
|
|
35
|
+
* information for an image
|
|
36
|
+
* {
|
|
37
|
+
* image : {
|
|
38
|
+
* id: int,
|
|
39
|
+
* number: int,
|
|
40
|
+
* amount: int,
|
|
41
|
+
* isFirst: bool,
|
|
42
|
+
* isLast: bool,
|
|
43
|
+
* description: string, // -> optional
|
|
44
|
+
* imgActions: list of string, // -> optional
|
|
45
|
+
* },
|
|
46
|
+
* annotations: {
|
|
47
|
+
* bBoxes: [{
|
|
48
|
+
* id: int, // -> Not required if status === annoStatus.NEW
|
|
49
|
+
* data: {},
|
|
50
|
+
* labelIds: list of int, // -> optional
|
|
51
|
+
* status: see annoStatus, // -> optional
|
|
52
|
+
* annoTime: float, // -> optional
|
|
53
|
+
* },...],
|
|
54
|
+
* points: []
|
|
55
|
+
* lines: []
|
|
56
|
+
* polygons: []
|
|
57
|
+
* }
|
|
58
|
+
* }
|
|
59
|
+
* @param {object} annoSaveResponse - Backend response when updating an annotation in backend
|
|
60
|
+
* {
|
|
61
|
+
* tempId: int or str, // temporal frontend Id
|
|
62
|
+
* dbId: int, // Id from backend
|
|
63
|
+
* newStatus: str // new Status for the annotation
|
|
64
|
+
* }
|
|
65
|
+
* @param {object} possibleLabels - Possible labels that can be assigned to
|
|
66
|
+
* an annotation.
|
|
67
|
+
* {
|
|
68
|
+
* id: int,
|
|
69
|
+
* description: str,
|
|
70
|
+
* label: str, (name of the label)
|
|
71
|
+
* color: str (color is optional)
|
|
72
|
+
* }
|
|
73
|
+
* @param {blob} imageBlob - The actual image blob that will be displayed
|
|
74
|
+
* @param {object} uiConfig - User interface configs
|
|
75
|
+
* {
|
|
76
|
+
* nodesRadius: int, strokeWidth: int,
|
|
77
|
+
* layoutOffset: {left:int, top:int, right:int, bottom:int}, -> Offset of the canvas inside the container
|
|
78
|
+
* imgBarVisible: bool,
|
|
79
|
+
* imgLabelInputVisible: bool,
|
|
80
|
+
* centerCanvasInContainer: bool, -> Center the canvas in the middle of the container.
|
|
81
|
+
* maxCanvas: bool -> Maximize Canvas Size. Do not fit canvas to image size.
|
|
82
|
+
* }
|
|
83
|
+
* @param {int} layoutUpdate - A counter that triggers a layout update
|
|
84
|
+
* everytime it is incremented.
|
|
85
|
+
* @param {string} selectedTool - The tool that is selected to draw an
|
|
86
|
+
* annotation. Possible choices are: 'bBox', 'point', 'line', 'polygon'
|
|
87
|
+
* @param {object} canvasConfig - Configuration for this canvas
|
|
88
|
+
* {
|
|
89
|
+
* annos:{
|
|
90
|
+
* tools: {
|
|
91
|
+
* point: bool,
|
|
92
|
+
* line: bool,
|
|
93
|
+
* polygon: bool,
|
|
94
|
+
* bbox: bool
|
|
95
|
+
* },
|
|
96
|
+
* multilabels: bool,
|
|
97
|
+
* actions: {
|
|
98
|
+
* draw: bool,
|
|
99
|
+
* label: bool,
|
|
100
|
+
* edit: bool,
|
|
101
|
+
* },
|
|
102
|
+
* maxAnnos: null or int,
|
|
103
|
+
* minArea: int
|
|
104
|
+
* },
|
|
105
|
+
* img: {
|
|
106
|
+
* multilabels: bool,
|
|
107
|
+
* actions: {
|
|
108
|
+
* label: bool,
|
|
109
|
+
* }
|
|
110
|
+
* },
|
|
111
|
+
* allowedToMarkExample: bool, -> Indicates wether the current user is allowed to mark an annotation as example.
|
|
112
|
+
* }
|
|
113
|
+
* @param {str or int} defaultLabel (optional) - Name or ID of the default label that is used
|
|
114
|
+
* when no label was selected by the annotator. If not set "no label" will be used.
|
|
115
|
+
* If ID is used, it needs to be one of the possible label ids.
|
|
116
|
+
* @param {bool} blocked Block canvas view with loading dimmer.
|
|
117
|
+
* @param {bool} preventScrolling Prevent scrolling on mouseEnter
|
|
118
|
+
* @param {bool} lockedAnnos A list of AnnoIds of annos that should only be displayed.
|
|
119
|
+
* Such annos can not be edited in any way.
|
|
120
|
+
* @event onAnnoSaveEvent - Callback with update information for a single
|
|
121
|
+
* annotation or the current image that can be used for backend updates
|
|
122
|
+
* args: {
|
|
123
|
+
* action: the action that was performed in frontend,
|
|
124
|
+
* anno: anno information,
|
|
125
|
+
* img: image information
|
|
126
|
+
* }
|
|
127
|
+
* @event onNotification - Callback for Notification messages
|
|
128
|
+
* args: {title: str, message: str, type: str}
|
|
129
|
+
* @event onKeyDown - Fires for keyDown on canvas
|
|
130
|
+
* @event onKeyUp - Fires for keyUp on canvas
|
|
131
|
+
* @event onAnnoEvent - Fires when an anno performed an action
|
|
132
|
+
* args: {anno: annoObject, newAnnos: list of annoObjects, pAction: str}
|
|
133
|
+
* @event onCanvasEvent - Fires on canvas event
|
|
134
|
+
* args: {action: action, data: dataObject}
|
|
135
|
+
* action -> CANVAS_SVG_UPDATE
|
|
136
|
+
* data: {width: int, height: int, scale: float, translateX: float,
|
|
137
|
+
* translateY:float}
|
|
138
|
+
* action -> CANVAS_UI_CONFIG_UPDATE
|
|
139
|
+
* action -> CANVAS_LABEL_INPUT_CLOSE
|
|
140
|
+
* action -> CANVAS_IMG_LOADED
|
|
141
|
+
* action -> CANVAS_IMGBAR_CLOSE
|
|
142
|
+
* @event onImgBarClose - Fires when close button on ImgBar was hit.
|
|
143
|
+
* @event onGetFunction - Get special canvas functions for manipulation from outside canvas
|
|
144
|
+
* deleteAllAnnos()
|
|
145
|
+
* unloadImage()
|
|
146
|
+
* resetZoom()
|
|
147
|
+
* getAnnos(annos,removeFrontendIds)
|
|
148
|
+
*/
|
|
149
|
+
class Canvas extends Component {
|
|
150
|
+
|
|
151
|
+
constructor(props) {
|
|
152
|
+
super(props)
|
|
153
|
+
this.state = {
|
|
154
|
+
|
|
155
|
+
svg: {
|
|
156
|
+
width: '100%',
|
|
157
|
+
height: '100%',
|
|
158
|
+
scale: 1.0,
|
|
159
|
+
translateX: 0,
|
|
160
|
+
translateY: 0
|
|
161
|
+
},
|
|
162
|
+
image: {
|
|
163
|
+
width: undefined,
|
|
164
|
+
height: undefined
|
|
165
|
+
},
|
|
166
|
+
imageOffset: {
|
|
167
|
+
x: 0,
|
|
168
|
+
y: 0
|
|
169
|
+
},
|
|
170
|
+
annos: [],
|
|
171
|
+
mode: modes.VIEW,
|
|
172
|
+
selectedAnnoId: undefined,
|
|
173
|
+
showSingleAnno: undefined,
|
|
174
|
+
showLabelInput: false,
|
|
175
|
+
imageLoaded: false,
|
|
176
|
+
imgLoadCount: 0,
|
|
177
|
+
imgLabelIds: [],
|
|
178
|
+
imgLabelChanged: false,
|
|
179
|
+
imgAnnoTime: 0,
|
|
180
|
+
imgLoadTimestamp: 0,
|
|
181
|
+
performedImageInit: false,
|
|
182
|
+
prevLabel: [],
|
|
183
|
+
imageBlob: undefined,
|
|
184
|
+
isJunk: props.isJunk,
|
|
185
|
+
imgBarVisible: false,
|
|
186
|
+
annoToolBarVisible: false,
|
|
187
|
+
possibleLabels: undefined,
|
|
188
|
+
annoCommentInputTrigger: 0,
|
|
189
|
+
imgActions: [],
|
|
190
|
+
}
|
|
191
|
+
this.img = React.createRef()
|
|
192
|
+
this.svg = React.createRef()
|
|
193
|
+
this.container = React.createRef()
|
|
194
|
+
this.hist = new UndoRedo()
|
|
195
|
+
this.keyMapper = new KeyMapper((keyAction) => this.handleKeyAction(keyAction))
|
|
196
|
+
this.mousePosAbs = undefined
|
|
197
|
+
this.clipboard = undefined
|
|
198
|
+
this.delayedBackendUpdates = {}
|
|
199
|
+
this.tempIdMap = {}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
componentDidMount() {
|
|
203
|
+
this.updatePossibleLabels()
|
|
204
|
+
if (Number.isInteger(this.props.defaultLabel)) {
|
|
205
|
+
|
|
206
|
+
this.setState({ prevLabel: [this.props.defaultLabel] })
|
|
207
|
+
}
|
|
208
|
+
if (this.props.onGetFunction) {
|
|
209
|
+
this.props.onGetFunction({
|
|
210
|
+
'deleteAllAnnos': () => this.deleteAllAnnos(),
|
|
211
|
+
'unloadImage': () => this.unloadImage(),
|
|
212
|
+
'resetZoom': () => this.resetZoom(),
|
|
213
|
+
'getAnnos': (annos, removeFrontendIds) => this.getAnnos(annos, removeFrontendIds)
|
|
214
|
+
})
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
componentDidUpdate(prevProps, prevState) {
|
|
219
|
+
if (prevProps.annoSaveResponse !== this.props.annoSaveResponse) {
|
|
220
|
+
this.updateAnnoBySaveResponse(this.props.annoSaveResponse)
|
|
221
|
+
}
|
|
222
|
+
if (prevProps.imageMeta !== this.props.imageMeta) {
|
|
223
|
+
if (this.props.imageMeta) {
|
|
224
|
+
this.setState({
|
|
225
|
+
imgLabelIds: this.props.imageMeta.labelIds,
|
|
226
|
+
imgAnnoTime: this.props.imageMeta.annoTime,
|
|
227
|
+
imgActions: this.props.imageMeta.imgActions ? this.props.imageMeta.imgActions : [],
|
|
228
|
+
imgLoadTimestamp: performance.now()
|
|
229
|
+
})
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (prevProps.annos !== this.props.annos) {
|
|
233
|
+
if (this.state.imageBlob) {
|
|
234
|
+
this.updateCanvasView(
|
|
235
|
+
annoConversion.fixBackendAnnos(this.props.annos)
|
|
236
|
+
)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if (prevProps.isJunk !== this.props.isJunk) {
|
|
240
|
+
if (this.state.isJunk !== this.props.isJunk) {
|
|
241
|
+
this.setState({
|
|
242
|
+
isJunk: this.props.isJunk
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
// do not save junk changes when image is currently changing (comparing junk state of previous and next image)
|
|
246
|
+
if (this.state.imageLoaded && !this.props.isImageChanging) {
|
|
247
|
+
this.handleAnnoSaveEvent(canvasActions.IMG_JUNK_UPDATE, undefined,
|
|
248
|
+
{ isJunk: this.props.isJunk })
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (this.state.imageBlob !== this.props.imageBlob) {
|
|
253
|
+
this.setState({ imageBlob: this.props.imageBlob })
|
|
254
|
+
}
|
|
255
|
+
if (this.props.possibleLabels !== prevProps.possibleLabels) {
|
|
256
|
+
this.updatePossibleLabels()
|
|
257
|
+
}
|
|
258
|
+
if (this.state.performedImageInit) {
|
|
259
|
+
// Initialize canvas history
|
|
260
|
+
this.setState({
|
|
261
|
+
performedImageInit: false,
|
|
262
|
+
annoToolBarVisible: false
|
|
263
|
+
})
|
|
264
|
+
if (this.props.uiConfig.imgBarVisible) {
|
|
265
|
+
this.setState({ imgBarVisible: true })
|
|
266
|
+
}
|
|
267
|
+
this.hist.clearHist()
|
|
268
|
+
this.hist.push({
|
|
269
|
+
...this.getAnnos(),
|
|
270
|
+
selectedAnnoId: undefined
|
|
271
|
+
}, 'init')
|
|
272
|
+
}
|
|
273
|
+
if (this.state.imageLoaded) {
|
|
274
|
+
// Selected annotation should be on top
|
|
275
|
+
this.putSelectedOnTop(prevState)
|
|
276
|
+
if (prevState.imgLoadCount !== this.state.imgLoadCount) {
|
|
277
|
+
this.updateCanvasView(
|
|
278
|
+
annoConversion.fixBackendAnnos(this.props.annos)
|
|
279
|
+
)
|
|
280
|
+
if (this.props.imageMeta) {
|
|
281
|
+
this.setImageLabels(this.props.imageMeta.labelIds)
|
|
282
|
+
this.setState({
|
|
283
|
+
performedImageInit: true
|
|
284
|
+
})
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
if (prevProps.layoutUpdate !== this.props.layoutUpdate) {
|
|
288
|
+
this.selectAnnotation(undefined)
|
|
289
|
+
this.updateCanvasView(annoConversion.canvasToBackendAnnos(
|
|
290
|
+
this.state.annos, this.state.svg, false, this.state.imageOffset
|
|
291
|
+
))
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
onImageLoad() {
|
|
298
|
+
this.setState({
|
|
299
|
+
imageLoaded: true,
|
|
300
|
+
imgLoadCount: this.state.imgLoadCount + 1,
|
|
301
|
+
showLabelInput: false,
|
|
302
|
+
showSingleAnno: undefined,
|
|
303
|
+
selectedAnnoId: undefined
|
|
304
|
+
})
|
|
305
|
+
this.triggerCanvasEvent(canvasActions.CANVAS_IMG_LOADED)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
onMouseOver() {
|
|
309
|
+
this.svg.current.focus()
|
|
310
|
+
//Prevent scrolling on svg
|
|
311
|
+
if (this.props.preventScrolling) {
|
|
312
|
+
document.body.style.overflow = 'hidden'
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
onMouseLeave() {
|
|
317
|
+
if (this.props.preventScrolling) {
|
|
318
|
+
document.body.style.overflow = ''
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
onWheel(e) {
|
|
323
|
+
// Zoom implementation. Note that svg is first scaled and
|
|
324
|
+
// second translated!
|
|
325
|
+
const up = e.deltaY < 0
|
|
326
|
+
const mousePos = this.getMousePosition(e)
|
|
327
|
+
const zoomFactor = 1.25
|
|
328
|
+
let nextScale
|
|
329
|
+
if (up) {
|
|
330
|
+
nextScale = this.state.svg.scale * zoomFactor
|
|
331
|
+
|
|
332
|
+
} else {
|
|
333
|
+
nextScale = this.state.svg.scale / zoomFactor
|
|
334
|
+
}
|
|
335
|
+
let newTranslation
|
|
336
|
+
//Constrain zoom
|
|
337
|
+
if (nextScale < 1.0) {
|
|
338
|
+
nextScale = 1.0
|
|
339
|
+
newTranslation = { x: 0, y: 0 }
|
|
340
|
+
} else if (nextScale > 200.0) {
|
|
341
|
+
nextScale = 200.0
|
|
342
|
+
newTranslation = wv.getZoomTranslation(mousePos, this.state.svg, nextScale)
|
|
343
|
+
} else {
|
|
344
|
+
newTranslation = wv.getZoomTranslation(mousePos, this.state.svg, nextScale)
|
|
345
|
+
}
|
|
346
|
+
this.setState({
|
|
347
|
+
svg: {
|
|
348
|
+
...this.state.svg,
|
|
349
|
+
scale: nextScale,
|
|
350
|
+
// translateX: -1*(mousePos.x * nextScale - mousePos.x)/nextScale,
|
|
351
|
+
// translateY: -1*(mousePos.y * nextScale - mousePos.y)/nextScale
|
|
352
|
+
translateX: newTranslation.x,
|
|
353
|
+
translateY: newTranslation.y
|
|
354
|
+
}
|
|
355
|
+
})
|
|
356
|
+
return false
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
onRightClick(e) {
|
|
360
|
+
e.preventDefault()
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
onMouseDown(e) {
|
|
364
|
+
if (e.button === 0) {
|
|
365
|
+
this.selectAnnotation(undefined)
|
|
366
|
+
}
|
|
367
|
+
else if (e.button === 1) {
|
|
368
|
+
this.setMode(modes.CAMERA_MOVE)
|
|
369
|
+
}
|
|
370
|
+
else if (e.button === 2) {
|
|
371
|
+
//Create annotation on right click
|
|
372
|
+
this.createNewAnnotation(e)
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
onAnnoMouseDown(e) {
|
|
377
|
+
if (e.button === 1) {
|
|
378
|
+
// this.collectAnnos()
|
|
379
|
+
this.setMode(modes.CAMERA_MOVE)
|
|
380
|
+
}
|
|
381
|
+
else if (e.button === 2) {
|
|
382
|
+
//Create annotation on right click
|
|
383
|
+
this.createNewAnnotation(e)
|
|
384
|
+
}
|
|
385
|
+
else if (e.button === 0) {
|
|
386
|
+
if (this.state.showLabelInput) {
|
|
387
|
+
const anno = this.findAnno(this.state.selectedAnnoId)
|
|
388
|
+
this.updateSelectedAnno(anno, modes.VIEW)
|
|
389
|
+
this.showSingleAnno(undefined)
|
|
390
|
+
this.showLabelInput(false)
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
onMouseUp(e) {
|
|
396
|
+
switch (e.button) {
|
|
397
|
+
case 1:
|
|
398
|
+
this.setMode(modes.VIEW)
|
|
399
|
+
break
|
|
400
|
+
default:
|
|
401
|
+
break
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
updateAnnoComment(comment) {
|
|
406
|
+
const anno = this.findAnno(this.state.selectedAnnoId)
|
|
407
|
+
anno.comment = comment
|
|
408
|
+
this.handleAnnoEvent(anno, canvasActions.ANNO_COMMENT_UPDATE)
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
handleKeyAction(action) {
|
|
412
|
+
const anno = this.findAnno(this.state.selectedAnnoId)
|
|
413
|
+
const camKeyStepSize = 20 * this.state.svg.scale
|
|
414
|
+
|
|
415
|
+
switch (action) {
|
|
416
|
+
case keyActions.EDIT_LABEL:
|
|
417
|
+
// Need to get the newest version of annotation data directly
|
|
418
|
+
// from annotation object, when editing label/ hitting enter
|
|
419
|
+
// in create mode, since annotation data in canvas are not updated
|
|
420
|
+
// to this point in time.
|
|
421
|
+
const ar = this.findAnnoRef(this.state.selectedAnnoId)
|
|
422
|
+
let myAnno = undefined
|
|
423
|
+
if (ar !== undefined) {
|
|
424
|
+
myAnno = ar.current.myAnno.current.getResult()
|
|
425
|
+
}
|
|
426
|
+
this.editAnnoLabel(myAnno)
|
|
427
|
+
break
|
|
428
|
+
case keyActions.DELETE_ANNO:
|
|
429
|
+
this.deleteAnnotation(anno)
|
|
430
|
+
break
|
|
431
|
+
case keyActions.TOGGLE_ANNO_COMMENT_INPUT:
|
|
432
|
+
if (this.state.selectedAnnoId) {
|
|
433
|
+
this.setState({ annoCommentInputTrigger: this.state.annoCommentInputTrigger + 1 })
|
|
434
|
+
}
|
|
435
|
+
break
|
|
436
|
+
case keyActions.DELETE_ANNO_IN_CREATION:
|
|
437
|
+
this.deleteAnnoInCreationMode(anno)
|
|
438
|
+
break
|
|
439
|
+
case keyActions.ENTER_ANNO_ADD_MODE:
|
|
440
|
+
if (anno) {
|
|
441
|
+
this.updateSelectedAnno(
|
|
442
|
+
anno, modes.ADD
|
|
443
|
+
)
|
|
444
|
+
}
|
|
445
|
+
break
|
|
446
|
+
case keyActions.LEAVE_ANNO_ADD_MODE:
|
|
447
|
+
if (anno) {
|
|
448
|
+
this.updateSelectedAnno(
|
|
449
|
+
anno, modes.VIEW
|
|
450
|
+
)
|
|
451
|
+
}
|
|
452
|
+
break
|
|
453
|
+
case keyActions.UNDO:
|
|
454
|
+
this.undo()
|
|
455
|
+
break
|
|
456
|
+
case keyActions.REDO:
|
|
457
|
+
this.redo()
|
|
458
|
+
break
|
|
459
|
+
case keyActions.TRAVERSE_ANNOS:
|
|
460
|
+
this.traverseAnnos()
|
|
461
|
+
break
|
|
462
|
+
case keyActions.CAM_MOVE_LEFT:
|
|
463
|
+
this.moveCamera(camKeyStepSize, 0)
|
|
464
|
+
break
|
|
465
|
+
case keyActions.CAM_MOVE_RIGHT:
|
|
466
|
+
this.moveCamera(-camKeyStepSize, 0)
|
|
467
|
+
break
|
|
468
|
+
case keyActions.CAM_MOVE_UP:
|
|
469
|
+
this.moveCamera(0, camKeyStepSize)
|
|
470
|
+
break
|
|
471
|
+
case keyActions.CAM_MOVE_DOWN:
|
|
472
|
+
this.moveCamera(0, -camKeyStepSize)
|
|
473
|
+
break
|
|
474
|
+
case keyActions.CAM_MOVE_STOP:
|
|
475
|
+
break
|
|
476
|
+
case keyActions.COPY_ANNOTATION:
|
|
477
|
+
this.copyAnnotation()
|
|
478
|
+
break
|
|
479
|
+
case keyActions.PASTE_ANNOTATION:
|
|
480
|
+
this.pasteAnnotation(0)
|
|
481
|
+
break
|
|
482
|
+
case keyActions.RECREATE_ANNO:
|
|
483
|
+
// recreate selected annotation using the anno id
|
|
484
|
+
if (this.state.selectedAnnoId) this.recreateAnnotation(this.state.selectedAnnoId)
|
|
485
|
+
break
|
|
486
|
+
default:
|
|
487
|
+
console.warn('Unknown key action', action)
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
onKeyDown(e) {
|
|
493
|
+
e.preventDefault()
|
|
494
|
+
this.keyMapper.keyDown(e.key)
|
|
495
|
+
if (this.props.onKeyDown) {
|
|
496
|
+
this.props.onKeyDown(e)
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
onKeyUp(e) {
|
|
501
|
+
e.preventDefault()
|
|
502
|
+
this.keyMapper.keyUp(e.key)
|
|
503
|
+
if (this.props.onKeyUp) {
|
|
504
|
+
this.props.onKeyUp(e)
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
onMouseMove(e) {
|
|
509
|
+
if (this.state.mode === modes.CAMERA_MOVE) {
|
|
510
|
+
this.moveCamera(e.movementX, e.movementY)
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
onLabelInputDeleteClick(annoId) {
|
|
515
|
+
this.removeSelectedAnno()
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Trigger canvas event
|
|
521
|
+
* @param {String} action Action that was performed
|
|
522
|
+
* @param {Object} data Data object of the action
|
|
523
|
+
*/
|
|
524
|
+
triggerCanvasEvent(action, data) {
|
|
525
|
+
if (this.props.onCanvasEvent) {
|
|
526
|
+
this.props.onCanvasEvent(action, data)
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
checkAndCorrectAnno(anno) {
|
|
531
|
+
// Check if annoation is within image bounds
|
|
532
|
+
const corrected = transformAnnos.correctAnnotation(anno.data, this.state.svg, this.state.imageOffset)
|
|
533
|
+
let newAnno = { ...anno, data: corrected }
|
|
534
|
+
const area = transformAnnos.getArea(corrected, this.state.svg, anno.type, this.state.image)
|
|
535
|
+
if (area !== undefined) {
|
|
536
|
+
if (area < this.props.canvasConfig.annos.minArea) {
|
|
537
|
+
this.handleNotification({
|
|
538
|
+
title: "Annotation to small",
|
|
539
|
+
message: 'Annotation area was ' + Math.round(area) + 'px but needs to be bigger than ' + this.props.canvasConfig.annos.minArea + ' px',
|
|
540
|
+
type: notificationType.WARNING
|
|
541
|
+
})
|
|
542
|
+
// newAnno = {...newAnno, mode: modes.DELETED}
|
|
543
|
+
newAnno = { ...newAnno, mode: modes.DELETED }
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
if (!this.checkAnnoLength(anno)) {
|
|
547
|
+
newAnno = { ...newAnno, mode: modes.DELETED }
|
|
548
|
+
}
|
|
549
|
+
return newAnno
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Handle actions that have been performed by an annotation
|
|
554
|
+
* @param {Number} anno Id of the annotation
|
|
555
|
+
* @param {String} pAction Action that was performed
|
|
556
|
+
*/
|
|
557
|
+
handleAnnoEvent(anno, pAction) {
|
|
558
|
+
console.log('handleAnnoEvent', pAction, anno)
|
|
559
|
+
let newAnnos = undefined
|
|
560
|
+
let actionHistoryStore = undefined
|
|
561
|
+
|
|
562
|
+
switch (pAction) {
|
|
563
|
+
case canvasActions.ANNO_ENTER_CREATE_MODE:
|
|
564
|
+
break
|
|
565
|
+
case canvasActions.ANNO_MARK_EXAMPLE:
|
|
566
|
+
newAnnos = this.updateSelectedAnno(anno, modes.VIEW)
|
|
567
|
+
this.pushHist(
|
|
568
|
+
newAnnos, anno.id,
|
|
569
|
+
pAction, this.state.showSingleAnno
|
|
570
|
+
)
|
|
571
|
+
this.handleAnnoSaveEvent(pAction, anno)
|
|
572
|
+
break
|
|
573
|
+
case canvasActions.ANNO_SELECTED:
|
|
574
|
+
this.selectAnnotation(anno.id)
|
|
575
|
+
// this.pushHist(
|
|
576
|
+
// this.state.annos, anno.id,
|
|
577
|
+
// pAction, this.state.showSingleAnno
|
|
578
|
+
// )
|
|
579
|
+
break
|
|
580
|
+
case canvasActions.ANNO_START_CREATING:
|
|
581
|
+
newAnnos = this.updateSelectedAnno(anno)
|
|
582
|
+
this.pushHist(
|
|
583
|
+
newAnnos, anno.id,
|
|
584
|
+
pAction, this.state.showSingleAnno
|
|
585
|
+
)
|
|
586
|
+
break
|
|
587
|
+
case canvasActions.ANNO_CREATED:
|
|
588
|
+
actionHistoryStore = [...this.state.imgActions, pAction]
|
|
589
|
+
anno = this.stopAnnotimeMeasure(anno)
|
|
590
|
+
newAnnos = this.updateSelectedAnno({ ...anno, status: annoStatus.DATABASE }, modes.VIEW)
|
|
591
|
+
this.pushHist(
|
|
592
|
+
newAnnos, anno.id,
|
|
593
|
+
pAction, undefined
|
|
594
|
+
)
|
|
595
|
+
this.showSingleAnno(undefined)
|
|
596
|
+
this.setState({ annoToolBarVisible: true })
|
|
597
|
+
this.handleAnnoSaveEvent(pAction, anno, { imgActions: actionHistoryStore })
|
|
598
|
+
break
|
|
599
|
+
case canvasActions.ANNO_MOVED:
|
|
600
|
+
actionHistoryStore = [...this.state.imgActions, pAction]
|
|
601
|
+
anno = this.stopAnnotimeMeasure(anno)
|
|
602
|
+
newAnnos = this.updateSelectedAnno(anno, modes.VIEW)
|
|
603
|
+
this.showSingleAnno(undefined)
|
|
604
|
+
this.pushHist(
|
|
605
|
+
newAnnos, anno.id,
|
|
606
|
+
pAction, undefined
|
|
607
|
+
)
|
|
608
|
+
this.setState({ annoToolBarVisible: true })
|
|
609
|
+
this.handleAnnoSaveEvent(pAction, anno, { imgActions: actionHistoryStore })
|
|
610
|
+
break
|
|
611
|
+
case canvasActions.ANNO_ENTER_MOVE_MODE:
|
|
612
|
+
anno = this.startAnnotimeMeasure(anno)
|
|
613
|
+
this.updateSelectedAnno(anno, modes.MOVE)
|
|
614
|
+
this.showSingleAnno(anno.id)
|
|
615
|
+
this.setState({ annoToolBarVisible: false })
|
|
616
|
+
break
|
|
617
|
+
case canvasActions.ANNO_ENTER_EDIT_MODE:
|
|
618
|
+
anno = this.startAnnotimeMeasure(anno)
|
|
619
|
+
this.updateSelectedAnno(anno, modes.EDIT)
|
|
620
|
+
// this.showSingleAnno(anno.id)
|
|
621
|
+
this.setState({ annoToolBarVisible: false })
|
|
622
|
+
break
|
|
623
|
+
case canvasActions.ANNO_ADDED_NODE:
|
|
624
|
+
actionHistoryStore = [...this.state.imgActions, pAction]
|
|
625
|
+
newAnnos = this.updateSelectedAnno(anno, modes.VIEW)
|
|
626
|
+
this.pushHist(
|
|
627
|
+
newAnnos, anno.id,
|
|
628
|
+
pAction, this.state.showSingleAnno
|
|
629
|
+
)
|
|
630
|
+
this.handleAnnoSaveEvent(pAction, anno, { imgActions: actionHistoryStore })
|
|
631
|
+
break
|
|
632
|
+
case canvasActions.ANNO_REMOVED_NODE:
|
|
633
|
+
actionHistoryStore = [...this.state.imgActions, pAction]
|
|
634
|
+
if (!this.checkAnnoLength(anno)) {
|
|
635
|
+
newAnnos = this.updateSelectedAnno(anno, modes.DELETED)
|
|
636
|
+
this.showSingleAnno(undefined)
|
|
637
|
+
} else {
|
|
638
|
+
newAnnos = this.updateSelectedAnno(anno, modes.CREATE)
|
|
639
|
+
}
|
|
640
|
+
this.pushHist(
|
|
641
|
+
newAnnos, anno.id,
|
|
642
|
+
pAction, this.state.showSingleAnno
|
|
643
|
+
)
|
|
644
|
+
if (anno.status !== annoStatus.NEW) {
|
|
645
|
+
this.handleAnnoSaveEvent(pAction, anno, { imgActions: actionHistoryStore })
|
|
646
|
+
}
|
|
647
|
+
break
|
|
648
|
+
case canvasActions.ANNO_EDITED:
|
|
649
|
+
actionHistoryStore = [...this.state.imgActions, pAction]
|
|
650
|
+
anno = this.stopAnnotimeMeasure(anno)
|
|
651
|
+
newAnnos = this.updateSelectedAnno(anno, modes.VIEW)
|
|
652
|
+
this.pushHist(
|
|
653
|
+
newAnnos, anno.id,
|
|
654
|
+
pAction, this.state.showSingleAnno
|
|
655
|
+
)
|
|
656
|
+
this.setState({ annoToolBarVisible: true })
|
|
657
|
+
this.handleAnnoSaveEvent(pAction, anno, { imgActions: actionHistoryStore })
|
|
658
|
+
break
|
|
659
|
+
case canvasActions.ANNO_DELETED:
|
|
660
|
+
actionHistoryStore = [...this.state.imgActions, pAction]
|
|
661
|
+
const res = this.updateSelectedAnno(anno, modes.DELETED, true)
|
|
662
|
+
newAnnos = res.newAnnos
|
|
663
|
+
this.selectAnnotation(undefined)
|
|
664
|
+
this.showSingleAnno(undefined)
|
|
665
|
+
this.pushHist(
|
|
666
|
+
newAnnos, undefined,
|
|
667
|
+
pAction, this.state.showSingleAnno
|
|
668
|
+
)
|
|
669
|
+
this.handleAnnoSaveEvent(pAction, res.newAnno, { imgActions: actionHistoryStore })
|
|
670
|
+
break
|
|
671
|
+
case canvasActions.ANNO_COMMENT_UPDATE:
|
|
672
|
+
actionHistoryStore = [...this.state.imgActions, pAction]
|
|
673
|
+
const res_comment = this.updateSelectedAnno(anno, modes.VIEW, true)
|
|
674
|
+
newAnnos = res_comment.newAnnos
|
|
675
|
+
this.pushHist(
|
|
676
|
+
newAnnos, anno.id,
|
|
677
|
+
pAction, this.state.showSingleAnno
|
|
678
|
+
)
|
|
679
|
+
this.handleNotification({
|
|
680
|
+
title: "Saved comment",
|
|
681
|
+
message: `Saved comment: ${anno.comment}`,
|
|
682
|
+
type: notificationType.SUCCESS
|
|
683
|
+
})
|
|
684
|
+
this.handleAnnoSaveEvent(pAction, res_comment.newAnno, { imgActions: actionHistoryStore })
|
|
685
|
+
break
|
|
686
|
+
case canvasActions.ANNO_LABEL_UPDATE:
|
|
687
|
+
actionHistoryStore = [...this.state.imgActions, pAction]
|
|
688
|
+
anno = this.stopAnnotimeMeasure(anno)
|
|
689
|
+
anno = this.checkAndCorrectAnno(anno)
|
|
690
|
+
console.log('ANNO_LABEL_UPDATE aftercheckAndCorrect', anno)
|
|
691
|
+
// this.updateSelectedAnno(anno, anno.mode)
|
|
692
|
+
if (anno.mode === modes.DELETED) {
|
|
693
|
+
this.updateSelectedAnno(anno, modes.DELETED)
|
|
694
|
+
} else {
|
|
695
|
+
this.updateSelectedAnno({ ...anno, status: annoStatus.DATABASE }, modes.VIEW)
|
|
696
|
+
}
|
|
697
|
+
// if (!this.checkAnnoLength(anno)){
|
|
698
|
+
// newAnnos = this.updateSelectedAnno(anno, modes.DELETED)
|
|
699
|
+
// } else {
|
|
700
|
+
// newAnnos = this.updateSelectedAnno(anno, modes.VIEW)
|
|
701
|
+
// }
|
|
702
|
+
this.setState({ annoToolBarVisible: true })
|
|
703
|
+
if (anno.mode !== modes.DELETED) {
|
|
704
|
+
this.pushHist(
|
|
705
|
+
newAnnos, anno.id,
|
|
706
|
+
pAction, undefined
|
|
707
|
+
)
|
|
708
|
+
this.handleAnnoSaveEvent(pAction, anno, { imgActions: actionHistoryStore })
|
|
709
|
+
}
|
|
710
|
+
break
|
|
711
|
+
case canvasActions.ANNO_CREATED_NODE:
|
|
712
|
+
actionHistoryStore = [...this.state.imgActions, pAction]
|
|
713
|
+
anno = this.stopAnnotimeMeasure(anno)
|
|
714
|
+
newAnnos = this.updateSelectedAnno(anno, modes.CREATE)
|
|
715
|
+
this.pushHist(
|
|
716
|
+
newAnnos, anno.id,
|
|
717
|
+
pAction, this.state.showSingleAnno
|
|
718
|
+
)
|
|
719
|
+
break
|
|
720
|
+
case canvasActions.ANNO_CREATED_FINAL_NODE:
|
|
721
|
+
actionHistoryStore = [...this.state.imgActions, pAction]
|
|
722
|
+
anno = this.stopAnnotimeMeasure(anno)
|
|
723
|
+
newAnnos = this.updateSelectedAnno({ ...anno, status: annoStatus.DATABASE }, modes.VIEW)
|
|
724
|
+
this.pushHist(
|
|
725
|
+
newAnnos, anno.id,
|
|
726
|
+
pAction, undefined
|
|
727
|
+
)
|
|
728
|
+
this.showSingleAnno(undefined)
|
|
729
|
+
this.setState({ annoToolBarVisible: true })
|
|
730
|
+
this.handleAnnoSaveEvent(pAction, anno, { imgActions: actionHistoryStore })
|
|
731
|
+
break
|
|
732
|
+
default:
|
|
733
|
+
console.warn('Action not handled', pAction)
|
|
734
|
+
break
|
|
735
|
+
}
|
|
736
|
+
if (actionHistoryStore) {
|
|
737
|
+
this.setState({ imgActions: actionHistoryStore })
|
|
738
|
+
}
|
|
739
|
+
if (this.props.onAnnoEvent) {
|
|
740
|
+
this.props.onAnnoEvent(anno, newAnnos, pAction)
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
handleAnnoSaveEvent(action, anno, img) {
|
|
745
|
+
const imgData = {
|
|
746
|
+
...img,
|
|
747
|
+
imgId: this.props.imageMeta.id,
|
|
748
|
+
annoTime: this.props.imageMeta.annoTime + (performance.now() - this.state.imgLoadTimestamp) / 1000
|
|
749
|
+
}
|
|
750
|
+
let backendAnno = undefined
|
|
751
|
+
if (anno) {
|
|
752
|
+
let myAnno = this.addDelayedBackendUpdate(anno, action)
|
|
753
|
+
if (!myAnno) return
|
|
754
|
+
if (myAnno.id in this.tempIdMap) {
|
|
755
|
+
myAnno = { ...myAnno, id: this.tempIdMap[myAnno.id] }
|
|
756
|
+
}
|
|
757
|
+
backendAnno = annoConversion.canvasToBackendSingleAnno(myAnno, this.state.svg,
|
|
758
|
+
false, this.state.imageOffset)
|
|
759
|
+
}
|
|
760
|
+
const saveData = {
|
|
761
|
+
anno: backendAnno,
|
|
762
|
+
img: imgData,
|
|
763
|
+
action
|
|
764
|
+
}
|
|
765
|
+
if (this.props.onAnnoSaveEvent) {
|
|
766
|
+
this.props.onAnnoSaveEvent(saveData)
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
onAnnoLabelInputUpdate(anno) {
|
|
771
|
+
this.updateSelectedAnno(anno)
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
onAnnoLabelInputClose() {
|
|
775
|
+
this.svg.current.focus()
|
|
776
|
+
this.showLabelInput(false)
|
|
777
|
+
this.showSingleAnno(undefined)
|
|
778
|
+
const anno = this.findAnno(this.state.selectedAnnoId)
|
|
779
|
+
this.handleAnnoEvent(anno, canvasActions.ANNO_LABEL_UPDATE)
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
handleImgBarClose() {
|
|
783
|
+
this.triggerCanvasEvent(canvasActions.CANVAS_IMGBAR_CLOSE)
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
gotNewLabel(label) {
|
|
787
|
+
let ret = false
|
|
788
|
+
if (label.length === 0) {
|
|
789
|
+
if (this.state.imgLabelIds.length !== 0) {
|
|
790
|
+
return true
|
|
791
|
+
} else {
|
|
792
|
+
return false
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
label.forEach(e => {
|
|
796
|
+
if (!this.state.imgLabelIds.includes(e)) ret = true
|
|
797
|
+
})
|
|
798
|
+
return ret
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
handleImgLabelUpdate(label) {
|
|
802
|
+
if (this.gotNewLabel(label)) {
|
|
803
|
+
const imgActions = [...this.state.imgActions, canvasActions.IMG_LABEL_UPDATE]
|
|
804
|
+
console.log('gotNewLabel', label)
|
|
805
|
+
this.setState({
|
|
806
|
+
imgLabelIds: label,
|
|
807
|
+
imgLabelChanged: true,
|
|
808
|
+
imgActions: imgActions,
|
|
809
|
+
})
|
|
810
|
+
this.pushHist(this.state.annos,
|
|
811
|
+
this.state.selectedAnnoId,
|
|
812
|
+
canvasActions.IMG_LABEL_UPDATE,
|
|
813
|
+
this.state.showSingleAnno,
|
|
814
|
+
label
|
|
815
|
+
)
|
|
816
|
+
const imgData = {
|
|
817
|
+
imgLabelIds: label,
|
|
818
|
+
imgLabelChanged: true,
|
|
819
|
+
imgActions: imgActions,
|
|
820
|
+
}
|
|
821
|
+
this.handleAnnoSaveEvent(canvasActions.IMG_LABEL_UPDATE, undefined, imgData)
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
handleCanvasClick(e) {
|
|
826
|
+
if (this.props.uiConfig.imgBarVisible) {
|
|
827
|
+
this.setState({ imgBarVisible: true })
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
handleImgBarMouseEnter(e) {
|
|
832
|
+
this.setState({ imgBarVisible: false })
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
handleImgLabelInputClose() {
|
|
836
|
+
this.triggerCanvasEvent(canvasActions.CANVAS_LABEL_INPUT_CLOSE)
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
handleSvgMouseMove(e) {
|
|
840
|
+
this.mousePosAbs = mouse.getMousePositionAbs(e, this.state.svg)
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
handleNotification(messageObj) {
|
|
844
|
+
if (this.props.onNotification) {
|
|
845
|
+
this.props.onNotification(messageObj)
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
handleHideLbl(lbl, hide) {
|
|
850
|
+
let hiddenSelected = false
|
|
851
|
+
const newAnnos = this.state.annos.map(anno => {
|
|
852
|
+
const newAnno = { ...anno }
|
|
853
|
+
if (anno.labelIds.includes(lbl.id)) {
|
|
854
|
+
newAnno.visible = !hide
|
|
855
|
+
if (anno.id === this.state.selectedAnnoId) hiddenSelected = true
|
|
856
|
+
} else if (anno.labelIds.length === 0) { // no label case
|
|
857
|
+
if (lbl.id === -1) { // -1 indicates no label
|
|
858
|
+
newAnno.visible = !hide
|
|
859
|
+
if (anno.id === this.state.selectedAnnoId) hiddenSelected = true
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
return newAnno
|
|
863
|
+
})
|
|
864
|
+
this.setState({ annos: newAnnos })
|
|
865
|
+
if (hiddenSelected) {
|
|
866
|
+
this.selectAnnotation(undefined)
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
handleMarkExample(anno) {
|
|
871
|
+
const newAnno = { ...anno }
|
|
872
|
+
if (newAnno.isExample == undefined) {
|
|
873
|
+
newAnno.isExample = true
|
|
874
|
+
} else if (newAnno.isExample) {
|
|
875
|
+
newAnno.isExample = false
|
|
876
|
+
} else {
|
|
877
|
+
newAnno.isExample = true
|
|
878
|
+
}
|
|
879
|
+
this.handleAnnoEvent(newAnno, canvasActions.ANNO_MARK_EXAMPLE)
|
|
880
|
+
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
/*************
|
|
884
|
+
* LOGIC *
|
|
885
|
+
**************/
|
|
886
|
+
copyAnnotation() {
|
|
887
|
+
this.clipboard = this.findAnno(this.state.selectedAnnoId)
|
|
888
|
+
this.handleNotification({
|
|
889
|
+
title: "Copyed annotation to clipboard",
|
|
890
|
+
message: 'Copyed ' + this.clipboard.type,
|
|
891
|
+
type: notificationType.SUCCESS
|
|
892
|
+
})
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
pasteAnnotation(offset = 0) {
|
|
896
|
+
// const corrected = transform.correctAnnotation(anno.data, this.props.svg, this.props.imageOffset)
|
|
897
|
+
if (this.clipboard) {
|
|
898
|
+
// let annos = [...this.state.annos]
|
|
899
|
+
const uid = _.uniqueId('new')
|
|
900
|
+
// this.handleAnnoEvent()
|
|
901
|
+
const newData = this.clipboard.data.map(e => {
|
|
902
|
+
return { x: e.x + offset, y: e.y + offset }
|
|
903
|
+
})
|
|
904
|
+
const newAnno = {
|
|
905
|
+
...this.clipboard,
|
|
906
|
+
id: uid,
|
|
907
|
+
annoTime: 0,
|
|
908
|
+
status: annoStatus.NEW,
|
|
909
|
+
mode: modes.VIEW,
|
|
910
|
+
data: transformAnnos.correctAnnotation(newData, this.state.svg, this.state.imageOffset)
|
|
911
|
+
}
|
|
912
|
+
// annos.push(newAnno)
|
|
913
|
+
// this.setState({annos: annos, selectedAnnoId: uid})
|
|
914
|
+
this.handleNotification({
|
|
915
|
+
title: "Pasted annotation to canvas",
|
|
916
|
+
message: 'Pasted and selected ' + this.clipboard.type,
|
|
917
|
+
type: notificationType.SUCCESS
|
|
918
|
+
})
|
|
919
|
+
this.handleAnnoEvent(newAnno, canvasActions.ANNO_CREATED)
|
|
920
|
+
// this.handleAnnoSaveEvent(canvasActions.ANNO_CREATED, newAnno)
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
|
|
925
|
+
checkAnnoLength(anno) {
|
|
926
|
+
if (anno.type === 'polygon' && anno.data.length < 3) {
|
|
927
|
+
this.handleNotification({
|
|
928
|
+
title: "Invalid polygon!",
|
|
929
|
+
message: 'A vaild polygon needs at least 3 points!',
|
|
930
|
+
type: notificationType.WARNING
|
|
931
|
+
})
|
|
932
|
+
return false
|
|
933
|
+
}
|
|
934
|
+
return true
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
startAnnotimeMeasure(anno) {
|
|
938
|
+
anno.timestamp = performance.now()
|
|
939
|
+
return anno
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
stopAnnotimeMeasure(anno) {
|
|
943
|
+
if (anno.timestamp === undefined) {
|
|
944
|
+
console.warn('No timestamp for annotime measurement. Check if you started measurement', anno)
|
|
945
|
+
} else {
|
|
946
|
+
let now = performance.now()
|
|
947
|
+
anno.annoTime += (now - anno.timestamp) / 1000
|
|
948
|
+
anno.timestamp = now
|
|
949
|
+
return anno
|
|
950
|
+
}
|
|
951
|
+
return anno
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
updatePossibleLabels() {
|
|
955
|
+
if (!this.props.possibleLabels) return
|
|
956
|
+
if (this.props.possibleLabels.length <= 0) return
|
|
957
|
+
let lbls = this.props.possibleLabels
|
|
958
|
+
lbls = lbls.map(e => {
|
|
959
|
+
if (!('color' in e)) {
|
|
960
|
+
return {
|
|
961
|
+
...e, color: colorlut.getColor(e.id)
|
|
962
|
+
}
|
|
963
|
+
} else {
|
|
964
|
+
return { ...e }
|
|
965
|
+
}
|
|
966
|
+
})
|
|
967
|
+
this.setState({
|
|
968
|
+
possibleLabels: [...lbls]
|
|
969
|
+
})
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
editAnnoLabel(anno) {
|
|
973
|
+
if (this.state.selectedAnnoId) {
|
|
974
|
+
let myAnno
|
|
975
|
+
if (anno === undefined) {
|
|
976
|
+
myAnno = this.findAnno(this.state.selectedAnnoId)
|
|
977
|
+
} else {
|
|
978
|
+
myAnno = { ...anno }
|
|
979
|
+
}
|
|
980
|
+
myAnno = this.startAnnotimeMeasure(myAnno)
|
|
981
|
+
this.showLabelInput()
|
|
982
|
+
this.updateSelectedAnno(myAnno, modes.EDIT_LABEL)
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
unloadImage() {
|
|
986
|
+
console.log('unloadImage', this.state, this.props.imageMeta)
|
|
987
|
+
if (this.state.imageLoaded) {
|
|
988
|
+
this.setState({ imageLoaded: false })
|
|
989
|
+
}
|
|
990
|
+
this.handleAnnoSaveEvent(canvasActions.IMG_ANNO_TIME_UPDATE, undefined, undefined)
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* Find a annotation by id in current state
|
|
994
|
+
*
|
|
995
|
+
* @param {int} annoId - Id of the annotation to find
|
|
996
|
+
*/
|
|
997
|
+
findAnno(annoId) {
|
|
998
|
+
return this.state.annos.find(e => {
|
|
999
|
+
return e.id === annoId
|
|
1000
|
+
})
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
findAnnoRef(annoId) {
|
|
1004
|
+
if (this.state.selectedAnnoId === undefined) return undefined
|
|
1005
|
+
return this.annoRefs.find(e => {
|
|
1006
|
+
if (e.current) {
|
|
1007
|
+
return e.current.isSelected()
|
|
1008
|
+
} else {
|
|
1009
|
+
return false
|
|
1010
|
+
}
|
|
1011
|
+
})
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
pushHist(annos, selectedAnnoId, pAction, showSingleAnno, imgLabelIds = this.state.imgLabelIds) {
|
|
1015
|
+
this.hist.push({
|
|
1016
|
+
...this.getAnnos(annos, false),
|
|
1017
|
+
selectedAnnoId: selectedAnnoId,
|
|
1018
|
+
showSingleAnno: showSingleAnno,
|
|
1019
|
+
imgLabelIds: imgLabelIds
|
|
1020
|
+
}, pAction)
|
|
1021
|
+
console.log('hist', this.hist)
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
undo() {
|
|
1025
|
+
this.handleNotification({
|
|
1026
|
+
title: "Redo/ Undo not supported",
|
|
1027
|
+
message: `Redo and Undo functions are currently not supported`,
|
|
1028
|
+
type: notificationType.WARNING
|
|
1029
|
+
})
|
|
1030
|
+
return
|
|
1031
|
+
//TODO: Make UNDO great again
|
|
1032
|
+
// if (!this.hist.isEmpty()){
|
|
1033
|
+
// const cState = this.hist.undo()
|
|
1034
|
+
// console.log('hist', this.hist)
|
|
1035
|
+
// this.setCanvasState(
|
|
1036
|
+
// cState.entry.annotations,
|
|
1037
|
+
// cState.entry.imgLabelIds,
|
|
1038
|
+
// cState.entry.selectedAnnoId,
|
|
1039
|
+
// cState.entry.showSingleAnno)
|
|
1040
|
+
// }
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
redo() {
|
|
1044
|
+
this.handleNotification({
|
|
1045
|
+
title: "Redo/ Undo not supported",
|
|
1046
|
+
message: `Redo and Undo functions are currently not supported`,
|
|
1047
|
+
type: notificationType.WARNING
|
|
1048
|
+
})
|
|
1049
|
+
return
|
|
1050
|
+
//TODO: Make REDO great again
|
|
1051
|
+
// if (!this.hist.isEmpty()){
|
|
1052
|
+
// const cState = this.hist.redo()
|
|
1053
|
+
// console.log('hist', this.hist)
|
|
1054
|
+
// this.setCanvasState(
|
|
1055
|
+
// cState.entry.annotations,
|
|
1056
|
+
// cState.entry.imgLabelIds,
|
|
1057
|
+
// cState.entry.selectedAnnoId,
|
|
1058
|
+
// cState.entry.showSingleAnno
|
|
1059
|
+
// )
|
|
1060
|
+
// }
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
deleteAnnotation(anno) {
|
|
1064
|
+
if (anno) {
|
|
1065
|
+
if (anno.mode === modes.CREATE) {
|
|
1066
|
+
const ar = this.findAnnoRef(this.state.selectedAnnoId)
|
|
1067
|
+
if (ar !== undefined) ar.current.myAnno.current.removeLastNode()
|
|
1068
|
+
|
|
1069
|
+
} else {
|
|
1070
|
+
this.handleAnnoEvent(anno, canvasActions.ANNO_DELETED)
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
deleteAnnoInCreationMode(anno) {
|
|
1076
|
+
if (anno) {
|
|
1077
|
+
if (anno.mode === modes.CREATE) {
|
|
1078
|
+
this.handleAnnoEvent(anno, canvasActions.ANNO_DELETED)
|
|
1079
|
+
} else {
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
deleteAllAnnos() {
|
|
1085
|
+
let newAnnos = []
|
|
1086
|
+
this.state.annos.forEach(e => {
|
|
1087
|
+
if ((typeof e.id) !== "string") {
|
|
1088
|
+
const anno = { ...e, status: annoStatus.DELETED }
|
|
1089
|
+
this.handleAnnoEvent(anno, canvasActions.ANNO_DELETED)
|
|
1090
|
+
}
|
|
1091
|
+
})
|
|
1092
|
+
this.selectAnnotation(undefined)
|
|
1093
|
+
this.showSingleAnno(undefined)
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
/**
|
|
1097
|
+
* Set state of Canvas annotations and imageLabels.
|
|
1098
|
+
*
|
|
1099
|
+
* @param {list} annotations - Annotations in backend format
|
|
1100
|
+
* @param {list} imgLabelIds - IDs of the image labels
|
|
1101
|
+
* @param {object} selectedAnno - The selected annotation
|
|
1102
|
+
* @param {int} showSingleAnno - The id of the single annotation
|
|
1103
|
+
* that should be visible
|
|
1104
|
+
*/
|
|
1105
|
+
setCanvasState(annotations, imgLabelIds, selectedAnnoId, showSingleAnno) {
|
|
1106
|
+
this.updateCanvasView({ ...annotations })
|
|
1107
|
+
this.setImageLabels([...imgLabelIds])
|
|
1108
|
+
this.selectAnnotation(selectedAnnoId)
|
|
1109
|
+
this.setState({ showSingleAnno: showSingleAnno })
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
isLocked(annoId) {
|
|
1113
|
+
if (this.props.lockedAnnos) {
|
|
1114
|
+
if (this.props.lockedAnnos.includes(annoId)) {
|
|
1115
|
+
return true
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
return false
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
selectAnnotation(annoId) {
|
|
1122
|
+
if (this.isLocked(annoId)) {
|
|
1123
|
+
this.handleNotification({
|
|
1124
|
+
title: "Annotation locked",
|
|
1125
|
+
message: `Annotation with id ${annoId} is locked and can not be edited`,
|
|
1126
|
+
type: notificationType.WARNING
|
|
1127
|
+
})
|
|
1128
|
+
return
|
|
1129
|
+
}
|
|
1130
|
+
if (annoId) {
|
|
1131
|
+
const anno = this.findAnno(annoId)
|
|
1132
|
+
this.setState({
|
|
1133
|
+
selectedAnnoId: annoId
|
|
1134
|
+
})
|
|
1135
|
+
if (anno) {
|
|
1136
|
+
if (anno.mode !== modes.CREATE) {
|
|
1137
|
+
this.setState({
|
|
1138
|
+
annoToolBarVisible: true
|
|
1139
|
+
})
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
} else {
|
|
1143
|
+
this.setState({
|
|
1144
|
+
selectedAnnoId: undefined,
|
|
1145
|
+
annoToolBarVisible: false
|
|
1146
|
+
})
|
|
1147
|
+
if (this.state.showLabelInput) {
|
|
1148
|
+
this.onAnnoLabelInputClose()
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
/**
|
|
1154
|
+
* Traverse annotations by key hit
|
|
1155
|
+
*/
|
|
1156
|
+
traverseAnnos() {
|
|
1157
|
+
if (this.state.annos.length > 0) {
|
|
1158
|
+
const myAnnos = this.state.annos.filter(e => {
|
|
1159
|
+
return e.status !== annoStatus.DELETED && !this.isLocked(e.id) && !(e.visible === false)
|
|
1160
|
+
})
|
|
1161
|
+
if (myAnnos.length > 0) {
|
|
1162
|
+
if (!this.state.selectedAnnoId) {
|
|
1163
|
+
this.selectAnnotation(myAnnos[0].id)
|
|
1164
|
+
} else {
|
|
1165
|
+
let currentIdx = myAnnos.findIndex(e => {
|
|
1166
|
+
return e.id === this.state.selectedAnnoId
|
|
1167
|
+
})
|
|
1168
|
+
if (currentIdx + 1 < myAnnos.length) {
|
|
1169
|
+
this.selectAnnotation(myAnnos[currentIdx + 1].id)
|
|
1170
|
+
} else {
|
|
1171
|
+
this.selectAnnotation(myAnnos[0].id)
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
getAnnos(annos = undefined, removeFrontedIds = true) {
|
|
1179
|
+
const myAnnos = annos ? annos : this.state.annos
|
|
1180
|
+
// const backendFormat = this.getAnnoBackendFormat(removeFrontedIds, myAnnos)
|
|
1181
|
+
const backendFormat = annoConversion.canvasToBackendAnnos(myAnnos,
|
|
1182
|
+
this.state.svg, removeFrontedIds, this.state.imageOffset)
|
|
1183
|
+
const finalData = {
|
|
1184
|
+
imgId: this.props.imageMeta.id,
|
|
1185
|
+
imgLabelIds: this.state.imgLabelIds,
|
|
1186
|
+
imgLabelChanged: this.state.imgLabelChanged,
|
|
1187
|
+
imgActions: this.state.imgActions,
|
|
1188
|
+
annotations: backendFormat,
|
|
1189
|
+
isJunk: this.state.isJunk,
|
|
1190
|
+
annoTime: this.props.imageMeta.annoTime + (performance.now() - this.state.imgLoadTimestamp) / 1000
|
|
1191
|
+
}
|
|
1192
|
+
return finalData
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
/**
|
|
1196
|
+
* Reset zoom level on Canvas
|
|
1197
|
+
*/
|
|
1198
|
+
resetZoom() {
|
|
1199
|
+
this.setState({
|
|
1200
|
+
svg: {
|
|
1201
|
+
...this.state.svg,
|
|
1202
|
+
translateX: 0,
|
|
1203
|
+
translateY: 0,
|
|
1204
|
+
scale: 1.0
|
|
1205
|
+
}
|
|
1206
|
+
})
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
moveCamera(movementX, movementY) {
|
|
1210
|
+
let trans_x = this.state.svg.translateX + movementX / this.state.svg.scale
|
|
1211
|
+
let trans_y = this.state.svg.translateY + movementY / this.state.svg.scale
|
|
1212
|
+
const vXMin = this.state.svg.width * 0.25
|
|
1213
|
+
const vXMax = this.state.svg.width * 0.75
|
|
1214
|
+
const yXMin = this.state.svg.height * 0.25
|
|
1215
|
+
const yXMax = this.state.svg.height * 0.75
|
|
1216
|
+
const vLeft = wv.getViewportCoordinates({ x: 0, y: 0 }, this.state.svg)
|
|
1217
|
+
const vRight = wv.getViewportCoordinates({ x: this.state.svg.width, y: this.state.svg.height }, this.state.svg)
|
|
1218
|
+
if (vLeft.vX >= vXMin) {
|
|
1219
|
+
trans_x = this.state.svg.translateX - 5
|
|
1220
|
+
} else if (vRight.vX <= vXMax) {
|
|
1221
|
+
trans_x = this.state.svg.translateX + 5
|
|
1222
|
+
}
|
|
1223
|
+
if (vLeft.vY >= yXMin) {
|
|
1224
|
+
trans_y = this.state.svg.translateY - 5
|
|
1225
|
+
} else if (vRight.vY <= yXMax) {
|
|
1226
|
+
trans_y = this.state.svg.translateY + 5
|
|
1227
|
+
}
|
|
1228
|
+
this.setState({
|
|
1229
|
+
svg: {
|
|
1230
|
+
...this.state.svg,
|
|
1231
|
+
translateX: trans_x,
|
|
1232
|
+
translateY: trans_y
|
|
1233
|
+
}
|
|
1234
|
+
})
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
setMode(mode) {
|
|
1238
|
+
if (this.state.mode !== mode) {
|
|
1239
|
+
this.setState({ mode: mode })
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
getMousePosition(e) {
|
|
1244
|
+
const absPos = this.getMousePositionAbs(e)
|
|
1245
|
+
return {
|
|
1246
|
+
x: (absPos.x) / this.state.svg.scale - this.state.svg.translateX,
|
|
1247
|
+
y: (absPos.y) / this.state.svg.scale - this.state.svg.translateY
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
getMousePositionAbs(e) {
|
|
1252
|
+
return {
|
|
1253
|
+
x: (e.pageX - this.svg.current.getBoundingClientRect().left),
|
|
1254
|
+
y: (e.pageY - this.svg.current.getBoundingClientRect().top)
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
showLabelInput(visible = true) {
|
|
1259
|
+
this.setState({
|
|
1260
|
+
showLabelInput: visible
|
|
1261
|
+
})
|
|
1262
|
+
if (visible) {
|
|
1263
|
+
this.showSingleAnno(this.state.selectedAnnoId)
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
createNewAnnotation(e) {
|
|
1268
|
+
//Do not create new Annotation if controlKey was pressed!
|
|
1269
|
+
let allowed = false
|
|
1270
|
+
if (this.keyMapper.controlDown) return
|
|
1271
|
+
if (this.props.selectedTool) {
|
|
1272
|
+
const maxAnnos = this.props.canvasConfig.annos.maxAnnos
|
|
1273
|
+
if (maxAnnos) {
|
|
1274
|
+
if (this.state.annos.length < maxAnnos) {
|
|
1275
|
+
allowed = true
|
|
1276
|
+
} else {
|
|
1277
|
+
console.warn('Maximum number of annotations reached! MaxAnnos:', maxAnnos)
|
|
1278
|
+
this.handleNotification({
|
|
1279
|
+
title: 'Maximum number of annotations reached!',
|
|
1280
|
+
message: `Only ${maxAnnos} annotations per image are allowed by config`,
|
|
1281
|
+
type: notificationType.WARNING
|
|
1282
|
+
})
|
|
1283
|
+
}
|
|
1284
|
+
} else {
|
|
1285
|
+
allowed = true
|
|
1286
|
+
}
|
|
1287
|
+
} else {
|
|
1288
|
+
console.warn('No annotation tool selected!')
|
|
1289
|
+
this.handleNotification({
|
|
1290
|
+
title: 'No tool selected!',
|
|
1291
|
+
message: 'Please select an annotation tool in the toolbar.',
|
|
1292
|
+
type: notificationType.INFO
|
|
1293
|
+
})
|
|
1294
|
+
}
|
|
1295
|
+
if (allowed) {
|
|
1296
|
+
const mousePos = this.getMousePosition(e)
|
|
1297
|
+
// const selAnno = this.findAnno(this.state.selectedAnnoId)
|
|
1298
|
+
let newAnno = {
|
|
1299
|
+
id: this.props.nextAnnoId ? this.props.nextAnnoId : _.uniqueId('new'),
|
|
1300
|
+
type: this.props.selectedTool,
|
|
1301
|
+
data: [{
|
|
1302
|
+
x: mousePos.x,
|
|
1303
|
+
y: mousePos.y
|
|
1304
|
+
}, {
|
|
1305
|
+
x: mousePos.x,
|
|
1306
|
+
y: mousePos.y
|
|
1307
|
+
}],
|
|
1308
|
+
mode: modes.CREATE,
|
|
1309
|
+
status: annoStatus.NEW,
|
|
1310
|
+
labelIds: this.state.prevLabel,
|
|
1311
|
+
selectedNode: 1,
|
|
1312
|
+
annoTime: 0.0
|
|
1313
|
+
}
|
|
1314
|
+
newAnno = this.startAnnotimeMeasure(newAnno)
|
|
1315
|
+
this.setState({
|
|
1316
|
+
annos: [...this.state.annos, newAnno],
|
|
1317
|
+
selectedAnnoId: newAnno.id,
|
|
1318
|
+
showSingleAnno: newAnno.id,
|
|
1319
|
+
annoToolBarVisible: false
|
|
1320
|
+
})
|
|
1321
|
+
if (this.props.selectedTool !== TOOLS.BBOX &&
|
|
1322
|
+
this.props.selectedTool !== TOOLS.POINT) {
|
|
1323
|
+
const merged = this.mergeSelectedAnno(newAnno)
|
|
1324
|
+
this.pushHist(
|
|
1325
|
+
merged.newAnnos,
|
|
1326
|
+
newAnno.id,
|
|
1327
|
+
canvasActions.ANNO_CREATED_NODE,
|
|
1328
|
+
newAnno.id
|
|
1329
|
+
)
|
|
1330
|
+
}
|
|
1331
|
+
this.handleAnnoEvent(newAnno, canvasActions.ANNO_ENTER_CREATE_MODE)
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
/**
|
|
1336
|
+
* recreate an existing annotation in case the creation process was not finished
|
|
1337
|
+
* @param {string} id of annotation
|
|
1338
|
+
*/
|
|
1339
|
+
recreateAnnotation(annoID) {
|
|
1340
|
+
console.log('AnnoSave -> recreateAnnotation ', annoID)
|
|
1341
|
+
|
|
1342
|
+
let annos = this.state.annos
|
|
1343
|
+
|
|
1344
|
+
// search for id of selected anno in all annos (should normally be last item in list, but to be sure)
|
|
1345
|
+
let annoIndex
|
|
1346
|
+
let anno
|
|
1347
|
+
|
|
1348
|
+
for (var k in annos) if (annos[k].id == annoID) {
|
|
1349
|
+
annoIndex = k
|
|
1350
|
+
anno = annos[k]
|
|
1351
|
+
break
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
// editing is only allowed on line and polygon
|
|
1355
|
+
if (!['line', 'polygon'].includes(anno.type)) return console.log("Cant recreate annotation: Type " + anno.type + " is forbidden")
|
|
1356
|
+
|
|
1357
|
+
// remove the old annotation
|
|
1358
|
+
this.state.annos.splice(annoIndex, 1)
|
|
1359
|
+
|
|
1360
|
+
// create a new annotation based on the datapoints of the old annotation
|
|
1361
|
+
let newAnno = {
|
|
1362
|
+
id: anno.id,
|
|
1363
|
+
type: anno.type,
|
|
1364
|
+
data: anno.data,
|
|
1365
|
+
mode: modes.CREATE,
|
|
1366
|
+
status: (anno.status === 'database' || anno.status === 'changed' ? annoStatus.CHANGED : annoStatus.NEW),
|
|
1367
|
+
labelIds: anno.labelIds,
|
|
1368
|
+
selectedNode: anno.data.length - 1,
|
|
1369
|
+
annoTime: anno.annoTime
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
newAnno = this.startAnnotimeMeasure(newAnno)
|
|
1373
|
+
this.setState({
|
|
1374
|
+
annos: [...this.state.annos, newAnno],
|
|
1375
|
+
selectedAnnoId: newAnno.id,
|
|
1376
|
+
showSingleAnno: newAnno.id,
|
|
1377
|
+
annoToolBarVisible: false
|
|
1378
|
+
})
|
|
1379
|
+
|
|
1380
|
+
console.log("Annotation recreated")
|
|
1381
|
+
this.handleAnnoEvent(newAnno, canvasActions.ANNO_ENTER_CREATE_MODE)
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
putSelectedOnTop(prevState) {
|
|
1385
|
+
// The selected annotation need to be rendered as last one in
|
|
1386
|
+
// oder to be above all other annotations.
|
|
1387
|
+
if (this.state.selectedAnnoId) {
|
|
1388
|
+
if (prevState.selectedAnnoId !== this.state.selectedAnnoId) {
|
|
1389
|
+
const annos = this.state.annos.filter((el) => {
|
|
1390
|
+
return el.id !== this.state.selectedAnnoId
|
|
1391
|
+
})
|
|
1392
|
+
const lastAnno = this.state.annos.find(el => {
|
|
1393
|
+
return el.id === this.state.selectedAnnoId
|
|
1394
|
+
})
|
|
1395
|
+
annos.push(lastAnno)
|
|
1396
|
+
this.setState({
|
|
1397
|
+
annos: [
|
|
1398
|
+
...annos
|
|
1399
|
+
]
|
|
1400
|
+
})
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
getLabel(lblId) {
|
|
1406
|
+
return this.state.possibleLabels.find(e => {
|
|
1407
|
+
return e.id === lblId
|
|
1408
|
+
})
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
getAnnoColor() {
|
|
1412
|
+
if (this.state.selectedAnnoId) {
|
|
1413
|
+
const anno = this.findAnno(this.state.selectedAnnoId)
|
|
1414
|
+
if (anno) {
|
|
1415
|
+
if (anno.labelIds.length > 0) {
|
|
1416
|
+
return this.getLabel(anno.labelIds[0]).color
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
return colorlut.getDefaultColor()
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
updateDelayedBackendUpdates(tempId, dbId) {
|
|
1424
|
+
console.log('updateDelayedBackendUpdates ', tempId, dbId, this.delayedBackendUpdates)
|
|
1425
|
+
if (tempId !== dbId) this.tempIdMap[tempId] = dbId
|
|
1426
|
+
if (tempId in this.delayedBackendUpdates) {
|
|
1427
|
+
if (this.delayedBackendUpdates[tempId] !== null) {
|
|
1428
|
+
const { anno, action } = this.delayedBackendUpdates[tempId]
|
|
1429
|
+
const myAnno = {
|
|
1430
|
+
...anno,
|
|
1431
|
+
status: anno.status === annoStatus.NEW ? annoStatus.CHANGED : anno.status
|
|
1432
|
+
}
|
|
1433
|
+
delete this.delayedBackendUpdates[tempId]
|
|
1434
|
+
console.log('PerformDelayedBackendUpdate', action, myAnno)
|
|
1435
|
+
this.handleAnnoSaveEvent(action, myAnno)
|
|
1436
|
+
} else {
|
|
1437
|
+
delete this.delayedBackendUpdates[tempId]
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
addDelayedBackendUpdate(anno, action) {
|
|
1443
|
+
// take care of tempIds while receiving a dbId from backend.
|
|
1444
|
+
// handling tempIds is only required if instant anno backend update is
|
|
1445
|
+
// used.
|
|
1446
|
+
if (this.props.onAnnoSaveEvent) {
|
|
1447
|
+
if ((typeof anno.id) === "string") {
|
|
1448
|
+
if (!(anno.id in this.tempIdMap)) {
|
|
1449
|
+
let myAnno = undefined
|
|
1450
|
+
if (anno.id in this.delayedBackendUpdates) {
|
|
1451
|
+
this.delayedBackendUpdates[anno.id] = { anno, action }
|
|
1452
|
+
} else {
|
|
1453
|
+
this.delayedBackendUpdates[anno.id] = null
|
|
1454
|
+
myAnno = anno
|
|
1455
|
+
}
|
|
1456
|
+
console.log('addDelayedBackendUpdate ', myAnno, action, anno, this.delayedBackendUpdates)
|
|
1457
|
+
return myAnno
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
// else if ((typeof anno.id) === "string"){
|
|
1461
|
+
// this.delayedBackendUpdates[anno.id] = {anno, action}
|
|
1462
|
+
// }
|
|
1463
|
+
} else {
|
|
1464
|
+
console.error('onAnnoSaveEvent needs to be provided in order to use SIA Canvas in a correct war!')
|
|
1465
|
+
}
|
|
1466
|
+
return anno
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
updateAnnoBySaveResponse(res) {
|
|
1470
|
+
if (!res) return
|
|
1471
|
+
if (res.tempId !== res.dbId) {
|
|
1472
|
+
//TODO: Replace tempId with dbId in undo/redo hist
|
|
1473
|
+
const anno = this.findAnno(res.tempId)
|
|
1474
|
+
if (!anno) return
|
|
1475
|
+
// anno.id = res.dbId
|
|
1476
|
+
// anno.status = annoStatus.DATABASE
|
|
1477
|
+
//TODO: Should not update if the anno is currently in edit or move mode
|
|
1478
|
+
// this.updateAnno(anno)
|
|
1479
|
+
// if (this.state.selectedAnnoId === res.tempId) this.setState({selectedAnnoId: res.dbId})
|
|
1480
|
+
this.updateDelayedBackendUpdates(res.tempId, res.dbId)
|
|
1481
|
+
}
|
|
1482
|
+
// else {
|
|
1483
|
+
// const anno = this.findAnno(res.dbId)
|
|
1484
|
+
// if (!anno) return
|
|
1485
|
+
// anno.status = res.newStatus
|
|
1486
|
+
// // this.updateAnno(anno)
|
|
1487
|
+
// }
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
/**
|
|
1491
|
+
* Update selected anno and override mode if desired
|
|
1492
|
+
*
|
|
1493
|
+
* @param {object} anno - The new annotation that becomes the selected anno
|
|
1494
|
+
* @param {string} mode - The new mode for the selected anno
|
|
1495
|
+
* @returns The new anno that was set as selectedAnno in state and
|
|
1496
|
+
* the new annos list that was set in state
|
|
1497
|
+
*/
|
|
1498
|
+
updateSelectedAnno(anno, mode = undefined, returnNewAnno = false) {
|
|
1499
|
+
if (!anno) return
|
|
1500
|
+
const { newAnnos, newAnno } = this.mergeSelectedAnno(anno, mode)
|
|
1501
|
+
this.setState({
|
|
1502
|
+
annos: newAnnos,
|
|
1503
|
+
selectedAnnoId: anno.id,
|
|
1504
|
+
prevLabel: anno.labelIds,
|
|
1505
|
+
})
|
|
1506
|
+
if (returnNewAnno) {
|
|
1507
|
+
return { newAnnos, newAnno }
|
|
1508
|
+
} else {
|
|
1509
|
+
return newAnnos
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
updateAnno(anno, mode = undefined, returnNewAnno = false) {
|
|
1514
|
+
if (!anno) return
|
|
1515
|
+
const { newAnnos, newAnno } = this.mergeSelectedAnno(anno, mode)
|
|
1516
|
+
this.setState({
|
|
1517
|
+
annos: newAnnos
|
|
1518
|
+
})
|
|
1519
|
+
if (returnNewAnno) {
|
|
1520
|
+
return { newAnnos, newAnno }
|
|
1521
|
+
} else {
|
|
1522
|
+
return newAnnos
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
mergeSelectedAnno(anno, mode = undefined) {
|
|
1527
|
+
let filtered = this.state.annos.filter((el) => {
|
|
1528
|
+
return el.id !== anno.id
|
|
1529
|
+
})
|
|
1530
|
+
filtered = filtered.map(e => { return { ...e, mode: modes.VIEW } })
|
|
1531
|
+
let newAnno
|
|
1532
|
+
if (mode) {
|
|
1533
|
+
newAnno = { ...anno, mode: mode }
|
|
1534
|
+
if (mode === modes.DELETED) {
|
|
1535
|
+
if (anno.status !== annoStatus.NEW) {
|
|
1536
|
+
newAnno = {
|
|
1537
|
+
...newAnno,
|
|
1538
|
+
status: annoStatus.DELETED
|
|
1539
|
+
}
|
|
1540
|
+
} else {
|
|
1541
|
+
newAnno = null
|
|
1542
|
+
}
|
|
1543
|
+
} else {
|
|
1544
|
+
newAnno = {
|
|
1545
|
+
...newAnno,
|
|
1546
|
+
status: anno.status !== annoStatus.NEW ? annoStatus.CHANGED : annoStatus.NEW
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
} else {
|
|
1550
|
+
newAnno = { ...anno }
|
|
1551
|
+
}
|
|
1552
|
+
if (newAnno !== null) {
|
|
1553
|
+
filtered.push(newAnno)
|
|
1554
|
+
}
|
|
1555
|
+
const newAnnos = [...filtered]
|
|
1556
|
+
return { newAnnos, newAnno }
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
showSingleAnno(annoId) {
|
|
1560
|
+
if (this.state.showSingleAnno !== annoId) {
|
|
1561
|
+
this.setState({ showSingleAnno: annoId })
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
updateImageSize() {
|
|
1566
|
+
|
|
1567
|
+
var container = this.props.container.current.getBoundingClientRect()
|
|
1568
|
+
var clientHeight = document.documentElement.clientHeight
|
|
1569
|
+
var canvasTop
|
|
1570
|
+
var canvasLeft
|
|
1571
|
+
var maxImgHeight
|
|
1572
|
+
var maxImgWidth
|
|
1573
|
+
const layoutOffset = this.props.uiConfig.layoutOffset
|
|
1574
|
+
if (layoutOffset) {
|
|
1575
|
+
canvasTop = container.top + layoutOffset.top
|
|
1576
|
+
canvasLeft = container.left + layoutOffset.left
|
|
1577
|
+
maxImgHeight = clientHeight - container.top - layoutOffset.bottom - layoutOffset.top
|
|
1578
|
+
maxImgWidth = container.right - canvasLeft - layoutOffset.right
|
|
1579
|
+
} else {
|
|
1580
|
+
canvasTop = container.top
|
|
1581
|
+
canvasLeft = container.left
|
|
1582
|
+
maxImgHeight = clientHeight - container.top
|
|
1583
|
+
maxImgWidth = container.right - canvasLeft
|
|
1584
|
+
}
|
|
1585
|
+
var ratio = this.img.current.naturalWidth / this.img.current.naturalHeight
|
|
1586
|
+
var imgWidth = "100%"
|
|
1587
|
+
var imgHeight = "100%"
|
|
1588
|
+
if (maxImgHeight * ratio > maxImgWidth) {
|
|
1589
|
+
imgWidth = maxImgWidth
|
|
1590
|
+
imgHeight = maxImgWidth / ratio
|
|
1591
|
+
} else {
|
|
1592
|
+
imgWidth = maxImgHeight * ratio
|
|
1593
|
+
imgHeight = maxImgHeight
|
|
1594
|
+
}
|
|
1595
|
+
var svg
|
|
1596
|
+
const imgOffset = { x: 0, y: 0 }
|
|
1597
|
+
if (this.props.uiConfig.maxCanvas) {
|
|
1598
|
+
imgOffset.x = (maxImgWidth - imgWidth) / 2
|
|
1599
|
+
imgOffset.y = (maxImgHeight - imgHeight) / 2
|
|
1600
|
+
console.log(`imgOffset: `, imgOffset)
|
|
1601
|
+
svg = {
|
|
1602
|
+
...this.state.svg, width: maxImgWidth, height: maxImgHeight,
|
|
1603
|
+
left: canvasLeft, top: canvasTop
|
|
1604
|
+
}
|
|
1605
|
+
} else {
|
|
1606
|
+
if (this.props.uiConfig.centerCanvasInContainer) {
|
|
1607
|
+
const resSpaceX = maxImgWidth - imgWidth
|
|
1608
|
+
if (resSpaceX > 2) {
|
|
1609
|
+
canvasLeft = canvasLeft + resSpaceX / 2
|
|
1610
|
+
}
|
|
1611
|
+
const resSpaceY = maxImgHeight - imgHeight
|
|
1612
|
+
if (resSpaceY > 2) {
|
|
1613
|
+
canvasTop = canvasTop + resSpaceY / 2
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
svg = {
|
|
1617
|
+
...this.state.svg, width: imgWidth, height: imgHeight,
|
|
1618
|
+
left: canvasLeft, top: canvasTop
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
this.setState({
|
|
1622
|
+
svg,
|
|
1623
|
+
image: {
|
|
1624
|
+
width: this.img.current.naturalWidth,
|
|
1625
|
+
height: this.img.current.naturalHeight,
|
|
1626
|
+
},
|
|
1627
|
+
imageOffset: imgOffset
|
|
1628
|
+
})
|
|
1629
|
+
this.svgUpdate(svg)
|
|
1630
|
+
return { imgWidth, imgHeight, imgOffset }
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
svgUpdate(svg) {
|
|
1634
|
+
this.triggerCanvasEvent(canvasActions.CANVAS_SVG_UPDATE, svg)
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
setImageLabels(labelIds) {
|
|
1638
|
+
if (labelIds !== this.state.imgLabelIds) {
|
|
1639
|
+
this.setState({
|
|
1640
|
+
imgLabelIds: labelIds
|
|
1641
|
+
})
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
updateCanvasView(annotations) {
|
|
1646
|
+
|
|
1647
|
+
|
|
1648
|
+
var annos = []
|
|
1649
|
+
//Annotation data should be present and a pixel accurate value
|
|
1650
|
+
//for svg should be calculated
|
|
1651
|
+
if (annotations) {
|
|
1652
|
+
const imgSize = this.updateImageSize()
|
|
1653
|
+
this.setState({ annos: [...annoConversion.backendAnnosToCanvas(annotations, { width: imgSize.imgWidth, height: imgSize.imgHeight }, imgSize.imgOffset)] })
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
renderAnnotations() {
|
|
1658
|
+
// Do not render annotations while moving the camera!
|
|
1659
|
+
if (this.state.mode !== modes.CAMERA_MOVE) {
|
|
1660
|
+
this.annoRefs = []
|
|
1661
|
+
const annos = this.state.annos.map((el) => {
|
|
1662
|
+
this.annoRefs.push(React.createRef())
|
|
1663
|
+
return <Annotation type={el.type}
|
|
1664
|
+
data={el} key={el.id} svg={{ ...this.state.svg }}
|
|
1665
|
+
imageOffset={this.state.imageOffset}
|
|
1666
|
+
ref={this.annoRefs[this.annoRefs.length - 1]}
|
|
1667
|
+
onMouseDown={e => this.onAnnoMouseDown(e)}
|
|
1668
|
+
onAction={(anno, pAction) => this.handleAnnoEvent(anno, pAction)}
|
|
1669
|
+
selectedAnno={this.state.selectedAnnoId}
|
|
1670
|
+
// onModeChange={(anno) => this.onAnnoModeChange(anno)}
|
|
1671
|
+
showSingleAnno={this.state.showSingleAnno}
|
|
1672
|
+
uiConfig={this.props.uiConfig}
|
|
1673
|
+
allowedActions={this.props.canvasConfig.annos.actions}
|
|
1674
|
+
possibleLabels={this.state.possibleLabels}
|
|
1675
|
+
image={this.state.image}
|
|
1676
|
+
canvasConfig={this.props.canvasConfig}
|
|
1677
|
+
onNotification={(messageObj) => this.handleNotification(messageObj)}
|
|
1678
|
+
defaultLabel={this.props.defaultLabel}
|
|
1679
|
+
/>
|
|
1680
|
+
})
|
|
1681
|
+
return <g>{annos}</g>
|
|
1682
|
+
} else {
|
|
1683
|
+
return null
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
renderImgLabelInput() {
|
|
1689
|
+
if (!this.props.imageMeta) return null
|
|
1690
|
+
return <Prompt
|
|
1691
|
+
onClick={() => this.handleImgLabelInputClose()}
|
|
1692
|
+
active={this.props.uiConfig.imgLabelInputVisible}
|
|
1693
|
+
header={<div>
|
|
1694
|
+
Add label for the whole image
|
|
1695
|
+
</div>}
|
|
1696
|
+
content={<div>
|
|
1697
|
+
<LabelInput
|
|
1698
|
+
// multilabels={true}
|
|
1699
|
+
multilabels={this.props.canvasConfig.img.multilabels}
|
|
1700
|
+
// relatedId={this.props.annos.image.id}
|
|
1701
|
+
visible={true}
|
|
1702
|
+
onLabelUpdate={label => this.handleImgLabelUpdate(label)}
|
|
1703
|
+
possibleLabelsProp={this.state.possibleLabels}
|
|
1704
|
+
initLabelIds={this.state.imgLabelIds}
|
|
1705
|
+
relatedId={this.props.imageMeta.id}
|
|
1706
|
+
defaultLabel={this.props.defaultLabel}
|
|
1707
|
+
// onLabelConfirmed = {label => this.handleImgLabelUpdate(label)}
|
|
1708
|
+
// disabled={!this.props.allowedActions.label}
|
|
1709
|
+
// renderPopup
|
|
1710
|
+
/>
|
|
1711
|
+
<Button basic color="green" inverted
|
|
1712
|
+
onClick={() => this.handleImgLabelInputClose()}
|
|
1713
|
+
>
|
|
1714
|
+
<Icon name='check'></Icon>
|
|
1715
|
+
OK
|
|
1716
|
+
</Button>
|
|
1717
|
+
</div>}
|
|
1718
|
+
/>
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
renderAnnoToolBar(selectedAnno) {
|
|
1722
|
+
let visible = this.state.annoToolBarVisible
|
|
1723
|
+
if (this.state.mode === modes.CAMERA_MOVE) visible = false
|
|
1724
|
+
return <AnnoToolBar visible={visible}
|
|
1725
|
+
selectedAnno={selectedAnno}
|
|
1726
|
+
svg={this.state.svg}
|
|
1727
|
+
onClick={() => this.editAnnoLabel()}
|
|
1728
|
+
color={this.getAnnoColor()}
|
|
1729
|
+
/>
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
renderAnnoLabelInpput(selectedAnno) {
|
|
1733
|
+
let visible = this.state.showLabelInput
|
|
1734
|
+
if (this.state.mode === modes.CAMERA_MOVE) visible = false
|
|
1735
|
+
return <AnnoLabelInput svg={this.state.svg}
|
|
1736
|
+
// svgRef={this.svg}
|
|
1737
|
+
onClose={() => this.onAnnoLabelInputClose()}
|
|
1738
|
+
onDeleteClick={annoId => this.onLabelInputDeleteClick(annoId)}
|
|
1739
|
+
selectedAnno={selectedAnno}
|
|
1740
|
+
visible={visible}
|
|
1741
|
+
onLabelUpdate={anno => this.onAnnoLabelInputUpdate(anno)}
|
|
1742
|
+
possibleLabels={this.state.possibleLabels}
|
|
1743
|
+
allowedActions={this.props.canvasConfig.annos.actions}
|
|
1744
|
+
multilabels={this.props.canvasConfig.annos.multilabels}
|
|
1745
|
+
mousePos={this.mousePosAbs}
|
|
1746
|
+
defaultLabel={this.props.defaultLabel}
|
|
1747
|
+
/>
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
render() {
|
|
1751
|
+
const selectedAnno = this.findAnno(this.state.selectedAnnoId)
|
|
1752
|
+
return (
|
|
1753
|
+
<div ref={this.container} >
|
|
1754
|
+
<div height={this.state.svg.height}
|
|
1755
|
+
style={{ position: 'fixed', top: this.state.svg.top, left: this.state.svg.left }}
|
|
1756
|
+
>
|
|
1757
|
+
{/* {this.renderAnnoCommentInput(selectedAnno)} */}
|
|
1758
|
+
{this.renderImgLabelInput()}
|
|
1759
|
+
<ImgBar container={this.container}
|
|
1760
|
+
visible={this.state.imgBarVisible}
|
|
1761
|
+
possibleLabels={this.state.possibleLabels}
|
|
1762
|
+
annos={this.props.annos}
|
|
1763
|
+
annoTaskId={this.props.annoTaskId}
|
|
1764
|
+
svg={this.state.svg}
|
|
1765
|
+
imageMeta={this.props.imageMeta}
|
|
1766
|
+
onClose={() => this.handleImgBarClose()}
|
|
1767
|
+
imgLabelIds={this.state.imgLabelIds}
|
|
1768
|
+
// onLabelUpdate={label => this.handleImgLabelUpdate(label)}
|
|
1769
|
+
// imgLabelIds={this.state.imgLabelIds}
|
|
1770
|
+
// multilabels={this.props.canvasConfig.img.multilabels}
|
|
1771
|
+
// allowedActions={this.props.canvasConfig.img.actions}
|
|
1772
|
+
onMouseEnter={e => this.handleImgBarMouseEnter(e)}
|
|
1773
|
+
/>
|
|
1774
|
+
<Dimmer active={!this.state.imageLoaded || this.props.blocked}><Loader>Loading</Loader></Dimmer>
|
|
1775
|
+
<Dimmer active={this.state.isJunk}>
|
|
1776
|
+
<Header as='h2' icon inverted style={{ background: 'rgba(0,0,0,0)' }}>
|
|
1777
|
+
<Icon name='ban' />
|
|
1778
|
+
Marked as Junk
|
|
1779
|
+
</Header>
|
|
1780
|
+
</Dimmer>
|
|
1781
|
+
{this.renderAnnoToolBar(selectedAnno)}
|
|
1782
|
+
{/* <div style={{position: 'fixed', top: this.props.container.top, left: this.props.container.left}}> */}
|
|
1783
|
+
{this.renderAnnoLabelInpput(selectedAnno)}
|
|
1784
|
+
<InfoBoxes container={this.props.container}
|
|
1785
|
+
layoutUpdate={this.props.layoutUpdate}
|
|
1786
|
+
annos={this.state.annos}
|
|
1787
|
+
selectedAnno={selectedAnno}
|
|
1788
|
+
possibleLabels={this.state.possibleLabels}
|
|
1789
|
+
allowedToMarkExample={this.props.canvasConfig.allowedToMarkExample}
|
|
1790
|
+
uiConfig={this.props.uiConfig}
|
|
1791
|
+
imgLoadCount={this.state.imgLoadCount}
|
|
1792
|
+
onCommentUpdate={comment => this.updateAnnoComment(comment)}
|
|
1793
|
+
onUiConfigUpdate={e => this.triggerCanvasEvent(canvasActions.CANVAS_UI_CONFIG_UPDATE, e)}
|
|
1794
|
+
onHideLbl={(lbl, hide) => this.handleHideLbl(lbl, hide)}
|
|
1795
|
+
onMarkExample={anno => this.handleMarkExample(anno)}
|
|
1796
|
+
commentInputTrigger={this.state.annoCommentInputTrigger}
|
|
1797
|
+
onGetAnnoExample={(exampleArgs) => this.props.onGetAnnoExample ? this.props.onGetAnnoExample(exampleArgs) : {}}
|
|
1798
|
+
exampleImg={this.props.exampleImg}
|
|
1799
|
+
/>
|
|
1800
|
+
<svg ref={this.svg} width={this.state.svg.width}
|
|
1801
|
+
height={this.state.svg.height}
|
|
1802
|
+
onKeyDown={e => this.onKeyDown(e)}
|
|
1803
|
+
onKeyUp={e => this.onKeyUp(e)}
|
|
1804
|
+
onClick={e => this.handleCanvasClick(e)}
|
|
1805
|
+
onMouseMove={e => this.handleSvgMouseMove(e)}
|
|
1806
|
+
tabIndex="0"
|
|
1807
|
+
>
|
|
1808
|
+
<g
|
|
1809
|
+
transform={`scale(${this.state.svg.scale}) translate(${this.state.svg.translateX}, ${this.state.svg.translateY})`}
|
|
1810
|
+
onMouseOver={() => { this.onMouseOver() }}
|
|
1811
|
+
onMouseLeave={() => { this.onMouseLeave() }}
|
|
1812
|
+
// onMouseEnter={() => this.svg.current.focus()}
|
|
1813
|
+
onMouseUp={(e) => { this.onMouseUp(e) }}
|
|
1814
|
+
onWheel={(e) => this.onWheel(e)}
|
|
1815
|
+
onMouseMove={(e) => { this.onMouseMove(e) }}
|
|
1816
|
+
>
|
|
1817
|
+
<image
|
|
1818
|
+
onContextMenu={(e) => this.onRightClick(e)}
|
|
1819
|
+
onMouseDown={(e) => this.onMouseDown(e)}
|
|
1820
|
+
href={this.props.imageBlob}
|
|
1821
|
+
width={this.state.svg.width}
|
|
1822
|
+
height={this.state.svg.height}
|
|
1823
|
+
/>
|
|
1824
|
+
{this.renderAnnotations()}
|
|
1825
|
+
</g>
|
|
1826
|
+
</svg>
|
|
1827
|
+
<img
|
|
1828
|
+
alt='sia' style={{ display: 'none' }} ref={this.img}
|
|
1829
|
+
onLoad={() => { this.onImageLoad() }} src={this.state.imageBlob}
|
|
1830
|
+
width="100%" height="100%"
|
|
1831
|
+
/>
|
|
1832
|
+
</div>
|
|
1833
|
+
{/* Placeholder for Layout*/}
|
|
1834
|
+
<div style={{ minHeight: this.state.svg.height }}></div>
|
|
1835
|
+
</div>)
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
export default Canvas
|