lost-sia 3.1.3 → 3.2.0-alpha0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Canvas/Canvas.js +1 -1
- package/dist/SiaViewer.d.ts +19 -0
- package/dist/SiaViewer.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -1
- package/dist/stories/Canvas/Canvas.stories.d.ts +3 -3
- package/dist/stories/Canvas/CanvasWithOffset.stories.d.ts +6 -6
- package/dist/stories/SIA/SiaViewer.stories.d.ts +52 -0
- package/package.json +3 -4
- package/src/Canvas/Canvas.tsx +5 -1
- package/src/SiaViewer.tsx +125 -0
- package/src/Toolbar/ToolbarItems/ImageToolItems/ImageLabelInput.tsx +1 -2
- package/src/index.ts +1 -1
- package/src/stories/SIA/SiaViewer.stories.tsx +105 -0
- package/src/stories/exampleData/exampleExternalAnnotations.ts +22 -22
- package/dist/assets/brand-icons-Cu_C0hZ4.svg +0 -1008
- package/dist/assets/brand-icons-F3SPCeH1.woff +0 -0
- package/dist/assets/brand-icons-XL9sxUpA.woff2 +0 -0
- package/dist/assets/brand-icons-sqJ2Pg7a.eot +0 -0
- package/dist/assets/brand-icons-ubhWoxly.ttf +0 -0
- package/dist/assets/flags-DOLqOU7Y.png +0 -0
- package/dist/assets/icons-BOCtAERH.woff +0 -0
- package/dist/assets/icons-CHzK1VD9.eot +0 -0
- package/dist/assets/icons-D29ZQHHw.ttf +0 -0
- package/dist/assets/icons-Du6TOHnR.woff2 +0 -0
- package/dist/assets/icons-RwhydX30.svg +0 -1518
- package/dist/assets/node_modules/semantic-ui-css/semantic.min-09YPtVE6.css +0 -1
- package/dist/assets/outline-icons-BfdLr8tr.svg +0 -366
- package/dist/assets/outline-icons-DD8jm0uy.ttf +0 -0
- package/dist/assets/outline-icons-DInHoiqI.woff2 +0 -0
- package/dist/assets/outline-icons-LX8adJ4n.eot +0 -0
- package/dist/assets/outline-icons-aQ88nltS.woff +0 -0
- package/src/AnnoExampleViewer.jsx +0 -69
- package/src/InfoBoxes/AnnoDetails.jsx +0 -165
- package/src/InfoBoxes/AnnoStats.jsx +0 -106
- package/src/InfoBoxes/InfoBox.jsx +0 -76
- package/src/InfoBoxes/InfoBoxArea.jsx +0 -152
- package/src/InfoBoxes/LabelInfo.jsx +0 -107
- package/src/SIASettingButton.jsx +0 -126
package/dist/Canvas/Canvas.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{jsxs as
|
|
1
|
+
import{jsxs as F,jsx as p,Fragment as qe}from"react/jsx-runtime";import{useState as g,useRef as Z,useEffect as v}from"react";import O from"../models/AnnotationTool.js";import r from"../models/EditorModes.js";import et from"../utils/KeyMapper.js";import a from"../models/KeyAction.js";import xe from"../Annotation/logic/Annotation.js";import tt from"../models/CanvasAction.js";import nt from"../Annotation/ui/AnnotationComponent.js";import Ce from"../utils/mouse.js";import N from"../models/AnnotationMode.js";import ot from"./LabelInput.js";import{FontAwesomeIcon as st}from"@fortawesome/react-fontawesome";import{faBan as rt}from"@fortawesome/free-solid-svg-icons";import b from"../models/AnnotationStatus.js";import A from"../utils/transform.js";import it from"../models/NotificationType.js";import at from"../utils/TimeUtils.js";import he from"../utils/windowViewport.js";const Dt=({annotations:h=[],annotationSettings:w,defaultLabelId:q,image:X,isFullscreen:ve=!1,isImageJunk:U=!1,isPolygonSelectionMode:Y=!1,polygonOperationResult:z={annotationsToDelete:[],polygonsToCreate:[]},possibleLabels:ee,preventScrolling:te=!0,selectedAnnotation:i,selectedAnnoTool:R,toolbarHeight:G=0,uiConfig:V,onAnnoCreated:Ie,onAnnoCreationFinished:ne,onAnnoChanged:oe,onAnnoEditing:Te=c=>{},onNotification:se=c=>{},onRequestNewAnnoId:P,onSelectAnnotation:I,onSetIsImageJunk:Me,onSetSelectedTool:Oe=c=>{},onShouldDeleteAnno:re,onTraverseAnnotationHistory:ie})=>{const[c,y]=g(r.VIEW),[be,we]=g(),[ae,De]=g(q),[k,Se]=g({x:-1,y:-1}),[ce,le]=g(0),L={x:k.x,y:k.y},[u,de]=g({x:-1,y:-1}),[d,H]=g({x:-1,y:-1}),[m,fe]=g({x:-1,y:-1}),[f,W]=g(1),[l,D]=g({x:0,y:0}),$={x:l.x+ce,y:l.y},[j,B]=g(),[J,S]=g(!1),ue=Z(null),x=Z(null),T=Z(null),C=((e,t)=>{if(e.x===0||e.y===0||t.x===0||t.y===0)return 0;const n=t.x/e.x,o=t.y/e.y;return Math.min(n,o)})(u,d),_e=()=>{if(T?.current===null)return{x:0,y:0};const e=u.x*C;if(V.imageCentered&&d.x>e){const E=(d.x-e)/2;le(E)}else le(0);const{top:t,left:n}=x.current.getBoundingClientRect(),o={x:n+window.scrollX,y:t+window.scrollY};Se(o)},Ne=new et(e=>Be(e)),Re=e=>{y(r.CREATE);const t=A.convertStageCoordinatesToPercentaged([e],C,u);R===O.BBox&&t.push(t[0]);const n=P(),o=new xe(n,R,t);if(we(performance.now()),ae!==void 0&&(o.labelIds=[ae]),Ie(o),R===O.Point){const s={...o,coordinates:[e],annoTime:0};Q(s)}},Ve=()=>{if(i&&![O.Line,O.Polygon].includes(i.type))return;const e=h.find(n=>n.internalId===i?.internalId);if(e===void 0)return;y(r.CREATE),Oe(e.type);const t={...e,mode:N.CREATE,status:b.CREATING,internalId:P(),selectedNode:e.coordinates.length-1};Te(t)},Pe=()=>{const e=i?i.internalId:0,t=h.find(n=>n.internalId>e);if(t)return I(t);if(h.length>0)return I(h[0])},ke=()=>{const e=i?i.internalId:0,t=[...h];t.sort((o,s)=>s.internalId-o.internalId);const n=t.find(o=>o.internalId<e);if(n)return I(n);if(h.length>0)return I(h.at(-1))},Le=()=>{if(i){const e=JSON.stringify(i);localStorage.setItem("lostAnnotationClipboard",e);const t={title:"Success",message:"Annotation copied",type:it.SUCCESS};se(t)}},We=()=>{const e=localStorage.getItem("lostAnnotationClipboard");if(e==null)return;const t=JSON.parse(e);t.internalId=P(),t.externalId="",ne(t,!0),I(t)},Ee=e=>{const t=A.getMostLeftPoints(e),n=A.getTopPoint(t)[0];return A.convertStageToPage(n,L,f,l)},Be=e=>{switch(e){case a.EDIT_LABEL:if(i){const t=A.convertPercentagedCoordinatesToStage(i.coordinates,u,m);B(Ee(t)),S(!0)}break;case a.DELETE_ANNO:i&&re(i.internalId);break;case a.DELETE_ANNO_IN_CREATION:c===r.CREATE&&(re(i.internalId),y(r.VIEW));break;case a.ENTER_ANNO_ADD_MODE:console.log("KeyAction TODO: ENTER_ANNO_ADD_MODE");break;case a.LEAVE_ANNO_ADD_MODE:console.log("KeyAction TODO: LEAVE_ANNO_ADD_MODE");break;case a.UNDO:ie(!0);break;case a.REDO:ie(!1);break;case a.TRAVERSE_ANNOS:Pe();break;case a.TRAVERSE_ANNOS_BACKWARDS:ke();break;case a.CAM_MOVE_LEFT:_(20*f,0);break;case a.CAM_MOVE_RIGHT:_(-20*f,0);break;case a.CAM_MOVE_UP:_(0,20*f);break;case a.CAM_MOVE_DOWN:_(0,-20*f);break;case a.CAM_MOVE_STOP:console.log("KeyAction TODO: CAM_MOVE_STOP");break;case a.COPY_ANNOTATION:Le();break;case a.PASTE_ANNOTATION:We();break;case a.RECREATE_ANNO:console.log("KeyAction TODO: RECREATE_ANNO"),Ve();break;case a.TOGGLE_IMAGE_JUNK:if(c===r.ADD||c===r.CREATE)return;Me(!U);break;default:console.log("Unknown KeyAction",e);break}},_=(e,t)=>{let n=l.x+e/f,o=l.y+t/f;const s=d.x*.45,E=d.x*.55,M=d.y*.45,Qe=d.y*.55,Ze={x:0,y:0},ye=he.getViewportCoordinates(l,d,f,Ze),Ae=he.getViewportCoordinates(l,d,f,d);ye.vX>=s?n=l.x-5:Ae.vX<=E?n=l.x+5:ye.vY>=M?o=l.y-5:Ae.vY<=Qe&&(o=l.y+5),D({x:n,y:o})},K=(e=>m.x<=0||m.y<=0||u.x<=0||u.y<=0?[]:h.map(n=>({...n,coordinates:A.convertPercentagedCoordinatesToStage(n.coordinates,u,m)})))(),Ke=()=>{if(y(r.VIEW),fe({x:-1,y:-1}),T.current!==null){const{width:e,height:t}=T.current.getBoundingClientRect();de({x:e,y:t})}W(1),D({x:0,y:0}),B(void 0),S(!1)};v(()=>{ue.current?.focus()},[]),v(()=>{const e=t=>{t.button===1&&c===r.CAMERA_MOVE&&y(r.VIEW)};return window.addEventListener("mouseup",e),()=>window.removeEventListener("mouseup",e)},[c]),v(()=>{if(x?.current!==void 0){Ke();const{width:e,height:t}=x.current.getBoundingClientRect(),n=t-G;H({x:e,y:n});const o=new ResizeObserver(()=>{const{width:s,height:E}=x.current.getBoundingClientRect(),M=E-G;H({x:s,y:M})});return o.observe(x.current),()=>o.disconnect()}},[X,ve]),v(()=>{_e()},[d,u,V]),v(()=>{if(x.current===null)return;const{width:e,height:t}=x.current.getBoundingClientRect(),n=t-G;H({x:e,y:n})},[x]),v(()=>{if(T.current===null)return;const{width:e,height:t}=T.current.getBoundingClientRect();de({x:e,y:t})},[X,d]),v(()=>{if(C===0)return;const e={x:u.x*C,y:u.y*C};fe(e)},[C,u]),v(()=>{Y&&z.polygonsToCreate!==void 0&&z.polygonsToCreate.forEach(e=>{const t=P(),n=new xe(t,e.type,A.convertPercentagedCoordinatesToStage(e.coordinates,u,m),N.VIEW,b.CREATED);e.labelIds!==void 0&&(n.labelIds=e.labelIds),Q(n)})},[z]);const Q=e=>{y(r.VIEW);const t={...e,mode:N.VIEW};if(e.type!==O.Point){const s=at.getRoundedDuration(be,performance.now());t.annoTime=s}const n=A.convertStageCoordinatesToPercentaged(e.coordinates,C,u);t.coordinates=n,oe(t);const o=R===O.Point||Y;ne(t,o)},Fe=e=>{Ne.keyDown(e.key,e.shiftKey,e.ctrlKey)&&e.preventDefault()},Xe=e=>{e.preventDefault()},Ue=e=>{if(e.button!==0){if(e.button===1){if(c===r.CREATE||c===r.ADD){e.preventDefault();return}y(r.CAMERA_MOVE)}else if(e.button===2){if(!w.canCreate||c===r.ADD||c===r.CREATE)return;const t=Ce.getAntiScaledMouseStagePosition(e,L,f,l),n={x:t.x-ce,y:t.y};Re(n)}}},Ye=()=>{te&&(document.body.style.overflow="hidden")},ze=e=>{e.button===1&&c===r.CAMERA_MOVE&&y(r.VIEW)},ge=(e,t)=>{c===r.CAMERA_MOVE&&_(e,t)},Ge=()=>{te&&(document.body.style.overflow="")},He=e=>{const o=(e.deltaY<0?1:-1)>0?f*1.25:f/1.25,s=Ce.getAntiScaledMouseStagePosition(e,L,f,l),E=f/o,M={x:E*(s.x+l.x)-s.x,y:E*(s.y+l.y)-s.y};o<1?(W(1),(l.x!=0||l.y!=0)&&D({x:0,y:0})):o>200?(W(200),D(M)):(W(o),D(M))},$e=(e,t)=>{if(t!==tt.ANNO_SELECTED){console.log("Unknown Canvas Action:",t);return}const n={...e,coordinates:A.convertStageCoordinatesToPercentaged([...e.coordinates],C,u)};I(n),w.canLabel&&B(Ee(e.coordinates))},me=e=>{const t=A.convertStageCoordinatesToPercentaged(e.coordinates,C,u),n={...e,coordinates:t};n.status===b.LOADED&&(n.status=b.CHANGED),oe(n)},je=()=>{if(c===r.CAMERA_MOVE)return p(qe,{});const t=[r.CREATE,r.ADD,r.MOVE].includes(c),n=K.map(o=>{const s=o.internalId===i?.internalId;return t&&!s?p("g",{},`annotationComponent_${o.internalId}`):p(nt,{scaledAnnotation:o,annotationSettings:w,possibleLabels:ee,svgScale:f,svgTranslation:$,pageToStageOffset:L,nodeRadius:V.nodeRadius,strokeWidth:V.strokeWidth,isSelected:s,isDisabled:Y&&s,onFinishAnnoCreate:Q,onLabelIconClicked:()=>S(!0),onAction:$e,onAnnoChanged:me,onAnnotationModeChange:E=>{E===N.MOVE&&y(r.MOVE),c===r.MOVE&&E===N.VIEW&&y(r.VIEW)},onNotification:se},`annotationComponent_${o.internalId}`)});if(i){const o=K.find(E=>E.internalId===i?.internalId),s=K.indexOf(o);n.push(n.splice(s,1)[0])}return p("g",{children:n})},Je=()=>p("circle",{cx:m.x/2,cy:m.y/2,r:"100%",style:{opacity:0},onContextMenu:e=>e.preventDefault(),onClick:()=>{S(!1)}}),pe={x:k.x+d.x/2,y:k.y+d.y/2};return F("div",{ref:x,style:{flex:"1 1 auto",minHeight:0,display:"flex",flexDirection:"column"},children:[w.canLabel&&p("div",{style:{position:"absolute",left:j?.x??0,top:j?.y??0,display:j?.y===void 0?"none":"inherit",zIndex:J?7e3:-1},children:p(ot,{defaultLabelId:q,isVisible:J,selectedLabelsIds:i?.labelIds,possibleLabels:ee,isMultilabel:w.canHaveMultipleLabels,onLabelSelect:e=>{if(setTimeout(()=>S(!1),0),e.length>0){const s=e.filter(E=>!i.labelIds.includes(E));s.length>0&&De(s[0])}const t=K.find(s=>s.internalId===i.internalId);if(!t)return;const n=t.status===b.LOADED?b.CHANGED:t.status,o={...i,coordinates:t.coordinates,labelIds:[...e],status:n};me(o)}})}),U&&F("div",{style:{position:"absolute",left:pe.x,top:pe.y,transform:"translate(-50%, -50%)",textAlign:"center",color:"white"},children:[p(st,{icon:rt,size:"5x",style:{marginBottom:15}}),p("h2",{children:"Marked as Junk"})]}),F("svg",{ref:ue,style:{flex:"1 1 auto",minHeight:0},onKeyDown:Fe,onKeyUp:Xe,onMouseMove:e=>ge(e.movementX,e.movementY),tabIndex:0,onMouseDown:e=>Ue(e),children:[F("g",{transform:`scale(${f}) translate(${$.x}, ${$.y})`,onMouseOver:Ye,onMouseLeave:Ge,onMouseUp:ze,onWheel:He,onMouseMove:e=>ge(e.movementX,e.movementY),onClick:()=>{I(void 0)},children:[p("image",{onContextMenu:e=>e.preventDefault(),href:X,ref:T,width:m.x>0?m.x:void 0,height:m.y>0?m.y:void 0}),je()]}),J&&Je(),U&&p("rect",{x:"0",y:"0",width:d.x,height:d.y,style:{opacity:.8},onContextMenu:e=>e.preventDefault(),onClick:()=>{B(void 0)}})]})]})};export{Dt as default};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { default as Annotation } from './Annotation/logic/Annotation';
|
|
2
|
+
import { ExternalAnnotation, Label, UiConfig } from './types';
|
|
3
|
+
type SiaViewerProps = {
|
|
4
|
+
image: string;
|
|
5
|
+
annotations?: ExternalAnnotation[];
|
|
6
|
+
possibleLabels: Label[];
|
|
7
|
+
isJunk?: boolean;
|
|
8
|
+
uiConfig?: Partial<UiConfig>;
|
|
9
|
+
enableZoom?: boolean;
|
|
10
|
+
canSelect?: boolean;
|
|
11
|
+
onSelectAnnotation?: (annotation?: Annotation) => void;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* View-only SIA component. Renders image + annotations without a toolbar and
|
|
15
|
+
* blocks all mutations (create / edit / delete / label). Zoom and selection
|
|
16
|
+
* can be toggled via props.
|
|
17
|
+
*/
|
|
18
|
+
declare const SiaViewer: ({ image, annotations: propAnnotations, possibleLabels, isJunk, uiConfig: propUiConfig, enableZoom, canSelect, onSelectAnnotation, }: SiaViewerProps) => import("react/jsx-runtime").JSX.Element;
|
|
19
|
+
export default SiaViewer;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{jsx as r}from"react/jsx-runtime";import{useState as s,useEffect as c}from"react";import p from"./Canvas/Canvas.js";import N from"./models/AnnotationMode.js";import h from"./models/AnnotationTool.js";const E={canCreate:!1,canEdit:!1,canLabel:!1,canHaveMultipleLabels:!1},f={nodeRadius:4,strokeWidth:4,imageCentered:!1},y={flex:"1 1 auto",minHeight:0,display:"flex",flexDirection:"column"},H=({image:n,annotations:e,possibleLabels:u,isJunk:m=!1,uiConfig:a,enableZoom:A=!0,canSelect:l=!0,onSelectAnnotation:I=()=>{}})=>{const[S,d]=s([]),[T,t]=s(),[C,g]=s(f);return c(()=>{if(n===void 0||e===void 0){d([]),t(void 0);return}let o=0;const v=e.map(i=>({...i,internalId:o++,mode:N.VIEW,selectedNode:1,status:i.status,annoTime:i.annoTime??0}));d(v),t(void 0)},[e,n]),c(()=>{g({...f,...a})},[a]),r("div",{style:y,onWheelCapture:o=>{A||o.stopPropagation()},children:r(p,{annotations:S,annotationSettings:E,image:n,isImageJunk:m,isPolygonSelectionMode:!1,possibleLabels:u,selectedAnnotation:l?T:void 0,selectedAnnoTool:h.Point,uiConfig:C,onAnnoCreated:()=>{},onAnnoChanged:()=>{},onAnnoCreationFinished:()=>{},onAnnoEditing:()=>{},onNotification:()=>{},onRequestNewAnnoId:()=>0,onSelectAnnotation:l?o=>{t(o),I(o)}:()=>{},onSetIsImageJunk:()=>{},onSetSelectedTool:()=>{},onShouldDeleteAnno:()=>{},onTraverseAnnotationHistory:()=>{}})})};export{H as default};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from './types';
|
|
2
2
|
export { default as IconButton } from './IconButton';
|
|
3
3
|
export { default as Sia } from './Sia';
|
|
4
|
+
export { default as SiaViewer } from './SiaViewer';
|
|
4
5
|
export { default as transform } from './utils/transform';
|
|
5
6
|
export { default as TagLabel } from './Toolbar/ToolbarItems/ImageToolItems/TagLabel';
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
/* empty css
|
|
1
|
+
/* empty css */import{default as t}from"./IconButton.js";import{default as f}from"./Sia.js";import{default as l}from"./SiaViewer.js";import{default as s}from"./utils/transform.js";import{default as d}from"./Toolbar/ToolbarItems/ImageToolItems/TagLabel.js";export{t as IconButton,f as Sia,l as SiaViewer,d as TagLabel,s as transform};
|
|
@@ -2,9 +2,9 @@ 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: any
|
|
6
|
-
onKeyDown: any
|
|
7
|
-
onKeyUp: any
|
|
5
|
+
onAnnoEvent: import('@vitest/spy').Mock<(...args: any[]) => any>;
|
|
6
|
+
onKeyDown: import('@vitest/spy').Mock<(...args: any[]) => any>;
|
|
7
|
+
onKeyUp: import('@vitest/spy').Mock<(...args: any[]) => any>;
|
|
8
8
|
};
|
|
9
9
|
declare const meta: {
|
|
10
10
|
title: 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: any
|
|
6
|
-
onKeyDown: any
|
|
7
|
-
onKeyUp: any
|
|
5
|
+
onAnnoEvent: import('@vitest/spy').Mock<(...args: any[]) => any>;
|
|
6
|
+
onKeyDown: import('@vitest/spy').Mock<(...args: any[]) => any>;
|
|
7
|
+
onKeyUp: import('@vitest/spy').Mock<(...args: any[]) => 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: any
|
|
26
|
-
onKeyDown: any
|
|
27
|
-
onKeyUp: any
|
|
25
|
+
onAnnoEvent: import('@vitest/spy').Mock<(...args: any[]) => any>;
|
|
26
|
+
onKeyDown: import('@vitest/spy').Mock<(...args: any[]) => any>;
|
|
27
|
+
onKeyUp: import('@vitest/spy').Mock<(...args: any[]) => any>;
|
|
28
28
|
};
|
|
29
29
|
};
|
|
30
30
|
export default meta;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { StoryObj } from '@storybook/react';
|
|
2
|
+
export declare const ActionsData: {};
|
|
3
|
+
declare const meta: {
|
|
4
|
+
title: string;
|
|
5
|
+
component: ({ image, annotations: propAnnotations, possibleLabels, isJunk, uiConfig: propUiConfig, enableZoom, canSelect, onSelectAnnotation, }: {
|
|
6
|
+
image: string;
|
|
7
|
+
annotations?: import('../..').ExternalAnnotation[];
|
|
8
|
+
possibleLabels: import('../..').Label[];
|
|
9
|
+
isJunk?: boolean;
|
|
10
|
+
uiConfig?: Partial<import('../..').UiConfig>;
|
|
11
|
+
enableZoom?: boolean;
|
|
12
|
+
canSelect?: boolean;
|
|
13
|
+
onSelectAnnotation?: (annotation?: import('../../models').Annotation) => void;
|
|
14
|
+
}) => import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
parameters: {
|
|
16
|
+
layout: string;
|
|
17
|
+
};
|
|
18
|
+
tags: string[];
|
|
19
|
+
excludeStories: RegExp;
|
|
20
|
+
args: {};
|
|
21
|
+
};
|
|
22
|
+
export default meta;
|
|
23
|
+
type Story = StoryObj<typeof meta>;
|
|
24
|
+
/**
|
|
25
|
+
* SiaViewer with point annotations. No toolbar, no editing.
|
|
26
|
+
* Try clicking annotations to select them and scrolling to zoom.
|
|
27
|
+
*/
|
|
28
|
+
export declare const WithPointAnnotations: Story;
|
|
29
|
+
/**
|
|
30
|
+
* SiaViewer with bbox annotations.
|
|
31
|
+
*/
|
|
32
|
+
export declare const WithBBoxAnnotations: Story;
|
|
33
|
+
/**
|
|
34
|
+
* SiaViewer with line annotations.
|
|
35
|
+
*/
|
|
36
|
+
export declare const WithLineAnnotations: Story;
|
|
37
|
+
/**
|
|
38
|
+
* SiaViewer with polygon annotations.
|
|
39
|
+
*/
|
|
40
|
+
export declare const WithPolygonAnnotations: Story;
|
|
41
|
+
/**
|
|
42
|
+
* SiaViewer with zoom disabled.
|
|
43
|
+
*/
|
|
44
|
+
export declare const ZoomDisabled: Story;
|
|
45
|
+
/**
|
|
46
|
+
* SiaViewer with selection disabled. Annotations cannot be clicked.
|
|
47
|
+
*/
|
|
48
|
+
export declare const SelectionDisabled: Story;
|
|
49
|
+
/**
|
|
50
|
+
* SiaViewer marked as junk.
|
|
51
|
+
*/
|
|
52
|
+
export declare const Junk: Story;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lost-sia",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0-alpha0",
|
|
4
4
|
"description": "Single Image Annotation Tool",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "l3p-cv/lost-sia",
|
|
@@ -63,9 +63,7 @@
|
|
|
63
63
|
"react": "^19.2.1",
|
|
64
64
|
"react-dom": "^19.2.1",
|
|
65
65
|
"react-draggable": "^4.5.0",
|
|
66
|
-
"sass": "^1.95.0"
|
|
67
|
-
"semantic-ui-css": "2.5.0",
|
|
68
|
-
"semantic-ui-react": "^2.0.3"
|
|
66
|
+
"sass": "^1.95.0"
|
|
69
67
|
},
|
|
70
68
|
"peerDependencies": {
|
|
71
69
|
"@coreui/react": "^5.9.1",
|
|
@@ -81,6 +79,7 @@
|
|
|
81
79
|
"@storybook/addon-links": "^10.1.6",
|
|
82
80
|
"@storybook/react": "^10.1.6",
|
|
83
81
|
"@storybook/react-vite": "^10.1.6",
|
|
82
|
+
"@storybook/test": "^8.6.15",
|
|
84
83
|
"@types/react": "^19.2.7",
|
|
85
84
|
"@typescript-eslint/eslint-plugin": "^8.46.1",
|
|
86
85
|
"@typescript-eslint/parser": "^8.46.1",
|
package/src/Canvas/Canvas.tsx
CHANGED
|
@@ -780,7 +780,9 @@ const Canvas = ({
|
|
|
780
780
|
onSelectAnnotation(percentagedAnnotation)
|
|
781
781
|
|
|
782
782
|
// get top left point of annotation
|
|
783
|
-
|
|
783
|
+
if (annotationSettings.canLabel) {
|
|
784
|
+
setLabelInputPosition(getAnnoTopLeftPagePosition(annotation.coordinates))
|
|
785
|
+
}
|
|
784
786
|
}
|
|
785
787
|
|
|
786
788
|
const handleOnAnnoChanged = (annotation: Annotation) => {
|
|
@@ -919,6 +921,7 @@ const Canvas = ({
|
|
|
919
921
|
flexDirection: 'column',
|
|
920
922
|
}}
|
|
921
923
|
>
|
|
924
|
+
{annotationSettings.canLabel && (
|
|
922
925
|
<div
|
|
923
926
|
style={{
|
|
924
927
|
position: 'absolute',
|
|
@@ -973,6 +976,7 @@ const Canvas = ({
|
|
|
973
976
|
}}
|
|
974
977
|
/>
|
|
975
978
|
</div>
|
|
979
|
+
)}
|
|
976
980
|
|
|
977
981
|
{isImageJunk && (
|
|
978
982
|
<div
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { CSSProperties, useEffect, useState, WheelEvent } from 'react'
|
|
2
|
+
import Canvas from './Canvas/Canvas'
|
|
3
|
+
import Annotation from './Annotation/logic/Annotation'
|
|
4
|
+
import AnnotationMode from './models/AnnotationMode'
|
|
5
|
+
import AnnotationTool from './models/AnnotationTool'
|
|
6
|
+
import { AnnotationSettings, ExternalAnnotation, Label, UiConfig } from './types'
|
|
7
|
+
|
|
8
|
+
type SiaViewerProps = {
|
|
9
|
+
image: string
|
|
10
|
+
annotations?: ExternalAnnotation[]
|
|
11
|
+
possibleLabels: Label[]
|
|
12
|
+
isJunk?: boolean
|
|
13
|
+
uiConfig?: Partial<UiConfig>
|
|
14
|
+
enableZoom?: boolean
|
|
15
|
+
canSelect?: boolean
|
|
16
|
+
onSelectAnnotation?: (annotation?: Annotation) => void
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const VIEW_ANNOTATION_SETTINGS: AnnotationSettings = {
|
|
20
|
+
canCreate: false,
|
|
21
|
+
canEdit: false,
|
|
22
|
+
canLabel: false,
|
|
23
|
+
canHaveMultipleLabels: false,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const DEFAULT_UI_CONFIG: UiConfig = {
|
|
27
|
+
nodeRadius: 4,
|
|
28
|
+
strokeWidth: 4,
|
|
29
|
+
imageCentered: false,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const containerStyle: CSSProperties = {
|
|
33
|
+
flex: '1 1 auto',
|
|
34
|
+
minHeight: 0,
|
|
35
|
+
display: 'flex',
|
|
36
|
+
flexDirection: 'column',
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* View-only SIA component. Renders image + annotations without a toolbar and
|
|
41
|
+
* blocks all mutations (create / edit / delete / label). Zoom and selection
|
|
42
|
+
* can be toggled via props.
|
|
43
|
+
*/
|
|
44
|
+
const SiaViewer = ({
|
|
45
|
+
image,
|
|
46
|
+
annotations: propAnnotations,
|
|
47
|
+
possibleLabels,
|
|
48
|
+
isJunk = false,
|
|
49
|
+
uiConfig: propUiConfig,
|
|
50
|
+
enableZoom = true,
|
|
51
|
+
canSelect = true,
|
|
52
|
+
onSelectAnnotation = () => {},
|
|
53
|
+
}: SiaViewerProps) => {
|
|
54
|
+
const [annotations, setAnnotations] = useState<Annotation[]>([])
|
|
55
|
+
const [selectedAnnotation, setSelectedAnnotation] = useState<
|
|
56
|
+
Annotation | undefined
|
|
57
|
+
>()
|
|
58
|
+
const [uiConfig, setUiConfig] = useState<UiConfig>(DEFAULT_UI_CONFIG)
|
|
59
|
+
|
|
60
|
+
// (re)initialize annotations whenever the annotations or image prop changes
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
if (image === undefined || propAnnotations === undefined) {
|
|
63
|
+
setAnnotations([])
|
|
64
|
+
setSelectedAnnotation(undefined)
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let internalId = 0
|
|
69
|
+
const next: Annotation[] = propAnnotations.map((externalAnno) => ({
|
|
70
|
+
...externalAnno,
|
|
71
|
+
internalId: internalId++,
|
|
72
|
+
mode: AnnotationMode.VIEW,
|
|
73
|
+
selectedNode: 1,
|
|
74
|
+
status: externalAnno.status,
|
|
75
|
+
annoTime: externalAnno.annoTime ?? 0,
|
|
76
|
+
}))
|
|
77
|
+
|
|
78
|
+
setAnnotations(next)
|
|
79
|
+
setSelectedAnnotation(undefined)
|
|
80
|
+
}, [propAnnotations, image])
|
|
81
|
+
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
setUiConfig({ ...DEFAULT_UI_CONFIG, ...propUiConfig })
|
|
84
|
+
}, [propUiConfig])
|
|
85
|
+
|
|
86
|
+
const stopZoom = (e: WheelEvent) => {
|
|
87
|
+
if (!enableZoom) e.stopPropagation()
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<div style={containerStyle} onWheelCapture={stopZoom}>
|
|
92
|
+
<Canvas
|
|
93
|
+
annotations={annotations}
|
|
94
|
+
annotationSettings={VIEW_ANNOTATION_SETTINGS}
|
|
95
|
+
image={image}
|
|
96
|
+
isImageJunk={isJunk}
|
|
97
|
+
isPolygonSelectionMode={false}
|
|
98
|
+
possibleLabels={possibleLabels}
|
|
99
|
+
selectedAnnotation={canSelect ? selectedAnnotation : undefined}
|
|
100
|
+
selectedAnnoTool={AnnotationTool.Point}
|
|
101
|
+
uiConfig={uiConfig}
|
|
102
|
+
onAnnoCreated={() => {}}
|
|
103
|
+
onAnnoChanged={() => {}}
|
|
104
|
+
onAnnoCreationFinished={() => {}}
|
|
105
|
+
onAnnoEditing={() => {}}
|
|
106
|
+
onNotification={() => {}}
|
|
107
|
+
onRequestNewAnnoId={() => 0}
|
|
108
|
+
onSelectAnnotation={
|
|
109
|
+
canSelect
|
|
110
|
+
? (annotation) => {
|
|
111
|
+
setSelectedAnnotation(annotation)
|
|
112
|
+
onSelectAnnotation(annotation)
|
|
113
|
+
}
|
|
114
|
+
: () => {}
|
|
115
|
+
}
|
|
116
|
+
onSetIsImageJunk={() => {}}
|
|
117
|
+
onSetSelectedTool={() => {}}
|
|
118
|
+
onShouldDeleteAnno={() => {}}
|
|
119
|
+
onTraverseAnnotationHistory={() => {}}
|
|
120
|
+
/>
|
|
121
|
+
</div>
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export default SiaViewer
|
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
import { Label } from '../../../types'
|
|
12
12
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
|
13
13
|
import { faTag } from '@fortawesome/free-solid-svg-icons'
|
|
14
|
-
import { IconProps } from 'semantic-ui-react'
|
|
15
14
|
import TagLabel from './TagLabel'
|
|
16
15
|
|
|
17
16
|
type ImageLabelInputProps = {
|
|
@@ -69,7 +68,7 @@ const ImageLabelInput = ({
|
|
|
69
68
|
if (!selectedLabelsIds || selectedLabelsIds.length === 0)
|
|
70
69
|
return (
|
|
71
70
|
<div style={{ marginTop: 6 }}>
|
|
72
|
-
<FontAwesomeIcon icon={faTag
|
|
71
|
+
<FontAwesomeIcon icon={faTag} />
|
|
73
72
|
</div>
|
|
74
73
|
)
|
|
75
74
|
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import 'semantic-ui-css/semantic.min.css'
|
|
2
1
|
import './SIA.scss'
|
|
3
2
|
|
|
4
3
|
// export all type definitions
|
|
@@ -6,5 +5,6 @@ export * from './types'
|
|
|
6
5
|
|
|
7
6
|
export { default as IconButton } from './IconButton'
|
|
8
7
|
export { default as Sia } from './Sia'
|
|
8
|
+
export { default as SiaViewer } from './SiaViewer'
|
|
9
9
|
export { default as transform } from './utils/transform'
|
|
10
10
|
export { default as TagLabel } from './Toolbar/ToolbarItems/ImageToolItems/TagLabel'
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react'
|
|
2
|
+
|
|
3
|
+
import SiaViewer from '../../SiaViewer'
|
|
4
|
+
|
|
5
|
+
import exampleImage from '../exampleData/exampleImage'
|
|
6
|
+
import exampleLabels from '../exampleData/exampleLabels'
|
|
7
|
+
import exampleExternalAnnotations from '../exampleData/exampleExternalAnnotations'
|
|
8
|
+
|
|
9
|
+
export const ActionsData = {}
|
|
10
|
+
|
|
11
|
+
const meta = {
|
|
12
|
+
title: 'Components/SiaViewer',
|
|
13
|
+
component: SiaViewer,
|
|
14
|
+
parameters: {
|
|
15
|
+
layout: 'fullscreen',
|
|
16
|
+
},
|
|
17
|
+
tags: ['!autodocs'],
|
|
18
|
+
excludeStories: /.*Data$/,
|
|
19
|
+
args: {
|
|
20
|
+
...ActionsData,
|
|
21
|
+
},
|
|
22
|
+
} satisfies Meta<typeof SiaViewer>
|
|
23
|
+
|
|
24
|
+
export default meta
|
|
25
|
+
type Story = StoryObj<typeof meta>
|
|
26
|
+
|
|
27
|
+
const defaultArgs = {
|
|
28
|
+
...ActionsData,
|
|
29
|
+
image: exampleImage,
|
|
30
|
+
possibleLabels: exampleLabels.voc,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* SiaViewer with point annotations. No toolbar, no editing.
|
|
35
|
+
* Try clicking annotations to select them and scrolling to zoom.
|
|
36
|
+
*/
|
|
37
|
+
export const WithPointAnnotations: Story = {
|
|
38
|
+
args: {
|
|
39
|
+
...defaultArgs,
|
|
40
|
+
annotations: exampleExternalAnnotations.point,
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* SiaViewer with bbox annotations.
|
|
46
|
+
*/
|
|
47
|
+
export const WithBBoxAnnotations: Story = {
|
|
48
|
+
args: {
|
|
49
|
+
...defaultArgs,
|
|
50
|
+
annotations: exampleExternalAnnotations.bbox,
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* SiaViewer with line annotations.
|
|
56
|
+
*/
|
|
57
|
+
export const WithLineAnnotations: Story = {
|
|
58
|
+
args: {
|
|
59
|
+
...defaultArgs,
|
|
60
|
+
annotations: exampleExternalAnnotations.line,
|
|
61
|
+
},
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* SiaViewer with polygon annotations.
|
|
66
|
+
*/
|
|
67
|
+
export const WithPolygonAnnotations: Story = {
|
|
68
|
+
args: {
|
|
69
|
+
...defaultArgs,
|
|
70
|
+
annotations: exampleExternalAnnotations.polygon,
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* SiaViewer with zoom disabled.
|
|
76
|
+
*/
|
|
77
|
+
export const ZoomDisabled: Story = {
|
|
78
|
+
args: {
|
|
79
|
+
...defaultArgs,
|
|
80
|
+
annotations: exampleExternalAnnotations.polygon,
|
|
81
|
+
enableZoom: false,
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* SiaViewer with selection disabled. Annotations cannot be clicked.
|
|
87
|
+
*/
|
|
88
|
+
export const SelectionDisabled: Story = {
|
|
89
|
+
args: {
|
|
90
|
+
...defaultArgs,
|
|
91
|
+
annotations: exampleExternalAnnotations.polygon,
|
|
92
|
+
canSelect: false,
|
|
93
|
+
},
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* SiaViewer marked as junk.
|
|
98
|
+
*/
|
|
99
|
+
export const Junk: Story = {
|
|
100
|
+
args: {
|
|
101
|
+
...defaultArgs,
|
|
102
|
+
annotations: exampleExternalAnnotations.polygon,
|
|
103
|
+
isJunk: true,
|
|
104
|
+
},
|
|
105
|
+
}
|
|
@@ -37,10 +37,10 @@ const point: ExternalAnnotation[] = [
|
|
|
37
37
|
const line: ExternalAnnotation[] = [
|
|
38
38
|
{
|
|
39
39
|
coordinates: [
|
|
40
|
-
{ x:
|
|
41
|
-
{ x:
|
|
42
|
-
{ x:
|
|
43
|
-
{ x:
|
|
40
|
+
{ x: 0.05, y: 0.05 },
|
|
41
|
+
{ x: 0.2, y: 0.1 },
|
|
42
|
+
{ x: 0.25, y: 0.1 },
|
|
43
|
+
{ x: 0.25, y: 0.2 },
|
|
44
44
|
],
|
|
45
45
|
labelIds: [5],
|
|
46
46
|
type: AnnotationTool.Line,
|
|
@@ -48,11 +48,11 @@ const line: ExternalAnnotation[] = [
|
|
|
48
48
|
},
|
|
49
49
|
{
|
|
50
50
|
coordinates: [
|
|
51
|
-
{ x:
|
|
52
|
-
{ x:
|
|
53
|
-
{ x:
|
|
54
|
-
{ x:
|
|
55
|
-
{ x:
|
|
51
|
+
{ x: 0.26, y: 0.3 },
|
|
52
|
+
{ x: 0.35, y: 0.33 },
|
|
53
|
+
{ x: 0.36, y: 0.32 },
|
|
54
|
+
{ x: 0.37, y: 0.3 },
|
|
55
|
+
{ x: 0.27, y: 0.25 },
|
|
56
56
|
],
|
|
57
57
|
labelIds: [8, 11],
|
|
58
58
|
type: AnnotationTool.Line,
|
|
@@ -63,8 +63,8 @@ const line: ExternalAnnotation[] = [
|
|
|
63
63
|
const bbox: ExternalAnnotation[] = [
|
|
64
64
|
{
|
|
65
65
|
coordinates: [
|
|
66
|
-
{ x:
|
|
67
|
-
{ x:
|
|
66
|
+
{ x: 0.05, y: 0.05 },
|
|
67
|
+
{ x: 0.2, y: 0.2 },
|
|
68
68
|
],
|
|
69
69
|
labelIds: [5],
|
|
70
70
|
type: AnnotationTool.BBox,
|
|
@@ -72,8 +72,8 @@ const bbox: ExternalAnnotation[] = [
|
|
|
72
72
|
},
|
|
73
73
|
{
|
|
74
74
|
coordinates: [
|
|
75
|
-
{ x:
|
|
76
|
-
{ x:
|
|
75
|
+
{ x: 0.25, y: 0.1 },
|
|
76
|
+
{ x: 0.45, y: 0.15 },
|
|
77
77
|
],
|
|
78
78
|
labelIds: [8, 11],
|
|
79
79
|
type: AnnotationTool.BBox,
|
|
@@ -84,10 +84,10 @@ const bbox: ExternalAnnotation[] = [
|
|
|
84
84
|
const polygon: ExternalAnnotation[] = [
|
|
85
85
|
{
|
|
86
86
|
coordinates: [
|
|
87
|
-
{ x:
|
|
88
|
-
{ x:
|
|
89
|
-
{ x:
|
|
90
|
-
{ x:
|
|
87
|
+
{ x: 0.05, y: 0.05 },
|
|
88
|
+
{ x: 0.2, y: 0.1 },
|
|
89
|
+
{ x: 0.25, y: 0.1 },
|
|
90
|
+
{ x: 0.25, y: 0.2 },
|
|
91
91
|
],
|
|
92
92
|
labelIds: [5],
|
|
93
93
|
status: AnnotationStatus.LOADED,
|
|
@@ -95,11 +95,11 @@ const polygon: ExternalAnnotation[] = [
|
|
|
95
95
|
},
|
|
96
96
|
{
|
|
97
97
|
coordinates: [
|
|
98
|
-
{ x:
|
|
99
|
-
{ x:
|
|
100
|
-
{ x:
|
|
101
|
-
{ x:
|
|
102
|
-
{ x:
|
|
98
|
+
{ x: 0.26, y: 0.3 },
|
|
99
|
+
{ x: 0.35, y: 0.33 },
|
|
100
|
+
{ x: 0.36, y: 0.32 },
|
|
101
|
+
{ x: 0.37, y: 0.3 },
|
|
102
|
+
{ x: 0.27, y: 0.25 },
|
|
103
103
|
],
|
|
104
104
|
labelIds: [8, 11],
|
|
105
105
|
status: AnnotationStatus.LOADED,
|