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
@@ -0,0 +1,318 @@
1
+ export function toSia(data, image, type, imgOffset) {
2
+ switch (type) {
3
+ case 'bBox':
4
+ const w = image.width * data.w
5
+ const h = image.height * data.h
6
+ const x0 = imgOffset.x + image.width * data.x - w / 2.0
7
+ const y0 = imgOffset.y + image.height * data.y - h / 2.0
8
+ return [
9
+ {
10
+ x: x0,
11
+ y: y0
12
+ },
13
+ {
14
+ x: x0 + w,
15
+ y: y0
16
+ },
17
+ {
18
+ x: x0 + w,
19
+ y: y0 + h
20
+ },
21
+ {
22
+ x: x0,
23
+ y: y0 + h
24
+ }
25
+ ]
26
+ case 'point':
27
+ return [{
28
+ x: imgOffset.x + image.width * data.x,
29
+ y: imgOffset.y + image.height * data.y
30
+ }]
31
+ case 'line':
32
+ case 'polygon':
33
+ return data.map((e) => {
34
+ return {
35
+ x: imgOffset.x + image.width * e.x,
36
+ y: imgOffset.y + image.height * e.y
37
+ }
38
+ })
39
+ default:
40
+
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Transform a sia annotation to backend format.
46
+ *
47
+ * @param {Array} data Annotation data
48
+ * @param {*} svg Image object {width, height}
49
+ * @param {String} type Type of the annotation bBox, point, line, polygon
50
+ * @returns Annotation data in backend style (relative, centered)
51
+ */
52
+ export function toBackend(data, svg, type, imgOffset = { x: 0, y: 0 }) {
53
+ const imgWidth = svg.width - 2 * imgOffset.x
54
+ const imgHeight = svg.height - 2 * imgOffset.y
55
+ switch (type) {
56
+ case 'bBox':
57
+ // const w = svg.width * data.w
58
+ // const h = svg.height * data.h
59
+ // const x0 = svg.width * data.x - w/2.0
60
+ // const y0 = svg.height * data.y - h/2.0
61
+
62
+ // console.error('GO On Here! w = max_x - min_x; h = max_y - min_y')
63
+ const xList = data.map(e => { return e.x })
64
+ const yList = data.map(e => { return e.y })
65
+ const minX = Math.min(...xList) - imgOffset.x
66
+ const maxX = Math.max(...xList) - imgOffset.x
67
+ const minY = Math.min(...yList) - imgOffset.y
68
+ const maxY = Math.max(...yList) - imgOffset.y
69
+ const w = maxX - minX
70
+ const h = maxY - minY
71
+ const x = minX + w / 2.0
72
+ const y = minY + h / 2.0
73
+ return {
74
+ x: x / imgWidth,
75
+ y: y / imgHeight,
76
+ w: w / imgWidth,
77
+ h: h / imgHeight
78
+ }
79
+ case 'point':
80
+ return {
81
+ x: (data[0].x - imgOffset.x) / imgWidth,
82
+ y: (data[0].y - imgOffset.y) / imgHeight
83
+ }
84
+ case 'line':
85
+ case 'polygon':
86
+ return data.map((e) => {
87
+ return {
88
+ x: (e.x - imgOffset.x) / imgWidth,
89
+ y: (e.y - imgOffset.y) / imgHeight
90
+ }
91
+ })
92
+ default:
93
+ console.warn("Wrong annotation type!")
94
+
95
+ }
96
+ }
97
+
98
+ export function getMinMaxPoints(data) {
99
+ const xList = data.map(e => { return e.x })
100
+ const yList = data.map(e => { return e.y })
101
+ const minPoint = { x: Math.min(...xList), y: Math.min(...yList) }
102
+ const maxPoint = { x: Math.max(...xList), y: Math.max(...yList) }
103
+ return [minPoint, maxPoint]
104
+ }
105
+
106
+ /**
107
+ * Get area relative to the image
108
+ *
109
+ * @param {Array} data Annotation data
110
+ * @param {*} scaledImg The scaled image object {width, height}
111
+ * @param {String} type Type of the annotation bBox, point, line, polygon
112
+ * @param {object} originalImg The original image
113
+ * @returns Area relative
114
+ */
115
+ export function getArea(data, scaledImg, type, originalImg) {
116
+ const relData = toBackend(data, scaledImg, type)
117
+ switch (type) {
118
+ case 'bBox':
119
+ return Math.abs(relData.w * relData.h) * originalImg.width * originalImg.height
120
+ // return relData.w*relData.h
121
+ case 'line':
122
+ case 'point':
123
+ return undefined
124
+ case 'polygon':
125
+ let area = 0.0; // Accumulates area in the loop
126
+ if (relData.length > 2) {
127
+ let j = relData.length - 1; // The last vertex is the 'previous' one to the first
128
+
129
+ for (let i = 0; i < relData.length; i++) {
130
+ area = area + (relData[j].x + relData[i].x) * (relData[j].y - relData[i].y);
131
+ j = i; //j is previous vertex to i
132
+ }
133
+ }
134
+ return Math.abs(area / 2) * originalImg.width * originalImg.height
135
+ default:
136
+ console.warn("Wrong annotation type!")
137
+ return undefined
138
+ }
139
+ }
140
+
141
+ export function move(data, movementX, movementY) {
142
+ return data.map(e => {
143
+ return {
144
+ x: e.x + movementX,
145
+ y: e.y + movementY
146
+ }
147
+ })
148
+ }
149
+
150
+
151
+ export function getBox(data, type) {
152
+
153
+ switch (type) {
154
+ case 'bBox':
155
+ return data
156
+ case 'point':
157
+ case 'line':
158
+ case 'polygon':
159
+ let maxX = 0
160
+ let maxY = 0
161
+ let minX = Infinity
162
+ let minY = Infinity
163
+ data.forEach(el => {
164
+ if (el.x > maxX) maxX = el.x
165
+ if (el.y > maxY) maxY = el.y
166
+ if (el.x < minX) minX = el.x
167
+ if (el.y < minY) minY = el.y
168
+ })
169
+ return [
170
+ { x: minX, y: minY }, { x: maxX, y: minY },
171
+ { x: minX, y: maxY }, { x: maxX, y: maxY }
172
+ ]
173
+ default:
174
+ break
175
+
176
+ }
177
+ }
178
+
179
+ export function getCenter(data, type) {
180
+ let box = undefined
181
+ switch (type) {
182
+ case 'point':
183
+ return data[0]
184
+ case 'line':
185
+ case 'polygon':
186
+ case 'bBox':
187
+ box = getBox(data, type)
188
+ const w = box[1].x - box[0].x
189
+ const h = box[3].y - box[0].y
190
+ return {
191
+ x: box[0].x + w / 2.0,
192
+ y: box[0].y + h / 2.0
193
+ }
194
+ default:
195
+
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Get point that is closest to the left browser side.
201
+ *
202
+ * @param {object} data list of points {x,y}
203
+ * @returns {object} A list of point [{x,y}...]. Multiple points are
204
+ * returned when multiple points have the same distance to the left side.
205
+ */
206
+ export function getMostLeftPoint(data) {
207
+ let minX = Infinity
208
+ let minXList = []
209
+ data.forEach(el => {
210
+ if (el.x < minX) {
211
+ minX = el.x
212
+ minXList = []
213
+ minXList.push(el)
214
+ } else if (el.x === minX) {
215
+ minXList.push(el)
216
+ }
217
+ })
218
+ return minXList
219
+ }
220
+
221
+ /**
222
+ * Get point that is closest to the top of the browser.
223
+ *
224
+ * @param {object} data list of points {x,y}
225
+ * @returns {object} A list of point [{x,y}...]. Multiple points are
226
+ * returned when multiple points have the same distance to the top.
227
+ */
228
+ export function getTopPoint(data) {
229
+ let minY = Infinity
230
+ let minYList = []
231
+ data.forEach(el => {
232
+ if (el.y < minY) {
233
+ minY = el.y
234
+ minYList = []
235
+ minYList.push(el)
236
+ } else if (el.y === minY) {
237
+ minYList.push(el)
238
+ }
239
+ })
240
+ return minYList
241
+ }
242
+
243
+ /**
244
+ * Check if all nodes of an annotation are within the image. If not,
245
+ * correct the annotation
246
+ * @param {object} data
247
+ * @param {object} image
248
+ * @param {object} imageOffset
249
+ */
250
+ export function correctAnnotation(data, image, imageOffset) {
251
+ const imgWidth = image.width - 2 * imageOffset.x
252
+ const imgHeight = image.height - 2 * imageOffset.y
253
+ const corrected = data.map(el => {
254
+ let x = el.x
255
+ let y = el.y
256
+ if (el.x <= imageOffset.x) {
257
+ x = imageOffset.x
258
+ } else if (el.x > (imgWidth + imageOffset.x)) {
259
+ x = imgWidth + imageOffset.x
260
+ }
261
+ if (el.y < imageOffset.y) {
262
+ y = imageOffset.y
263
+ } else if (el.y > (imgHeight + imageOffset.y)) {
264
+ y = imgHeight + imageOffset.y
265
+ }
266
+ return { x: x, y: y }
267
+ })
268
+ return corrected
269
+ }
270
+
271
+ /**
272
+ * Rotate annotation around center.
273
+ * @param {*} data list of points {x:int,y:int}
274
+ * @param {*} center Center to rotate point
275
+ * @param {*} angle Rotation angle
276
+ */
277
+ export function rotateAnnotation(data, center, angle) {
278
+ angle = (angle) * (Math.PI / 180); // Convert to radians
279
+ const rotated = data.map(point => {
280
+ return {
281
+ x: Math.round(Math.cos(angle) * (point.x - center.x) - Math.sin(angle) * (point.y - center.y) + center.x),
282
+ y: Math.round(Math.sin(angle) * (point.x - center.x) + Math.cos(angle) * (point.y - center.y) + center.y)
283
+ }
284
+ })
285
+ return rotated;
286
+ }
287
+
288
+ /**
289
+ * Resize annotation data in canvas format to new image size
290
+ * @param {*} data Annotation data in canvas format [{x:int, y:int},...]
291
+ * @param {*} sizeOld Size of old image {width:int, height:int}
292
+ * @param {*} sizeNew Size of new image {width:int, height:int}
293
+ */
294
+ export function resizeAnnoData(data, sizeOld, sizeNew) {
295
+ const xRatio = sizeNew.width / sizeOld.width
296
+ const yRatio = sizeNew.height / sizeOld.height
297
+ return data.map(e => {
298
+ return {
299
+ x: parseInt(e.x * xRatio),
300
+ y: parseInt(e.y * yRatio)
301
+ }
302
+ })
303
+ }
304
+
305
+ export default {
306
+ toSia,
307
+ toBackend,
308
+ getMinMaxPoints,
309
+ getArea,
310
+ move,
311
+ getBox,
312
+ getCenter,
313
+ getMostLeftPoint,
314
+ getTopPoint,
315
+ correctAnnotation,
316
+ rotateAnnotation,
317
+ resizeAnnoData
318
+ }
@@ -0,0 +1,57 @@
1
+ export const SIA_INITIAL_UI_CONFIG = {
2
+ nodeRadius: 4,
3
+ strokeWidth: 4,
4
+ annoDetails: {
5
+ visible: false
6
+ },
7
+ labelInfo: {
8
+ visible: false
9
+ },
10
+ annoStats: {
11
+ visible: false
12
+ }
13
+ }
14
+
15
+ // read sia-ui-config from localStorage
16
+ const iniFromStorage = localStorage.getItem('sia-ui-config') ? JSON.parse(localStorage.getItem('sia-ui-config')) : SIA_INITIAL_UI_CONFIG
17
+
18
+
19
+ // collect object keys and its type in order to describe the object
20
+ const collectDescription = (obj, array) => {
21
+ Object.keys(obj).forEach(key => {
22
+
23
+ const description = {
24
+ key: key,
25
+ type: typeof (obj[key])
26
+ }
27
+ array.push(description)
28
+
29
+ if (typeof obj[key] === 'object') {
30
+ collectDescription(obj[key], array)
31
+ }
32
+ })
33
+ }
34
+
35
+ // collect object description for ini and storage ui-config
36
+
37
+ let iniObjectDescriptor = []
38
+ let storageObjectDescriptor = []
39
+
40
+ collectDescription(SIA_INITIAL_UI_CONFIG, iniObjectDescriptor)
41
+ collectDescription(iniFromStorage, storageObjectDescriptor)
42
+
43
+
44
+ // compare both object descriptions
45
+
46
+ const getUiConfig = () => {
47
+ if (JSON.stringify(iniObjectDescriptor) === JSON.stringify(storageObjectDescriptor)) {
48
+ return iniFromStorage
49
+ }
50
+ else {
51
+ // ini description dif from storage: send ini ui-config and store it to local storage
52
+ return SIA_INITIAL_UI_CONFIG
53
+ }
54
+ }
55
+
56
+ export const uiConfig = getUiConfig()
57
+
@@ -0,0 +1,35 @@
1
+ export function getViewportCoordinates(w, svg){
2
+ const window = {
3
+ xMin:-1*svg.translateX,
4
+ xMax: -1*svg.translateX + (svg.width)/svg.scale,
5
+ yMin: -1*svg.translateY,
6
+ yMax: -1*svg.translateY + (svg.height)/svg.scale
7
+
8
+ }
9
+ const viewport = {
10
+ xMin: 0,
11
+ xMax: svg.width,
12
+ yMin: 0,
13
+ yMax: svg.height
14
+ }
15
+ const scaleX = (viewport.xMax - viewport.xMin)/ (window.xMax - window.xMin)
16
+ const scaleY = (viewport.yMax - viewport.yMin)/ (window.yMax - window.yMin)
17
+
18
+ const vX = viewport.xMin + (w.x - window.xMin) * scaleX
19
+ const vY = viewport.yMin + (w.y - window.yMin) * scaleY
20
+ return {window, viewport, vX, vY, scaleX, scaleY}
21
+ }
22
+
23
+ /**
24
+ *
25
+ * @param {*} w0 Point in image coordinate system
26
+ * @param {*} svg Svg with old translation values and old scales
27
+ * @param {*} s1 New scale/zoom
28
+ */
29
+ export function getZoomTranslation(w0, svg, s1){
30
+ const s0 = svg.scale
31
+ let translation = {x:0,y:0}
32
+ translation.x = (s0/s1) * (w0.x + svg.translateX) - w0.x
33
+ translation.y = (s0/s1) * (w0.y + svg.translateY) - w0.y
34
+ return translation
35
+ }
package/CHANGELOG.md DELETED
@@ -1,163 +0,0 @@
1
- # Changelog
2
- All notable changes to this project will be documented in this file.
3
-
4
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
-
7
- ## [1.2.0] - 2022-05-12
8
- ### Added
9
- - Added preventScrolling prop onMouseEnter in canvas
10
-
11
- ## [1.1.3] - 2022-05-12
12
- ### Changed
13
- - Move import of semantic-ui-css into index js
14
-
15
- ## [1.1.2] - 2022-05-12
16
- ### Added
17
- - Sia dummy data for quick testing
18
- ### Changed
19
- - Moved semantic ui css into sia component
20
- ### Fixed
21
- - Toolbar: Do not crash if no filter props is provided
22
-
23
- ## [1.1.1] - 2022-05-11
24
- ### Changed
25
- - Update sematic-ui-react to version 2.0.3
26
-
27
- ## [1.1.0] - 2022-05-10
28
- ### Added
29
- - More fine grained visibility level for toolbar elements
30
-
31
- ## [1.0.3] - 2022-05-10
32
- ### Fixed
33
- - Export of toolbarEvents and tools
34
-
35
- ## [1.0.2] - 2022-05-09
36
- ### Fixed
37
- - Improved internal uiConfig handling
38
-
39
- ## [1.0.1] - 2022-05-06
40
- ### Fixed
41
- - Export of Sia and other components
42
-
43
- ## [1.0.0] - 2022-05-06
44
- ### Added
45
- - Possibility to assign comments to 2D-Annotations
46
- - InfoBox (AnnoStats) that shows the number of annotations per label (see https://github.com/l3p-cv/lost/issues/86 and https://github.com/l3p-cv/lost/issues/160)
47
- - When clicking on a label in the InfoBox all annotations of this label will be hidden. Anothter click will show the annotations again. (see also https://github.com/l3p-cv/lost/issues/161)
48
- - Added auto save feature
49
- - Anno Example Viewer + Image Example in LabelInfo box
50
- ### Changed
51
- - Move InfoBoxes to Canvas
52
- ### Breaking Change
53
- - Restructured props and event handling for canvas and toolbar
54
- - Created new Sia component
55
- ### Fixed
56
- - Fixed correctAnnotation method to work correctly in maxCanvas mode
57
- - Will prevent annotations from being created outside of the image
58
-
59
- ## [0.9.0] - 2021-12-23
60
- ### Added
61
- - Delete annotation in creation mode when hitting *Escape*-Key
62
- - Show img description in ImgBar if available
63
- - Max canvas size mode. Where canvas takes the maximum container size and is not
64
- image oriented as before. Add prop *maxCanvas={true}* to canvas in order to enable.
65
- ### Changed
66
- - Be able to deal with mixed color possible labels -> where a part of labels
67
- will have a specified color and the other part has no provided color
68
-
69
- ## [0.8.0] - 2021-10-14
70
- ### Added
71
- - Added lockedAnnos prop for Canvas in order to lock annos by id. Locked annos
72
- can not be edited
73
-
74
- ## [0.7.0] - 2021-10-13
75
- ### Added
76
- - Update annotations on **annos**-prob change
77
-
78
- ## [0.6.0] - 2021-09-28
79
- ### Added
80
- - Edit mode for Polygons -> Edit polygons again that already have been created
81
-
82
- ## [0.5.2] - 2021-07-22
83
- ### Changed
84
- - do not spam logs with toSia function
85
- ## [0.5.1] - 2021-04-17
86
- ### Fixed
87
- - Prevent to crash when hitting delete and no annotation was selected
88
- - Prevent to crash when no AnnoRef was found
89
-
90
- ## [0.5.0] - 2021-01-04
91
- ### Added
92
- - Added onAnnoPerformedAction callback to canvas
93
- ### Changed
94
- - Removed all logs
95
-
96
- ## [0.4.1] - 2020-12-16
97
- ### Changed
98
- - Do not unload image when props.annos change
99
-
100
- ## [0.4.0] - 2020-12-16
101
- ### Added
102
- - A possible label can now be selected as default label by id
103
- - Added maxAnnos to canvasConfig. This allows to define a maximum number of annotations that are allowed per image.
104
-
105
- ## [0.3.0] - 2020-12-11
106
- ### Added
107
- - Frontend annotation time measurement
108
- * Annotation time is now measured in frontend, based on user events. For each annotation individual user interaction time is measured.
109
- - Delete last node of polygon/ line when hitting delete key in create mode
110
- - Added copy & paste for annotations
111
- - Added prop to block canvas
112
- ### Fixed
113
- - Do not lose polygon annotation when hitting enter in create mode
114
- - Do not allow to draw a polygon consisting of two points
115
- - Do not collapse line with two points, when confirming with enter
116
-
117
- ## [0.2.2] - 2020-08-06
118
- ### Fixed
119
- - Do not clutter background through sia css file, when importing sia canvas component
120
-
121
- ## [0.2.1] - 2020-06-20
122
- ### Fixed
123
- - Fixed multiple image load events for the same image, that lead to wrong svg sizes
124
-
125
- ## [0.2.0] - 2020-06-05
126
- ### Fixed
127
- - Added camera move on wasd keys
128
- - Provide canvas key events via prop callback
129
- ### Changed
130
- - Show annotation nodes in foreground and label above annotation to prevent that nodes are not accessible by the annotator (see https://github.com/l3p-cv/lost/issues/74)
131
-
132
- ## [0.1.2] - 2020-04-06
133
- ### Removed
134
- - Removed most sia logs that clutter the console
135
-
136
- ## [0.1.1] - 2020-04-06
137
- ### Fixed
138
- - Fixed undefined possible labels in canvas
139
-
140
- ## [0.1.0] - 2020-04-06
141
- ### Added
142
- - Always reset annotation mode to *view* when getAnnoBackendFormat is called
143
- - Delete annotation in sia canvas correctly, when they are moved out of the image.
144
- - Prevent user from moving image out of canvas
145
- - Confirm label in LabelInput by click on the respective label
146
- - Configure name of the default Label by a prop
147
- - Provide method to reset canvas zoom
148
- - It is now possible to define custom label colors via canvas *possibleLabels* props
149
-
150
- ### Fixed
151
- - Fixed jumping camera when zooming into the image
152
- - Fixed LabelInput on wrong position when image was zoomed
153
-
154
- ## [0.0.2] - 2020-04-02
155
- ### Fixed
156
- - Fixed crash on changing image when label input is active.
157
-
158
- ## [0.0.1] - 2020-04-01
159
- ### Fixed
160
- - Fixed all annotations lost bug. (see also https://github.com/l3p-cv/lost/issues/51)
161
- * When a new annotation was created and deleted before a backend update was performed, SIA sent this annotation to backend for an db update
162
- * The backend then tried to update a db record that did not exists which caused an exception.
163
- * The result was that all annotation where lost