lost-sia 1.2.0 → 2.0.0-alpha0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +0 -0
  3. package/package.json +60 -47
  4. package/src/AnnoExampleViewer.jsx +57 -0
  5. package/src/AnnoLabelInput.jsx +99 -0
  6. package/src/AnnoToolBar.jsx +132 -0
  7. package/src/Annotation/AnnoBar.jsx +148 -0
  8. package/src/Annotation/Annotation.jsx +358 -0
  9. package/src/Annotation/Annotation.scss +47 -0
  10. package/src/Annotation/BBox.jsx +291 -0
  11. package/src/Annotation/Edge.jsx +82 -0
  12. package/src/Annotation/InfSelectionArea.jsx +71 -0
  13. package/src/Annotation/Line.jsx +60 -0
  14. package/src/Annotation/Node.jsx +278 -0
  15. package/src/Annotation/Point.jsx +196 -0
  16. package/src/Annotation/Polygon.jsx +361 -0
  17. package/src/Canvas.jsx +1839 -0
  18. package/src/ImgBar.jsx +123 -0
  19. package/src/InfoBoxes/AnnoDetails.jsx +166 -0
  20. package/src/InfoBoxes/AnnoStats.jsx +104 -0
  21. package/src/InfoBoxes/InfoBox.jsx +78 -0
  22. package/src/InfoBoxes/InfoBoxArea.jsx +155 -0
  23. package/src/InfoBoxes/LabelInfo.jsx +95 -0
  24. package/src/LabelInput.jsx +224 -0
  25. package/src/Prompt.jsx +46 -0
  26. package/src/SIA.scss +10 -0
  27. package/src/SIAFilterButton.jsx +178 -0
  28. package/src/SIASettingButton.jsx +122 -0
  29. package/src/Sia.jsx +485 -0
  30. package/src/SiaPopup.jsx +10 -0
  31. package/src/ToolBar.jsx +399 -0
  32. package/src/Toolbar.css +13 -0
  33. package/src/ToolbarItem.jsx +25 -0
  34. package/src/filterTools.js +3 -0
  35. package/src/index.js +17 -0
  36. package/src/siaDummyData.js +265 -0
  37. package/src/test.js +7 -0
  38. package/src/types/annoStatus.js +4 -0
  39. package/src/types/canvasActions.js +57 -0
  40. package/src/types/cursorstyles.js +3 -0
  41. package/src/types/modes.js +8 -0
  42. package/src/types/notificationType.js +4 -0
  43. package/src/types/toolbarEvents.js +33 -0
  44. package/src/types/tools.js +11 -0
  45. package/src/utils/annoConversion.js +115 -0
  46. package/src/utils/colorlut.js +67 -0
  47. package/src/utils/constraints.js +75 -0
  48. package/src/utils/hist.js +67 -0
  49. package/src/utils/keyActions.js +107 -0
  50. package/src/utils/mouse.js +14 -0
  51. package/src/utils/siaIcons.jsx +95 -0
  52. package/src/utils/transform.js +318 -0
  53. package/src/utils/uiConfig.js +57 -0
  54. package/src/utils/windowViewport.js +35 -0
  55. package/CHANGELOG.md +0 -163
  56. package/dist/index.css +0 -24823
  57. package/dist/index.es.js +0 -10431
  58. package/dist/index.es.js.map +0 -1
  59. package/dist/index.js +0 -10446
  60. package/dist/index.js.map +0 -1
package/src/ImgBar.jsx ADDED
@@ -0,0 +1,123 @@
1
+ import React, { useState, useEffect } from 'react'
2
+ import { Menu } from 'semantic-ui-react'
3
+
4
+ const ImgBar = ({ svg, imgLabelIds, possibleLabels, imageMeta, annos, annoTaskId, visible, onLabelUpdate, onMouseEnter, onClose }) => {
5
+ const [position, setPosition] = useState({ top: 0, left: 0 })
6
+
7
+ useEffect(() => {
8
+ if (svg) {
9
+ setPosition({
10
+ ...position,
11
+ left: svg.left,
12
+ top: svg.top,
13
+ })
14
+ }
15
+ }, [svg])
16
+
17
+ // const handleLabelUpdate = (label) => {
18
+ // if (onLabelUpdate) {
19
+ // onLabelUpdate(label)
20
+ // }
21
+ // }
22
+
23
+ // const handleClose = () => {
24
+ // if (onClose) {
25
+ // onClose()
26
+ // }
27
+ // }
28
+
29
+ const handleMouseEnter = (e) => {
30
+ if (onMouseEnter) {
31
+ onMouseEnter(e)
32
+ }
33
+ }
34
+
35
+ // renderImgLabelInput(){
36
+ // if (this.props.allowedActions.label){
37
+ // return <Menu.Item style={{padding: "5px"}}>
38
+ // <LabelInput
39
+ // // multilabels={true}
40
+ // multilabels={this.props.multilabels}
41
+ // relatedId={this.props.annos.image.id}
42
+ // visible={this.props.visible}
43
+ // onLabelUpdate={label => this.handleLabelUpdate(label)}
44
+ // possibleLabels={this.props.possibleLabels}
45
+ // initLabelIds={this.props.imgLabelIds}
46
+ // relatedId={this.props.annos.image.id}
47
+ // disabled={!this.props.allowedActions.label}
48
+ // />
49
+ // </Menu.Item>
50
+ // } else {
51
+ // return null
52
+ // }
53
+ // }
54
+
55
+ const renderImgLabels = () => {
56
+ let label = ''
57
+ if (imgLabelIds && imgLabelIds.length > 0) {
58
+ let labelObject
59
+ imgLabelIds.forEach((lbl, idx) => {
60
+ labelObject = possibleLabels.find(el => {
61
+ return el.id === lbl
62
+ })
63
+ if (idx > 0) label += ', '
64
+ label += labelObject.label
65
+ })
66
+ return <Menu.Item>
67
+ {label}
68
+ </Menu.Item>
69
+ } else {
70
+ return null
71
+ }
72
+ }
73
+
74
+ const renderImgDescription = () => {
75
+ if (imageMeta.description) {
76
+ return <Menu.Item>
77
+ {imageMeta.description}
78
+ </Menu.Item>
79
+ } else {
80
+ return null
81
+ }
82
+ }
83
+
84
+ const renderAnnoTaskId = () => {
85
+
86
+ if (!annoTaskId) return null
87
+
88
+ return <Menu.Item>
89
+ Annotask ID: {annoTaskId}
90
+ </Menu.Item>
91
+ }
92
+
93
+ if (!visible) return null
94
+ if (!imageMeta) return null
95
+
96
+ return (
97
+ <div style={{
98
+ position: 'fixed',
99
+ top: position.top,
100
+ left: position.left,
101
+ width: svg.width,
102
+ minWidth: '300px'
103
+ }}
104
+ onMouseEnter={e => { handleMouseEnter(e) }}
105
+ >
106
+ <Menu inverted style={{ opacity: 0.9, justifyContent: 'center', alignItems: 'center' }}>
107
+ {/* { renderImgLabelInput() } */}
108
+ {renderImgDescription()}
109
+ {renderAnnoTaskId()}
110
+ <Menu.Item>
111
+ {/* { annos.image.url.split('/').pop() +" (ID: " + annos.image.id + ")" } */}
112
+ {"ID: " + imageMeta.id}
113
+ </Menu.Item>
114
+ <Menu.Item>
115
+ {imageMeta.number + " / " + imageMeta.amount}
116
+ </Menu.Item>
117
+ {renderImgLabels()}
118
+ </Menu>
119
+ </div>
120
+ )
121
+ }
122
+
123
+ export default ImgBar
@@ -0,0 +1,166 @@
1
+ import React, { useState, useEffect, useRef } from 'react'
2
+ import { List, Divider, TextArea, Form, Label, Icon } from 'semantic-ui-react'
3
+ import InfoBox from './InfoBox'
4
+ import PopUp from '../SiaPopup'
5
+
6
+ const AnnoDetails = (props) => {
7
+ const [comment, setComment] = useState('')
8
+ const [example, setExample] = useState(false)
9
+ const [showSaveBtn, setShowSaveBtn] = useState(false)
10
+ const tARef = useRef()
11
+ useEffect(() => {
12
+ if (props.anno) {
13
+ if (props.anno.comment) {
14
+ setComment(props.anno.comment)
15
+ } else {
16
+ setComment('')
17
+ }
18
+ }
19
+ }, [props.anno])
20
+
21
+ useEffect(() => {
22
+ if (tARef.current) {
23
+ tARef.current.focus()
24
+ }
25
+ }, [props.commentInputTrigger])
26
+
27
+ const onDismiss = () => {
28
+ if (props.onDismiss) {
29
+ props.onDismiss()
30
+ }
31
+ }
32
+
33
+ const onCommentUpdate = () => {
34
+ if (props.onCommentUpdate) {
35
+ props.onCommentUpdate(comment)
36
+ }
37
+ setShowSaveBtn(false)
38
+ }
39
+
40
+ const onMarkExampleClick = () => {
41
+ // setExample(!example)
42
+ if (props.onMarkExample) {
43
+ props.onMarkExample(props.anno)
44
+ }
45
+ }
46
+
47
+ const renderSaveBtn = () => {
48
+ if (showSaveBtn) {
49
+ return <Label as="a" corner="right" icon="save" color="red"></Label>
50
+ }
51
+ }
52
+
53
+ const renderComment = () => {
54
+ return (
55
+ <div>
56
+ <Form onClick={() => {}}>
57
+ {renderSaveBtn()}
58
+ <TextArea
59
+ placeholder="Write a comment"
60
+ ref={tARef}
61
+ value={comment}
62
+ rows={2}
63
+ onBlur={() => onCommentUpdate()}
64
+ onFocus={() => setShowSaveBtn(true)}
65
+ onChange={(e) => setComment(e.target.value)}
66
+ ></TextArea>
67
+ </Form>
68
+ </div>
69
+ )
70
+ }
71
+ const renderLabels = () => {
72
+ if (props.anno) {
73
+ const selectedLabelIds = props.anno.labelIds
74
+ if (!selectedLabelIds) return 'No Label'
75
+
76
+ let lbls = ''
77
+ props.anno.labelIds.forEach((lbl, idx) => {
78
+ const labelObject = props.possibleLabels.find((el) => {
79
+ return el.id === lbl
80
+ })
81
+ if (idx > 0) lbls += ', '
82
+ lbls += labelObject.label
83
+ })
84
+ if (!lbls) return 'No Label'
85
+ return lbls
86
+ } else {
87
+ return 'No Label'
88
+ }
89
+ }
90
+
91
+ const renderExampleMark = () => {
92
+ if (!props.allowedToMarkExample) return null
93
+ let color = 'grey'
94
+ let iconName = 'bookmark outline'
95
+ if (props.anno) {
96
+ if (props.anno.isExample) {
97
+ color = 'yellow'
98
+ iconName = 'bookmark'
99
+ }
100
+ }
101
+ const mark = (
102
+ <Label
103
+ as="a"
104
+ color={color}
105
+ style={{ opacity: 0.8 }}
106
+ size="medium"
107
+ corner="left"
108
+ onClick={() => {
109
+ onMarkExampleClick()
110
+ }}
111
+ >
112
+ <Icon name={iconName} />
113
+ </Label>
114
+ )
115
+ return (
116
+ <PopUp
117
+ content="Mark this annotation as example for other annotators"
118
+ trigger={mark}
119
+ />
120
+ )
121
+ }
122
+
123
+ const renderDescription = () => {
124
+ if (props.anno) {
125
+ return (
126
+ <div>
127
+ {renderExampleMark()}
128
+ {/* <Header>
129
+ Labels
130
+ </Header>
131
+ <div>
132
+ {renderLabels()}
133
+ </div> */}
134
+ <List>
135
+ <List.Item icon="at" content={props.anno.id} />
136
+ <List.Item icon="tag" content={renderLabels()} />
137
+ <List.Item
138
+ icon="time"
139
+ content={`${props.anno.annoTime.toLocaleString('us', {
140
+ minimumFractionDigits: 2,
141
+ maximumFractionDigits: 2,
142
+ })} seconds`}
143
+ />
144
+ {/* <List.Item icon='time' content={props.anno.annoTime} /> */}
145
+ </List>
146
+ {/* <Divider horizontal> Comment </Divider> */}
147
+ {renderComment()}
148
+ </div>
149
+ )
150
+ } else {
151
+ return 'No annotation selected!'
152
+ }
153
+ }
154
+
155
+ return (
156
+ <InfoBox
157
+ header={'Annotation Details'}
158
+ content={renderDescription()}
159
+ visible={props.visible}
160
+ defaultPos={props.defaultPos}
161
+ onDismiss={(e) => onDismiss()}
162
+ />
163
+ )
164
+ }
165
+
166
+ export default AnnoDetails
@@ -0,0 +1,104 @@
1
+ import React, {useState, useEffect} from 'react'
2
+ // import {connect} from 'react-redux'
3
+ import { List, Label} from 'semantic-ui-react'
4
+ import InfoBox from './InfoBox'
5
+ import * as colorlut from '../utils/colorlut'
6
+ // import actions from '../../../../actions'
7
+ // import * as transform from '../utils/transform'
8
+ // const { siaShowImgBar } = actions
9
+
10
+ const AnnoStats = (props) => {
11
+
12
+ const [hideList, setHideList] = useState([])
13
+
14
+ useEffect(() => {
15
+ setHideList([])
16
+ }, [props.imgLoadCount])
17
+
18
+ const onDismiss = () => {
19
+ if (props.onDismiss){
20
+ props.onDismiss()
21
+ }
22
+ }
23
+
24
+ const onLblClick = (lbl) => {
25
+ let hideLbl = false
26
+ if (hideList.includes(lbl.id)){
27
+ setHideList(hideList.filter(e => e !== lbl.id))
28
+ hideLbl = false
29
+ } else {
30
+ // hideList.push(lbl.id)
31
+ setHideList([...hideList, lbl.id])
32
+ hideLbl = true
33
+ }
34
+ if (props.onHideLbl){
35
+ props.onHideLbl(lbl, hideLbl)
36
+ }
37
+ }
38
+
39
+ const renderRow = (s) => {
40
+
41
+ const opacity = hideList.includes(s.id) ? 0.5 : 1.0
42
+ return <List.Item key={s.id}>
43
+ <List.Content>
44
+ <Label as='a' tag style={{background:s.color, opacity:opacity}}
45
+ onClick={() => onLblClick(s)}
46
+ >
47
+ {s.label}
48
+ <Label.Detail>{s.amount}</Label.Detail>
49
+ </Label>
50
+ </List.Content>
51
+ </List.Item>
52
+ }
53
+
54
+ const renderRows = () => {
55
+ let stats = {}
56
+ props.annos.forEach(a => {
57
+ // console.log('render rows', a)
58
+ if (a.status !== 'deleted'){
59
+ a.labelIds.forEach(lblId => {
60
+ if (lblId in stats){
61
+ stats[lblId] += 1
62
+ } else {
63
+ stats[lblId] = 1
64
+ }
65
+ })
66
+ if (a.labelIds.length === 0) {
67
+ if (-1 in stats){
68
+ stats[-1] += 1
69
+ } else {
70
+ stats[-1] = 1
71
+ }
72
+ }
73
+ }
74
+
75
+ })
76
+ const res = Object.entries(stats).map(([key, value]) => {
77
+ let lbl = props.possibleLabels.find(e => {
78
+ return e.id === parseInt(key)})
79
+ if (!lbl){
80
+ lbl = {'id':-1, 'label': 'No Label', 'color':colorlut.getDefaultColor()}
81
+ }
82
+
83
+ lbl.amount = value
84
+ // return renderRow({class:idToLbl[key], amount:value, color:idToColor[key]})
85
+ return renderRow(lbl)
86
+ })
87
+ return res
88
+ }
89
+ const renderDescription = () => {
90
+ return <List>
91
+ {renderRows()}
92
+ </List>
93
+ }
94
+
95
+ return <InfoBox
96
+ header={'Annotations per Label'}
97
+ content={renderDescription()}
98
+ visible={props.visible}
99
+ defaultPos={props.defaultPos}
100
+ onDismiss={e => onDismiss()}
101
+ />
102
+ }
103
+
104
+ export default AnnoStats
@@ -0,0 +1,78 @@
1
+ import React, {Component} from 'react'
2
+ import { Header, Message, Divider } from 'semantic-ui-react'
3
+ import Draggable from 'react-draggable';
4
+
5
+ class InfoBox extends Component{
6
+
7
+ constructor(props) {
8
+ super(props)
9
+ this.state = {
10
+ style: {
11
+ position:'fixed',
12
+ top: 200,
13
+ left: 200,
14
+ width: 250
15
+ }
16
+ }
17
+ }
18
+
19
+ componentDidMount(){
20
+ this.updateStyle()
21
+ }
22
+
23
+ componentDidUpdate(prevProps){
24
+ if (this.props.defaultPos !== prevProps.defaultPos){
25
+ this.updateStyle()
26
+ }
27
+ }
28
+
29
+ handleOnStop(e){
30
+ if (this.props.onStop){
31
+ this.props.onStop(e)
32
+ }
33
+ }
34
+
35
+ updateStyle(){
36
+ this.setState({
37
+ style: {...this.state.style,
38
+ ...this.props.defaultPos}
39
+ })
40
+ }
41
+
42
+ onDismiss(e){
43
+ if (this.props.onDismiss){
44
+ this.props.onDismiss(e)
45
+ }
46
+ }
47
+
48
+
49
+ render(){
50
+ if (!this.props.visible) return null
51
+ return(
52
+ <Draggable handle=".handle" onStop={e => this.handleOnStop(e)}
53
+ >
54
+ <div
55
+ style={this.state.style}>
56
+
57
+ <Message
58
+ style={{opacity:0.98}}
59
+ onDismiss={e => {this.onDismiss(e)}}
60
+ size="small"
61
+ >
62
+ <Header textAlign='center' as='h5' className="handle" style={{cursor: 'grab'}}>{this.props.header}</Header>
63
+ {/* <Message.Header textAlign='center' className="handle" style={{cursor: 'grab'}}>{this.props.header}</Message.Header> */}
64
+ <Divider></Divider>
65
+ <Message.Content>
66
+ {this.props.content}
67
+ </Message.Content>
68
+ </Message>
69
+
70
+ </div>
71
+
72
+
73
+ </Draggable>
74
+ )
75
+ }
76
+ }
77
+
78
+ export default InfoBox
@@ -0,0 +1,155 @@
1
+ import React, {Component} from 'react'
2
+ // import {connect} from 'react-redux'
3
+ import AnnoDetails from './AnnoDetails'
4
+ import AnnoStats from './AnnoStats'
5
+ import LabelInfo from './LabelInfo'
6
+ // import actions from '../../../../actions'
7
+ // const { siaShowImgBar, siaSetUIConfig } = actions
8
+
9
+ class InfoBoxes extends Component{
10
+
11
+ constructor(props) {
12
+ super(props)
13
+ this.state = {
14
+ position: {
15
+ top: 0,
16
+ left: 0,
17
+ }
18
+ }
19
+ }
20
+
21
+ componentDidMount(){
22
+ this.updateLayout()
23
+
24
+ }
25
+
26
+ componentDidUpdate(prevProps){
27
+
28
+ if (this.props.layoutUpdate !== prevProps.layoutUpdate){
29
+ this.updateLayout()
30
+ }
31
+ if (this.props.commentInputTrigger !== prevProps.commentInputTrigger){
32
+ if (!this.props.uiConfig.annoDetails.visible){
33
+ this.showAnnoDetails(true)
34
+ }
35
+ }
36
+ }
37
+
38
+ updateLayout(){
39
+ if (this.props.container.current){
40
+ const container = this.props.container.current.getBoundingClientRect()
41
+ this.setState({
42
+ position: {...this.state.position,
43
+ left: container.right - 250,
44
+ top: container.top,
45
+ }
46
+ })
47
+ }
48
+ }
49
+
50
+ showAnnoDetails(show=true){
51
+ this.props.onUiConfigUpdate(
52
+ {...this.props.uiConfig,
53
+ annoDetails: {
54
+ ...this.props.uiConfig.annoDetails,
55
+ visible: show
56
+ }
57
+ }
58
+ )
59
+ }
60
+
61
+ onDismiss(type){
62
+ if (this.props.onUiConfigUpdate){
63
+ switch (type){
64
+ case 'AnnoDetails':
65
+ this.showAnnoDetails(false)
66
+ break
67
+ case 'LabelInfo':
68
+ this.props.onUiConfigUpdate(
69
+ {...this.props.uiConfig,
70
+ labelInfo: {
71
+ ...this.props.uiConfig.labelInfo,
72
+ visible: false
73
+ }
74
+ }
75
+ )
76
+ break
77
+ case 'AnnoStats':
78
+ this.props.onUiConfigUpdate(
79
+ {...this.props.uiConfig,
80
+ annoStats: {
81
+ ...this.props.uiConfig.annoStats,
82
+ visible: false
83
+ }
84
+ }
85
+ )
86
+ break
87
+ default:
88
+ break
89
+ }
90
+ }
91
+ }
92
+
93
+ onCommentUpdate(comment){
94
+ if (this.props.onCommentUpdate){
95
+ this.props.onCommentUpdate(comment)
96
+ }
97
+ }
98
+
99
+ onMarkExample(anno){
100
+ if (this.props.onMarkExample){
101
+ this.props.onMarkExample(anno)
102
+ }
103
+ }
104
+
105
+ onHideLbl(lbl, hide){
106
+ if(this.props.onHideLbl){
107
+ this.props.onHideLbl(lbl, hide)
108
+ }
109
+ }
110
+
111
+ render(){
112
+ if (!this.props.annos) return null
113
+ // if (!this.props.selectedAnno) return null
114
+ return(
115
+ <div >
116
+ <LabelInfo selectedAnno={this.props.selectedAnno}
117
+ possibleLabels={this.props.possibleLabels}
118
+ defaultPos={this.state.position}
119
+ onDismiss={() => this.onDismiss('LabelInfo')}
120
+ visible={this.props.uiConfig.labelInfo.visible}
121
+ onGetAnnoExample={(exampleArgs) => this.props.onGetAnnoExample ? this.props.onGetAnnoExample(exampleArgs):{} }
122
+ exampleImg={this.props.exampleImg}
123
+ />
124
+ <AnnoDetails anno={this.props.selectedAnno}
125
+ possibleLabels={this.props.possibleLabels}
126
+ defaultPos={{
127
+ left: this.state.position.left - 300,
128
+ top: this.state.position.top
129
+ }}
130
+ onDismiss={() => this.onDismiss('AnnoDetails')}
131
+ onCommentUpdate={(comment) => this.onCommentUpdate(comment)}
132
+ onMarkExample={(anno) => this.onMarkExample(anno)}
133
+ allowedToMarkExample={this.props.allowedToMarkExample}
134
+ commentInputTrigger={this.props.commentInputTrigger}
135
+ visible={this.props.uiConfig.annoDetails.visible}
136
+ />
137
+ <AnnoStats selectedAnno={this.props.selectedAnno}
138
+ annos={this.props.annos}
139
+ possibleLabels={this.props.possibleLabels}
140
+ defaultPos={{
141
+ left: this.state.position.left,
142
+ top: this.state.position.top + 400
143
+ }}
144
+ // defaultPos={this.state.position}
145
+ onDismiss={() => this.onDismiss('AnnoStats')}
146
+ onHideLbl={(lbl, hide) => this.onHideLbl(lbl, hide)}
147
+ visible={this.props.uiConfig.annoStats.visible}
148
+ imgLoadCount={this.props.imgLoadCount}
149
+ />
150
+ </div>
151
+ )
152
+ }
153
+ }
154
+
155
+ export default InfoBoxes