lost-sia 3.0.0 → 3.1.1-alpha3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Annotation/logic/Annotation.js +1 -1
- package/dist/Annotation/ui/AnnotationComponent.js +1 -1
- package/dist/Annotation/ui/atoms/AnnoBar.js +1 -1
- package/dist/Annotation/ui/tools/Point.js +1 -1
- package/dist/Canvas/Canvas.d.ts +2 -1
- package/dist/Canvas/Canvas.js +1 -1
- package/dist/Canvas/LabelInput.js +1 -1
- package/dist/IconButton.js +1 -1
- package/dist/Sia.d.ts +4 -3
- package/dist/Sia.js +1 -1
- package/dist/assets/node_modules/semantic-ui-css/semantic.min-09YPtVE6.css +1 -0
- package/dist/stories/AnnotationTools.stories.d.ts +2 -1
- package/dist/stories/Canvas/Canvas.stories.d.ts +5 -4
- package/dist/stories/Canvas/CanvasWithOffset.stories.d.ts +6 -6
- package/dist/stories/MinimalSia.stories.d.ts +3 -1
- package/dist/stories/SIA/SIA.stories.d.ts +3 -1
- package/dist/types.d.ts +5 -0
- package/dist/utils/KeyMapper.js +1 -1
- package/package.json +20 -22
- package/src/Annotation/ui/atoms/AnnoBar.tsx +3 -1
- package/src/Canvas/Canvas.tsx +33 -23
- package/src/Canvas/LabelInput.tsx +1 -1
- package/src/Sia.tsx +118 -5
- package/src/types.ts +6 -0
- package/dist/assets/node_modules/semantic-ui-css/semantic.min-Bvulf91l.css +0 -346
|
@@ -3,7 +3,7 @@ import { AllowedTools } from '../types';
|
|
|
3
3
|
export declare const ActionsData: {};
|
|
4
4
|
declare const meta: {
|
|
5
5
|
title: string;
|
|
6
|
-
component: ({ additionalButtons, allowedTools: propAllowedTools, polygonOperationResult, annotationSettings: propAnnotationSettings, uiConfig: propUiConfig, defaultAnnotationTool, defaultLabelId, image, isLoading, isPolygonSelectionMode, initialAnnotations, initialImageLabelIds, initialIsImageJunk, possibleLabels, onAnnoCreated, onAnnoCreationFinished, onAnnoChanged, onAnnoDeleted, onImageLabelsChanged, onIsImageJunk, onNotification, onSelectAnnotation, }: {
|
|
6
|
+
component: ({ additionalButtons, allowedTools: propAllowedTools, polygonOperationResult, annotationSettings: propAnnotationSettings, uiConfig: propUiConfig, defaultAnnotationTool, defaultLabelId, image, isLoading, isPolygonSelectionMode, initialAnnotations, initialImageLabelIds, initialIsImageJunk, possibleLabels, onAnnoCreated, onAnnoCreationFinished, onAnnoChanged, onAnnoDeleted, onImageLabelsChanged, onIsImageJunk, onNotification, onSelectAnnotation, onTimeTravel, }: {
|
|
7
7
|
additionalButtons?: import('react').ReactElement;
|
|
8
8
|
allowedTools?: AllowedTools;
|
|
9
9
|
polygonOperationResult?: import('..').PolygonOperationResult;
|
|
@@ -26,6 +26,7 @@ declare const meta: {
|
|
|
26
26
|
onIsImageJunk?: (isJunk: boolean) => void;
|
|
27
27
|
onNotification?: (notification: import('..').SIANotification) => void;
|
|
28
28
|
onSelectAnnotation?: (annotation: import('../models').Annotation) => void;
|
|
29
|
+
onTimeTravel?: (timeTravelAction: import('..').TimeTravelChanges) => void;
|
|
29
30
|
}) => import("react/jsx-runtime").JSX.Element;
|
|
30
31
|
parameters: {
|
|
31
32
|
layout: string;
|
|
@@ -2,13 +2,13 @@ import { StoryObj } from '@storybook/react';
|
|
|
2
2
|
import { default as AnnotationTool } from '../../models/AnnotationTool';
|
|
3
3
|
import { AnnotationSettings, UiConfig } from '../../types';
|
|
4
4
|
export declare const ActionsData: {
|
|
5
|
-
onAnnoEvent:
|
|
6
|
-
onKeyDown:
|
|
7
|
-
onKeyUp:
|
|
5
|
+
onAnnoEvent: any;
|
|
6
|
+
onKeyDown: any;
|
|
7
|
+
onKeyUp: any;
|
|
8
8
|
};
|
|
9
9
|
declare const meta: {
|
|
10
10
|
title: string;
|
|
11
|
-
component: ({ annotations, annotationSettings, defaultLabelId, image, isFullscreen, isImageJunk, isPolygonSelectionMode, polygonOperationResult, possibleLabels, preventScrolling, selectedAnnotation, selectedAnnoTool, toolbarHeight, uiConfig, onAnnoCreated, onAnnoCreationFinished, onAnnoChanged, onAnnoEditing, onNotification, onRequestNewAnnoId, onSelectAnnotation, onSetIsImageJunk, onSetSelectedTool, onShouldDeleteAnno, }: {
|
|
11
|
+
component: ({ annotations, annotationSettings, defaultLabelId, image, isFullscreen, isImageJunk, isPolygonSelectionMode, polygonOperationResult, possibleLabels, preventScrolling, selectedAnnotation, selectedAnnoTool, toolbarHeight, uiConfig, onAnnoCreated, onAnnoCreationFinished, onAnnoChanged, onAnnoEditing, onNotification, onRequestNewAnnoId, onSelectAnnotation, onSetIsImageJunk, onSetSelectedTool, onShouldDeleteAnno, onTraverseAnnotationHistory, }: {
|
|
12
12
|
annotations?: import('../../models').Annotation[];
|
|
13
13
|
annotationSettings: AnnotationSettings;
|
|
14
14
|
defaultLabelId?: number;
|
|
@@ -33,6 +33,7 @@ declare const meta: {
|
|
|
33
33
|
onSetIsImageJunk: (newJunkState: boolean) => void;
|
|
34
34
|
onSetSelectedTool: (tool: AnnotationTool) => void;
|
|
35
35
|
onShouldDeleteAnno: (internalAnnoId: number) => void;
|
|
36
|
+
onTraverseAnnotationHistory: (isUndo: boolean) => void;
|
|
36
37
|
}) => import("react/jsx-runtime").JSX.Element;
|
|
37
38
|
parameters: {
|
|
38
39
|
layout: string;
|
|
@@ -2,9 +2,9 @@ import { StoryObj } from '@storybook/react';
|
|
|
2
2
|
import { default as AnnotationTool } from '../../models/AnnotationTool';
|
|
3
3
|
import { UiConfig } from '../../types';
|
|
4
4
|
export declare const ActionsData: {
|
|
5
|
-
onAnnoEvent:
|
|
6
|
-
onKeyDown:
|
|
7
|
-
onKeyUp:
|
|
5
|
+
onAnnoEvent: any;
|
|
6
|
+
onKeyDown: any;
|
|
7
|
+
onKeyUp: any;
|
|
8
8
|
};
|
|
9
9
|
declare const meta: {
|
|
10
10
|
title: string;
|
|
@@ -22,9 +22,9 @@ declare const meta: {
|
|
|
22
22
|
tags: string[];
|
|
23
23
|
excludeStories: RegExp;
|
|
24
24
|
args: {
|
|
25
|
-
onAnnoEvent:
|
|
26
|
-
onKeyDown:
|
|
27
|
-
onKeyUp:
|
|
25
|
+
onAnnoEvent: any;
|
|
26
|
+
onKeyDown: any;
|
|
27
|
+
onKeyUp: any;
|
|
28
28
|
};
|
|
29
29
|
};
|
|
30
30
|
export default meta;
|
|
@@ -3,7 +3,7 @@ import { Label } from '../types';
|
|
|
3
3
|
export declare const ActionsData: {};
|
|
4
4
|
declare const meta: {
|
|
5
5
|
title: string;
|
|
6
|
-
component: ({ additionalButtons, allowedTools: propAllowedTools, polygonOperationResult, annotationSettings: propAnnotationSettings, uiConfig: propUiConfig, defaultAnnotationTool, defaultLabelId, image, isLoading, isPolygonSelectionMode, initialAnnotations, initialImageLabelIds, initialIsImageJunk, possibleLabels, onAnnoCreated, onAnnoCreationFinished, onAnnoChanged, onAnnoDeleted, onImageLabelsChanged, onIsImageJunk, onNotification, onSelectAnnotation, }: {
|
|
6
|
+
component: ({ additionalButtons, allowedTools: propAllowedTools, polygonOperationResult, annotationSettings: propAnnotationSettings, uiConfig: propUiConfig, defaultAnnotationTool, defaultLabelId, image, isLoading, isPolygonSelectionMode, initialAnnotations, initialImageLabelIds, initialIsImageJunk, possibleLabels, onAnnoCreated, onAnnoCreationFinished, onAnnoChanged, onAnnoDeleted, onImageLabelsChanged, onIsImageJunk, onNotification, onSelectAnnotation, onTimeTravel, }: {
|
|
7
7
|
additionalButtons?: import('react').ReactElement;
|
|
8
8
|
allowedTools?: import('..').AllowedTools;
|
|
9
9
|
polygonOperationResult?: import('..').PolygonOperationResult;
|
|
@@ -26,6 +26,7 @@ declare const meta: {
|
|
|
26
26
|
onIsImageJunk?: (isJunk: boolean) => void;
|
|
27
27
|
onNotification?: (notification: import('..').SIANotification) => void;
|
|
28
28
|
onSelectAnnotation?: (annotation: import('../models').Annotation) => void;
|
|
29
|
+
onTimeTravel?: (timeTravelAction: import('..').TimeTravelChanges) => void;
|
|
29
30
|
}) => import("react/jsx-runtime").JSX.Element;
|
|
30
31
|
parameters: {
|
|
31
32
|
layout: string;
|
|
@@ -56,6 +57,7 @@ declare const meta: {
|
|
|
56
57
|
onIsImageJunk?: (isJunk: boolean) => void;
|
|
57
58
|
onNotification?: (notification: import('..').SIANotification) => void;
|
|
58
59
|
onSelectAnnotation?: (annotation: import('../models').Annotation) => void;
|
|
60
|
+
onTimeTravel?: (timeTravelAction: import('..').TimeTravelChanges) => void;
|
|
59
61
|
}>) => import("react/jsx-runtime").JSX.Element)[];
|
|
60
62
|
};
|
|
61
63
|
export default meta;
|
|
@@ -3,7 +3,7 @@ import { UiConfig } from '../../types';
|
|
|
3
3
|
export declare const ActionsData: {};
|
|
4
4
|
declare const meta: {
|
|
5
5
|
title: string;
|
|
6
|
-
component: ({ additionalButtons, allowedTools: propAllowedTools, polygonOperationResult, annotationSettings: propAnnotationSettings, uiConfig: propUiConfig, defaultAnnotationTool, defaultLabelId, image, isLoading, isPolygonSelectionMode, initialAnnotations, initialImageLabelIds, initialIsImageJunk, possibleLabels, onAnnoCreated, onAnnoCreationFinished, onAnnoChanged, onAnnoDeleted, onImageLabelsChanged, onIsImageJunk, onNotification, onSelectAnnotation, }: {
|
|
6
|
+
component: ({ additionalButtons, allowedTools: propAllowedTools, polygonOperationResult, annotationSettings: propAnnotationSettings, uiConfig: propUiConfig, defaultAnnotationTool, defaultLabelId, image, isLoading, isPolygonSelectionMode, initialAnnotations, initialImageLabelIds, initialIsImageJunk, possibleLabels, onAnnoCreated, onAnnoCreationFinished, onAnnoChanged, onAnnoDeleted, onImageLabelsChanged, onIsImageJunk, onNotification, onSelectAnnotation, onTimeTravel, }: {
|
|
7
7
|
additionalButtons?: import('react').ReactElement;
|
|
8
8
|
allowedTools?: import('../..').AllowedTools;
|
|
9
9
|
polygonOperationResult?: import('../..').PolygonOperationResult;
|
|
@@ -26,6 +26,7 @@ declare const meta: {
|
|
|
26
26
|
onIsImageJunk?: (isJunk: boolean) => void;
|
|
27
27
|
onNotification?: (notification: import('../..').SIANotification) => void;
|
|
28
28
|
onSelectAnnotation?: (annotation: import('../../models').Annotation) => void;
|
|
29
|
+
onTimeTravel?: (timeTravelAction: import('../..').TimeTravelChanges) => void;
|
|
29
30
|
}) => import("react/jsx-runtime").JSX.Element;
|
|
30
31
|
parameters: {
|
|
31
32
|
layout: string;
|
|
@@ -56,6 +57,7 @@ declare const meta: {
|
|
|
56
57
|
onIsImageJunk?: (isJunk: boolean) => void;
|
|
57
58
|
onNotification?: (notification: import('../..').SIANotification) => void;
|
|
58
59
|
onSelectAnnotation?: (annotation: import('../../models').Annotation) => void;
|
|
60
|
+
onTimeTravel?: (timeTravelAction: import('../..').TimeTravelChanges) => void;
|
|
59
61
|
}>) => import("react/jsx-runtime").JSX.Element)[];
|
|
60
62
|
};
|
|
61
63
|
export default meta;
|
package/dist/types.d.ts
CHANGED
package/dist/utils/KeyMapper.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
import e from"../models/KeyAction.js";class c{isControlDown=!1;keyActionHandler;constructor(t=void 0){this.keyActionHandler=t}keyDown(t,r=!1,i=!1){switch(t){case"Enter":this.triggerKeyAction(e.EDIT_LABEL);break;case"Delete":this.triggerKeyAction(e.DELETE_ANNO);break;case"Backspace":this.triggerKeyAction(e.DELETE_ANNO);break;case"z":i&&this.triggerKeyAction(e.UNDO);break;case"y":i&&this.triggerKeyAction(e.REDO);break;case"Tab":r?this.triggerKeyAction(e.TRAVERSE_ANNOS_BACKWARDS):this.triggerKeyAction(e.TRAVERSE_ANNOS);break;case"w":this.triggerKeyAction(e.CAM_MOVE_UP);break;case"s":this.triggerKeyAction(e.CAM_MOVE_DOWN);break;case"a":this.triggerKeyAction(e.CAM_MOVE_LEFT);break;case"d":this.triggerKeyAction(e.CAM_MOVE_RIGHT);break;case"e":this.triggerKeyAction(e.RECREATE_ANNO);break;case"j":this.triggerKeyAction(e.TOGGLE_IMAGE_JUNK);break;case"c":i?this.triggerKeyAction(e.COPY_ANNOTATION):this.triggerKeyAction(e.TOGGLE_ANNO_COMMENT_INPUT);break;case"v":i&&this.triggerKeyAction(e.PASTE_ANNOTATION);break;case"Escape":this.triggerKeyAction(e.DELETE_ANNO_IN_CREATION);break;default:return!1}return!0}triggerKeyAction(t){this.keyActionHandler&&this.keyActionHandler(t)}}export{c as default};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lost-sia",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.1-alpha3",
|
|
4
4
|
"description": "Single Image Annotation Tool",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "l3p-cv/lost-sia",
|
|
@@ -57,36 +57,34 @@
|
|
|
57
57
|
"pretty": "prettier --write \"./**/*.{js,jsx,mjs,cjs,ts,tsx,json}\""
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@fortawesome/free-regular-svg-icons": "^
|
|
61
|
-
"@fortawesome/free-solid-svg-icons": "^
|
|
62
|
-
"@fortawesome/react-fontawesome": "^
|
|
63
|
-
"react": "^
|
|
64
|
-
"react-dom": "^
|
|
65
|
-
"react-draggable": "^4.
|
|
66
|
-
"sass": "^1.
|
|
60
|
+
"@fortawesome/free-regular-svg-icons": "^7.1.0",
|
|
61
|
+
"@fortawesome/free-solid-svg-icons": "^7.1.0",
|
|
62
|
+
"@fortawesome/react-fontawesome": "^3.1.1",
|
|
63
|
+
"react": "^19.2.1",
|
|
64
|
+
"react-dom": "^19.2.1",
|
|
65
|
+
"react-draggable": "^4.5.0",
|
|
66
|
+
"sass": "^1.95.0",
|
|
67
67
|
"semantic-ui-css": "2.5.0",
|
|
68
68
|
"semantic-ui-react": "^2.0.3"
|
|
69
69
|
},
|
|
70
70
|
"peerDependencies": {
|
|
71
71
|
"@coreui/react": "^5.9.1",
|
|
72
72
|
"prop-types": "^15.5.4",
|
|
73
|
-
"react": "^
|
|
74
|
-
"react-dom": "^
|
|
73
|
+
"react": "^19.2.1",
|
|
74
|
+
"react-dom": "^19.2.1"
|
|
75
75
|
},
|
|
76
76
|
"devDependencies": {
|
|
77
77
|
"@eslint/eslintrc": "^3.3.1",
|
|
78
78
|
"@eslint/js": "^9.39.1",
|
|
79
79
|
"@microsoft/api-extractor": "^7.52.13",
|
|
80
|
-
"@storybook/addon-docs": "^
|
|
81
|
-
"@storybook/addon-links": "^
|
|
82
|
-
"@storybook/react": "^
|
|
83
|
-
"@storybook/react-vite": "^
|
|
84
|
-
"@
|
|
85
|
-
"@types/react": "^19.1.16",
|
|
80
|
+
"@storybook/addon-docs": "^10.1.6",
|
|
81
|
+
"@storybook/addon-links": "^10.1.6",
|
|
82
|
+
"@storybook/react": "^10.1.6",
|
|
83
|
+
"@storybook/react-vite": "^10.1.6",
|
|
84
|
+
"@types/react": "^19.2.7",
|
|
86
85
|
"@typescript-eslint/eslint-plugin": "^8.46.1",
|
|
87
86
|
"@typescript-eslint/parser": "^8.46.1",
|
|
88
|
-
"@vitejs/plugin-react": "^
|
|
89
|
-
"cross-env": "^7.0.3",
|
|
87
|
+
"@vitejs/plugin-react": "^5.1.2",
|
|
90
88
|
"eslint": "^9.39.1",
|
|
91
89
|
"eslint-config-standard": "^17.1.0",
|
|
92
90
|
"eslint-config-standard-react": "^13.0.0",
|
|
@@ -96,15 +94,15 @@
|
|
|
96
94
|
"eslint-plugin-react": "^7.37.5",
|
|
97
95
|
"eslint-plugin-standard": "^5.0.0",
|
|
98
96
|
"eslint-plugin-storybook": "^10.0.7",
|
|
99
|
-
"glob": "^
|
|
97
|
+
"glob": "^13.0.0",
|
|
100
98
|
"lodash-es": "^4.17.21",
|
|
101
99
|
"prettier": "^3.3.3",
|
|
102
100
|
"react-redux": "^9.1.2",
|
|
103
101
|
"redux": "^5.0.1",
|
|
104
|
-
"storybook": "^
|
|
102
|
+
"storybook": "^10.1.6",
|
|
105
103
|
"unplugin-dts": "^1.0.0-beta.6",
|
|
106
|
-
"vite": "^
|
|
107
|
-
"vitest": "^
|
|
104
|
+
"vite": "^7.2.7",
|
|
105
|
+
"vitest": "^4.0.15"
|
|
108
106
|
},
|
|
109
107
|
"eslintConfig": {
|
|
110
108
|
"extends": [
|
|
@@ -48,7 +48,9 @@ const AnnoBar = ({
|
|
|
48
48
|
useEffect(() => {
|
|
49
49
|
// get the most top left point from the annotation
|
|
50
50
|
const topPoints: Point[] = transform.getTopPoint(annotationCoordinates)
|
|
51
|
-
const
|
|
51
|
+
const newTopLeftPoints: Point[] = transform.getMostLeftPoints(topPoints)
|
|
52
|
+
const newTopLeftPoint: Point =
|
|
53
|
+
newTopLeftPoints.length > 0 ? newTopLeftPoints[0] : { x: 0, y: 0 }
|
|
52
54
|
|
|
53
55
|
setTopLeftPoint(newTopLeftPoint)
|
|
54
56
|
|
package/src/Canvas/Canvas.tsx
CHANGED
|
@@ -63,6 +63,7 @@ type CanvasProps = {
|
|
|
63
63
|
onSetIsImageJunk: (newJunkState: boolean) => void
|
|
64
64
|
onSetSelectedTool: (tool: AnnotationTool) => void
|
|
65
65
|
onShouldDeleteAnno: (internalAnnoId: number) => void
|
|
66
|
+
onTraverseAnnotationHistory: (isUndo: boolean) => void
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
const Canvas = ({
|
|
@@ -92,6 +93,7 @@ const Canvas = ({
|
|
|
92
93
|
onSetIsImageJunk,
|
|
93
94
|
onSetSelectedTool = (_) => {},
|
|
94
95
|
onShouldDeleteAnno,
|
|
96
|
+
onTraverseAnnotationHistory,
|
|
95
97
|
}: CanvasProps) => {
|
|
96
98
|
const [editorMode, setEditorMode] = useState<EditorModes>(EditorModes.VIEW)
|
|
97
99
|
const [annoTimestamp, setAnnoTimestamp] = useState<number | undefined>()
|
|
@@ -335,10 +337,28 @@ const Canvas = ({
|
|
|
335
337
|
onSelectAnnotation(annotationToPaste)
|
|
336
338
|
}
|
|
337
339
|
|
|
340
|
+
/** Returns the page-space position of an annotation's top-left corner.
|
|
341
|
+
* @param stageCoords - annotation coordinates already in stage (pixel) space
|
|
342
|
+
*/
|
|
343
|
+
const getAnnoTopLeftPagePosition = (stageCoords: Point[]): Point => {
|
|
344
|
+
const leftPoints: Point[] = transform.getMostLeftPoints(stageCoords)
|
|
345
|
+
const topLeftPoint: Point = transform.getTopPoint(leftPoints)[0]
|
|
346
|
+
return transform.convertStageToPage(topLeftPoint, pageToStageOffset, svgScale, svgTranslation)
|
|
347
|
+
}
|
|
348
|
+
|
|
338
349
|
const handleKeyAction = (keyAction: KeyAction) => {
|
|
339
350
|
switch (keyAction) {
|
|
340
351
|
case KeyAction.EDIT_LABEL:
|
|
341
|
-
if (selectedAnnotation)
|
|
352
|
+
if (selectedAnnotation) {
|
|
353
|
+
// selectedAnnotation coordinates are in the percentaged system — convert to stage first
|
|
354
|
+
const stageCoords = transform.convertPercentagedCoordinatesToStage(
|
|
355
|
+
selectedAnnotation.coordinates,
|
|
356
|
+
imgSize,
|
|
357
|
+
stageSize,
|
|
358
|
+
)
|
|
359
|
+
setLabelInputPosition(getAnnoTopLeftPagePosition(stageCoords))
|
|
360
|
+
setIsLabelInputVisible(true)
|
|
361
|
+
}
|
|
342
362
|
break
|
|
343
363
|
case KeyAction.DELETE_ANNO:
|
|
344
364
|
if (selectedAnnotation) onShouldDeleteAnno(selectedAnnotation.internalId)
|
|
@@ -360,10 +380,10 @@ const Canvas = ({
|
|
|
360
380
|
console.log('KeyAction TODO: LEAVE_ANNO_ADD_MODE')
|
|
361
381
|
break
|
|
362
382
|
case KeyAction.UNDO:
|
|
363
|
-
|
|
383
|
+
onTraverseAnnotationHistory(true)
|
|
364
384
|
break
|
|
365
385
|
case KeyAction.REDO:
|
|
366
|
-
|
|
386
|
+
onTraverseAnnotationHistory(false)
|
|
367
387
|
break
|
|
368
388
|
case KeyAction.TRAVERSE_ANNOS:
|
|
369
389
|
traverseAnnos()
|
|
@@ -741,16 +761,7 @@ const Canvas = ({
|
|
|
741
761
|
onSelectAnnotation(percentagedAnnotation)
|
|
742
762
|
|
|
743
763
|
// get top left point of annotation
|
|
744
|
-
|
|
745
|
-
const topLeftPoint: Point = transform.getTopPoint(leftPoints)[0]
|
|
746
|
-
const pageTopLeftPoint: Point = transform.convertStageToPage(
|
|
747
|
-
topLeftPoint,
|
|
748
|
-
pageToStageOffset,
|
|
749
|
-
svgScale,
|
|
750
|
-
svgTranslation,
|
|
751
|
-
)
|
|
752
|
-
|
|
753
|
-
setLabelInputPosition(pageTopLeftPoint)
|
|
764
|
+
setLabelInputPosition(getAnnoTopLeftPagePosition(annotation.coordinates))
|
|
754
765
|
}
|
|
755
766
|
|
|
756
767
|
const handleOnAnnoChanged = (annotation: Annotation) => {
|
|
@@ -919,22 +930,21 @@ const Canvas = ({
|
|
|
919
930
|
}
|
|
920
931
|
}
|
|
921
932
|
|
|
933
|
+
// Look up the current annotation from scaledAnnotations
|
|
934
|
+
const currentScaled = scaledAnnotations.find(
|
|
935
|
+
(a) => a.internalId === selectedAnnotation.internalId,
|
|
936
|
+
)
|
|
937
|
+
if (!currentScaled) return
|
|
938
|
+
|
|
922
939
|
// change the status to CHANGED when the annotation was loaded (initialAnnotations)
|
|
923
940
|
const newAnnotationStatus: AnnotationStatus =
|
|
924
|
-
|
|
941
|
+
currentScaled.status === AnnotationStatus.LOADED
|
|
925
942
|
? AnnotationStatus.CHANGED
|
|
926
|
-
:
|
|
943
|
+
: currentScaled.status
|
|
927
944
|
|
|
928
|
-
// selectedAnnotation comes from SIA and is therefore in the percentaged system
|
|
929
|
-
// convert it first
|
|
930
|
-
// also update the new labels
|
|
931
945
|
const updatedAnno: Annotation = {
|
|
932
946
|
...selectedAnnotation,
|
|
933
|
-
coordinates:
|
|
934
|
-
selectedAnnotation.coordinates,
|
|
935
|
-
imgSize,
|
|
936
|
-
stageSize,
|
|
937
|
-
),
|
|
947
|
+
coordinates: currentScaled.coordinates,
|
|
938
948
|
labelIds: [...selectedLabelIds],
|
|
939
949
|
status: newAnnotationStatus,
|
|
940
950
|
}
|
|
@@ -67,7 +67,7 @@ const LabelInput = ({
|
|
|
67
67
|
</CPopover>
|
|
68
68
|
|
|
69
69
|
<CDropdown visible={isVisible} autoClose={false} style={{ marginTop: -25 }}>
|
|
70
|
-
{/* this invisible toggle has to be here,
|
|
70
|
+
{/* this invisible toggle has to be here, otherwise the menu is not showing as intended */}
|
|
71
71
|
<CDropdownToggle style={{ display: 'none' }} />
|
|
72
72
|
<CDropdownMenu>
|
|
73
73
|
<div className="px-3 py-2">
|
package/src/Sia.tsx
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
Label,
|
|
14
14
|
PolygonOperationResult,
|
|
15
15
|
SIANotification,
|
|
16
|
+
TimeTravelChanges,
|
|
16
17
|
UiConfig,
|
|
17
18
|
} from './types'
|
|
18
19
|
|
|
@@ -39,12 +40,13 @@ type SiaProps = {
|
|
|
39
40
|
onIsImageJunk?: (isJunk: boolean) => void
|
|
40
41
|
onNotification?: (notification: SIANotification) => void
|
|
41
42
|
onSelectAnnotation?: (annotation: Annotation) => void
|
|
43
|
+
onTimeTravel?: (timeTravelAction: TimeTravelChanges) => void
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
/**
|
|
45
47
|
* Main SIA component
|
|
46
48
|
*/
|
|
47
|
-
const
|
|
49
|
+
const Sia = ({
|
|
48
50
|
additionalButtons,
|
|
49
51
|
allowedTools: propAllowedTools,
|
|
50
52
|
polygonOperationResult = { annotationsToDelete: [], polygonsToCreate: [] },
|
|
@@ -67,6 +69,7 @@ const Sia2 = ({
|
|
|
67
69
|
onIsImageJunk = () => {},
|
|
68
70
|
onNotification = (_) => {},
|
|
69
71
|
onSelectAnnotation = (_) => {},
|
|
72
|
+
onTimeTravel = (_) => {},
|
|
70
73
|
}: SiaProps) => {
|
|
71
74
|
const marginBetweenToolbarAndContainerPixels: number = 10
|
|
72
75
|
|
|
@@ -78,6 +81,12 @@ const Sia2 = ({
|
|
|
78
81
|
const [annotations, setAnnotations] = useState<Annotation[]>([])
|
|
79
82
|
const [annotationSettings, setAnnotationSettings] = useState<AnnotationSettings>()
|
|
80
83
|
|
|
84
|
+
// tracks how far we went back in the history
|
|
85
|
+
const [annotationHistoryIndex, setAnnotationHistoryIndex] = useState<
|
|
86
|
+
number | undefined
|
|
87
|
+
>()
|
|
88
|
+
const [annotationHistory, setAnnotationHistory] = useState<Annotation[][]>([])
|
|
89
|
+
|
|
81
90
|
const [uiConfig, setUiConfig] = useState<UiConfig>()
|
|
82
91
|
|
|
83
92
|
const [selectedAnnotation, setSelectedAnnotation] = useState<Annotation>()
|
|
@@ -86,6 +95,26 @@ const Sia2 = ({
|
|
|
86
95
|
defaultAnnotationTool ?? AnnotationTool.Point,
|
|
87
96
|
)
|
|
88
97
|
|
|
98
|
+
const updateAnnotationHistory = (annotations: Annotation[]) => {
|
|
99
|
+
const _annotations = [...annotations]
|
|
100
|
+
const _annotationHistory = [...annotationHistory]
|
|
101
|
+
|
|
102
|
+
// user did some changes from within the past
|
|
103
|
+
// time to create an alternative timeline and delete the original one
|
|
104
|
+
if (annotationHistoryIndex !== undefined) {
|
|
105
|
+
// remove everything after the state the user is
|
|
106
|
+
_annotationHistory.splice(annotationHistoryIndex + 1)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// update the list with out latest change (it is always living in the present)
|
|
110
|
+
_annotationHistory.push(_annotations)
|
|
111
|
+
|
|
112
|
+
// keep history index marker in the present
|
|
113
|
+
setAnnotationHistoryIndex(undefined)
|
|
114
|
+
|
|
115
|
+
setAnnotationHistory(_annotationHistory)
|
|
116
|
+
}
|
|
117
|
+
|
|
89
118
|
// for adjusting the container/canvas size
|
|
90
119
|
// const [toolbarHeight, setToolbarHeight] = useState<number>(-1);
|
|
91
120
|
|
|
@@ -121,6 +150,7 @@ const Sia2 = ({
|
|
|
121
150
|
|
|
122
151
|
setAnnotations(_annotations)
|
|
123
152
|
setSelectedAnnotation(undefined)
|
|
153
|
+
updateAnnotationHistory(_annotations)
|
|
124
154
|
|
|
125
155
|
// inform the outside world about our changes
|
|
126
156
|
onAnnoDeleted(removedAnno, _annotations)
|
|
@@ -159,6 +189,7 @@ const Sia2 = ({
|
|
|
159
189
|
setUsedInternalIds([...new Array(internalAnnoId).keys()])
|
|
160
190
|
|
|
161
191
|
setAnnotations(_annotations)
|
|
192
|
+
updateAnnotationHistory(_annotations)
|
|
162
193
|
}
|
|
163
194
|
|
|
164
195
|
const createNewInternalAnnotationId = (): number => {
|
|
@@ -206,11 +237,86 @@ const Sia2 = ({
|
|
|
206
237
|
onIsImageJunk(newJunkState)
|
|
207
238
|
}
|
|
208
239
|
|
|
240
|
+
const handleTraverseAnnotationHistory = (isUndo: boolean) => {
|
|
241
|
+
// undefined -> last element
|
|
242
|
+
const _annotationHistoryIndex = annotationHistoryIndex ?? annotationHistory.length - 1
|
|
243
|
+
|
|
244
|
+
const isPresent = _annotationHistoryIndex == annotationHistory.length - 1
|
|
245
|
+
const isFirst = _annotationHistoryIndex == 0
|
|
246
|
+
|
|
247
|
+
// we cannot go into the future (yet) or past the past
|
|
248
|
+
if ((isPresent && !isUndo) || (isFirst && isUndo)) return
|
|
249
|
+
|
|
250
|
+
// request time travel using state update
|
|
251
|
+
const newHistoryIndex = _annotationHistoryIndex + (isUndo ? -1 : 1)
|
|
252
|
+
setAnnotationHistoryIndex(newHistoryIndex)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const getTimeTravelInductedChanges = (
|
|
256
|
+
timeTravelledAnnotations: Annotation[],
|
|
257
|
+
): TimeTravelChanges => {
|
|
258
|
+
const addedAnnotations: Annotation[] = []
|
|
259
|
+
const removedAnnotations: Annotation[] = []
|
|
260
|
+
const changedAnnotations: Annotation[] = []
|
|
261
|
+
|
|
262
|
+
// check which items have been added or changed
|
|
263
|
+
for (const travelledAnno of timeTravelledAnnotations) {
|
|
264
|
+
const currentAnno = annotations.find(
|
|
265
|
+
(anno) => anno.internalId === travelledAnno.internalId,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
if (!currentAnno) {
|
|
269
|
+
addedAnnotations.push(travelledAnno)
|
|
270
|
+
} else if (JSON.stringify(currentAnno) !== JSON.stringify(travelledAnno)) {
|
|
271
|
+
changedAnnotations.push(travelledAnno)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// check which items have been deleted
|
|
276
|
+
for (const anno of annotations) {
|
|
277
|
+
const travelledAnno = timeTravelledAnnotations.find(
|
|
278
|
+
(tAnno) => tAnno.internalId === anno.internalId,
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
if (!travelledAnno) {
|
|
282
|
+
removedAnnotations.push(anno)
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return { addedAnnotations, removedAnnotations, changedAnnotations }
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
useEffect(() => {
|
|
290
|
+
if (
|
|
291
|
+
annotationHistoryIndex == undefined ||
|
|
292
|
+
annotationHistoryIndex < 0 ||
|
|
293
|
+
annotationHistoryIndex > annotationHistory.length - 1
|
|
294
|
+
)
|
|
295
|
+
return
|
|
296
|
+
|
|
297
|
+
// update the shown annotations when we travel in time using the history index
|
|
298
|
+
const refSelectedAnnotationsFromHistory = annotationHistory[annotationHistoryIndex]
|
|
299
|
+
const selectedAnnotationsFromHistory: Annotation[] = [
|
|
300
|
+
...refSelectedAnnotationsFromHistory,
|
|
301
|
+
]
|
|
302
|
+
|
|
303
|
+
setAnnotations(selectedAnnotationsFromHistory)
|
|
304
|
+
|
|
305
|
+
const timeTravelInductedChanges: TimeTravelChanges = getTimeTravelInductedChanges(
|
|
306
|
+
selectedAnnotationsFromHistory,
|
|
307
|
+
)
|
|
308
|
+
onTimeTravel(timeTravelInductedChanges)
|
|
309
|
+
}, [annotationHistoryIndex])
|
|
310
|
+
|
|
209
311
|
useEffect(() => {
|
|
210
312
|
// remove current annotations when the image changes
|
|
211
313
|
if (image === undefined) {
|
|
212
314
|
setAnnotations([])
|
|
213
315
|
setSelectedAnnotation(undefined)
|
|
316
|
+
|
|
317
|
+
// reset time machine
|
|
318
|
+
setAnnotationHistory([])
|
|
319
|
+
setAnnotationHistoryIndex(undefined)
|
|
214
320
|
}
|
|
215
321
|
}, [image])
|
|
216
322
|
|
|
@@ -397,6 +503,7 @@ const Sia2 = ({
|
|
|
397
503
|
setAnnotations(_annotations)
|
|
398
504
|
setSelectedAnnotation(annotation)
|
|
399
505
|
onAnnoCreated(annotation, _annotations)
|
|
506
|
+
// dont update history here - we dont have a finished anno at this point
|
|
400
507
|
}}
|
|
401
508
|
onAnnoChanged={(changedAnno: Annotation) => {
|
|
402
509
|
// update annotation list
|
|
@@ -411,6 +518,11 @@ const Sia2 = ({
|
|
|
411
518
|
_annotations[annoListIndex] = changedAnno
|
|
412
519
|
setAnnotations(_annotations)
|
|
413
520
|
|
|
521
|
+
// only update history for full/finished annotations
|
|
522
|
+
if (changedAnno.status !== AnnotationStatus.CREATING) {
|
|
523
|
+
updateAnnotationHistory(_annotations)
|
|
524
|
+
}
|
|
525
|
+
|
|
414
526
|
// inform the outside world about our change
|
|
415
527
|
onAnnoChanged(changedAnno, _annotations)
|
|
416
528
|
}}
|
|
@@ -445,6 +557,8 @@ const Sia2 = ({
|
|
|
445
557
|
}
|
|
446
558
|
}
|
|
447
559
|
}
|
|
560
|
+
// mark annotation as fully created before storing it
|
|
561
|
+
changedAnno.status = AnnotationStatus.CREATED
|
|
448
562
|
|
|
449
563
|
// are we just marking an existing annotation as finished or did we created it in the same frame
|
|
450
564
|
if (hasAnnoJustBeenCreated) _annotations.push(changedAnno)
|
|
@@ -457,9 +571,7 @@ const Sia2 = ({
|
|
|
457
571
|
}
|
|
458
572
|
|
|
459
573
|
setAnnotations(_annotations)
|
|
460
|
-
|
|
461
|
-
// mark annotation as fully created
|
|
462
|
-
changedAnno.status = AnnotationStatus.CREATED
|
|
574
|
+
updateAnnotationHistory(_annotations)
|
|
463
575
|
|
|
464
576
|
// inform the outer world about our changes
|
|
465
577
|
onAnnoCreationFinished(changedAnno, _annotations)
|
|
@@ -474,6 +586,7 @@ const Sia2 = ({
|
|
|
474
586
|
}}
|
|
475
587
|
onSetSelectedTool={setSelectedAnnoTool}
|
|
476
588
|
onShouldDeleteAnno={deleteAnnotationByInternalId}
|
|
589
|
+
onTraverseAnnotationHistory={handleTraverseAnnotationHistory}
|
|
477
590
|
/>
|
|
478
591
|
)}
|
|
479
592
|
</div>
|
|
@@ -481,4 +594,4 @@ const Sia2 = ({
|
|
|
481
594
|
)
|
|
482
595
|
}
|
|
483
596
|
|
|
484
|
-
export default
|
|
597
|
+
export default Sia
|