lost-sia 2.0.1-alpha10 → 2.0.1-alpha12

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 (33) hide show
  1. package/dist/Annotation/logic/Annotation.d.ts +1 -2
  2. package/dist/Annotation/logic/Annotation.js +1 -1
  3. package/dist/Annotation/ui/AnnotationComponent.js +1 -1
  4. package/dist/Canvas/Canvas.js +1 -1
  5. package/dist/Sia2.js +1 -1
  6. package/dist/Toolbar/Toolbar.js +1 -1
  7. package/dist/Toolbar/ToolbarItems/ImageToolItems/ImageLabelInput.d.ts +12 -0
  8. package/dist/Toolbar/ToolbarItems/ImageToolItems/ImageLabelInput.js +1 -0
  9. package/dist/Toolbar/ToolbarItems/ImageTools.js +1 -1
  10. package/dist/stories/Toolbar/ImageTools/ImageLabel.stories.d.ts +1 -5
  11. package/dist/types.d.ts +0 -1
  12. package/dist/utils/TimeUtils.d.ts +4 -0
  13. package/dist/utils/TimeUtils.js +1 -0
  14. package/dist/utils/index.d.ts +2 -1
  15. package/dist/utils/index.js +1 -1
  16. package/package.json +1 -1
  17. package/src/Annotation/logic/Annotation.ts +1 -3
  18. package/src/Annotation/ui/AnnotationComponent.tsx +31 -7
  19. package/src/Canvas/Canvas.tsx +53 -29
  20. package/src/Sia2.tsx +10 -17
  21. package/src/Toolbar/Toolbar.tsx +4 -4
  22. package/src/Toolbar/ToolbarItems/ImageToolItems/ImageLabelInput.tsx +125 -0
  23. package/src/Toolbar/ToolbarItems/ImageTools.tsx +16 -27
  24. package/src/types.ts +0 -1
  25. package/src/utils/TimeUtils.ts +14 -0
  26. package/src/utils/index.ts +2 -1
  27. package/dist/Annotation/logic/AnnotationUtils.d.ts +0 -30
  28. package/dist/Annotation/logic/AnnotationUtils.js +0 -1
  29. package/dist/Toolbar/ToolbarItems/ImageToolItems/ImageLabel.d.ts +0 -8
  30. package/dist/Toolbar/ToolbarItems/ImageToolItems/ImageLabel.js +0 -1
  31. package/src/Annotation/logic/AnnotationUtils.ts +0 -30
  32. package/src/Toolbar/ToolbarItems/ImageToolItems/ImageLabel.tsx +0 -62
  33. package/src/utils/index.js +0 -1
@@ -5,14 +5,13 @@ import { Point } from '../../types';
5
5
  declare class Annotation {
6
6
  internalId: number;
7
7
  externalId?: string;
8
- annoTime?: number;
8
+ annoTime: number;
9
9
  coordinates: Point[];
10
10
  labelIds?: number[];
11
11
  mode: AnnotationMode;
12
12
  selectedNode: number;
13
13
  status: AnnotationStatus;
14
14
  type: AnnotationTool;
15
- timestamp?: DOMHighResTimeStamp;
16
15
  constructor(internalId: number, type: AnnotationTool, coordinates: Point[], mode?: AnnotationMode, status?: AnnotationStatus, externalId?: string);
17
16
  }
18
17
  export default Annotation;
@@ -1 +1 @@
1
- var r=Object.defineProperty;var m=(s,e,o)=>e in s?r(s,e,{enumerable:!0,configurable:!0,writable:!0,value:o}):s[e]=o;var t=(s,e,o)=>m(s,typeof e!="symbol"?e+"":e,o);import l from"../../models/AnnotationMode.js";import h from"../../models/AnnotationStatus.js";class u{constructor(e,o,n,i=l.CREATE,a=h.CREATING,d=""){t(this,"internalId");t(this,"externalId");t(this,"annoTime");t(this,"coordinates");t(this,"labelIds");t(this,"mode");t(this,"selectedNode");t(this,"status");t(this,"type");t(this,"timestamp");this.internalId=e,this.externalId=d,this.labelIds=[],this.type=o,this.mode=i,this.status=a,this.coordinates=n,this.selectedNode=1,this.timestamp=performance.now(),this.annoTime=0}}export{u as default};
1
+ var r=Object.defineProperty;var l=(o,e,s)=>e in o?r(o,e,{enumerable:!0,configurable:!0,writable:!0,value:s}):o[e]=s;var t=(o,e,s)=>l(o,typeof e!="symbol"?e+"":e,s);import h from"../../models/AnnotationMode.js";import m from"../../models/AnnotationStatus.js";class u{constructor(e,s,n,i=h.CREATE,a=m.CREATING,d=""){t(this,"internalId");t(this,"externalId");t(this,"annoTime");t(this,"coordinates");t(this,"labelIds");t(this,"mode");t(this,"selectedNode");t(this,"status");t(this,"type");this.internalId=e,this.externalId=d,this.labelIds=[],this.type=s,this.mode=i,this.status=a,this.coordinates=n,this.selectedNode=1,this.annoTime=0}}export{u as default};
@@ -1 +1 @@
1
- import{jsxs as q,jsx as u}from"react/jsx-runtime";import h from"../../models/AnnotationTool.js";import{getDefaultColor as L}from"../../utils/color.js";import z from"./tools/Point.js";import G from"./tools/Line.js";import H from"./atoms/AnnoBar.js";import J from"../../models/CanvasAction.js";import K from"./tools/BBox.js";import Q from"./tools/Polygon.js";import{useState as A,useRef as T,useEffect as b}from"react";import t from"../../models/AnnotationMode.js";const io=({scaledAnnotation:r,annotationSettings:d,possibleLabels:N,svgScale:i,svgTranslation:f,pageToStageOffset:C,strokeWidth:x,nodeRadius:B,isSelected:c,isDisabled:P=!1,onFinishAnnoCreate:R,onLabelIconClicked:V,onAction:W=(e,m)=>{},onAnnoChanged:_=e=>{},onAnnotationModeChange:k=e=>{},onNotification:F=e=>{}})=>{const[e,m]=A(r.coordinates),[n,s]=A(r.mode),[O,D]=A(!1),p=T(e);b(()=>{p.current=e},[e]);const I=()=>{s(t.VIEW);const o={...r,coordinates:p.current};R(o)},j=o=>N.find(E=>E.id===o),M=(()=>{if(!r.labelIds||r.labelIds.length==0)return L();const o=j(r.labelIds[0]);return o===void 0||o.color===void 0?L():o.color})(),l={stroke:M,fill:M,strokeWidth:x/i,r:B/i},a=o=>{m(o);let E=o;[t.ADD,t.MOVE].includes(n)&&(E=o.slice(0,-1)),_({...r,coordinates:E})},y=o=>{n!==t.CREATE&&n!==t.ADD&&s(t.MOVE),m(o)},g=()=>{s(t.VIEW),_({...r,coordinates:p.current})};b(()=>{k(n)},[n]),b(()=>{n===t.CREATE||n===t.ADD||m(r.coordinates)},[r]);const w=()=>{switch(r.type){case h.Point:return u(z,{isSelected:c,annotationSettings:d,coordinates:e[0],pageToStageOffset:C,svgScale:i,svgTranslation:f,style:l,onMoving:o=>{s(t.MOVE),m([o])},onMoved:g,onIsDraggingStateChanged:D});case h.Line:return u(G,{annotationSettings:d,coordinates:e,isSelected:c,pageToStageOffset:C,annotationMode:n,setAnnotationMode:s,svgScale:i,svgTranslation:f,style:l,onAddNode:a,onDeleteNode:a,onMoving:y,onMoved:g,onIsDraggingStateChanged:D,onFinishAnnoCreate:I});case h.BBox:return u(K,{annotationMode:n,annotationSettings:d,startCoords:e[0],endCoords:e[1],isSelected:c,pageToStageOffset:C,style:l,svgScale:i,svgTranslation:f,onDeleteNode:()=>{console.log("TODO")},onIsDraggingStateChanged:D,onFinishAnnoCreate:I,onMoving:y,onMoved:g});case h.Polygon:return u(Q,{annotationSettings:d,coordinates:e,isSelected:c,isDisabled:P,pageToStageOffset:C,annotationMode:n,setAnnotationMode:s,svgScale:i,svgTranslation:f,style:l,onAddNode:a,onDeleteNode:a,onMoving:y,onMoved:g,onNotification:F,onIsDraggingStateChanged:D,onFinishAnnoCreate:I})}};return q("g",{onClick:o=>{o.stopPropagation(),W(r,J.ANNO_SELECTED)},children:[!O&&n!==t.CREATE&&u(H,{annotationCoordinates:e,canLabel:d.canLabel,labels:N,color:M,isSelected:c,selectedLabelIds:r.labelIds,style:l,svgScale:i,onLabelIconClicked:V}),w()]})};export{io as default};
1
+ import{jsxs as G,jsx as f}from"react/jsx-runtime";import h from"../../models/AnnotationTool.js";import{getDefaultColor as L}from"../../utils/color.js";import H from"./tools/Point.js";import J from"./tools/Line.js";import K from"./atoms/AnnoBar.js";import Q from"../../models/CanvasAction.js";import X from"./tools/BBox.js";import Y from"./tools/Polygon.js";import{useState as I,useRef as x,useEffect as M}from"react";import e from"../../models/AnnotationMode.js";import Z from"../../utils/TimeUtils.js";const fo=({scaledAnnotation:r,annotationSettings:m,possibleLabels:b,svgScale:i,svgTranslation:a,pageToStageOffset:l,strokeWidth:B,nodeRadius:P,isSelected:d,isDisabled:V=!1,onFinishAnnoCreate:w,onLabelIconClicked:W,onAction:k=(n,C)=>{},onAnnoChanged:A=n=>{},onAnnotationModeChange:F=n=>{},onNotification:O=n=>{}})=>{const[n,C]=I(r.coordinates),[t,c]=I(r.mode),[j,D]=I(!1),R=x(void 0),[_,U]=I();M(()=>{R.current=_},[_]);const y=x(n);M(()=>{y.current=n},[n]);const N=()=>{c(e.VIEW);const o={...r,coordinates:y.current};w(o)},q=o=>b.find(s=>s.id===o),T=(()=>{if(!r.labelIds||r.labelIds.length==0)return L();const o=q(r.labelIds[0]);return o===void 0||o.color===void 0?L():o.color})(),u={stroke:T,fill:T,strokeWidth:B/i,r:P/i},p=o=>{C(o);let s=o;[e.ADD,e.MOVE].includes(t)&&(s=o.slice(0,-1)),A({...r,coordinates:s})},g=o=>{[e.ADD,e.CREATE,e.MOVE].includes(t)||(c(e.MOVE),U(performance.now())),C(o)},E=()=>{c(e.VIEW);const o=Z.getRoundedDuration(R.current,performance.now()),s=isNaN(r.annoTime)||r.annoTime===null?o:r.annoTime+o;A({...r,coordinates:y.current,annoTime:s})};M(()=>{F(t)},[t]),M(()=>{t===e.CREATE||t===e.ADD||C(r.coordinates)},[r]);const z=()=>{switch(r.type){case h.Point:return f(H,{isSelected:d,annotationSettings:m,coordinates:n[0],pageToStageOffset:l,svgScale:i,svgTranslation:a,style:u,onMoving:o=>g([o]),onMoved:E,onIsDraggingStateChanged:D});case h.Line:return f(J,{annotationSettings:m,coordinates:n,isSelected:d,pageToStageOffset:l,annotationMode:t,setAnnotationMode:c,svgScale:i,svgTranslation:a,style:u,onAddNode:p,onDeleteNode:p,onMoving:g,onMoved:E,onIsDraggingStateChanged:D,onFinishAnnoCreate:N});case h.BBox:return f(X,{annotationMode:t,annotationSettings:m,startCoords:n[0],endCoords:n[1],isSelected:d,pageToStageOffset:l,style:u,svgScale:i,svgTranslation:a,onDeleteNode:()=>{console.log("TODO")},onIsDraggingStateChanged:D,onFinishAnnoCreate:N,onMoving:g,onMoved:E});case h.Polygon:return f(Y,{annotationSettings:m,coordinates:n,isSelected:d,isDisabled:V,pageToStageOffset:l,annotationMode:t,setAnnotationMode:c,svgScale:i,svgTranslation:a,style:u,onAddNode:p,onDeleteNode:p,onMoving:g,onMoved:E,onNotification:O,onIsDraggingStateChanged:D,onFinishAnnoCreate:N})}};return G("g",{onClick:o=>{o.stopPropagation(),k(r,Q.ANNO_SELECTED)},children:[!j&&t!==e.CREATE&&f(K,{annotationCoordinates:n,canLabel:m.canLabel,labels:b,color:T,isSelected:d,selectedLabelIds:r.labelIds,style:u,svgScale:i,onLabelIconClicked:W}),z()]})};export{fo as default};
@@ -1 +1 @@
1
- import{jsxs as P,jsx as h,Fragment as fe}from"react/jsx-runtime";import{useState as m,useRef as ge,useEffect as S}from"react";import b from"../models/AnnotationTool.js";import i from"../models/EditorModes.js";import Ue from"../utils/KeyMapper.js";import c from"../models/KeyAction.js";import ye from"../Annotation/logic/Annotation.js";import Xe from"../models/CanvasAction.js";import $e from"../Annotation/ui/AnnotationComponent.js";import Ee from"../utils/mouse2.js";import _ from"../models/AnnotationMode.js";import je from"./LabelInput.js";import{FontAwesomeIcon as Ge}from"@fortawesome/react-fontawesome";import{faBan as He}from"@fortawesome/free-solid-svg-icons";import L from"../models/AnnotationStatus.js";import p from"../utils/transform2.js";import Je from"../models/NotificationType.js";const ht=({annotations:C=[],annotationSettings:W,defaultLabelId:G,image:H,isFullscreen:he=!1,isImageJunk:J=!1,isPolygonSelectionMode:Q=!1,polygonOperationResult:B={annotationsToDelete:[],polygonsToCreate:[]},possibleLabels:Z,preventScrolling:q=!0,selectedAnnotation:r,selectedAnnoTool:N,toolbarHeight:K=0,uiConfig:F,onAnnoCreated:me,onAnnoCreationFinished:ee,onAnnoChanged:te,onAnnoEditing:xe=O=>{},onNotification:ne=O=>{},onRequestNewAnnoId:z,onSelectAnnotation:I,onSetSelectedTool:pe=O=>{},onShouldDeleteAnno:oe})=>{const[O,T]=m(i.VIEW),[re,Ce]=m(G),[k,Oe]=m({x:-1,y:-1}),[se,ie]=m(0),R={x:k.x,y:k.y},[a,Y]=m({x:-1,y:-1}),[l,U]=m({x:-1,y:-1}),[g,ce]=m({x:-1,y:-1}),[d,V]=m(1),[u,w]=m({x:0,y:0}),X={x:u.x+se,y:u.y},[y,$]=m(),[ae,D]=m(!1),E=ge(null),f=ge(null),v=((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)})(a,l),Te=()=>{if((f==null?void 0:f.current)===null)return{x:0,y:0};const e=a.x*v;if(F.imageCentered&&l.x>e){const x=(l.x-e)/2;ie(x)}else ie(0);const{top:t,left:n}=E.current.getBoundingClientRect(),o={x:n+window.scrollX,y:t+window.scrollY};Oe(o)},ve=new Ue(e=>be(e)),Ie=e=>{T(i.CREATE);const t=p.convertStageCoordinatesToPercentaged([e],v,a);N===b.BBox&&t.push(t[0]);const n=z(),o=new ye(n,N,t);if(re!==void 0&&(o.labelIds=[re]),me(o),N===b.Point){const s={...o,coordinates:[e]};j(s)}},Me=()=>{if(r&&![b.Line,b.Polygon].includes(r.type))return;const e=C.find(t=>t.internalId===(r==null?void 0:r.internalId));e!==void 0&&(e.mode=_.CREATE,e.status=L.CREATING,e.selectedNode=e.coordinates.length-1,T(i.ADD),pe(e.type),xe(e))},Se=()=>{const e=r?r.internalId:0,t=C.find(n=>n.internalId>e);if(t)return I(t);if(C.length>0)return I(C[0])},we=()=>{const e=r?r.internalId:0,t=[...C];t.sort((o,s)=>s.internalId-o.internalId);const n=t.find(o=>o.internalId<e);if(n)return I(n);if(C.length>0)return I(C[C.length-1])},De=()=>{if(r){const e=JSON.stringify(r);localStorage.setItem("lostAnnotationClipboard",e);const t={title:"Success",message:"Annotation copied",type:Je.SUCCESS};ne(t)}},Ae=()=>{const e=localStorage.getItem("lostAnnotationClipboard");if(e==null)return;const t=JSON.parse(e);t.internalId=z(),t.externalId="",ee(t,!0),I(t)},be=e=>{switch(e){case c.EDIT_LABEL:r&&D(!0);break;case c.DELETE_ANNO:r&&oe(r.internalId);break;case c.DELETE_ANNO_IN_CREATION:O===i.CREATE&&(oe(r.internalId),T(i.VIEW));break;case c.ENTER_ANNO_ADD_MODE:console.log("KeyAction TODO: ENTER_ANNO_ADD_MODE");break;case c.LEAVE_ANNO_ADD_MODE:console.log("KeyAction TODO: LEAVE_ANNO_ADD_MODE");break;case c.UNDO:console.log("KeyAction TODO: UNDO");break;case c.REDO:console.log("KeyAction TODO: REDO");break;case c.TRAVERSE_ANNOS:Se();break;case c.TRAVERSE_ANNOS_BACKWARDS:we();break;case c.CAM_MOVE_LEFT:A(20*d,0);break;case c.CAM_MOVE_RIGHT:A(-20*d,0);break;case c.CAM_MOVE_UP:A(0,20*d);break;case c.CAM_MOVE_DOWN:A(0,-20*d);break;case c.CAM_MOVE_STOP:console.log("KeyAction TODO: CAM_MOVE_STOP");break;case c.COPY_ANNOTATION:De();break;case c.PASTE_ANNOTATION:Ae();break;case c.RECREATE_ANNO:console.log("KeyAction TODO: RECREATE_ANNO"),Me();break;default:console.log("Unknown KeyAction",e);break}},A=(e,t)=>{let n=u.x+e/d,o=u.y+t/d;const s=l.x*-.25,x=l.y*-.25,M=l.x*.75,Ye=l.y*.75;n<s&&(n+=25),n>M&&(n-=25),o<x&&(o+=25),o>Ye&&(o-=25),w({x:n,y:o})},_e=(e=>g.x<=0||g.y<=0||a.x<=0||a.y<=0?[]:C.map(n=>({...n,coordinates:p.convertPercentagedCoordinatesToStage(n.coordinates,a,g)})))(),Ne=()=>{if(T(i.VIEW),ce({x:-1,y:-1}),f.current!==null){const{width:e,height:t}=f.current.getBoundingClientRect();Y({x:e,y:t})}V(1),w({x:0,y:0}),$(void 0),D(!1)};S(()=>{if((E==null?void 0:E.current)!==void 0){const{width:e,height:t}=E.current.getBoundingClientRect(),n=t-K;U({x:e,y:n});const o=new ResizeObserver(()=>{const{width:s,height:x}=E.current.getBoundingClientRect(),M=x-K;U({x:s,y:M})});return o.observe(E.current),()=>o.disconnect()}Ne()},[H,he]),S(()=>{Te()},[f,u,l]),S(()=>{if(E.current===null)return;const{width:e,height:t}=E.current.getBoundingClientRect(),n=t-K;U({x:e,y:n})},[E]),S(()=>{if(f.current===null)return;const{width:e,height:t}=f.current.getBoundingClientRect();Y({x:e,y:t});const n=new ResizeObserver(()=>{const{width:o,height:s}=f.current.getBoundingClientRect();Y({x:o,y:s})});return n.observe(f.current),()=>n.disconnect()},[f]),S(()=>{if(v===0)return;const e={x:a.x*v,y:a.y*v};ce(e)},[v,a]),S(()=>{if(!Q)return;const e=z();B.polygonsToCreate!==void 0&&B.polygonsToCreate.forEach(t=>{const n=new ye(e,t.type,p.convertPercentagedCoordinatesToStage(t.coordinates,a,g),_.VIEW,L.CREATED);j(n)})},[B]);const j=e=>{T(i.VIEW),e.mode=_.VIEW;const t=p.convertStageCoordinatesToPercentaged(e.coordinates,v,a);e.coordinates=t,te(e),ee(e,N===b.Point)},ke=e=>{e.preventDefault(),ve.keyDown(e.key,e.shiftKey,e.ctrlKey)},Re=e=>{e.preventDefault()},Ve=e=>{if(e.button!==0){if(e.button===1)T(i.CAMERA_MOVE);else if(e.button===2){if(!W.canCreate||O===i.ADD)return;const t=Ee.getAntiScaledMouseStagePosition(e,R,d,u),n={x:t.x-se,y:t.y};Ie(n)}}},Pe=()=>{q&&(document.body.style.overflow="hidden")},Le=e=>{switch(e.button){case 1:T(i.VIEW);break}},de=(e,t)=>{O===i.CAMERA_MOVE&&A(e,t)},We=()=>{q&&(document.body.style.overflow="")},Be=e=>{const o=(e.deltaY<0?1:-1)>0?d*1.25:d/1.25,s=Ee.getAntiScaledMouseStagePosition(e,R,d,u),x=d/o,M={x:x*(s.x+u.x)-s.x,y:x*(s.y+u.y)-s.y};o<1?(V(1),(u.x!=0||u.y!=0)&&w({x:0,y:0})):o>200?(V(200),w(M)):(V(o),w(M))},Ke=(e,t)=>{if(t!==Xe.ANNO_SELECTED){console.log("Unknown Canvas Action:",t);return}const n={...e,coordinates:p.convertStageCoordinatesToPercentaged([...e.coordinates],v,a)};I(n);const o=p.getMostLeftPoints(e.coordinates),s=p.getTopPoint(o)[0],x=p.convertStageToPage(s,R,d,u);$(x)},le=e=>{const t=p.convertStageCoordinatesToPercentaged(e.coordinates,v,a),n={...e,coordinates:t};te(n)},Fe=()=>{if(O===i.CAMERA_MOVE)return h(fe,{});const e=_e.map(t=>[i.CREATE,i.ADD,i.MOVE].includes(O)&&t.internalId!==(r==null?void 0:r.internalId)?h(fe,{}):h($e,{scaledAnnotation:t,annotationSettings:W,possibleLabels:Z,svgScale:d,svgTranslation:X,pageToStageOffset:R,nodeRadius:F.nodeRadius,strokeWidth:F.strokeWidth,isSelected:t.internalId===(r==null?void 0:r.internalId),isDisabled:Q&&t.internalId===(r==null?void 0:r.internalId),onFinishAnnoCreate:j,onLabelIconClicked:()=>D(!0),onAction:Ke,onAnnoChanged:le,onAnnotationModeChange:o=>{o===_.MOVE&&T(i.MOVE),O===i.MOVE&&o===_.VIEW&&T(i.VIEW)},onNotification:ne},`annotationComponent_${t.internalId}`));return h("g",{children:e})},ze=()=>h("circle",{cx:g.x/2,cy:g.y/2,r:"100%",style:{opacity:0},onContextMenu:e=>e.preventDefault(),onClick:()=>{D(!1)}}),ue={x:k.x+l.x/2,y:k.y+l.y/2};return P("div",{ref:E,style:{width:"100%",height:"100%"},children:[h("div",{style:{position:"absolute",left:(y==null?void 0:y.x)!==void 0?y.x:0,top:(y==null?void 0:y.y)!==void 0?y.y:0,display:(y==null?void 0:y.y)!==void 0?"inherit":"none",zIndex:7e3},children:h(je,{defaultLabelId:G,isVisible:ae,selectedLabelsIds:r==null?void 0:r.labelIds,possibleLabels:Z,isMultilabel:W.canHaveMultipleLabels,onLabelSelect:e=>{if(D(!1),e.length>0){const o=e.filter(s=>!r.labelIds.includes(s));o.length>0&&Ce(o[0])}const t=r.status===L.LOADED?L.CHANGED:r.status,n={...r,coordinates:p.convertPercentagedCoordinatesToStage(r.coordinates,a,g),labelIds:[...e],status:t};le(n)}})}),J&&P("div",{style:{position:"absolute",left:ue.x,top:ue.y,transform:"translate(-50%, -50%)",textAlign:"center",color:"white"},children:[h(Ge,{icon:He,size:"5x",style:{marginBottom:15}}),h("h2",{children:"Marked as Junk"})]}),P("svg",{width:"100%",height:"100%",onKeyDown:ke,onKeyUp:Re,onMouseMove:e=>de(e.movementX,e.movementY),tabIndex:0,children:[P("g",{transform:`scale(${d}) translate(${X.x}, ${X.y})`,onMouseOver:Pe,onMouseLeave:We,onMouseUp:Le,onWheel:Be,onMouseMove:e=>de(e.movementX,e.movementY),onClick:()=>{I(void 0)},children:[h("image",{onContextMenu:e=>e.preventDefault(),onMouseDown:e=>Ve(e),href:H,ref:f,width:g.x>0?g.x:void 0,height:g.y>0?g.y:void 0}),Fe()]}),ae&&ze(),J&&h("rect",{x:"0",y:"0",width:l.x,height:l.y,style:{opacity:.8},onContextMenu:e=>e.preventDefault(),onClick:()=>{$(void 0)}})]})]})};export{ht as default};
1
+ import{jsxs as L,jsx as p,Fragment as Xe}from"react/jsx-runtime";import{useState as m,useRef as fe,useEffect as D}from"react";import M from"../models/AnnotationTool.js";import i from"../models/EditorModes.js";import He from"../utils/KeyMapper.js";import a from"../models/KeyAction.js";import ge from"../Annotation/logic/Annotation.js";import $e from"../models/CanvasAction.js";import Ge from"../Annotation/ui/AnnotationComponent.js";import me from"../utils/mouse2.js";import N from"../models/AnnotationMode.js";import je from"./LabelInput.js";import{FontAwesomeIcon as Je}from"@fortawesome/react-fontawesome";import{faBan as Qe}from"@fortawesome/free-solid-svg-icons";import S from"../models/AnnotationStatus.js";import x from"../utils/transform2.js";import Ze from"../models/NotificationType.js";import qe from"../utils/TimeUtils.js";const Ct=({annotations:C=[],annotationSettings:W,defaultLabelId:J,image:Q,isFullscreen:ye=!1,isImageJunk:Z=!1,isPolygonSelectionMode:B=!1,polygonOperationResult:K={annotationsToDelete:[],polygonsToCreate:[]},possibleLabels:q,preventScrolling:ee=!0,selectedAnnotation:r,selectedAnnoTool:k,toolbarHeight:F=0,uiConfig:z,onAnnoCreated:Ee,onAnnoCreationFinished:te,onAnnoChanged:ne,onAnnoEditing:he=T=>{},onNotification:oe=T=>{},onRequestNewAnnoId:U,onSelectAnnotation:v,onSetSelectedTool:pe=T=>{},onShouldDeleteAnno:re})=>{const[T,A]=m(i.VIEW),[xe,Ce]=m(),[se,Te]=m(J),[R,Ae]=m({x:-1,y:-1}),[ie,ae]=m(0),V={x:R.x,y:R.y},[c,Y]=m({x:-1,y:-1}),[u,X]=m({x:-1,y:-1}),[y,ce]=m({x:-1,y:-1}),[d,P]=m(1),[f,w]=m({x:0,y:0}),H={x:f.x+ie,y:f.y},[E,$]=m(),[G,b]=m(!1),h=fe(null),g=fe(null),O=((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)})(c,u),Oe=()=>{if((g==null?void 0:g.current)===null)return{x:0,y:0};const e=c.x*O;if(z.imageCentered&&u.x>e){const l=(u.x-e)/2;ae(l)}else ae(0);const{top:t,left:n}=h.current.getBoundingClientRect(),o={x:n+window.scrollX,y:t+window.scrollY};Ae(o)},ve=new He(e=>_e(e)),Ie=e=>{A(i.CREATE);const t=x.convertStageCoordinatesToPercentaged([e],O,c);k===M.BBox&&t.push(t[0]);const n=U(),o=new ge(n,k,t);if(Ce(performance.now()),se!==void 0&&(o.labelIds=[se]),Ee(o),k===M.Point){const s={...o,coordinates:[e],annoTime:0};j(s)}},De=()=>{if(r&&![M.Line,M.Polygon].includes(r.type))return;const e=C.find(t=>t.internalId===(r==null?void 0:r.internalId));e!==void 0&&(e.mode=N.CREATE,e.status=S.CREATING,e.selectedNode=e.coordinates.length-1,A(i.ADD),pe(e.type),he(e))},Me=()=>{const e=r?r.internalId:0,t=C.find(n=>n.internalId>e);if(t)return v(t);if(C.length>0)return v(C[0])},Se=()=>{const e=r?r.internalId:0,t=[...C];t.sort((o,s)=>s.internalId-o.internalId);const n=t.find(o=>o.internalId<e);if(n)return v(n);if(C.length>0)return v(C[C.length-1])},we=()=>{if(r){const e=JSON.stringify(r);localStorage.setItem("lostAnnotationClipboard",e);const t={title:"Success",message:"Annotation copied",type:Ze.SUCCESS};oe(t)}},be=()=>{const e=localStorage.getItem("lostAnnotationClipboard");if(e==null)return;const t=JSON.parse(e);t.internalId=U(),t.externalId="",te(t,!0),v(t)},_e=e=>{switch(e){case a.EDIT_LABEL:r&&b(!0);break;case a.DELETE_ANNO:r&&re(r.internalId);break;case a.DELETE_ANNO_IN_CREATION:T===i.CREATE&&(re(r.internalId),A(i.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:console.log("KeyAction TODO: UNDO");break;case a.REDO:console.log("KeyAction TODO: REDO");break;case a.TRAVERSE_ANNOS:Me();break;case a.TRAVERSE_ANNOS_BACKWARDS:Se();break;case a.CAM_MOVE_LEFT:_(20*d,0);break;case a.CAM_MOVE_RIGHT:_(-20*d,0);break;case a.CAM_MOVE_UP:_(0,20*d);break;case a.CAM_MOVE_DOWN:_(0,-20*d);break;case a.CAM_MOVE_STOP:console.log("KeyAction TODO: CAM_MOVE_STOP");break;case a.COPY_ANNOTATION:we();break;case a.PASTE_ANNOTATION:be();break;case a.RECREATE_ANNO:console.log("KeyAction TODO: RECREATE_ANNO"),De();break;default:console.log("Unknown KeyAction",e);break}},_=(e,t)=>{let n=f.x+e/d,o=f.y+t/d;const s=u.x*-.25,l=u.y*-.25,I=u.x*.75,Ye=u.y*.75;n<s&&(n+=25),n>I&&(n-=25),o<l&&(o+=25),o>Ye&&(o-=25),w({x:n,y:o})},Ne=(e=>y.x<=0||y.y<=0||c.x<=0||c.y<=0?[]:C.map(n=>({...n,coordinates:x.convertPercentagedCoordinatesToStage(n.coordinates,c,y)})))(),ke=()=>{if(A(i.VIEW),ce({x:-1,y:-1}),g.current!==null){const{width:e,height:t}=g.current.getBoundingClientRect();Y({x:e,y:t})}P(1),w({x:0,y:0}),$(void 0),b(!1)};D(()=>{if((h==null?void 0:h.current)!==void 0){const{width:e,height:t}=h.current.getBoundingClientRect(),n=t-F;X({x:e,y:n});const o=new ResizeObserver(()=>{const{width:s,height:l}=h.current.getBoundingClientRect(),I=l-F;X({x:s,y:I})});return o.observe(h.current),()=>o.disconnect()}ke()},[Q,ye]),D(()=>{Oe()},[g,f,u]),D(()=>{if(h.current===null)return;const{width:e,height:t}=h.current.getBoundingClientRect(),n=t-F;X({x:e,y:n})},[h]),D(()=>{if(g.current===null)return;const{width:e,height:t}=g.current.getBoundingClientRect();Y({x:e,y:t});const n=new ResizeObserver(()=>{const{width:o,height:s}=g.current.getBoundingClientRect();Y({x:o,y:s})});return n.observe(g.current),()=>n.disconnect()},[g]),D(()=>{if(O===0)return;const e={x:c.x*O,y:c.y*O};ce(e)},[O,c]),D(()=>{B&&K.polygonsToCreate!==void 0&&K.polygonsToCreate.forEach(e=>{const t=U(),n=new ge(t,e.type,x.convertPercentagedCoordinatesToStage(e.coordinates,c,y),N.VIEW,S.CREATED);j(n)})},[K]);const j=e=>{A(i.VIEW);const t={...e,mode:N.VIEW};if(e.type!==M.Point){const s=qe.getRoundedDuration(xe,performance.now());t.annoTime=s}const n=x.convertStageCoordinatesToPercentaged(e.coordinates,O,c);t.coordinates=n,ne(t);const o=k===M.Point||B;te(t,o)},Re=e=>{e.preventDefault(),ve.keyDown(e.key,e.shiftKey,e.ctrlKey)},Ve=e=>{e.preventDefault()},Pe=e=>{if(e.button!==0){if(e.button===1)A(i.CAMERA_MOVE);else if(e.button===2){if(!W.canCreate||T===i.ADD)return;const t=me.getAntiScaledMouseStagePosition(e,V,d,f),n={x:t.x-ie,y:t.y};Ie(n)}}},Le=()=>{ee&&(document.body.style.overflow="hidden")},We=e=>{switch(e.button){case 1:A(i.VIEW);break}},de=(e,t)=>{T===i.CAMERA_MOVE&&_(e,t)},Be=()=>{ee&&(document.body.style.overflow="")},Ke=e=>{const o=(e.deltaY<0?1:-1)>0?d*1.25:d/1.25,s=me.getAntiScaledMouseStagePosition(e,V,d,f),l=d/o,I={x:l*(s.x+f.x)-s.x,y:l*(s.y+f.y)-s.y};o<1?(P(1),(f.x!=0||f.y!=0)&&w({x:0,y:0})):o>200?(P(200),w(I)):(P(o),w(I))},Fe=(e,t)=>{if(t!==$e.ANNO_SELECTED){console.log("Unknown Canvas Action:",t);return}const n={...e,coordinates:x.convertStageCoordinatesToPercentaged([...e.coordinates],O,c)};v(n);const o=x.getMostLeftPoints(e.coordinates),s=x.getTopPoint(o)[0],l=x.convertStageToPage(s,V,d,f);$(l)},le=e=>{const t=x.convertStageCoordinatesToPercentaged(e.coordinates,O,c),n={...e,coordinates:t};n.status===S.LOADED&&(n.status=S.CHANGED),ne(n)},ze=()=>{if(T===i.CAMERA_MOVE)return p(Xe,{});const t=[i.CREATE,i.ADD,i.MOVE].includes(T),n=Ne.map(o=>{const s=o.internalId===(r==null?void 0:r.internalId);return t&&!s?p("g",{},`annotationComponent_${o.internalId}`):p(Ge,{scaledAnnotation:o,annotationSettings:W,possibleLabels:q,svgScale:d,svgTranslation:H,pageToStageOffset:V,nodeRadius:z.nodeRadius,strokeWidth:z.strokeWidth,isSelected:s,isDisabled:B&&s,onFinishAnnoCreate:j,onLabelIconClicked:()=>b(!0),onAction:Fe,onAnnoChanged:le,onAnnotationModeChange:l=>{l===N.MOVE&&A(i.MOVE),T===i.MOVE&&l===N.VIEW&&A(i.VIEW)},onNotification:oe},`annotationComponent_${o.internalId}`)});return p("g",{children:n})},Ue=()=>p("circle",{cx:y.x/2,cy:y.y/2,r:"100%",style:{opacity:0},onContextMenu:e=>e.preventDefault(),onClick:()=>{b(!1)}}),ue={x:R.x+u.x/2,y:R.y+u.y/2};return L("div",{ref:h,style:{width:"100%",height:"100%"},children:[p("div",{style:{position:"absolute",left:(E==null?void 0:E.x)!==void 0?E.x:0,top:(E==null?void 0:E.y)!==void 0?E.y:0,display:(E==null?void 0:E.y)!==void 0?"inherit":"none",zIndex:G?7e3:-1},children:p(je,{defaultLabelId:J,isVisible:G,selectedLabelsIds:r==null?void 0:r.labelIds,possibleLabels:q,isMultilabel:W.canHaveMultipleLabels,onLabelSelect:e=>{if(b(!1),e.length>0){const o=e.filter(s=>!r.labelIds.includes(s));o.length>0&&Te(o[0])}const t=r.status===S.LOADED?S.CHANGED:r.status,n={...r,coordinates:x.convertPercentagedCoordinatesToStage(r.coordinates,c,y),labelIds:[...e],status:t};le(n)}})}),Z&&L("div",{style:{position:"absolute",left:ue.x,top:ue.y,transform:"translate(-50%, -50%)",textAlign:"center",color:"white"},children:[p(Je,{icon:Qe,size:"5x",style:{marginBottom:15}}),p("h2",{children:"Marked as Junk"})]}),L("svg",{width:"100%",height:"100%",onKeyDown:Re,onKeyUp:Ve,onMouseMove:e=>de(e.movementX,e.movementY),tabIndex:0,children:[L("g",{transform:`scale(${d}) translate(${H.x}, ${H.y})`,onMouseOver:Le,onMouseLeave:Be,onMouseUp:We,onWheel:Ke,onMouseMove:e=>de(e.movementX,e.movementY),onClick:()=>{v(void 0)},children:[p("image",{onContextMenu:e=>e.preventDefault(),onMouseDown:e=>Pe(e),href:Q,ref:g,width:y.x>0?y.x:void 0,height:y.y>0?y.y:void 0}),ze()]}),G&&Ue(),Z&&p("rect",{x:"0",y:"0",width:u.x,height:u.y,style:{opacity:.8},onContextMenu:e=>e.preventDefault(),onClick:()=>{$(void 0)}})]})]})};export{Ct as default};
package/dist/Sia2.js CHANGED
@@ -1 +1 @@
1
- import{jsx as r,jsxs as F}from"react/jsx-runtime";import{useRef as rn,useState as s,useEffect as m}from"react";import{CSpinner as U}from"@coreui/react";import ln from"./Canvas/Canvas.js";import cn from"./models/AnnotationTool.js";import fn from"./Toolbar/Toolbar.js";import un from"./models/AnnotationMode.js";import In from"./models/AnnotationStatus.js";const gn=({additionalButtons:J,allowedTools:h,polygonOperationResult:l={annotationsToDelete:[],polygonsToCreate:[]},annotationSettings:v,uiConfig:x,defaultAnnotationTool:A,defaultLabelId:P,image:f,isLoading:g=!1,isPolygonSelectionMode:w=!1,initialAnnotations:S=[],initialImageLabelIds:W=[],initialIsImageJunk:q=!1,possibleLabels:b,onAnnoCreated:z=(c,u)=>{},onAnnoCreationFinished:H=(c,u)=>{},onAnnoChanged:M=(c,u)=>{},onAnnoDeleted:V=(c,u)=>{},onImageLabelsChanged:G=()=>{},onIsImageJunk:K=()=>{},onNotification:Q=c=>{},onSelectAnnotation:X=c=>{}})=>{const u=rn(null),[p,y]=s(),[i,a]=s([]),[D,Y]=s(),[Z,$]=s(),[d,_]=s(),[E,k]=s(A!==void 0?A:cn.Point),[R,mn]=s({height:"100%"}),[O,nn]=s(W),[L,j]=s(),[C,tn]=s(!1),[N,B]=s([]),T=n=>{const e=i.findIndex(I=>I.internalId===n),t=[...i],o=t.splice(e,1)[0];a(t),V(o,t)},en=()=>{d!==void 0&&T(d.internalId)},on=()=>{let n=0;const e=S.map(t=>({...t,internalId:n++,mode:un.VIEW,selectedNode:1,status:t.status,annoTime:t.annoTime!==void 0?t.annoTime:0,timestamp:t.timestamp!==void 0?t.timestamp:performance.now()}));B([...Array(n).keys()]),a(e)},sn=()=>{let n=0;for(;N.includes(n);)n++;const e=[...N];return e.push(n),B(e),n};m(()=>{f===void 0&&(a([]),_(void 0))},[f]),m(()=>{j(q),!(f!==void 0||S.length===0)&&on()},[S]),m(()=>{const e={...{canCreate:!0,canEdit:!0,canHaveMultipleLabels:!1,canLabel:!0,minimalArea:250},...v};Y(e)},[v]),m(()=>{const e={...{nodeRadius:4,strokeWidth:4,imageCentered:!1},...x};$(e)},[x]),m(()=>{const n={bbox:!0,point:!0,line:!0,junk:!0,polygon:!0};if(h===void 0)return y(n);y(h)},[h]);const an={position:"fixed",top:0,left:0,zIndex:6e3,backgroundColor:"#ffff",width:"100%",height:"100%",padding:15};return p===void 0?r("div",{className:"d-flex justify-content-center",children:r(U,{color:"primary",style:{width:"5rem",height:"5rem"}})}):F("div",{style:{...C?an:{},width:"100%",height:"100%"},children:[r("div",{ref:u,style:{marginBottom:10},children:r(fn,{annotationSettings:D,allowedTools:p,additionalButtons:J,isDisabled:g,isFullscreen:C,isImageJunk:L,imageLabelIds:O,possibleLabels:b,selectedTool:E,onImageLabelsChanged:n=>{nn(n),G(n)},onSetIsFullscreen:tn,onSetIsImageJunk:n=>{j(n),K(n)},onSetSelectedTool:k,onShouldDeleteSelectedAnnotation:en})}),F("div",{style:R,children:[g&&r("div",{className:"d-flex justify-content-center",children:r(U,{color:"primary",style:{width:"5rem",height:"5rem",marginTop:200}})}),f&&i&&r(ln,{annotations:i,annotationSettings:D,defaultLabelId:P,image:f,isFullscreen:C,isImageJunk:L,isPolygonSelectionMode:w,selectedAnnotation:d,selectedAnnoTool:E,polygonOperationResult:l,possibleLabels:b,uiConfig:Z,onAnnoCreated:n=>{const e=[...i];e.push(n),a(e),_(n),z(n,e)},onAnnoChanged:n=>{const e=i.findIndex(o=>o.internalId===n.internalId),t=[...i];t[e]=n,a(t),M(n,t)},onAnnoCreationFinished:(n,e)=>{const t=[...i];if(w&&((l==null?void 0:l.annotationsToDelete)!==void 0&&(l.annotationsToDelete.push(d),l.annotationsToDelete.forEach(o=>{T(o.internalId);const I=t.findIndex(dn=>dn.internalId===o.internalId);t.splice(I,1)})),t.push(n)),e)t.push(n);else{const o=i.findIndex(I=>I.internalId===n.internalId);t[o]=n}a(t),n.status=In.CREATED,H(n,t)},onAnnoEditing:n=>{const e=[...i],t=e.findIndex(o=>o.internalId===(d==null?void 0:d.internalId));e[t]=n,a(e)},onNotification:Q,onRequestNewAnnoId:sn,onSelectAnnotation:n=>{_(n),X(n)},onSetSelectedTool:k,onShouldDeleteAnno:T})]})]})};export{gn as default};
1
+ import{jsx as d,jsxs as J}from"react/jsx-runtime";import{useRef as dn,useState as s,useEffect as f}from"react";import{CSpinner as P}from"@coreui/react";import ln from"./Canvas/Canvas.js";import cn from"./models/AnnotationTool.js";import fn from"./Toolbar/Toolbar.js";import un from"./models/AnnotationMode.js";import In from"./models/AnnotationStatus.js";const gn=({additionalButtons:W,allowedTools:h,polygonOperationResult:l={annotationsToDelete:[],polygonsToCreate:[]},annotationSettings:A,uiConfig:x,defaultAnnotationTool:g,defaultLabelId:q,image:u,isLoading:w=!1,isPolygonSelectionMode:y=!1,initialAnnotations:S=[],initialImageLabelIds:_=[],initialIsImageJunk:z=!1,possibleLabels:b,onAnnoCreated:H=(c,I)=>{},onAnnoCreationFinished:M=(c,I)=>{},onAnnoChanged:V=(c,I)=>{},onAnnoDeleted:G=(c,I)=>{},onImageLabelsChanged:K=()=>{},onIsImageJunk:Q=()=>{},onNotification:X=c=>{},onSelectAnnotation:Y=c=>{}})=>{const I=dn(null),[D,E]=s(),[i,a]=s([]),[k,Z]=s(),[$,R]=s(),[r,C]=s(),[j,N]=s(g!==void 0?g:cn.Point),[O,mn]=s({height:"100%"}),[nn,p]=s(_),[B,F]=s(),[T,tn]=s(!1),[L,U]=s([]),v=n=>{const t=i.findIndex(m=>m.internalId===n),e=[...i],o=e.splice(t,1)[0];a(e),G(o,e)},en=()=>{r!==void 0&&v(r.internalId)},on=()=>{let n=0;const t=S.map(e=>({...e,internalId:n++,mode:un.VIEW,selectedNode:1,status:e.status,annoTime:e.annoTime!==void 0?e.annoTime:0}));U([...Array(n).keys()]),a(t)},sn=()=>{let n=0;for(;L.includes(n);)n++;const t=[...L];return t.push(n),U(t),n};f(()=>{u===void 0&&(a([]),C(void 0))},[u]),f(()=>{F(z),!(u!==void 0||S.length===0)&&on()},[S]),f(()=>{p(_)},[_]),f(()=>{const t={...{canCreate:!0,canEdit:!0,canHaveMultipleLabels:!1,canLabel:!0,minimalArea:250},...A};Z(t)},[A]),f(()=>{const t={...{nodeRadius:4,strokeWidth:4,imageCentered:!1},...x};R(t)},[x]),f(()=>{const n={bbox:!0,point:!0,line:!0,junk:!0,polygon:!0};if(h===void 0)return E(n);E(h)},[h]);const an={position:"fixed",top:0,left:0,zIndex:6e3,backgroundColor:"#ffff",width:"100%",height:"100%",padding:15};return D===void 0?d("div",{className:"d-flex justify-content-center",children:d(P,{color:"primary",style:{width:"5rem",height:"5rem"}})}):J("div",{style:{...T?an:{},width:"100%",height:"100%"},children:[d("div",{ref:I,style:{marginBottom:10},children:d(fn,{annotationSettings:k,allowedTools:D,additionalButtons:W,isDisabled:w,isFullscreen:T,isImageJunk:B,imageLabelIds:nn,possibleLabels:b,selectedTool:j,onImageLabelsChanged:n=>{p(n),K(n)},onSetIsFullscreen:tn,onSetIsImageJunk:n=>{F(n),Q(n)},onSetSelectedTool:N,onShouldDeleteSelectedAnnotation:en})}),J("div",{style:O,children:[w&&d("div",{className:"d-flex justify-content-center",children:d(P,{color:"primary",style:{width:"5rem",height:"5rem",marginTop:200}})}),u&&i&&d(ln,{annotations:i,annotationSettings:k,defaultLabelId:q,image:u,isFullscreen:T,isImageJunk:B,isPolygonSelectionMode:y,selectedAnnotation:r,selectedAnnoTool:j,polygonOperationResult:l,possibleLabels:b,uiConfig:$,onAnnoCreated:n=>{const t=[...i];t.push(n),a(t),C(n),H(n,t)},onAnnoChanged:n=>{const t=i.findIndex(o=>o.internalId===n.internalId);if(t===-1)return;const e=[...i];e[t]=n,a(e),V(n,e)},onAnnoCreationFinished:(n,t)=>{const e=[...i];if(y&&(l==null?void 0:l.annotationsToDelete)!==void 0&&(l.annotationsToDelete.push(r),l.annotationsToDelete.forEach(o=>{v(o.internalId);const m=e.findIndex(rn=>rn.internalId===o.internalId);e.splice(m,1)})),t)e.push(n);else{const o=i.findIndex(m=>m.internalId===n.internalId);e[o]=n}a(e),n.status=In.CREATED,M(n,e)},onAnnoEditing:n=>{const t=[...i],e=t.findIndex(o=>o.internalId===(r==null?void 0:r.internalId));t[e]=n,a(t)},onNotification:X,onRequestNewAnnoId:sn,onSelectAnnotation:n=>{C(n),Y(n)},onSetSelectedTool:N,onShouldDeleteAnno:v})]})]})};export{gn as default};
@@ -1 +1 @@
1
- import{jsxs as g,jsx as r}from"react/jsx-runtime";import{CRow as j,CCol as m}from"@coreui/react";import u from"./ToolbarItems/AnnoToolSelector.js";import T from"./ToolbarItems/ImageTools.js";import k from"./ToolbarItems/AccessibilityTools.js";const R=({annotationSettings:t,allowedTools:e,additionalButtons:x,isImageJunk:s=!1,imageLabelIds:c=[],isDisabled:o=!1,isFullscreen:l=!1,possibleLabels:f,selectedTool:n,onImageLabelsChanged:p=()=>{},onSetIsFullscreen:a=()=>{},onSetIsImageJunk:i=()=>{},onSetSelectedTool:h=()=>{},onShouldDeleteSelectedAnnotation:C=()=>{}})=>g(j,{xs:{gutterY:2},children:[r(m,{xs:4,sm:2,xxl:2,children:r(T,{canJunk:e.junk,isImageJunk:s,imageLabelIds:c,isDisabled:o,isFullscreen:l,possibleLabels:f,onImageLabelsChanged:p,onSetIsImageJunk:i})}),r(m,{xs:2,lg:2,children:r(k,{isDisabled:o,isFullscreen:l,onSetIsFullscreen:a})}),t.canCreate&&r(m,{xs:8,sm:5,md:4,xl:3,xxl:3,children:r(u,{allowedTools:e,isDisabled:o,selectedTool:n,onSetSelectedTool:h,onShouldDeleteSelectedAnnotation:C})}),x&&x]});export{R as default};
1
+ import{jsxs as j,jsx as o}from"react/jsx-runtime";import{CRow as C,CCol as e}from"@coreui/react";import g from"./ToolbarItems/AnnoToolSelector.js";import y from"./ToolbarItems/ImageTools.js";import T from"./ToolbarItems/AccessibilityTools.js";const J=({annotationSettings:c,allowedTools:t,additionalButtons:n,isImageJunk:m=!1,imageLabelIds:f=[],isDisabled:r=!1,isFullscreen:a=!1,possibleLabels:l,selectedTool:s,onImageLabelsChanged:p=()=>{},onSetIsFullscreen:x=()=>{},onSetIsImageJunk:i=()=>{},onSetSelectedTool:u=()=>{},onShouldDeleteSelectedAnnotation:h=()=>{}})=>j(C,{className:"d-flex justify-content-center flex-wrap align-items-center gap-0 py-2 px-4",children:[o(e,{xs:"auto",children:o(y,{canJunk:t.junk,isImageJunk:m,imageLabelIds:f,isDisabled:r,isFullscreen:a,possibleLabels:l,onImageLabelsChanged:p,onSetIsImageJunk:i})}),o(e,{xs:"auto",children:o(T,{isDisabled:r,isFullscreen:a,onSetIsFullscreen:x})}),c.canCreate&&o(e,{xs:"auto",children:o(g,{allowedTools:t,isDisabled:r,selectedTool:s,onSetSelectedTool:u,onShouldDeleteSelectedAnnotation:h})}),n&&n]});export{J as default};
@@ -0,0 +1,12 @@
1
+ import { Label } from '../../../types';
2
+ type ImageLabelInputProps = {
3
+ defaultLabelId?: number;
4
+ isDisabled: boolean;
5
+ isVisible: boolean;
6
+ selectedLabelsIds: number[];
7
+ possibleLabels: Label[];
8
+ isMultilabel?: boolean;
9
+ onLabelSelect: (selectedLabelIds: number[]) => void;
10
+ };
11
+ declare const ImageLabelInput: ({ isDisabled, isVisible, selectedLabelsIds, possibleLabels, isMultilabel, onLabelSelect, }: ImageLabelInputProps) => import("react/jsx-runtime").JSX.Element;
12
+ export default ImageLabelInput;
@@ -0,0 +1 @@
1
+ import{jsxs as s,jsx as o}from"react/jsx-runtime";import{useState as C}from"react";import{CDropdown as w,CDropdownToggle as L,CDropdownMenu as v,CFormInput as x,CDropdownDivider as y,CDropdownItem as d}from"@coreui/react";import{FontAwesomeIcon as D}from"@fortawesome/react-fontawesome";import{faTag as T}from"@fortawesome/free-solid-svg-icons";import F from"./TagLabel.js";const B=({isDisabled:c,isVisible:m,selectedLabelsIds:t,possibleLabels:n,isMultilabel:p=!1,onLabelSelect:u})=>{const[i,f]=C(""),a=n.filter(e=>e.name.toLowerCase().includes(i.toLowerCase())),g=e=>{let r=[];if(p){r=[...t];const l=t.indexOf(e.id);l!==-1?r.splice(l,1):r.push(e.id)}else r=[e.id];u(r)},h=()=>n.filter(r=>t.includes(r.id));return s(w,{visible:m,autoClose:!1,children:[o(L,{variant:"outline",caret:!1,color:c?"secondary":"primary",style:{paddingTop:0,paddingBottom:0},as:"div",children:t.length===0?o("div",{style:{marginTop:6},children:o(D,{icon:T,size:"lg"})}):h().map(r=>o(F,{name:r.name,color:r.color,size:25,triangleSize:17,style:{marginLeft:1,marginTop:5}},r.name))}),s(v,{children:[o("div",{className:"px-3 py-2",children:o(x,{placeholder:"Filter label...",value:i,onChange:e=>f(e.target.value),autoFocus:!0})}),o(y,{}),a.length>0?a.map(e=>o(d,{onClick:()=>g(e),children:e.name},e.id)):o(d,{disabled:!0,children:"No results"})]})]})};export{B as default};
@@ -1 +1 @@
1
- import{jsxs as u,jsx as o}from"react/jsx-runtime";import{CButtonGroup as b,CPopover as v,CButton as r}from"@coreui/react";import{faTag as g,faBan as h}from"@fortawesome/free-solid-svg-icons";import{FontAwesomeIcon as i}from"@fortawesome/react-fontawesome";import x from"./ImageToolItems/ImageLabel.js";import{useState as I,useEffect as y}from"react";const S=({canJunk:a,isDisabled:t=!1,isFullscreen:s=!1,isImageJunk:l=!1,imageLabelIds:n=[],possibleLabels:m,onImageLabelsChanged:p=()=>{},onSetIsImageJunk:c=()=>{}})=>{const[f,e]=I(!1),d={"--cui-popover-max-width":"800px",zIndex:7e3};return y(()=>{e(!1)},[s]),u(b,{role:"group","aria-label":"Image Tools",children:[o(v,{placement:"bottom",visible:f,onShow:()=>e(!0),onHide:()=>e(!1),content:o(x,{selectedLabelIds:n,possibleLabels:m,onImageLabelsChanged:p}),style:d,children:o(r,{color:"primary",variant:"outline",disabled:t,children:o(i,{icon:g,size:"lg"})})}),a&&o(r,{color:"primary",variant:l?void 0:"outline",disabled:t,onClick:()=>c(!l),children:o(i,{icon:h,size:"lg"})})]})};export{S as default};
1
+ import{jsxs as u,jsx as e}from"react/jsx-runtime";import{CButtonGroup as c,CButton as b}from"@coreui/react";import{faBan as d}from"@fortawesome/free-solid-svg-icons";import{FontAwesomeIcon as I}from"@fortawesome/react-fontawesome";import{useState as g,useEffect as L}from"react";import x from"./ImageToolItems/ImageLabelInput.js";const P=({canJunk:a,isDisabled:o=!1,isFullscreen:s=!1,isImageJunk:l=!1,imageLabelIds:i=[],possibleLabels:t,onImageLabelsChanged:f=()=>{},onSetIsImageJunk:m=()=>{}})=>{const[n,r]=g(!1);return L(()=>{r(!1)},[s]),u(c,{role:"group","aria-label":"Image Tools",children:[t&&e(x,{isDisabled:o,isMultilabel:!0,isVisible:n,selectedLabelsIds:i,possibleLabels:t,onLabelSelect:p=>{r(!1),f(p)}}),a&&e(b,{color:"primary",variant:l?void 0:"outline",disabled:o,onClick:()=>m(!l),children:e(I,{icon:d,size:"lg"})})]})};export{P as default};
@@ -1,11 +1,7 @@
1
1
  import { StoryObj } from '@storybook/react';
2
2
  declare const meta: {
3
3
  title: string;
4
- component: ({ possibleLabels, selectedLabelIds, onImageLabelsChanged, }: {
5
- possibleLabels: import('../../..').Label[];
6
- selectedLabelIds?: number[];
7
- onImageLabelsChanged?: (selectedImageIds: number[]) => void;
8
- }) => import("react/jsx-runtime").JSX.Element;
4
+ component: any;
9
5
  argTypes: {};
10
6
  parameters: {
11
7
  layout: string;
package/dist/types.d.ts CHANGED
@@ -22,7 +22,6 @@ export type ExternalAnnotation = {
22
22
  status: AnnotationStatus;
23
23
  labelIds: number[];
24
24
  type: AnnotationTool;
25
- timestamp?: DOMHighResTimeStamp;
26
25
  };
27
26
  export type Label = {
28
27
  id: number;
@@ -0,0 +1,4 @@
1
+ declare const _default: {
2
+ getRoundedDuration: (startTimestamp: number, stopTimestamp: number) => number;
3
+ };
4
+ export default _default;
@@ -0,0 +1 @@
1
+ const r=(t,o)=>{const n=((o-t)/1e3).toFixed(2);return parseFloat(n)},u={getRoundedDuration:r};export{u as default};
@@ -1,2 +1,3 @@
1
1
  import { uiConfig, SIA_INITIAL_UI_CONFIG } from './uiConfig';
2
- export { uiConfig, SIA_INITIAL_UI_CONFIG };
2
+ import { default as TimeUtils } from './TimeUtils';
3
+ export { uiConfig, SIA_INITIAL_UI_CONFIG, TimeUtils };
@@ -1 +1 @@
1
- import{SIA_INITIAL_UI_CONFIG as _,uiConfig as f}from"./uiConfig.js";export{_ as SIA_INITIAL_UI_CONFIG,f as uiConfig};
1
+ import{SIA_INITIAL_UI_CONFIG as e,uiConfig as f}from"./uiConfig.js";import{default as r}from"./TimeUtils.js";export{e as SIA_INITIAL_UI_CONFIG,r as TimeUtils,f as uiConfig};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lost-sia",
3
- "version": "2.0.1-alpha10",
3
+ "version": "2.0.1-alpha12",
4
4
  "description": "Single Image Annotation Tool",
5
5
  "license": "MIT",
6
6
  "repository": "l3p-cv/lost-sia",
@@ -6,14 +6,13 @@ import { Point } from "../../types";
6
6
  class Annotation {
7
7
  internalId: number;
8
8
  externalId?: string;
9
- annoTime?: number;
9
+ annoTime: number;
10
10
  coordinates: Point[];
11
11
  labelIds?: number[];
12
12
  mode: AnnotationMode; // do we even need this globally? - only really used inside AnnotationComponent
13
13
  selectedNode: number;
14
14
  status: AnnotationStatus;
15
15
  type: AnnotationTool;
16
- timestamp?: DOMHighResTimeStamp;
17
16
 
18
17
  constructor(
19
18
  internalId: number,
@@ -31,7 +30,6 @@ class Annotation {
31
30
  this.status = status;
32
31
  this.coordinates = coordinates;
33
32
  this.selectedNode = 1;
34
- this.timestamp = performance.now();
35
33
  this.annoTime = 0.0;
36
34
  }
37
35
  }
@@ -10,6 +10,7 @@ import Polygon from "./tools/Polygon";
10
10
  import { useEffect, useRef, useState } from "react";
11
11
  import { AnnotationSettings, Label, Point, SIANotification } from "../../types";
12
12
  import AnnotationMode from "../../models/AnnotationMode";
13
+ import TimeUtils from "../../utils/TimeUtils";
13
14
 
14
15
  type AnnotationComponentProps = {
15
16
  scaledAnnotation: Annotation;
@@ -57,6 +58,13 @@ const AnnotationComponent = ({
57
58
  );
58
59
  const [isDragging, setIsDragging] = useState<boolean>(false);
59
60
 
61
+ const annoTimestampRef = useRef<number | undefined>(undefined);
62
+ const [annoTimestamp, setAnnoTimestamp] = useState<number | undefined>();
63
+
64
+ useEffect(() => {
65
+ annoTimestampRef.current = annoTimestamp;
66
+ }, [annoTimestamp]);
67
+
60
68
  /**
61
69
  * during user editing of the annotation, multiple events are fired by the children
62
70
  * onMoving for updating the data
@@ -128,21 +136,40 @@ const AnnotationComponent = ({
128
136
 
129
137
  const onMoving = (newCoords: Point[]) => {
130
138
  if (
131
- annotationMode !== AnnotationMode.CREATE &&
132
- annotationMode !== AnnotationMode.ADD
133
- )
139
+ ![
140
+ AnnotationMode.ADD,
141
+ AnnotationMode.CREATE,
142
+ AnnotationMode.MOVE,
143
+ ].includes(annotationMode)
144
+ ) {
134
145
  setAnnotationMode(AnnotationMode.MOVE);
135
146
 
147
+ setAnnoTimestamp(performance.now());
148
+ }
149
+
136
150
  setCoordinates(newCoords);
137
151
  };
138
152
 
139
153
  const onMoved = () => {
140
154
  setAnnotationMode(AnnotationMode.VIEW);
141
155
 
156
+ const annoEditDuration: number = TimeUtils.getRoundedDuration(
157
+ annoTimestampRef.current,
158
+ performance.now(),
159
+ );
160
+
161
+ // add annotation time (or set it if there was no time before)
162
+ // null seems to be a number in the JS world
163
+ const newAnnoTime: number =
164
+ isNaN(scaledAnnotation.annoTime) || scaledAnnotation.annoTime === null
165
+ ? annoEditDuration
166
+ : scaledAnnotation.annoTime + annoEditDuration;
167
+
142
168
  // moving finished - send event to canvas
143
169
  onAnnoChanged({
144
170
  ...scaledAnnotation,
145
171
  coordinates: coordinatesRef.current,
172
+ annoTime: newAnnoTime,
146
173
  });
147
174
  };
148
175
 
@@ -173,10 +200,7 @@ const AnnotationComponent = ({
173
200
  svgScale={svgScale}
174
201
  svgTranslation={svgTranslation}
175
202
  style={annotationStyle}
176
- onMoving={(newPoint: Point) => {
177
- setAnnotationMode(AnnotationMode.MOVE);
178
- setCoordinates([newPoint]);
179
- }}
203
+ onMoving={(point: Point) => onMoving([point])}
180
204
  onMoved={onMoved}
181
205
  onIsDraggingStateChanged={setIsDragging}
182
206
  />
@@ -30,6 +30,7 @@ import { IconProp } from "@fortawesome/fontawesome-svg-core";
30
30
  import AnnotationStatus from "../models/AnnotationStatus";
31
31
  import transform2 from "../utils/transform2";
32
32
  import NotificationType from "../models/NotificationType";
33
+ import TimeUtils from "../utils/TimeUtils";
33
34
 
34
35
  type CanvasProps = {
35
36
  annotations?: Annotation[];
@@ -91,6 +92,7 @@ const Canvas = ({
91
92
  onShouldDeleteAnno,
92
93
  }: CanvasProps) => {
93
94
  const [editorMode, setEditorMode] = useState<EditorModes>(EditorModes.VIEW);
95
+ const [annoTimestamp, setAnnoTimestamp] = useState<number | undefined>();
94
96
 
95
97
  // remember which label was added last
96
98
  const [currentLabelId, setCurrentLabelId] = useState<number | undefined>(
@@ -222,6 +224,8 @@ const Canvas = ({
222
224
  percentagedInitialCoords,
223
225
  );
224
226
 
227
+ setAnnoTimestamp(performance.now());
228
+
225
229
  // automatically select the last used label
226
230
  if (currentLabelId !== undefined) newAnnotation.labelIds = [currentLabelId];
227
231
 
@@ -233,9 +237,10 @@ const Canvas = ({
233
237
  if (selectedAnnoTool === AnnotationTool.Point) {
234
238
  // onFinishCreateAnno assumes coordinates are in stage
235
239
  // quickly convert them before calling it
236
- const newPointAnnotation = {
240
+ const newPointAnnotation: Annotation = {
237
241
  ...newAnnotation,
238
242
  coordinates: [antiScaledMouseStagePosition],
243
+ annoTime: 0, // its literally one frame
239
244
  };
240
245
  onFinishCreateAnno(newPointAnnotation);
241
246
  }
@@ -547,14 +552,12 @@ const Canvas = ({
547
552
 
548
553
  useEffect(() => {
549
554
  if (!isPolygonSelectionMode) return;
550
-
551
- const newAnnotationInternalId: number = onRequestNewAnnoId();
552
-
553
555
  if (polygonOperationResult.polygonsToCreate === undefined) return;
554
556
 
555
557
  // create all polygons calculated from the outside world
556
558
  polygonOperationResult.polygonsToCreate.forEach(
557
559
  (polygonToCreate: ToolCoordinates) => {
560
+ const newAnnotationInternalId: number = onRequestNewAnnoId();
558
561
  const newAnnotation: Annotation = new Annotation(
559
562
  newAnnotationInternalId,
560
563
  polygonToCreate.type,
@@ -575,7 +578,19 @@ const Canvas = ({
575
578
  const onFinishCreateAnno = (fullyCreatedAnnotation: Annotation) => {
576
579
  setEditorMode(EditorModes.VIEW);
577
580
 
578
- fullyCreatedAnnotation.mode = AnnotationMode.VIEW;
581
+ const newAnnotation: Annotation = {
582
+ ...fullyCreatedAnnotation,
583
+ mode: AnnotationMode.VIEW,
584
+ };
585
+
586
+ // handle annoTime (not for points though - they are created in only one frame)
587
+ if (fullyCreatedAnnotation.type !== AnnotationTool.Point) {
588
+ const annoEditDuration: number = TimeUtils.getRoundedDuration(
589
+ annoTimestamp,
590
+ performance.now(),
591
+ );
592
+ newAnnotation.annoTime = annoEditDuration;
593
+ }
579
594
 
580
595
  // convert the coordinates from our local scaled sytem into the percentaged one
581
596
  const percentagedCoordinates =
@@ -584,15 +599,15 @@ const Canvas = ({
584
599
  imageToStageFactor,
585
600
  imgSize,
586
601
  );
587
- fullyCreatedAnnotation.coordinates = percentagedCoordinates;
602
+ newAnnotation.coordinates = percentagedCoordinates;
588
603
 
589
- onAnnoChanged(fullyCreatedAnnotation);
604
+ onAnnoChanged(newAnnotation);
590
605
 
591
- // if point: inform annotation that we just created it
592
- onAnnoCreationFinished(
593
- fullyCreatedAnnotation,
594
- selectedAnnoTool === AnnotationTool.Point,
595
- );
606
+ // inform annotation that we just created it
607
+ const hasAnnoJustBeenCreated: boolean =
608
+ selectedAnnoTool === AnnotationTool.Point || isPolygonSelectionMode;
609
+
610
+ onAnnoCreationFinished(newAnnotation, hasAnnoJustBeenCreated);
596
611
  };
597
612
 
598
613
  const onKeyDown = (e: KeyboardEvent) => {
@@ -756,6 +771,10 @@ const Canvas = ({
756
771
  coordinates: percentagedCoordinates,
757
772
  };
758
773
 
774
+ // mark loaded annotations as changed (they wont be saved otherwise)
775
+ if (newAnnotation.status === AnnotationStatus.LOADED)
776
+ newAnnotation.status = AnnotationStatus.CHANGED;
777
+
759
778
  // send event to parent component
760
779
  onAnnoChanged(newAnnotation);
761
780
  };
@@ -764,21 +783,29 @@ const Canvas = ({
764
783
  // hide all annotations when image is moved
765
784
  if (editorMode === EditorModes.CAMERA_MOVE) return <></>;
766
785
 
786
+ const editorModesOtherAnnosShouldBeHiddenIn = [
787
+ EditorModes.CREATE,
788
+ EditorModes.ADD,
789
+ EditorModes.MOVE,
790
+ ];
791
+
792
+ const shouldHideOtherAnnos: boolean =
793
+ editorModesOtherAnnosShouldBeHiddenIn.includes(editorMode);
794
+
767
795
  // draw the annotation using the AnnotationComponent and the scaled coordinates
768
796
  const annos: ReactElement[] = scaledAnnotations.map(
769
797
  (scaledAnnotation: Annotation): ReactElement => {
770
798
  // only show selected anno in specific editor modes
771
- const editorModesOtherAnnosShouldBeHiddenIn = [
772
- EditorModes.CREATE,
773
- EditorModes.ADD,
774
- EditorModes.MOVE,
775
- ];
776
-
777
- if (
778
- editorModesOtherAnnosShouldBeHiddenIn.includes(editorMode) &&
779
- scaledAnnotation.internalId !== selectedAnnotation?.internalId
780
- )
781
- return <></>;
799
+ const isAnnoSelected: boolean =
800
+ scaledAnnotation.internalId === selectedAnnotation?.internalId;
801
+
802
+ if (shouldHideOtherAnnos && !isAnnoSelected)
803
+ return (
804
+ // yes, this is for returning nothing
805
+ // we still need to provide a key, otherwise we got 10 nothings and react cannot differ between them (it wants to)
806
+ // use an empty svg g element because <></> cannot have a key
807
+ <g key={`annotationComponent_${scaledAnnotation.internalId}`} />
808
+ );
782
809
 
783
810
  return (
784
811
  <AnnotationComponent
@@ -791,13 +818,10 @@ const Canvas = ({
791
818
  pageToStageOffset={pageToStageOffset}
792
819
  nodeRadius={uiConfig.nodeRadius}
793
820
  strokeWidth={uiConfig.strokeWidth}
794
- isSelected={
795
- scaledAnnotation.internalId === selectedAnnotation?.internalId
796
- }
821
+ isSelected={isAnnoSelected}
797
822
  isDisabled={
798
823
  // dont let annotation be selected twice in polygon selection mode
799
- isPolygonSelectionMode &&
800
- scaledAnnotation.internalId === selectedAnnotation?.internalId
824
+ isPolygonSelectionMode && isAnnoSelected
801
825
  }
802
826
  onFinishAnnoCreate={onFinishCreateAnno}
803
827
  onLabelIconClicked={() => setIsLabelInputVisible(true)}
@@ -858,7 +882,7 @@ const Canvas = ({
858
882
  left: labelInputPosition?.x !== undefined ? labelInputPosition.x : 0,
859
883
  top: labelInputPosition?.y !== undefined ? labelInputPosition.y : 0,
860
884
  display: labelInputPosition?.y !== undefined ? "inherit" : "none",
861
- zIndex: 7000,
885
+ zIndex: isLabelInputVisible ? 7000 : -1,
862
886
  }}
863
887
  >
864
888
  <LabelInput
package/src/Sia2.tsx CHANGED
@@ -158,10 +158,6 @@ const Sia2 = ({
158
158
  status: externalAnno.status,
159
159
  annoTime:
160
160
  externalAnno.annoTime !== undefined ? externalAnno.annoTime : 0.0,
161
- timestamp:
162
- externalAnno.timestamp !== undefined
163
- ? externalAnno.timestamp
164
- : performance.now(),
165
161
  };
166
162
 
167
163
  return _anno;
@@ -206,6 +202,10 @@ const Sia2 = ({
206
202
  createInitialAnnotations();
207
203
  }, [initialAnnotations]);
208
204
 
205
+ useEffect(() => {
206
+ setImageLabelIds(initialImageLabelIds);
207
+ }, [initialImageLabelIds]);
208
+
209
209
  // update annotation settings if changed in the parent
210
210
  useEffect(() => {
211
211
  const defaultAnnotationSettigs: AnnotationSettings = {
@@ -364,6 +364,10 @@ const Sia2 = ({
364
364
  const annoListIndex: number = annotations.findIndex(
365
365
  (anno) => anno.internalId === changedAnno.internalId,
366
366
  );
367
+
368
+ // only fire event if item found
369
+ if (annoListIndex === -1) return;
370
+
367
371
  const _annotations: Annotation[] = [...annotations];
368
372
  _annotations[annoListIndex] = changedAnno;
369
373
  setAnnotations(_annotations);
@@ -373,7 +377,7 @@ const Sia2 = ({
373
377
  }}
374
378
  onAnnoCreationFinished={(
375
379
  changedAnno: Annotation,
376
- hasAnnotationExisted: boolean,
380
+ hasAnnoJustBeenCreated: boolean,
377
381
  ) => {
378
382
  // update annotation list
379
383
  const _annotations: Annotation[] = [...annotations];
@@ -404,18 +408,10 @@ const Sia2 = ({
404
408
  },
405
409
  );
406
410
  }
407
-
408
- // the polygon selection mode hands annotations to SIA in one single frame
409
- // add the new annotation here
410
- _annotations.push(changedAnno);
411
411
  }
412
412
 
413
- // point annotations are created in one frame
414
- // they dont exist in the annotations list yet, so just append them
415
- // if (changedAnno.type === AnnotationTool.Point)
416
-
417
413
  // are we just marking an existing annotation as finished or did we created it in the same frame
418
- if (hasAnnotationExisted) _annotations.push(changedAnno);
414
+ if (hasAnnoJustBeenCreated) _annotations.push(changedAnno);
419
415
  else {
420
416
  // all other annotation types
421
417
  const annoListIndex: number = annotations.findIndex(
@@ -442,10 +438,7 @@ const Sia2 = ({
442
438
 
443
439
  _annotations[selectedAnnotationId] = annotation;
444
440
 
445
- // _annotations.push(annotation);
446
441
  setAnnotations(_annotations);
447
- // setSelectedAnnotation(annotation);
448
- // onAnnoCreated(annotation, _annotations);
449
442
  }}
450
443
  onNotification={onNotification}
451
444
  onRequestNewAnnoId={createNewInternalAnnotationId}
@@ -41,8 +41,8 @@ const Toolbar = ({
41
41
  onShouldDeleteSelectedAnnotation = () => {},
42
42
  }: ToolbarProps) => {
43
43
  return (
44
- <CRow xs={{ gutterY: 2 }}>
45
- <CCol xs={4} sm={2} xxl={2}>
44
+ <CRow className="d-flex justify-content-center flex-wrap align-items-center gap-0 py-2 px-4">
45
+ <CCol xs="auto">
46
46
  <ImageTools
47
47
  canJunk={allowedTools.junk}
48
48
  isImageJunk={isImageJunk}
@@ -55,7 +55,7 @@ const Toolbar = ({
55
55
  />
56
56
  </CCol>
57
57
 
58
- <CCol xs={2} lg={2}>
58
+ <CCol xs="auto">
59
59
  <AccessibilityTools
60
60
  isDisabled={isDisabled}
61
61
  isFullscreen={isFullscreen}
@@ -64,7 +64,7 @@ const Toolbar = ({
64
64
  </CCol>
65
65
 
66
66
  {annotationSettings.canCreate && (
67
- <CCol xs={8} sm={5} md={4} xl={3} xxl={3}>
67
+ <CCol xs="auto">
68
68
  <AnnoToolSelector
69
69
  allowedTools={allowedTools}
70
70
  isDisabled={isDisabled}
@@ -0,0 +1,125 @@
1
+ import { useState } from "react";
2
+ import {
3
+ CDropdown,
4
+ CDropdownMenu,
5
+ CDropdownItem,
6
+ CFormInput,
7
+ CDropdownDivider,
8
+ CDropdownToggle,
9
+ } from "@coreui/react";
10
+ import { Label } from "../../../types";
11
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
12
+ import { faTag } from "@fortawesome/free-solid-svg-icons";
13
+ import { IconProps } from "semantic-ui-react";
14
+ import TagLabel from "./TagLabel";
15
+
16
+ type ImageLabelInputProps = {
17
+ defaultLabelId?: number;
18
+ isDisabled: boolean;
19
+ isVisible: boolean;
20
+ selectedLabelsIds: number[];
21
+ possibleLabels: Label[];
22
+ isMultilabel?: boolean;
23
+ onLabelSelect: (selectedLabelIds: number[]) => void;
24
+ };
25
+
26
+ const ImageLabelInput = ({
27
+ isDisabled,
28
+ isVisible,
29
+ selectedLabelsIds,
30
+ possibleLabels,
31
+ isMultilabel = false,
32
+ onLabelSelect,
33
+ }: ImageLabelInputProps) => {
34
+ const [filter, setFilter] = useState("");
35
+
36
+ const filteredLabels: Label[] = possibleLabels.filter((label: Label) =>
37
+ label.name.toLowerCase().includes(filter.toLowerCase()),
38
+ );
39
+
40
+ const updateSelectedLabels = (clickedLabel: Label) => {
41
+ let newLabelIds: number[] = [];
42
+
43
+ if (isMultilabel) {
44
+ newLabelIds = [...selectedLabelsIds];
45
+ // check if item in list (get its index if so)
46
+ const foundIndex: number = selectedLabelsIds.indexOf(clickedLabel.id);
47
+ // add label if not in list, remove label if in list
48
+ if (foundIndex !== -1) newLabelIds.splice(foundIndex, 1);
49
+ else newLabelIds.push(clickedLabel.id);
50
+ }
51
+ // single-label: just replace list with clicked item
52
+ else newLabelIds = [clickedLabel.id];
53
+
54
+ onLabelSelect(newLabelIds);
55
+ };
56
+
57
+ const getSelectedLabels = () => {
58
+ const selectedLabels: Label[] = possibleLabels.filter((label: Label) =>
59
+ selectedLabelsIds.includes(label.id),
60
+ );
61
+
62
+ return selectedLabels;
63
+ };
64
+
65
+ const renderLabels = () => {
66
+ if (selectedLabelsIds.length === 0)
67
+ return (
68
+ <div style={{ marginTop: 6 }}>
69
+ <FontAwesomeIcon icon={faTag as IconProps} size="lg" />
70
+ </div>
71
+ );
72
+
73
+ const selectedLabels = getSelectedLabels();
74
+ return selectedLabels.map((label: Label) => (
75
+ <TagLabel
76
+ key={label.name}
77
+ name={label.name}
78
+ color={label.color}
79
+ size={25}
80
+ triangleSize={17}
81
+ style={{ marginLeft: 1, marginTop: 5 }}
82
+ />
83
+ ));
84
+ };
85
+
86
+ return (
87
+ <CDropdown visible={isVisible} autoClose={false}>
88
+ {/* this invisible toggle has to be here, othervise the menu is not showing as intended */}
89
+ <CDropdownToggle
90
+ variant="outline"
91
+ caret={false}
92
+ color={isDisabled ? "secondary" : "primary"}
93
+ style={{ paddingTop: 0, paddingBottom: 0 }}
94
+ as="div"
95
+ >
96
+ {renderLabels()}
97
+ </CDropdownToggle>
98
+ <CDropdownMenu>
99
+ <div className="px-3 py-2">
100
+ <CFormInput
101
+ placeholder="Filter label..."
102
+ value={filter}
103
+ onChange={(e) => setFilter(e.target.value)}
104
+ autoFocus
105
+ />
106
+ </div>
107
+ <CDropdownDivider />
108
+ {filteredLabels.length > 0 ? (
109
+ filteredLabels.map((label: Label) => (
110
+ <CDropdownItem
111
+ key={label.id}
112
+ onClick={() => updateSelectedLabels(label)}
113
+ >
114
+ {label.name}
115
+ </CDropdownItem>
116
+ ))
117
+ ) : (
118
+ <CDropdownItem disabled>No results</CDropdownItem>
119
+ )}
120
+ </CDropdownMenu>
121
+ </CDropdown>
122
+ );
123
+ };
124
+
125
+ export default ImageLabelInput;
@@ -1,10 +1,10 @@
1
- import { CButton, CButtonGroup, CPopover } from "@coreui/react";
2
- import { faBan, faTag } from "@fortawesome/free-solid-svg-icons";
1
+ import { CButton, CButtonGroup } from "@coreui/react";
2
+ import { faBan } from "@fortawesome/free-solid-svg-icons";
3
3
  import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
4
4
  import { IconProp } from "@fortawesome/fontawesome-svg-core";
5
- import ImageLabel from "./ImageToolItems/ImageLabel";
6
5
  import { useEffect, useState } from "react";
7
6
  import { Label } from "../../types";
7
+ import ImageLabelInput from "./ImageToolItems/ImageLabelInput";
8
8
 
9
9
  type ImageToolsProps = {
10
10
  canJunk: boolean;
@@ -30,11 +30,6 @@ const ImageTools = ({
30
30
  const [isLabelPopupVisible, setIsLabelPopupVisible] =
31
31
  useState<boolean>(false);
32
32
 
33
- const customPopoverStyle = {
34
- "--cui-popover-max-width": "800px",
35
- zIndex: 7000,
36
- };
37
-
38
33
  // close modal when the fullscreen state changes
39
34
  useEffect(() => {
40
35
  setIsLabelPopupVisible(false);
@@ -42,25 +37,19 @@ const ImageTools = ({
42
37
 
43
38
  return (
44
39
  <CButtonGroup role="group" aria-label="Image Tools">
45
- <CPopover
46
- placement="bottom"
47
- visible={isLabelPopupVisible}
48
- // make sure the visible var stays updated (otherwise we cannot close it)
49
- onShow={() => setIsLabelPopupVisible(true)}
50
- onHide={() => setIsLabelPopupVisible(false)}
51
- content={
52
- <ImageLabel
53
- selectedLabelIds={imageLabelIds}
54
- possibleLabels={possibleLabels}
55
- onImageLabelsChanged={onImageLabelsChanged}
56
- />
57
- }
58
- style={customPopoverStyle}
59
- >
60
- <CButton color="primary" variant="outline" disabled={isDisabled}>
61
- <FontAwesomeIcon icon={faTag as IconProp} size="lg" />
62
- </CButton>
63
- </CPopover>
40
+ {possibleLabels && (
41
+ <ImageLabelInput
42
+ isDisabled={isDisabled}
43
+ isMultilabel={true}
44
+ isVisible={isLabelPopupVisible}
45
+ selectedLabelsIds={imageLabelIds}
46
+ possibleLabels={possibleLabels}
47
+ onLabelSelect={(selectedLabelIds: number[]) => {
48
+ setIsLabelPopupVisible(false);
49
+ onImageLabelsChanged(selectedLabelIds);
50
+ }}
51
+ />
52
+ )}
64
53
 
65
54
  {canJunk && (
66
55
  <CButton
package/src/types.ts CHANGED
@@ -25,7 +25,6 @@ export type ExternalAnnotation = {
25
25
  status: AnnotationStatus;
26
26
  labelIds: number[];
27
27
  type: AnnotationTool;
28
- timestamp?: DOMHighResTimeStamp;
29
28
  };
30
29
 
31
30
  export type Label = {
@@ -0,0 +1,14 @@
1
+ const getRoundedDuration = (
2
+ startTimestamp: number,
3
+ stopTimestamp: number,
4
+ ): number => {
5
+ const duration: number = (stopTimestamp - startTimestamp) / 1000;
6
+ const roundedDurationString: string = duration.toFixed(2);
7
+ const roundedDuration: number = parseFloat(roundedDurationString);
8
+
9
+ return roundedDuration;
10
+ };
11
+
12
+ export default {
13
+ getRoundedDuration,
14
+ };
@@ -1,3 +1,4 @@
1
1
  import { uiConfig, SIA_INITIAL_UI_CONFIG } from "./uiConfig";
2
+ import TimeUtils from "./TimeUtils";
2
3
 
3
- export { uiConfig, SIA_INITIAL_UI_CONFIG };
4
+ export { uiConfig, SIA_INITIAL_UI_CONFIG, TimeUtils };
@@ -1,30 +0,0 @@
1
- import { Point } from '../../types';
2
- import { default as Annotation } from './Annotation';
3
- declare const _default: {
4
- addNode: (annotation: Annotation, point: Point) => {
5
- internalId: number;
6
- externalId?: string;
7
- annoTime?: number;
8
- coordinates: Point[];
9
- labelIds?: number[];
10
- mode: import('../../models').AnnotationMode;
11
- selectedNode: number;
12
- status: import('../../models').AnnotationStatus;
13
- type: import('../../models').AnnotationTool;
14
- timestamp?: DOMHighResTimeStamp;
15
- };
16
- startAnnotimeMeasure: (annotation: Annotation) => {
17
- internalId: number;
18
- externalId?: string;
19
- annoTime?: number;
20
- coordinates: Point[];
21
- labelIds?: number[];
22
- mode: import('../../models').AnnotationMode;
23
- selectedNode: number;
24
- status: import('../../models').AnnotationStatus;
25
- type: import('../../models').AnnotationTool;
26
- timestamp?: DOMHighResTimeStamp;
27
- };
28
- stopAnnotimeMeasure: (annotation: Annotation) => [Annotation, number];
29
- };
30
- export default _default;
@@ -1 +0,0 @@
1
- const a=(t,n)=>{const o={...t};return o.coordinates.push(n),o},s=t=>{const n={...t};return n.timestamp=performance.now(),n},r=t=>{const n={...t},o=performance.now(),e=(o-n.timestamp)/1e3;return n.annoTime+=e,n.timestamp=o,[n,e]},i={addNode:a,startAnnotimeMeasure:s,stopAnnotimeMeasure:r};export{i as default};
@@ -1,8 +0,0 @@
1
- import { Label } from '../../../types';
2
- type ImageLabelProps = {
3
- possibleLabels: Label[];
4
- selectedLabelIds?: number[];
5
- onImageLabelsChanged?: (selectedImageIds: number[]) => void;
6
- };
7
- declare const ImageLabel: ({ possibleLabels, selectedLabelIds, onImageLabelsChanged, }: ImageLabelProps) => import("react/jsx-runtime").JSX.Element;
8
- export default ImageLabel;
@@ -1 +0,0 @@
1
- import{jsx as e,jsxs as t}from"react/jsx-runtime";import{CContainer as a,CRow as l,CCol as n}from"@coreui/react";import m from"./TagLabel.js";import h from"../../../Canvas/LabelInput.js";const p=({possibleLabels:i,selectedLabelIds:c=[],onImageLabelsChanged:s=()=>{}})=>{const d=()=>i.filter(r=>c.includes(r.id));return e(a,{children:t(l,{children:[t(n,{children:[e(l,{children:e("b",{children:"Selected Labels:"})}),e(l,{xs:{gutterY:1},children:d().map(r=>e(n,{children:e(m,{name:r.name,color:r.color})},r.name))})]}),t(n,{children:[e(l,{children:e("b",{children:"Change selection:"})}),e(l,{style:{minWidth:250,minHeight:100},children:e(h,{isVisible:!0,isMultilabel:!0,selectedLabelsIds:c,possibleLabels:i,onLabelSelect:s})})]})]})})};export{p as default};
@@ -1,30 +0,0 @@
1
- import { Point } from "../../types";
2
- import Annotation from "./Annotation";
3
-
4
- const addNode = (annotation: Annotation, point: Point) => {
5
- const _annotation = { ...annotation };
6
- _annotation.coordinates.push(point);
7
- return _annotation;
8
- };
9
-
10
- const startAnnotimeMeasure = (annotation: Annotation) => {
11
- const _annotation = { ...annotation };
12
- _annotation.timestamp = performance.now();
13
- return _annotation;
14
- };
15
-
16
- const stopAnnotimeMeasure = (annotation: Annotation): [Annotation, number] => {
17
- const _annotation = { ...annotation };
18
- const now = performance.now();
19
- const duration = (now - _annotation.timestamp) / 1000;
20
- _annotation.annoTime += duration;
21
- _annotation.timestamp = now;
22
-
23
- return [_annotation, duration];
24
- };
25
-
26
- export default {
27
- addNode,
28
- startAnnotimeMeasure,
29
- stopAnnotimeMeasure,
30
- };
@@ -1,62 +0,0 @@
1
- import { CCol, CContainer, CRow } from "@coreui/react";
2
- import TagLabel from "./TagLabel";
3
- import LabelInput from "../../../Canvas/LabelInput";
4
- import { Label } from "../../../types";
5
-
6
- type ImageLabelProps = {
7
- possibleLabels: Label[];
8
- selectedLabelIds?: number[];
9
- onImageLabelsChanged?: (selectedImageIds: number[]) => void;
10
- };
11
-
12
- const ImageLabel = ({
13
- possibleLabels,
14
- selectedLabelIds = [],
15
- onImageLabelsChanged = () => {},
16
- }: ImageLabelProps) => {
17
- const getSelectedLabels = () => {
18
- const selectedLabels: Label[] = possibleLabels.filter((label: Label) =>
19
- selectedLabelIds.includes(label.id),
20
- );
21
-
22
- return selectedLabels;
23
- };
24
-
25
- const renderLabels = () => {
26
- const selectedLabels = getSelectedLabels();
27
- return selectedLabels.map((label: Label) => (
28
- <CCol key={label.name}>
29
- <TagLabel name={label.name} color={label.color} />
30
- </CCol>
31
- ));
32
- };
33
-
34
- return (
35
- <CContainer>
36
- <CRow>
37
- <CCol>
38
- <CRow>
39
- <b>Selected Labels:</b>
40
- </CRow>
41
- <CRow xs={{ gutterY: 1 }}>{renderLabels()}</CRow>
42
- </CCol>
43
- <CCol>
44
- <CRow>
45
- <b>Change selection:</b>
46
- </CRow>
47
- <CRow style={{ minWidth: 250, minHeight: 100 }}>
48
- <LabelInput
49
- isVisible={true}
50
- isMultilabel={true}
51
- selectedLabelsIds={selectedLabelIds}
52
- possibleLabels={possibleLabels}
53
- onLabelSelect={onImageLabelsChanged}
54
- />
55
- </CRow>
56
- </CCol>
57
- </CRow>
58
- </CContainer>
59
- );
60
- };
61
-
62
- export default ImageLabel;
@@ -1 +0,0 @@
1
- export { uiConfig, SIA_INITIAL_UI_CONFIG } from "./uiConfig";