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.
- package/dist/Annotation/logic/Annotation.d.ts +1 -2
- package/dist/Annotation/logic/Annotation.js +1 -1
- package/dist/Annotation/ui/AnnotationComponent.js +1 -1
- package/dist/Canvas/Canvas.js +1 -1
- package/dist/Sia2.js +1 -1
- package/dist/Toolbar/Toolbar.js +1 -1
- package/dist/Toolbar/ToolbarItems/ImageToolItems/ImageLabelInput.d.ts +12 -0
- package/dist/Toolbar/ToolbarItems/ImageToolItems/ImageLabelInput.js +1 -0
- package/dist/Toolbar/ToolbarItems/ImageTools.js +1 -1
- package/dist/stories/Toolbar/ImageTools/ImageLabel.stories.d.ts +1 -5
- package/dist/types.d.ts +0 -1
- package/dist/utils/TimeUtils.d.ts +4 -0
- package/dist/utils/TimeUtils.js +1 -0
- package/dist/utils/index.d.ts +2 -1
- package/dist/utils/index.js +1 -1
- package/package.json +1 -1
- package/src/Annotation/logic/Annotation.ts +1 -3
- package/src/Annotation/ui/AnnotationComponent.tsx +31 -7
- package/src/Canvas/Canvas.tsx +53 -29
- package/src/Sia2.tsx +10 -17
- package/src/Toolbar/Toolbar.tsx +4 -4
- package/src/Toolbar/ToolbarItems/ImageToolItems/ImageLabelInput.tsx +125 -0
- package/src/Toolbar/ToolbarItems/ImageTools.tsx +16 -27
- package/src/types.ts +0 -1
- package/src/utils/TimeUtils.ts +14 -0
- package/src/utils/index.ts +2 -1
- package/dist/Annotation/logic/AnnotationUtils.d.ts +0 -30
- package/dist/Annotation/logic/AnnotationUtils.js +0 -1
- package/dist/Toolbar/ToolbarItems/ImageToolItems/ImageLabel.d.ts +0 -8
- package/dist/Toolbar/ToolbarItems/ImageToolItems/ImageLabel.js +0 -1
- package/src/Annotation/logic/AnnotationUtils.ts +0 -30
- package/src/Toolbar/ToolbarItems/ImageToolItems/ImageLabel.tsx +0 -62
- 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
|
|
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
|
|
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
|
|
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};
|
package/dist/Canvas/Canvas.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{jsxs as
|
|
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
|
|
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};
|
package/dist/Toolbar/Toolbar.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{jsxs as
|
|
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
|
|
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:
|
|
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
|
@@ -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};
|
package/dist/utils/index.d.ts
CHANGED
package/dist/utils/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{SIA_INITIAL_UI_CONFIG as
|
|
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
|
@@ -6,14 +6,13 @@ import { Point } from "../../types";
|
|
|
6
6
|
class Annotation {
|
|
7
7
|
internalId: number;
|
|
8
8
|
externalId?: string;
|
|
9
|
-
annoTime
|
|
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
|
-
|
|
132
|
-
|
|
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={(
|
|
177
|
-
setAnnotationMode(AnnotationMode.MOVE);
|
|
178
|
-
setCoordinates([newPoint]);
|
|
179
|
-
}}
|
|
203
|
+
onMoving={(point: Point) => onMoving([point])}
|
|
180
204
|
onMoved={onMoved}
|
|
181
205
|
onIsDraggingStateChanged={setIsDragging}
|
|
182
206
|
/>
|
package/src/Canvas/Canvas.tsx
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
602
|
+
newAnnotation.coordinates = percentagedCoordinates;
|
|
588
603
|
|
|
589
|
-
onAnnoChanged(
|
|
604
|
+
onAnnoChanged(newAnnotation);
|
|
590
605
|
|
|
591
|
-
//
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
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
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
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
|
-
|
|
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 (
|
|
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}
|
package/src/Toolbar/Toolbar.tsx
CHANGED
|
@@ -41,8 +41,8 @@ const Toolbar = ({
|
|
|
41
41
|
onShouldDeleteSelectedAnnotation = () => {},
|
|
42
42
|
}: ToolbarProps) => {
|
|
43
43
|
return (
|
|
44
|
-
<CRow
|
|
45
|
-
<CCol xs=
|
|
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=
|
|
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=
|
|
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
|
|
2
|
-
import { faBan
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
@@ -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
|
+
};
|
package/src/utils/index.ts
CHANGED
|
@@ -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;
|
package/src/utils/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { uiConfig, SIA_INITIAL_UI_CONFIG } from "./uiConfig";
|