lost-sia 3.1.3 → 3.2.0-alpha1

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 (42) hide show
  1. package/dist/Canvas/Canvas.js +1 -1
  2. package/dist/SiaViewer.d.ts +19 -0
  3. package/dist/SiaViewer.js +1 -0
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.js +1 -1
  6. package/dist/stories/Canvas/Canvas.stories.d.ts +3 -3
  7. package/dist/stories/Canvas/CanvasWithOffset.stories.d.ts +6 -6
  8. package/dist/stories/SIA/SiaViewer.stories.d.ts +52 -0
  9. package/dist/utils/index.d.ts +1 -0
  10. package/dist/utils/index.js +1 -1
  11. package/package.json +3 -4
  12. package/src/Canvas/Canvas.tsx +36 -1
  13. package/src/SiaViewer.tsx +125 -0
  14. package/src/Toolbar/ToolbarItems/ImageToolItems/ImageLabelInput.tsx +1 -2
  15. package/src/index.ts +1 -1
  16. package/src/stories/SIA/SiaViewer.stories.tsx +105 -0
  17. package/src/stories/exampleData/exampleExternalAnnotations.ts +22 -22
  18. package/src/utils/index.ts +1 -0
  19. package/dist/assets/brand-icons-Cu_C0hZ4.svg +0 -1008
  20. package/dist/assets/brand-icons-F3SPCeH1.woff +0 -0
  21. package/dist/assets/brand-icons-XL9sxUpA.woff2 +0 -0
  22. package/dist/assets/brand-icons-sqJ2Pg7a.eot +0 -0
  23. package/dist/assets/brand-icons-ubhWoxly.ttf +0 -0
  24. package/dist/assets/flags-DOLqOU7Y.png +0 -0
  25. package/dist/assets/icons-BOCtAERH.woff +0 -0
  26. package/dist/assets/icons-CHzK1VD9.eot +0 -0
  27. package/dist/assets/icons-D29ZQHHw.ttf +0 -0
  28. package/dist/assets/icons-Du6TOHnR.woff2 +0 -0
  29. package/dist/assets/icons-RwhydX30.svg +0 -1518
  30. package/dist/assets/node_modules/semantic-ui-css/semantic.min-09YPtVE6.css +0 -1
  31. package/dist/assets/outline-icons-BfdLr8tr.svg +0 -366
  32. package/dist/assets/outline-icons-DD8jm0uy.ttf +0 -0
  33. package/dist/assets/outline-icons-DInHoiqI.woff2 +0 -0
  34. package/dist/assets/outline-icons-LX8adJ4n.eot +0 -0
  35. package/dist/assets/outline-icons-aQ88nltS.woff +0 -0
  36. package/src/AnnoExampleViewer.jsx +0 -69
  37. package/src/InfoBoxes/AnnoDetails.jsx +0 -165
  38. package/src/InfoBoxes/AnnoStats.jsx +0 -106
  39. package/src/InfoBoxes/InfoBox.jsx +0 -76
  40. package/src/InfoBoxes/InfoBoxArea.jsx +0 -152
  41. package/src/InfoBoxes/LabelInfo.jsx +0 -107
  42. package/src/SIASettingButton.jsx +0 -126
@@ -1 +1 @@
1
- import{jsxs as K,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 _ 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 w 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:F,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:N,toolbarHeight:G=0,uiConfig:R,onAnnoCreated:Ie,onAnnoCreationFinished:ne,onAnnoChanged:oe,onAnnoEditing:Te=c=>{},onNotification:se=c=>{},onRequestNewAnnoId:V,onSelectAnnotation:I,onSetIsImageJunk:Me,onSetSelectedTool:Oe=c=>{},onShouldDeleteAnno:re,onTraverseAnnotationHistory:ie})=>{const[c,y]=g(r.VIEW),[we,be]=g(),[ae,De]=g(q),[P,Se]=g({x:-1,y:-1}),[ce,le]=g(0),k={x:P.x,y:P.y},[u,de]=g({x:-1,y:-1}),[d,H]=g({x:-1,y:-1}),[m,fe]=g({x:-1,y:-1}),[f,L]=g(1),[l,b]=g({x:0,y:0}),$={x:l.x+ce,y:l.y},[j,W]=g(),[J,D]=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(R.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);N===O.BBox&&t.push(t[0]);const n=V(),o=new xe(n,N,t);if(be(performance.now()),ae!==void 0&&(o.labelIds=[ae]),Ie(o),N===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:_.CREATE,status:w.CREATING,internalId:V(),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=V(),t.externalId="",ne(t,!0),I(t)},Ee=e=>{const t=A.getMostLeftPoints(e),n=A.getTopPoint(t)[0];return A.convertStageToPage(n,k,f,l)},Be=e=>{switch(e){case a.EDIT_LABEL:if(i){const t=A.convertPercentagedCoordinatesToStage(i.coordinates,u,m);W(Ee(t)),D(!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:S(20*f,0);break;case a.CAM_MOVE_RIGHT:S(-20*f,0);break;case a.CAM_MOVE_UP:S(0,20*f);break;case a.CAM_MOVE_DOWN:S(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}},S=(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),b({x:n,y:o})},B=(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})}L(1),b({x:0,y:0}),W(void 0),D(!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,R]),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=V(),n=new xe(t,e.type,A.convertPercentagedCoordinatesToStage(e.coordinates,u,m),_.VIEW,w.CREATED);e.labelIds!==void 0&&(n.labelIds=e.labelIds),Q(n)})},[z]);const Q=e=>{y(r.VIEW);const t={...e,mode:_.VIEW};if(e.type!==O.Point){const s=at.getRoundedDuration(we,performance.now());t.annoTime=s}const n=A.convertStageCoordinatesToPercentaged(e.coordinates,C,u);t.coordinates=n,oe(t);const o=N===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(!F.canCreate||c===r.ADD||c===r.CREATE)return;const t=Ce.getAntiScaledMouseStagePosition(e,k,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&&S(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,k,f,l),E=f/o,M={x:E*(s.x+l.x)-s.x,y:E*(s.y+l.y)-s.y};o<1?(L(1),(l.x!=0||l.y!=0)&&b({x:0,y:0})):o>200?(L(200),b(M)):(L(o),b(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(Ee(e.coordinates))},me=e=>{const t=A.convertStageCoordinatesToPercentaged(e.coordinates,C,u),n={...e,coordinates:t};n.status===w.LOADED&&(n.status=w.CHANGED),oe(n)},je=()=>{if(c===r.CAMERA_MOVE)return p(qe,{});const t=[r.CREATE,r.ADD,r.MOVE].includes(c),n=B.map(o=>{const s=o.internalId===i?.internalId;return t&&!s?p("g",{},`annotationComponent_${o.internalId}`):p(nt,{scaledAnnotation:o,annotationSettings:F,possibleLabels:ee,svgScale:f,svgTranslation:$,pageToStageOffset:k,nodeRadius:R.nodeRadius,strokeWidth:R.strokeWidth,isSelected:s,isDisabled:Y&&s,onFinishAnnoCreate:Q,onLabelIconClicked:()=>D(!0),onAction:$e,onAnnoChanged:me,onAnnotationModeChange:E=>{E===_.MOVE&&y(r.MOVE),c===r.MOVE&&E===_.VIEW&&y(r.VIEW)},onNotification:se},`annotationComponent_${o.internalId}`)});if(i){const o=B.find(E=>E.internalId===i?.internalId),s=B.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:()=>{D(!1)}}),pe={x:P.x+d.x/2,y:P.y+d.y/2};return K("div",{ref:x,style:{flex:"1 1 auto",minHeight:0,display:"flex",flexDirection:"column"},children:[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:F.canHaveMultipleLabels,onLabelSelect:e=>{if(setTimeout(()=>D(!1),0),e.length>0){const s=e.filter(E=>!i.labelIds.includes(E));s.length>0&&De(s[0])}const t=B.find(s=>s.internalId===i.internalId);if(!t)return;const n=t.status===w.LOADED?w.CHANGED:t.status,o={...i,coordinates:t.coordinates,labelIds:[...e],status:n};me(o)}})}),U&&K("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"})]}),K("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:[K("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:()=>{W(void 0)}})]})]})};export{Dt as default};
1
+ import{jsxs as U,jsx as p,Fragment as qe}from"react/jsx-runtime";import{useState as g,useRef as q,useEffect as C}from"react";import b from"../models/AnnotationTool.js";import s 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 he from"../utils/mouse.js";import R from"../models/AnnotationMode.js";import ot from"./LabelInput.js";import{FontAwesomeIcon as rt}from"@fortawesome/react-fontawesome";import{faBan as st}from"@fortawesome/free-solid-svg-icons";import w from"../models/AnnotationStatus.js";import A from"../utils/transform.js";import it from"../models/NotificationType.js";import at from"../utils/TimeUtils.js";import Ce from"../utils/windowViewport.js";const Dt=({annotations:I=[],annotationSettings:D,defaultLabelId:ee,image:T,isFullscreen:Ie=!1,isImageJunk:Y=!1,isPolygonSelectionMode:z=!1,polygonOperationResult:G={annotationsToDelete:[],polygonsToCreate:[]},possibleLabels:te,preventScrolling:ne=!0,selectedAnnotation:i,selectedAnnoTool:V,toolbarHeight:H=0,uiConfig:P,onAnnoCreated:ve,onAnnoCreationFinished:oe,onAnnoChanged:re,onAnnoEditing:Te=c=>{},onNotification:se=c=>{},onRequestNewAnnoId:k,onSelectAnnotation:v,onSetIsImageJunk:Me,onSetSelectedTool:Oe=c=>{},onShouldDeleteAnno:ie,onTraverseAnnotationHistory:ae})=>{const[c,m]=g(s.VIEW),[be,we]=g(),[ce,De]=g(ee),[L,Se]=g({x:-1,y:-1}),[le,de]=g(0),W={x:L.x,y:L.y},[f,B]=g({x:-1,y:-1}),[d,$]=g({x:-1,y:-1}),[y,ue]=g({x:-1,y:-1}),[u,K]=g(1),[l,S]=g({x:0,y:0}),j={x:l.x+le,y:l.y},[J,F]=g(),[Q,_]=g(!1),fe=q(null),x=q(null),M=q(null),h=((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)})(f,d),_e=()=>{if(M?.current===null)return{x:0,y:0};const e=f.x*h;if(P.imageCentered&&d.x>e){const E=(d.x-e)/2;de(E)}else de(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=>{m(s.CREATE);const t=A.convertStageCoordinatesToPercentaged([e],h,f);V===b.BBox&&t.push(t[0]);const n=k(),o=new xe(n,V,t);if(we(performance.now()),ce!==void 0&&(o.labelIds=[ce]),ve(o),V===b.Point){const r={...o,coordinates:[e],annoTime:0};Z(r)}},Ve=()=>{if(i&&![b.Line,b.Polygon].includes(i.type))return;const e=I.find(n=>n.internalId===i?.internalId);if(e===void 0)return;m(s.CREATE),Oe(e.type);const t={...e,mode:R.CREATE,status:w.CREATING,internalId:k(),selectedNode:e.coordinates.length-1};Te(t)},Pe=()=>{const e=i?i.internalId:0,t=I.find(n=>n.internalId>e);if(t)return v(t);if(I.length>0)return v(I[0])},ke=()=>{const e=i?i.internalId:0,t=[...I];t.sort((o,r)=>r.internalId-o.internalId);const n=t.find(o=>o.internalId<e);if(n)return v(n);if(I.length>0)return v(I.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=k(),t.externalId="",oe(t,!0),v(t)},Ee=e=>{const t=A.getMostLeftPoints(e),n=A.getTopPoint(t)[0];return A.convertStageToPage(n,W,u,l)},Be=e=>{switch(e){case a.EDIT_LABEL:if(i){const t=A.convertPercentagedCoordinatesToStage(i.coordinates,f,y);F(Ee(t)),_(!0)}break;case a.DELETE_ANNO:i&&ie(i.internalId);break;case a.DELETE_ANNO_IN_CREATION:c===s.CREATE&&(ie(i.internalId),m(s.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:ae(!0);break;case a.REDO:ae(!1);break;case a.TRAVERSE_ANNOS:Pe();break;case a.TRAVERSE_ANNOS_BACKWARDS:ke();break;case a.CAM_MOVE_LEFT:N(20*u,0);break;case a.CAM_MOVE_RIGHT:N(-20*u,0);break;case a.CAM_MOVE_UP:N(0,20*u);break;case a.CAM_MOVE_DOWN:N(0,-20*u);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===s.ADD||c===s.CREATE)return;Me(!Y);break;default:console.log("Unknown KeyAction",e);break}},N=(e,t)=>{let n=l.x+e/u,o=l.y+t/u;const r=d.x*.45,E=d.x*.55,O=d.y*.45,Qe=d.y*.55,Ze={x:0,y:0},me=Ce.getViewportCoordinates(l,d,u,Ze),Ae=Ce.getViewportCoordinates(l,d,u,d);me.vX>=r?n=l.x-5:Ae.vX<=E?n=l.x+5:me.vY>=O?o=l.y-5:Ae.vY<=Qe&&(o=l.y+5),S({x:n,y:o})},X=(e=>y.x<=0||y.y<=0||f.x<=0||f.y<=0?[]:I.map(n=>({...n,coordinates:A.convertPercentagedCoordinatesToStage(n.coordinates,f,y)})))(),Ke=()=>{if(m(s.VIEW),ue({x:-1,y:-1}),M.current!==null){const{width:e,height:t}=M.current.getBoundingClientRect();B({x:e,y:t})}K(1),S({x:0,y:0}),F(void 0),_(!1)};C(()=>{fe.current?.focus()},[]),C(()=>{const e=t=>{t.button===1&&c===s.CAMERA_MOVE&&m(s.VIEW)};return window.addEventListener("mouseup",e),()=>window.removeEventListener("mouseup",e)},[c]),C(()=>{if(x?.current!==void 0){Ke();const{width:e,height:t}=x.current.getBoundingClientRect(),n=t-H;$({x:e,y:n});const o=new ResizeObserver(()=>{const{width:r,height:E}=x.current.getBoundingClientRect(),O=E-H;$({x:r,y:O})});return o.observe(x.current),()=>o.disconnect()}},[T,Ie]),C(()=>{_e()},[d,f,P]),C(()=>{if(x.current===null)return;const{width:e,height:t}=x.current.getBoundingClientRect(),n=t-H;$({x:e,y:n})},[x]),C(()=>{if(M.current===null)return;const{width:e,height:t}=M.current.getBoundingClientRect();B({x:e,y:t})},[T,d]),C(()=>{if(!T){B({x:-1,y:-1});return}let e=!1;const t=new Image;return t.onload=()=>{if(e)return;const{naturalWidth:n,naturalHeight:o}=t;n>0&&o>0&&B({x:n,y:o})},t.src=T,()=>{e=!0,t.onload=null}},[T]),C(()=>{if(h===0)return;const e={x:f.x*h,y:f.y*h};ue(e)},[h,f]),C(()=>{z&&G.polygonsToCreate!==void 0&&G.polygonsToCreate.forEach(e=>{const t=k(),n=new xe(t,e.type,A.convertPercentagedCoordinatesToStage(e.coordinates,f,y),R.VIEW,w.CREATED);e.labelIds!==void 0&&(n.labelIds=e.labelIds),Z(n)})},[G]);const Z=e=>{m(s.VIEW);const t={...e,mode:R.VIEW};if(e.type!==b.Point){const r=at.getRoundedDuration(be,performance.now());t.annoTime=r}const n=A.convertStageCoordinatesToPercentaged(e.coordinates,h,f);t.coordinates=n,re(t);const o=V===b.Point||z;oe(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===s.CREATE||c===s.ADD){e.preventDefault();return}m(s.CAMERA_MOVE)}else if(e.button===2){if(!D.canCreate||c===s.ADD||c===s.CREATE)return;const t=he.getAntiScaledMouseStagePosition(e,W,u,l),n={x:t.x-le,y:t.y};Re(n)}}},Ye=()=>{ne&&(document.body.style.overflow="hidden")},ze=e=>{e.button===1&&c===s.CAMERA_MOVE&&m(s.VIEW)},ge=(e,t)=>{c===s.CAMERA_MOVE&&N(e,t)},Ge=()=>{ne&&(document.body.style.overflow="")},He=e=>{const o=(e.deltaY<0?1:-1)>0?u*1.25:u/1.25,r=he.getAntiScaledMouseStagePosition(e,W,u,l),E=u/o,O={x:E*(r.x+l.x)-r.x,y:E*(r.y+l.y)-r.y};o<1?(K(1),(l.x!=0||l.y!=0)&&S({x:0,y:0})):o>200?(K(200),S(O)):(K(o),S(O))},$e=(e,t)=>{if(t!==tt.ANNO_SELECTED){console.log("Unknown Canvas Action:",t);return}const n={...e,coordinates:A.convertStageCoordinatesToPercentaged([...e.coordinates],h,f)};v(n),D.canLabel&&F(Ee(e.coordinates))},ye=e=>{const t=A.convertStageCoordinatesToPercentaged(e.coordinates,h,f),n={...e,coordinates:t};n.status===w.LOADED&&(n.status=w.CHANGED),re(n)},je=()=>{if(c===s.CAMERA_MOVE)return p(qe,{});const t=[s.CREATE,s.ADD,s.MOVE].includes(c),n=X.map(o=>{const r=o.internalId===i?.internalId;return t&&!r?p("g",{},`annotationComponent_${o.internalId}`):p(nt,{scaledAnnotation:o,annotationSettings:D,possibleLabels:te,svgScale:u,svgTranslation:j,pageToStageOffset:W,nodeRadius:P.nodeRadius,strokeWidth:P.strokeWidth,isSelected:r,isDisabled:z&&r,onFinishAnnoCreate:Z,onLabelIconClicked:()=>_(!0),onAction:$e,onAnnoChanged:ye,onAnnotationModeChange:E=>{E===R.MOVE&&m(s.MOVE),c===s.MOVE&&E===R.VIEW&&m(s.VIEW)},onNotification:se},`annotationComponent_${o.internalId}`)});if(i){const o=X.find(E=>E.internalId===i?.internalId),r=X.indexOf(o);n.push(n.splice(r,1)[0])}return p("g",{children:n})},Je=()=>p("circle",{cx:y.x/2,cy:y.y/2,r:"100%",style:{opacity:0},onContextMenu:e=>e.preventDefault(),onClick:()=>{_(!1)}}),pe={x:L.x+d.x/2,y:L.y+d.y/2};return U("div",{ref:x,style:{flex:"1 1 auto",minHeight:0,display:"flex",flexDirection:"column"},children:[D.canLabel&&p("div",{style:{position:"absolute",left:J?.x??0,top:J?.y??0,display:J?.y===void 0?"none":"inherit",zIndex:Q?7e3:-1},children:p(ot,{defaultLabelId:ee,isVisible:Q,selectedLabelsIds:i?.labelIds,possibleLabels:te,isMultilabel:D.canHaveMultipleLabels,onLabelSelect:e=>{if(setTimeout(()=>_(!1),0),e.length>0){const r=e.filter(E=>!i.labelIds.includes(E));r.length>0&&De(r[0])}const t=X.find(r=>r.internalId===i.internalId);if(!t)return;const n=t.status===w.LOADED?w.CHANGED:t.status,o={...i,coordinates:t.coordinates,labelIds:[...e],status:n};ye(o)}})}),Y&&U("div",{style:{position:"absolute",left:pe.x,top:pe.y,transform:"translate(-50%, -50%)",textAlign:"center",color:"white"},children:[p(rt,{icon:st,size:"5x",style:{marginBottom:15}}),p("h2",{children:"Marked as Junk"})]}),U("svg",{ref:fe,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:[U("g",{transform:`scale(${u}) translate(${j.x}, ${j.y})`,onMouseOver:Ye,onMouseLeave:Ge,onMouseUp:ze,onWheel:He,onMouseMove:e=>ge(e.movementX,e.movementY),onClick:()=>{v(void 0)},children:[p("image",{onContextMenu:e=>e.preventDefault(),href:T,ref:M,width:y.x>0?y.x:void 0,height:y.y>0?y.y:void 0}),je()]}),Q&&Je(),Y&&p("rect",{x:"0",y:"0",width:d.x,height:d.y,style:{opacity:.8},onContextMenu:e=>e.preventDefault(),onClick:()=>{F(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 *//* empty css */import{default as e}from"./IconButton.js";import{default as m}from"./Sia.js";import{default as l}from"./utils/transform.js";import{default as u}from"./Toolbar/ToolbarItems/ImageToolItems/TagLabel.js";export{e as IconButton,m as Sia,u as TagLabel,l as transform};
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;
@@ -1,2 +1,3 @@
1
1
  export { default as TimeUtils } from './TimeUtils';
2
2
  export { uiConfig, SIA_INITIAL_UI_CONFIG } from './uiConfig';
3
+ export { getColor, getDefaultColor } from './color';
@@ -1 +1 @@
1
- import{default as e}from"./TimeUtils.js";import{SIA_INITIAL_UI_CONFIG as i,uiConfig as r}from"./uiConfig.js";export{i as SIA_INITIAL_UI_CONFIG,e as TimeUtils,r as uiConfig};
1
+ import{default as r}from"./TimeUtils.js";import{SIA_INITIAL_UI_CONFIG as f,uiConfig as I}from"./uiConfig.js";import{getColor as i,getDefaultColor as m}from"./color.js";export{f as SIA_INITIAL_UI_CONFIG,r as TimeUtils,i as getColor,m as getDefaultColor,I as uiConfig};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lost-sia",
3
- "version": "3.1.3",
3
+ "version": "3.2.0-alpha1",
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",
@@ -572,6 +572,37 @@ const Canvas = ({
572
572
  setImgSize({ x: width, y: height })
573
573
  }, [image, canvasSize])
574
574
 
575
+ // Source of truth for the image's natural size.
576
+ // The SVG <image> element above loads asynchronously, so reading
577
+ // imageRef.getBoundingClientRect() (the effect above) runs before the
578
+ // remote image has decoded and resolves to {0, 0}. That left imgSize at
579
+ // 0, which trips the getFittedImageScale guard (returns 0), so stageSize
580
+ // never got set and the <image> rendered at its natural (huge) pixel size.
581
+ // Preloading via an HTMLImageElement gives us naturalWidth/Height as soon
582
+ // as the bytes are decoded, regardless of the SVG <image> lifecycle.
583
+ useEffect(() => {
584
+ if (!image) {
585
+ setImgSize({ x: -1, y: -1 })
586
+ return
587
+ }
588
+
589
+ let cancelled = false
590
+ const probe = new Image()
591
+ probe.onload = () => {
592
+ if (cancelled) return
593
+ const { naturalWidth, naturalHeight } = probe
594
+ if (naturalWidth > 0 && naturalHeight > 0) {
595
+ setImgSize({ x: naturalWidth, y: naturalHeight })
596
+ }
597
+ }
598
+ probe.src = image
599
+
600
+ return () => {
601
+ cancelled = true
602
+ probe.onload = null
603
+ }
604
+ }, [image])
605
+
575
606
  useEffect(() => {
576
607
  if (imageToStageFactor === 0) return
577
608
 
@@ -780,7 +811,9 @@ const Canvas = ({
780
811
  onSelectAnnotation(percentagedAnnotation)
781
812
 
782
813
  // get top left point of annotation
783
- setLabelInputPosition(getAnnoTopLeftPagePosition(annotation.coordinates))
814
+ if (annotationSettings.canLabel) {
815
+ setLabelInputPosition(getAnnoTopLeftPagePosition(annotation.coordinates))
816
+ }
784
817
  }
785
818
 
786
819
  const handleOnAnnoChanged = (annotation: Annotation) => {
@@ -919,6 +952,7 @@ const Canvas = ({
919
952
  flexDirection: 'column',
920
953
  }}
921
954
  >
955
+ {annotationSettings.canLabel && (
922
956
  <div
923
957
  style={{
924
958
  position: 'absolute',
@@ -973,6 +1007,7 @@ const Canvas = ({
973
1007
  }}
974
1008
  />
975
1009
  </div>
1010
+ )}
976
1011
 
977
1012
  {isImageJunk && (
978
1013
  <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 as IconProps} />
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: 50, y: 50 },
41
- { x: 200, y: 100 },
42
- { x: 250, y: 100 },
43
- { x: 250, y: 200 },
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: 259.883, y: 300.424 },
52
- { x: 350, y: 331.5263919270834 },
53
- { x: 355, y: 320 },
54
- { x: 370, y: 300 },
55
- { x: 270, y: 250 },
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: 50, y: 50 },
67
- { x: 200, y: 200 },
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: 250, y: 100 },
76
- { x: 450, y: 150 },
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: 50, y: 50 },
88
- { x: 200, y: 100 },
89
- { x: 250, y: 100 },
90
- { x: 250, y: 200 },
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: 259.883, y: 300.424 },
99
- { x: 350, y: 331.5263919270834 },
100
- { x: 355, y: 320 },
101
- { x: 370, y: 300 },
102
- { x: 270, y: 250 },
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,
@@ -1,2 +1,3 @@
1
1
  export { default as TimeUtils } from './TimeUtils'
2
2
  export { uiConfig, SIA_INITIAL_UI_CONFIG } from './uiConfig'
3
+ export { getColor, getDefaultColor } from './color'