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.
Files changed (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +0 -0
  3. package/package.json +60 -47
  4. package/src/AnnoExampleViewer.jsx +57 -0
  5. package/src/AnnoLabelInput.jsx +99 -0
  6. package/src/AnnoToolBar.jsx +132 -0
  7. package/src/Annotation/AnnoBar.jsx +148 -0
  8. package/src/Annotation/Annotation.jsx +358 -0
  9. package/src/Annotation/Annotation.scss +47 -0
  10. package/src/Annotation/BBox.jsx +291 -0
  11. package/src/Annotation/Edge.jsx +82 -0
  12. package/src/Annotation/InfSelectionArea.jsx +71 -0
  13. package/src/Annotation/Line.jsx +60 -0
  14. package/src/Annotation/Node.jsx +278 -0
  15. package/src/Annotation/Point.jsx +196 -0
  16. package/src/Annotation/Polygon.jsx +361 -0
  17. package/src/Canvas.jsx +1839 -0
  18. package/src/ImgBar.jsx +123 -0
  19. package/src/InfoBoxes/AnnoDetails.jsx +166 -0
  20. package/src/InfoBoxes/AnnoStats.jsx +104 -0
  21. package/src/InfoBoxes/InfoBox.jsx +78 -0
  22. package/src/InfoBoxes/InfoBoxArea.jsx +155 -0
  23. package/src/InfoBoxes/LabelInfo.jsx +95 -0
  24. package/src/LabelInput.jsx +224 -0
  25. package/src/Prompt.jsx +46 -0
  26. package/src/SIA.scss +10 -0
  27. package/src/SIAFilterButton.jsx +178 -0
  28. package/src/SIASettingButton.jsx +122 -0
  29. package/src/Sia.jsx +485 -0
  30. package/src/SiaPopup.jsx +10 -0
  31. package/src/ToolBar.jsx +399 -0
  32. package/src/Toolbar.css +13 -0
  33. package/src/ToolbarItem.jsx +25 -0
  34. package/src/filterTools.js +3 -0
  35. package/src/index.js +17 -0
  36. package/src/siaDummyData.js +265 -0
  37. package/src/test.js +7 -0
  38. package/src/types/annoStatus.js +4 -0
  39. package/src/types/canvasActions.js +57 -0
  40. package/src/types/cursorstyles.js +3 -0
  41. package/src/types/modes.js +8 -0
  42. package/src/types/notificationType.js +4 -0
  43. package/src/types/toolbarEvents.js +33 -0
  44. package/src/types/tools.js +11 -0
  45. package/src/utils/annoConversion.js +115 -0
  46. package/src/utils/colorlut.js +67 -0
  47. package/src/utils/constraints.js +75 -0
  48. package/src/utils/hist.js +67 -0
  49. package/src/utils/keyActions.js +107 -0
  50. package/src/utils/mouse.js +14 -0
  51. package/src/utils/siaIcons.jsx +95 -0
  52. package/src/utils/transform.js +318 -0
  53. package/src/utils/uiConfig.js +57 -0
  54. package/src/utils/windowViewport.js +35 -0
  55. package/CHANGELOG.md +0 -163
  56. package/dist/index.css +0 -24823
  57. package/dist/index.es.js +0 -10431
  58. package/dist/index.es.js.map +0 -1
  59. package/dist/index.js +0 -10446
  60. 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