lost-sia 3.1.3 → 3.1.4

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 (3) hide show
  1. package/dist/Sia.js +1 -1
  2. package/package.json +1 -1
  3. package/src/Sia.tsx +71 -53
package/dist/Sia.js CHANGED
@@ -1 +1 @@
1
- import{jsx as u,jsxs as V}from"react/jsx-runtime";import{useRef as Tn,useState as a,useEffect as I}from"react";import{CSpinner as K}from"@coreui/react";import Cn from"./Canvas/Canvas.js";import _n from"./models/AnnotationTool.js";import pn from"./Toolbar/Toolbar.js";import Hn from"./models/AnnotationMode.js";import O from"./models/AnnotationStatus.js";const jn=({additionalButtons:Q,allowedTools:S,polygonOperationResult:T={annotationsToDelete:[],polygonsToCreate:[]},annotationSettings:b,uiConfig:k,defaultAnnotationTool:X,defaultLabelId:Y,image:A,isLoading:D=!1,isPolygonSelectionMode:E=!1,initialAnnotations:y=void 0,initialImageLabelIds:C=void 0,initialIsImageJunk:Z=!1,possibleLabels:F,onAnnoCreated:$=(l,p)=>{},onAnnoCreationFinished:nn=l=>{},onAnnoChanged:tn=l=>{},onAnnoDeleted:_=(l,p)=>{},onImageLabelsChanged:en=()=>{},onIsImageJunk:on=()=>{},onNotification:sn=l=>{},onSelectAnnotation:an=l=>{},onTimeTravel:rn=l=>{}})=>{const p=Tn(null),[N,J]=a(),[c,r]=a([]),[j,dn]=a(),[d,H]=a(),[h,B]=a([]),[ln,cn]=a(),[m,g]=a(),[L,R]=a(X??_n.Point),v=n=>{const t=[...n],e=[...h];d!==void 0&&e.splice(d+1),e.push(t),H(void 0),B(e)},[fn,P]=a(C),[U,W]=a(),[w,un]=a(!1),[q,z]=a([]),G=n=>{const t=c.findIndex(o=>o.internalId===n),e=[...c],s=e.splice(t,1)[0];r(e),g(void 0),v(e),_(s,e)},In=()=>{m!==void 0&&G(m.internalId)},mn=()=>{let n=0;const t=y.map(e=>({...e,internalId:n++,mode:Hn.VIEW,selectedNode:1,status:e.status,annoTime:e.annoTime??0}));z([...new Array(n).keys()]),r(t),v(t)},An=()=>{let n=0;for(;q.includes(n);)n++;const t=[...q];return t.push(n),z(t),n},hn=n=>{const t=[...c],e=t.findIndex(i=>i.internalId===m?.internalId);if(e===-1)return;const s=t.splice(e,1)[0];_(s,t);const o=[...t];o.push(n),r(o),g(n)},M=n=>{W(n),on(n)},gn=n=>{const t=d??h.length-1,e=t==h.length-1,s=t==0;if(e&&!n||s&&n)return;const o=t+(n?-1:1);H(o)},yn=n=>{const t=[],e=[],s=[];for(const o of n){const i=c.find(f=>f.internalId===o.internalId);i?JSON.stringify(i)!==JSON.stringify(o)&&s.push(o):t.push(o)}for(const o of c)n.find(f=>f.internalId===o.internalId)||e.push(o);return{addedAnnotations:t,removedAnnotations:e,changedAnnotations:s}};I(()=>{if(d==null||d<0||d>h.length-1)return;const t=[...h[d]];r(t);const e=yn(t);rn(e)},[d]),I(()=>{A===void 0&&(r([]),g(void 0),B([]),H(void 0))},[A]),I(()=>{W(Z),!(A!==void 0||y===void 0||y.length===0)&&mn()},[y]),I(()=>{P(C)},[C]),I(()=>{const t={...{canCreate:!0,canEdit:!0,canHaveMultipleLabels:!1,canLabel:!0,minimalArea:250},...b};dn(t)},[b]),I(()=>{const t={...{nodeRadius:4,strokeWidth:4,imageCentered:!1},...k};cn(t)},[k]),I(()=>{const n={bbox:!0,point:!0,line:!0,junk:!0,polygon:!0};if(S===void 0)return J(n);J(S)},[S]);const vn={position:"fixed",top:0,left:0,zIndex:1040,backgroundColor:"#ffff",width:"100%",height:"100%",padding:15},xn={flex:"1 1 auto",minHeight:0,display:"flex",flexDirection:"column"};return N===void 0?u("div",{className:"d-flex justify-content-center",children:u(K,{color:"primary",style:{width:"5rem",height:"5rem"}})}):V("div",{style:{...w?vn:{},flex:"1 1 auto",minHeight:0,display:"flex",flexDirection:"column"},children:[u("div",{ref:p,style:{marginBottom:10},children:u(pn,{annotationSettings:j,allowedTools:N,additionalButtons:Q,isDisabled:D,isFullscreen:w,isImageJunk:U,imageLabelIds:fn,possibleLabels:F,selectedTool:L,onImageLabelsChanged:n=>{P(n),en(n)},onSetIsFullscreen:un,onSetIsImageJunk:M,onSetSelectedTool:R,onShouldDeleteSelectedAnnotation:In})}),V("div",{style:xn,children:[D&&u("div",{className:"d-flex justify-content-center",children:u(K,{color:"primary",style:{width:"5rem",height:"5rem",marginTop:200}})}),A&&c&&u(Cn,{annotations:c,annotationSettings:j,defaultLabelId:Y,image:A,isFullscreen:w,isImageJunk:U,isPolygonSelectionMode:E,selectedAnnotation:m,selectedAnnoTool:L,polygonOperationResult:T,possibleLabels:F,uiConfig:ln,onAnnoCreated:n=>{r(t=>{const e=[...t,n];return $(n,e),e}),g(n)},onAnnoChanged:n=>{r(t=>{const e=t.findIndex(o=>o.internalId===n.internalId);if(e===-1)return t;const s=[...t];return s[e]=n,n.status!==O.CREATING&&v(s),s}),tn(n)},onAnnoCreationFinished:(n,t)=>{const e={...n,status:O.CREATED},s=E&&T?.annotationsToDelete!==void 0?[...T.annotationsToDelete,...m!==void 0?[m]:[]]:[];r(o=>{const i=[...o];for(const f of s){const x=i.findIndex(Sn=>Sn.internalId===f.internalId);x!==-1&&i.splice(x,1)}if(t)i.push(e);else{const f=o.findIndex(x=>x.internalId===e.internalId);i[f]=e}return v(i),i});for(const o of s)_(o);nn(e)},onAnnoEditing:hn,onSetIsImageJunk:M,onNotification:sn,onRequestNewAnnoId:An,onSelectAnnotation:n=>{g(n),an(n)},onSetSelectedTool:R,onShouldDeleteAnno:G,onTraverseAnnotationHistory:gn})]})]})};export{jn as default};
1
+ import{jsx as m,jsxs as Q}from"react/jsx-runtime";import{useRef as k,useState as r,useEffect as l}from"react";import{CSpinner as X}from"@coreui/react";import Cn from"./Canvas/Canvas.js";import _n from"./models/AnnotationTool.js";import Hn from"./Toolbar/Toolbar.js";import wn from"./models/AnnotationMode.js";import Y from"./models/AnnotationStatus.js";const jn=({additionalButtons:Z,allowedTools:T,polygonOperationResult:p={annotationsToDelete:[],polygonsToCreate:[]},annotationSettings:D,uiConfig:E,defaultAnnotationTool:$,defaultLabelId:nn,image:g,isLoading:F=!1,isPolygonSelectionMode:N=!1,initialAnnotations:v=void 0,initialImageLabelIds:C=void 0,initialIsImageJunk:tn=!1,possibleLabels:J,onAnnoCreated:en=(u,H)=>{},onAnnoCreationFinished:on=u=>{},onAnnoChanged:sn=u=>{},onAnnoDeleted:_=(u,H)=>{},onImageLabelsChanged:rn=()=>{},onIsImageJunk:an=()=>{},onNotification:dn=u=>{},onSelectAnnotation:R=u=>{},onTimeTravel:cn=u=>{}})=>{const H=k(null),[j,B]=r(),[I,f]=r([]),[L,ln]=r(),[c,w]=r(),[x,P]=r([]),[fn,un]=r(),[A,h]=r(),[U,W]=r($??_n.Point),a=k(I);l(()=>{a.current=I},[I]);const y=k(c);l(()=>{y.current=c},[c]);const S=n=>{const t=[...n];P(e=>{const s=[...e];return y.current!==void 0&&s.splice(y.current+1),s.push(t),s}),y.current=void 0,w(void 0)},[In,q]=r(C),[z,G]=r(),[b,mn]=r(!1),[M,V]=r([]),K=n=>{const t=a.current,e=t.findIndex(i=>i.internalId===n);if(e===-1)return;const s=[...t],o=s.splice(e,1)[0];a.current=s,f(s),h(void 0),S(s),_(o,s)},An=()=>{A!==void 0&&K(A.internalId)},hn=()=>{let n=0;const t=v.map(e=>({...e,internalId:n++,mode:wn.VIEW,selectedNode:1,status:e.status,annoTime:e.annoTime??0}));V([...new Array(n).keys()]),f(t),S(t)},gn=()=>{let n=0;for(;M.includes(n);)n++;const t=[...M];return t.push(n),V(t),n},vn=n=>{const t=[...a.current],e=t.findIndex(i=>i.internalId===A?.internalId);if(e===-1)return;const s=t.splice(e,1)[0];_(s,t);const o=[...t];o.push(n),f(o),h(n)},O=n=>{G(n),an(n)},xn=n=>{const t=c??x.length-1,e=t==x.length-1,s=t==0;if(e&&!n||s&&n)return;const o=t+(n?-1:1);w(o)},yn=n=>{const t=[],e=[],s=[];for(const o of n){const i=I.find(d=>d.internalId===o.internalId);i?JSON.stringify(i)!==JSON.stringify(o)&&s.push(o):t.push(o)}for(const o of I)n.find(d=>d.internalId===o.internalId)||e.push(o);return{addedAnnotations:t,removedAnnotations:e,changedAnnotations:s}};l(()=>{if(c==null||c<0||c>x.length-1)return;const t=[...x[c]];f(t);const e=yn(t);cn(e)},[c]),l(()=>{g===void 0&&(f([]),h(void 0),P([]),w(void 0))},[g]),l(()=>{G(tn),!(g!==void 0||v===void 0||v.length===0)&&hn()},[v]),l(()=>{q(C)},[C]),l(()=>{const t={...{canCreate:!0,canEdit:!0,canHaveMultipleLabels:!1,canLabel:!0,minimalArea:250},...D};ln(t)},[D]),l(()=>{const t={...{nodeRadius:4,strokeWidth:4,imageCentered:!1},...E};un(t)},[E]),l(()=>{const n={bbox:!0,point:!0,line:!0,junk:!0,polygon:!0};if(T===void 0)return B(n);B(T)},[T]);const Sn={position:"fixed",top:0,left:0,zIndex:1040,backgroundColor:"#ffff",width:"100%",height:"100%",padding:15},Tn={flex:"1 1 auto",minHeight:0,display:"flex",flexDirection:"column"};return j===void 0?m("div",{className:"d-flex justify-content-center",children:m(X,{color:"primary",style:{width:"5rem",height:"5rem"}})}):Q("div",{style:{...b?Sn:{},flex:"1 1 auto",minHeight:0,display:"flex",flexDirection:"column"},children:[m("div",{ref:H,style:{marginBottom:10},children:m(Hn,{annotationSettings:L,allowedTools:j,additionalButtons:Z,isDisabled:F,isFullscreen:b,isImageJunk:z,imageLabelIds:In,possibleLabels:J,selectedTool:U,onImageLabelsChanged:n=>{q(n),rn(n)},onSetIsFullscreen:mn,onSetIsImageJunk:O,onSetSelectedTool:W,onShouldDeleteSelectedAnnotation:An})}),Q("div",{style:Tn,children:[F&&m("div",{className:"d-flex justify-content-center",children:m(X,{color:"primary",style:{width:"5rem",height:"5rem",marginTop:200}})}),g&&I&&m(Cn,{annotations:I,annotationSettings:L,defaultLabelId:nn,image:g,isFullscreen:b,isImageJunk:z,isPolygonSelectionMode:N,selectedAnnotation:A,selectedAnnoTool:U,polygonOperationResult:p,possibleLabels:J,uiConfig:fn,onAnnoCreated:n=>{const t=[...a.current,n];a.current=t,f(t),en(n,t),h(n)},onAnnoChanged:n=>{const t=a.current.findIndex(e=>e.internalId===n.internalId);if(t!==-1){const e=[...a.current];e[t]=n,a.current=e,f(e),n.status!==Y.CREATING&&S(e)}sn(n)},onAnnoCreationFinished:(n,t)=>{const e={...n,status:Y.CREATED},s=N&&p?.annotationsToDelete!==void 0?[...p.annotationsToDelete,...A!==void 0?[A]:[]]:[],o=[...a.current];for(const i of s){const d=o.findIndex(pn=>pn.internalId===i.internalId);d!==-1&&o.splice(d,1)}if(t){const i=o.findIndex(d=>d.internalId===e.internalId);i!==-1&&o.splice(i,1),o.push(e)}else{const i=o.findIndex(d=>d.internalId===e.internalId);i!==-1&&(o[i]=e)}a.current=o,f(o),S(o),h(e),R(e);for(const i of s)_(i);on(e)},onAnnoEditing:vn,onSetIsImageJunk:O,onNotification:dn,onRequestNewAnnoId:gn,onSelectAnnotation:n=>{h(n),R(n)},onSetSelectedTool:W,onShouldDeleteAnno:K,onTraverseAnnotationHistory:xn})]})]})};export{jn as default};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lost-sia",
3
- "version": "3.1.3",
3
+ "version": "3.1.4",
4
4
  "description": "Single Image Annotation Tool",
5
5
  "license": "MIT",
6
6
  "repository": "l3p-cv/lost-sia",
package/src/Sia.tsx CHANGED
@@ -95,24 +95,39 @@ const Sia = ({
95
95
  defaultAnnotationTool ?? AnnotationTool.Point,
96
96
  )
97
97
 
98
+ // refs to always have the latest state without stale closure issues
99
+ const annotationsRef = useRef<Annotation[]>(annotations)
100
+ useEffect(() => {
101
+ annotationsRef.current = annotations
102
+ }, [annotations])
103
+
104
+ const annotationHistoryIndexRef = useRef<number | undefined>(annotationHistoryIndex)
105
+ useEffect(() => {
106
+ annotationHistoryIndexRef.current = annotationHistoryIndex
107
+ }, [annotationHistoryIndex])
108
+
98
109
  const updateAnnotationHistory = (annotations: Annotation[]) => {
99
110
  const _annotations = [...annotations]
100
- const _annotationHistory = [...annotationHistory]
101
111
 
102
- // user did some changes from within the past
103
- // time to create an alternative timeline and delete the original one
104
- if (annotationHistoryIndex !== undefined) {
105
- // remove everything after the state the user is
106
- _annotationHistory.splice(annotationHistoryIndex + 1)
107
- }
112
+ setAnnotationHistory((prevHistory) => {
113
+ const _annotationHistory = [...prevHistory]
114
+
115
+ // user did some changes from within the past
116
+ // time to create an alternative timeline and delete the original one
117
+ if (annotationHistoryIndexRef.current !== undefined) {
118
+ // remove everything after the state the user is
119
+ _annotationHistory.splice(annotationHistoryIndexRef.current + 1)
120
+ }
108
121
 
109
122
  // update the list with out latest change (it is always living in the present)
110
- _annotationHistory.push(_annotations)
123
+ _annotationHistory.push(_annotations)
124
+ return _annotationHistory
125
+ })
126
+
111
127
 
112
128
  // keep history index marker in the present
129
+ annotationHistoryIndexRef.current = undefined
113
130
  setAnnotationHistoryIndex(undefined)
114
-
115
- setAnnotationHistory(_annotationHistory)
116
131
  }
117
132
 
118
133
  // for adjusting the container/canvas size
@@ -137,17 +152,19 @@ const Sia = ({
137
152
  const [usedInternalIds, setUsedInternalIds] = useState<number[]>([])
138
153
 
139
154
  const deleteAnnotationByInternalId = (internalId: number) => {
140
- // get index of selected annotation
141
- const annoListIndex: number = annotations.findIndex(
155
+ const currentAnnotations = annotationsRef.current
156
+ const annoListIndex: number = currentAnnotations.findIndex(
142
157
  (anno) => anno.internalId === internalId,
143
158
  )
144
159
 
145
- // dereference list to force state update
146
- const _annotations: Annotation[] = [...annotations]
160
+ if (annoListIndex === -1) return
147
161
 
162
+ const _annotations: Annotation[] = [...currentAnnotations]
163
+
148
164
  // remove annotation
149
165
  const removedAnno: Annotation = _annotations.splice(annoListIndex, 1)[0]
150
166
 
167
+ annotationsRef.current = _annotations
151
168
  setAnnotations(_annotations)
152
169
  setSelectedAnnotation(undefined)
153
170
  updateAnnotationHistory(_annotations)
@@ -206,7 +223,7 @@ const Sia = ({
206
223
  }
207
224
 
208
225
  const handleAnnoEditing = (annotation: Annotation) => {
209
- const _annotations: Annotation[] = [...annotations]
226
+ const _annotations: Annotation[] = [...annotationsRef.current]
210
227
 
211
228
  // annotation is being edited - remove it from current annotations for this time
212
229
  const selectedAnnotationIndex: number = _annotations.findIndex(
@@ -498,34 +515,30 @@ const Sia = ({
498
515
  possibleLabels={possibleLabels}
499
516
  uiConfig={uiConfig}
500
517
  onAnnoCreated={(annotation: Annotation) => {
501
- setAnnotations((prev) => {
502
- const _annotations = [...prev, annotation]
503
- onAnnoCreated(annotation, _annotations)
504
- return _annotations
505
- })
518
+ const _annotations = [...annotationsRef.current, annotation]
519
+ annotationsRef.current = _annotations
520
+ setAnnotations(_annotations)
521
+ onAnnoCreated(annotation, _annotations)
506
522
  setSelectedAnnotation(annotation)
507
523
  // dont update history here - we dont have a finished anno at this point
508
524
  }}
509
525
  onAnnoChanged={(changedAnno: Annotation) => {
510
- setAnnotations((prev) => {
511
- // update annotation list
512
- const annoListIndex: number = prev.findIndex(
513
- (anno) => anno.internalId === changedAnno.internalId,
514
- )
515
-
516
- // only fire event if item found
517
- if (annoListIndex === -1) return prev
526
+ const annoListIndex: number = annotationsRef.current.findIndex(
527
+ (anno) => anno.internalId === changedAnno.internalId,
528
+ )
518
529
 
519
- const _annotations: Annotation[] = [...prev]
530
+ if (annoListIndex !== -1) {
531
+ const _annotations: Annotation[] = [...annotationsRef.current]
520
532
  _annotations[annoListIndex] = changedAnno
533
+ annotationsRef.current = _annotations
534
+ setAnnotations(_annotations)
535
+
521
536
 
522
537
  // only update history for full/finished annotations
523
538
  if (changedAnno.status !== AnnotationStatus.CREATING) {
524
539
  updateAnnotationHistory(_annotations)
525
540
  }
526
-
527
- return _annotations
528
- })
541
+ }
529
542
 
530
543
  // inform the outside world about our change
531
544
  // (kept outside the updater — side effects must not run inside setState)
@@ -551,30 +564,35 @@ const Sia = ({
551
564
  ]
552
565
  : []
553
566
 
554
- setAnnotations((prev) => {
555
- // update annotation list
556
- const _annotations: Annotation[] = [...prev]
567
+ const _annotations: Annotation[] = [...annotationsRef.current]
557
568
 
558
- // remove the source annotations that were consumed by the polygon operation
559
- for (const annotation of annosToDelete) {
560
- const annoListIndex: number = _annotations.findIndex(
561
- (anno) => anno.internalId === annotation.internalId,
562
- )
563
- if (annoListIndex !== -1) _annotations.splice(annoListIndex, 1)
564
- }
565
-
566
- if (hasAnnoJustBeenCreated) _annotations.push(finishedAnno)
567
- else {
568
- const annoListIndex: number = prev.findIndex(
569
- (anno) => anno.internalId === finishedAnno.internalId,
570
- )
571
- _annotations[annoListIndex] = finishedAnno
572
- }
569
+ // remove annotations consumed by polygon operation
570
+ for (const annotation of annosToDelete) {
571
+ const annoListIndex: number = _annotations.findIndex(
572
+ (anno) => anno.internalId === annotation.internalId,
573
+ )
574
+ if (annoListIndex !== -1) _annotations.splice(annoListIndex, 1)
575
+ }
573
576
 
574
- updateAnnotationHistory(_annotations)
577
+ if (hasAnnoJustBeenCreated) {
578
+ // replace the CREATING entry with the finished CREATED version
579
+ const existingIdx = _annotations.findIndex(
580
+ (a) => a.internalId === finishedAnno.internalId,
581
+ )
582
+ if (existingIdx !== -1) _annotations.splice(existingIdx, 1)
583
+ _annotations.push(finishedAnno)
584
+ } else {
585
+ const annoListIndex: number = _annotations.findIndex(
586
+ (anno) => anno.internalId === finishedAnno.internalId,
587
+ )
588
+ if (annoListIndex !== -1) _annotations[annoListIndex] = finishedAnno
589
+ }
575
590
 
576
- return _annotations
577
- })
591
+ annotationsRef.current = _annotations
592
+ setAnnotations(_annotations)
593
+ updateAnnotationHistory(_annotations)
594
+ setSelectedAnnotation(finishedAnno)
595
+ onSelectAnnotation(finishedAnno)
578
596
 
579
597
  // Notify the server about the deletions (outside of the updater/setState!!!)
580
598
  for (const annotation of annosToDelete) {