@abidibo/react-cam-roi 0.0.12 → 0.0.13
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/README.md +36 -10
- package/dist/Components/Button/Button.module.css +4 -2
- package/dist/Components/EnumField/index.d.ts +4 -4
- package/dist/Components/RoiEditor/Canvas.js +3 -3
- package/dist/Components/RoiEditor/Header.d.ts +2 -0
- package/dist/Components/RoiEditor/Header.js +22 -0
- package/dist/Components/RoiEditor/Header.module.css +32 -0
- package/dist/Components/RoiEditor/Hooks.js +25 -24
- package/dist/Components/RoiEditor/ParametersModalForm/index.d.ts +8 -2
- package/dist/Components/RoiEditor/ParametersModalForm/index.js +28 -19
- package/dist/Components/RoiEditor/Polygon.d.ts +4 -4
- package/dist/Components/RoiEditor/Polygon.js +5 -4
- package/dist/Components/RoiEditor/Polyline.d.ts +4 -4
- package/dist/Components/RoiEditor/Polyline.js +5 -4
- package/dist/Components/RoiEditor/Rectangle.d.ts +3 -3
- package/dist/Components/RoiEditor/Rectangle.js +5 -4
- package/dist/Components/RoiEditor/RoisInfo.d.ts +2 -0
- package/dist/Components/RoiEditor/RoisInfo.js +43 -0
- package/dist/Components/RoiEditor/ShapesList.js +51 -21
- package/dist/Components/RoiEditor/ShapesList.module.css +32 -1
- package/dist/Components/RoiEditor/Toolbar.js +5 -13
- package/dist/Components/RoiEditor/Toolbar.module.css +0 -12
- package/dist/Components/RoiEditor/Types.d.ts +4 -0
- package/dist/Components/RoiEditor/Utils.js +27 -20
- package/dist/Components/RoiEditor/index.d.ts +1 -1
- package/dist/Components/RoiEditor/index.js +7 -3
- package/dist/Components/RoleField.d.ts +7 -0
- package/dist/Components/RoleField.js +35 -0
- package/dist/Providers/EditorProvider.d.ts +2 -2
- package/dist/Providers/EditorProvider.js +2 -2
- package/dist/Providers/UiProvider.d.ts +12 -1
- package/dist/Providers/UiProvider.js +13 -1
- package/dist/Utils/index.d.ts +2 -0
- package/dist/Utils/index.js +12 -0
- package/package.json +2 -2
@@ -0,0 +1,43 @@
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
2
|
+
import { useContext } from 'react';
|
3
|
+
import { useEditorContext } from '../../Providers/EditorProvider';
|
4
|
+
import { UiContext } from '../../Providers/UiProvider';
|
5
|
+
import { OperatorEnum } from './Types';
|
6
|
+
import { formatString, humanize } from '../../Utils';
|
7
|
+
const RoisInfo = () => {
|
8
|
+
var _a;
|
9
|
+
const { strings, Typography } = useContext(UiContext);
|
10
|
+
const { configuration } = useEditorContext();
|
11
|
+
if (!((_a = configuration.rois) === null || _a === void 0 ? void 0 : _a.length))
|
12
|
+
return null;
|
13
|
+
return (_jsxs("div", { children: [_jsxs(Typography, { component: "div", children: [strings.roisToBeDrawn, ":"] }), _jsx("ul", { children: configuration.rois.map(r => {
|
14
|
+
var _a, _b;
|
15
|
+
let rule;
|
16
|
+
const data = {
|
17
|
+
role: humanize(r.role),
|
18
|
+
type: r.type,
|
19
|
+
threshold: (_a = r.multiplicity) === null || _a === void 0 ? void 0 : _a.threshold,
|
20
|
+
};
|
21
|
+
switch ((_b = r.multiplicity) === null || _b === void 0 ? void 0 : _b.operator) {
|
22
|
+
case OperatorEnum.Eq:
|
23
|
+
rule = formatString(strings.roiMultiplicityEqRule, data);
|
24
|
+
break;
|
25
|
+
case OperatorEnum.Lt:
|
26
|
+
rule = formatString(strings.roiMultiplicityLtRule, data);
|
27
|
+
break;
|
28
|
+
case OperatorEnum.Lte:
|
29
|
+
rule = formatString(strings.roiMultiplicityLteRule, data);
|
30
|
+
break;
|
31
|
+
case OperatorEnum.Gt:
|
32
|
+
rule = formatString(strings.roiMultiplicityGtRule, data);
|
33
|
+
break;
|
34
|
+
case OperatorEnum.Gte:
|
35
|
+
rule = formatString(strings.roiMultiplicityGteRule, data);
|
36
|
+
break;
|
37
|
+
default:
|
38
|
+
rule = formatString(strings.roiMultiplicityNoRule, data);
|
39
|
+
}
|
40
|
+
return (_jsx("li", { children: rule }, r.role));
|
41
|
+
}) })] }));
|
42
|
+
};
|
43
|
+
export default RoisInfo;
|
@@ -2,46 +2,76 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
2
2
|
import { useContext, useEffect, useState } from 'react';
|
3
3
|
import { useEditorContext } from '../../Providers/EditorProvider';
|
4
4
|
import { UiContext } from '../../Providers/UiProvider';
|
5
|
-
import { css } from '../../Utils';
|
5
|
+
import { css, humanize } from '../../Utils';
|
6
6
|
import Dispatcher from '../../Utils/Dispatcher';
|
7
7
|
import ParametersModalForm from './ParametersModalForm';
|
8
8
|
import styles from './ShapesList.module.css';
|
9
9
|
const ShapesList = () => {
|
10
|
-
var _a, _b, _c, _d, _e, _f;
|
10
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
11
11
|
const { strings, Typography, IconButton, DeleteIcon, AnnotateIcon, SelectIcon, CopyIcon, themeMode } = useContext(UiContext);
|
12
|
-
const { shapes, removeShape, configuration, metadata, setMetadata } = useEditorContext();
|
12
|
+
const { shapes, removeShape, configuration, metadata, setMetadata, addShape, editorId } = useEditorContext();
|
13
13
|
const [selected, setSelected] = useState([]);
|
14
|
-
const [form, setForm] = useState({
|
14
|
+
const [form, setForm] = useState({
|
15
|
+
isOpen: false,
|
16
|
+
shapeId: '',
|
17
|
+
type: null,
|
18
|
+
shape: null,
|
19
|
+
});
|
20
|
+
// open metadata form immediately after drawing the shape
|
21
|
+
useEffect(() => {
|
22
|
+
const openForm = (_, { id, type, shape }) => {
|
23
|
+
setForm({ isOpen: true, shapeId: id, type, shape });
|
24
|
+
};
|
25
|
+
Dispatcher.register(`canvas:${editorId}:shapeAdded`, openForm);
|
26
|
+
return () => {
|
27
|
+
Dispatcher.unregister(`canvas:${editorId}:shapeAdded`, openForm);
|
28
|
+
};
|
29
|
+
}, [editorId]);
|
15
30
|
useEffect(() => {
|
16
31
|
const setSelectedShapes = (_, event) => { var _a; return setSelected((_a = event === null || event === void 0 ? void 0 : event.map((s) => s.id)) !== null && _a !== void 0 ? _a : []); };
|
17
|
-
Dispatcher.register(
|
32
|
+
Dispatcher.register(`canvas:${editorId}:shapeSelected`, setSelectedShapes);
|
18
33
|
return () => {
|
19
|
-
Dispatcher.unregister(
|
34
|
+
Dispatcher.unregister(`canvas:${editorId}:shapeSelected`, setSelectedShapes);
|
20
35
|
};
|
21
|
-
}, [shapes]);
|
22
|
-
const handleCopyShape = (id) => () => {
|
23
|
-
|
36
|
+
}, [shapes, editorId]);
|
37
|
+
const handleCopyShape = (id) => (evt) => {
|
38
|
+
evt.stopPropagation();
|
39
|
+
Dispatcher.emit(`canvas:${editorId}:copyShape`, id);
|
24
40
|
};
|
25
41
|
const handleRemoveShape = (id) => () => {
|
26
|
-
Dispatcher.emit(
|
42
|
+
Dispatcher.emit(`canvas:${editorId}:removeShape`, id);
|
27
43
|
removeShape(id);
|
28
44
|
};
|
29
45
|
const handleSelectShape = (id) => () => {
|
30
|
-
Dispatcher.emit(
|
46
|
+
Dispatcher.emit(`canvas:${editorId}:selectShape`, id);
|
31
47
|
};
|
32
48
|
const handleEditShapeMetadata = (id) => () => {
|
33
|
-
setForm({ isOpen: true, shapeId: id });
|
49
|
+
setForm({ isOpen: true, shapeId: id, type: null, shape: null });
|
50
|
+
};
|
51
|
+
const handleSubmitMetadata = (shapeId) => (data, properties) => {
|
52
|
+
// if in creation mode, add the shape
|
53
|
+
if (form.type !== null) {
|
54
|
+
addShape(shapeId, form.type, form.shape);
|
55
|
+
}
|
56
|
+
setMetadata(Object.assign(Object.assign({}, metadata), { rois: [...metadata.rois.filter((r) => r.id !== shapeId), Object.assign({ id: shapeId, parameters: data }, properties)] }));
|
57
|
+
setForm({ isOpen: false, shapeId: '', type: null, shape: null });
|
34
58
|
};
|
35
|
-
const
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
setForm({ isOpen: false, shapeId: '' });
|
59
|
+
const handleCloseMetadataForm = () => {
|
60
|
+
// if in creation mode do not add shape and delete shape from canvas
|
61
|
+
if (form.type !== null) {
|
62
|
+
Dispatcher.emit(`canvas:${editorId}:removeShape`, form.shapeId);
|
63
|
+
}
|
64
|
+
setForm({ isOpen: false, shapeId: '', type: null, shape: null });
|
41
65
|
};
|
42
66
|
const iconColor = themeMode === 'light' ? 'black' : 'white';
|
43
|
-
return (_jsxs(_Fragment, { children: [_jsxs("table", { className: css('shapes-table', styles, themeMode), children: [Object.keys(shapes).length > 0 && (_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: _jsx(Typography, { style: { fontWeight: 'bold' }, children: strings.
|
44
|
-
|
45
|
-
|
67
|
+
return (_jsxs(_Fragment, { children: [_jsxs("table", { className: css('shapes-table', styles, themeMode), children: [Object.keys(shapes).length > 0 && (_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: _jsx(Typography, { style: { fontWeight: 'bold' }, children: strings.name }) }), _jsx("th", { children: _jsx(Typography, { style: { fontWeight: 'bold' }, children: strings.role }) }), _jsx("th", { children: _jsx(Typography, { style: { fontWeight: 'bold' }, children: strings.type }) }), _jsx("th", {})] }) })), _jsx("tbody", { children: Object.keys(shapes).map((id, idx) => {
|
68
|
+
var _a;
|
69
|
+
const m = metadata.rois.find((roi) => roi.id === id);
|
70
|
+
return (_jsxs("tr", { onClick: handleSelectShape(id), className: selected.indexOf(id) > -1
|
71
|
+
? css('shapes-row-selected', styles, themeMode)
|
72
|
+
: idx % 2 === 0
|
73
|
+
? css('shapes-row-even', styles, themeMode)
|
74
|
+
: css('shapes-row-odd', styles, themeMode), children: [_jsx("td", { children: _jsxs("div", { className: styles.shapesTableName, children: [_jsx("div", { className: styles.shapesTableColor, style: { backgroundColor: shapes[id].shape.stroke } }), _jsx(Typography, { children: m === null || m === void 0 ? void 0 : m.name })] }) }), _jsx("td", { children: _jsx(Typography, { children: humanize((_a = m === null || m === void 0 ? void 0 : m.role) !== null && _a !== void 0 ? _a : '') }) }), _jsx("td", { children: _jsx(Typography, { children: strings[shapes[id].type] }) }), _jsxs("td", { children: [_jsx(IconButton, { onClick: handleSelectShape(id), children: _jsx(SelectIcon, { color: iconColor }) }), _jsx(IconButton, { onClick: handleCopyShape(id), children: _jsx(CopyIcon, { color: iconColor }) }), _jsx(IconButton, { onClick: handleEditShapeMetadata(id), children: _jsx(AnnotateIcon, { color: iconColor }) }), _jsx(IconButton, { onClick: handleRemoveShape(id), children: _jsx(DeleteIcon, { color: iconColor }) })] })] }, id));
|
75
|
+
}) })] }), form.isOpen && (_jsx(ParametersModalForm, { shapeType: form.type || shapes[form.shapeId].type, shapeName: (_b = (_a = metadata.rois.find((roi) => roi.id === form.shapeId)) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : '', shapeRole: (_d = (_c = metadata.rois.find((roi) => roi.id === form.shapeId)) === null || _c === void 0 ? void 0 : _c.role) !== null && _d !== void 0 ? _d : '', parameters: (_f = (_e = configuration.rois.find((roi) => roi.type === (form.type || shapes[form.shapeId].type))) === null || _e === void 0 ? void 0 : _e.parameters) !== null && _f !== void 0 ? _f : [], data: (_k = (_h = (_g = metadata.rois.find((roi) => roi.id === form.shapeId)) === null || _g === void 0 ? void 0 : _g.parameters) !== null && _h !== void 0 ? _h : (_j = configuration.rois.find((roi) => roi.type === (form.type || shapes[form.shapeId].type))) === null || _j === void 0 ? void 0 : _j.parameters) !== null && _k !== void 0 ? _k : [], title: strings.shapeParametersMetadata, onClose: handleCloseMetadataForm, onSubmit: handleSubmitMetadata(form.shapeId) }))] }));
|
46
76
|
};
|
47
77
|
export default ShapesList;
|
@@ -4,7 +4,7 @@
|
|
4
4
|
}
|
5
5
|
|
6
6
|
.shapes-table th {
|
7
|
-
padding: .8rem .3rem
|
7
|
+
padding: .8rem .3rem;
|
8
8
|
text-align: left;
|
9
9
|
}
|
10
10
|
|
@@ -34,3 +34,34 @@
|
|
34
34
|
.shapes-row-selected-dark {
|
35
35
|
background-color: #666;
|
36
36
|
}
|
37
|
+
|
38
|
+
.shapes-row-even-light {
|
39
|
+
background-color: #f7f7f7;
|
40
|
+
}
|
41
|
+
|
42
|
+
.shapes-row-even-dark {
|
43
|
+
background-color: #444;
|
44
|
+
}
|
45
|
+
|
46
|
+
.shapes-row-odd-light {
|
47
|
+
background-color: #f0f0f0;
|
48
|
+
}
|
49
|
+
|
50
|
+
.shapes-row-odd-dark {
|
51
|
+
background-color: #555;
|
52
|
+
}
|
53
|
+
|
54
|
+
.shapesTableName {
|
55
|
+
display: flex;
|
56
|
+
align-items: center;
|
57
|
+
}
|
58
|
+
|
59
|
+
.shapesTableColor {
|
60
|
+
border: 1px solid #000;
|
61
|
+
box-sizing: border-box;
|
62
|
+
display: inline-block;
|
63
|
+
width: 24px;
|
64
|
+
height: 24px;
|
65
|
+
border-radius: 50%;
|
66
|
+
margin-right: .5rem;
|
67
|
+
}
|
@@ -1,33 +1,25 @@
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
2
|
-
import { useContext
|
3
|
-
import AnnotateIcon from '../../Icons/AnnotateIcon';
|
2
|
+
import { useContext } from 'react';
|
4
3
|
import PointerIcon from '../../Icons/PointerIcon';
|
5
4
|
import PolygonIcon from '../../Icons/PolygonIcon';
|
6
5
|
import PolylineIcon from '../../Icons/PolylineIcon';
|
7
6
|
import RectangleIcon from '../../Icons/RectangleIcon';
|
8
|
-
import SaveIcon from '../../Icons/SaveIcon';
|
9
7
|
import { useEditorContext } from '../../Providers/EditorProvider';
|
10
8
|
import { UiContext } from '../../Providers/UiProvider';
|
11
9
|
import { css } from '../../Utils';
|
12
10
|
import ColorPicker from './ColorPicker';
|
13
|
-
import ParametersModalForm from './ParametersModalForm';
|
14
11
|
import styles from './Toolbar.module.css';
|
15
|
-
import {
|
12
|
+
import { enableRois } from './Utils';
|
16
13
|
const Toolbar = () => {
|
17
|
-
var _a
|
14
|
+
var _a;
|
18
15
|
const { IconButton, themeMode, primaryColor, Typography, strings } = useContext(UiContext);
|
19
|
-
const { activeTool, setActiveTool, configuration
|
20
|
-
const [form, setForm] = useState({ isOpen: false });
|
16
|
+
const { activeTool, setActiveTool, configuration } = useEditorContext();
|
21
17
|
const iconColor = (tool) => (tool === activeTool ? primaryColor : themeMode === 'light' ? 'black' : 'white');
|
22
18
|
const setTool = (tool) => () => setActiveTool(tool);
|
23
|
-
const handleSubmitMetadata = (data) => {
|
24
|
-
setMetadata(Object.assign(Object.assign({}, metadata), { parameters: data }));
|
25
|
-
setForm({ isOpen: false });
|
26
|
-
};
|
27
19
|
const hideForbiddenTools = (_a = configuration.options) === null || _a === void 0 ? void 0 : _a.hideForbiddenTools;
|
28
20
|
const polylineEnabled = configuration.rois.find((r) => r.type === "polyline" /* ToolEnum.Polyline */);
|
29
21
|
const polygonEnabled = configuration.rois.find((r) => r.type === "polygon" /* ToolEnum.Polygon */);
|
30
22
|
const rectangleEnabled = configuration.rois.find((r) => r.type === "rect" /* ToolEnum.Rectangle */);
|
31
|
-
return (_jsxs(_Fragment, { children: [
|
23
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: css('toolbar', styles, themeMode), children: enableRois(configuration) && (_jsxs(_Fragment, { children: [_jsx(IconButton, { onClick: setTool("pointer" /* ToolEnum.Pointer */), children: _jsx(PointerIcon, { color: iconColor("pointer" /* ToolEnum.Pointer */) }) }), (!hideForbiddenTools || polylineEnabled) && (_jsx(IconButton, { onClick: setTool("polyline" /* ToolEnum.Polyline */), disabled: !polylineEnabled, children: _jsx(PolylineIcon, { color: iconColor("polyline" /* ToolEnum.Polyline */) }) })), (!hideForbiddenTools || polygonEnabled) && (_jsx(IconButton, { onClick: setTool("polygon" /* ToolEnum.Polygon */), disabled: !polygonEnabled, children: _jsx(PolygonIcon, { color: iconColor("polygon" /* ToolEnum.Polygon */) }) })), (!hideForbiddenTools || rectangleEnabled) && (_jsx(IconButton, { onClick: setTool("rect" /* ToolEnum.Rectangle */), disabled: !rectangleEnabled, children: _jsx(RectangleIcon, { color: iconColor("rect" /* ToolEnum.Rectangle */) }) })), _jsx(ColorPicker, { style: { marginLeft: 'auto', marginRight: '.5rem' } })] })) }), enableRois(configuration) && (_jsx("div", { className: css('toolbar-helper', styles, themeMode), children: _jsxs(Typography, { children: [strings[activeTool], ": ", strings[`${activeTool}HelpText`]] }) }))] }));
|
32
24
|
};
|
33
25
|
export default Toolbar;
|
@@ -73,6 +73,8 @@ export type Metadata = {
|
|
73
73
|
rois: {
|
74
74
|
id: string;
|
75
75
|
parameters: OutputParameter[];
|
76
|
+
name: string;
|
77
|
+
role: string;
|
76
78
|
}[];
|
77
79
|
};
|
78
80
|
export type OutputShapeRect = {
|
@@ -107,6 +109,8 @@ export interface OutputParameter {
|
|
107
109
|
export interface OutputRoi {
|
108
110
|
parameters: OutputParameter[];
|
109
111
|
type: ShapeType;
|
112
|
+
name: string;
|
113
|
+
role: string;
|
110
114
|
shape: OutputShapeRect | OutputShapePolyline | OutputShapePolygon;
|
111
115
|
}
|
112
116
|
export interface Output {
|
@@ -1,4 +1,5 @@
|
|
1
|
-
import {
|
1
|
+
import { formatString } from '../../Utils';
|
2
|
+
import { OperatorEnum, } from './Types';
|
2
3
|
export const notify = {
|
3
4
|
info: (message) => alert(`Info: ${message}`),
|
4
5
|
warn: (message) => alert(`Warning: ${message}`),
|
@@ -72,49 +73,55 @@ export const validate = (configuration, shapes, metadata, strings) => {
|
|
72
73
|
switch (roi.multiplicity.operator) {
|
73
74
|
case OperatorEnum.Eq:
|
74
75
|
if (Object.values(shapes).filter((s) => s.type === roi.type).length !== roi.multiplicity.threshold) {
|
75
|
-
errors.push(strings.shapesOfTypeShouldBeEqualToThreshold
|
76
|
-
|
77
|
-
|
76
|
+
errors.push(formatString(strings.shapesOfTypeShouldBeEqualToThreshold, {
|
77
|
+
type: String(roi.type),
|
78
|
+
threshold: roi.multiplicity.threshold,
|
79
|
+
}));
|
78
80
|
}
|
79
81
|
break;
|
80
82
|
case OperatorEnum.Lt:
|
81
83
|
if (Object.values(shapes).filter((s) => s.type === roi.type).length >= roi.multiplicity.threshold) {
|
82
|
-
errors.push(strings.shapesOfTypeShouldBeLessThanThreshold
|
83
|
-
|
84
|
-
|
84
|
+
errors.push(formatString(strings.shapesOfTypeShouldBeLessThanThreshold, {
|
85
|
+
type: String(roi.type),
|
86
|
+
threshold: roi.multiplicity.threshold,
|
87
|
+
}));
|
85
88
|
}
|
86
89
|
break;
|
87
90
|
case OperatorEnum.Lte:
|
88
91
|
if (Object.values(shapes).filter((s) => s.type === roi.type).length > roi.multiplicity.threshold) {
|
89
|
-
errors.push(strings.shapesOfTypeShouldBeLessThanOrEqualToThreshold
|
90
|
-
|
91
|
-
|
92
|
+
errors.push(formatString(strings.shapesOfTypeShouldBeLessThanOrEqualToThreshold, {
|
93
|
+
type: String(roi.type),
|
94
|
+
threshold: roi.multiplicity.threshold,
|
95
|
+
}));
|
92
96
|
}
|
93
97
|
break;
|
94
98
|
case OperatorEnum.Gt:
|
95
99
|
if (Object.values(shapes).filter((s) => s.type === roi.type).length <= roi.multiplicity.threshold) {
|
96
|
-
errors.push(strings.shapesOfTypeShouldBeGreaterThanThreshold
|
97
|
-
|
98
|
-
|
100
|
+
errors.push(formatString(strings.shapesOfTypeShouldBeGreaterThanThreshold, {
|
101
|
+
type: String(roi.type),
|
102
|
+
threshold: roi.multiplicity.threshold,
|
103
|
+
}));
|
99
104
|
}
|
100
105
|
break;
|
101
106
|
case OperatorEnum.Gte:
|
102
107
|
if (Object.values(shapes).filter((s) => s.type === roi.type).length < roi.multiplicity.threshold) {
|
103
|
-
errors.push(strings.shapesOfTypeShouldBeGreaterThanOrEqualToThreshold
|
104
|
-
|
105
|
-
|
108
|
+
errors.push(formatString(strings.shapesOfTypeShouldBeGreaterThanOrEqualToThreshold, {
|
109
|
+
type: String(roi.type),
|
110
|
+
threshold: roi.multiplicity.threshold,
|
111
|
+
}));
|
106
112
|
}
|
107
113
|
}
|
108
114
|
}
|
109
115
|
});
|
110
116
|
// check rois metadata
|
111
|
-
Object.keys(shapes).forEach(shapeId => {
|
117
|
+
Object.keys(shapes).forEach((shapeId) => {
|
112
118
|
var _a, _b;
|
113
119
|
const type = shapes[shapeId].type;
|
114
120
|
const confParameters = (_b = (_a = configuration.rois.find((r) => r.type === type)) === null || _a === void 0 ? void 0 : _a.parameters) !== null && _b !== void 0 ? _b : [];
|
115
121
|
confParameters.forEach((p) => {
|
116
122
|
var _a, _b;
|
117
|
-
if (p.required &&
|
123
|
+
if (p.required &&
|
124
|
+
isEmpty((_b = (_a = metadata.rois.find((r) => r.id === shapeId)) === null || _a === void 0 ? void 0 : _a.parameters.find((p) => p.codename === p.codename)) === null || _b === void 0 ? void 0 : _b.value)) {
|
118
125
|
errors.push(strings.missingRequiredValuesInShapeParameters.replace('{id}', shapeId));
|
119
126
|
}
|
120
127
|
});
|
@@ -129,7 +136,7 @@ export const fabricShapeToOutputShape = (shape, type) => {
|
|
129
136
|
left: shape.left,
|
130
137
|
width: shape.width,
|
131
138
|
height: shape.height,
|
132
|
-
color: shape.stroke
|
139
|
+
color: shape.stroke,
|
133
140
|
};
|
134
141
|
case "polygon" /* ToolEnum.Polygon */:
|
135
142
|
case "polyline" /* ToolEnum.Polyline */:
|
@@ -137,7 +144,7 @@ export const fabricShapeToOutputShape = (shape, type) => {
|
|
137
144
|
points: shape.get('points'),
|
138
145
|
top: shape.top,
|
139
146
|
left: shape.left,
|
140
|
-
color: shape.stroke
|
147
|
+
color: shape.stroke,
|
141
148
|
};
|
142
149
|
}
|
143
150
|
};
|
@@ -10,8 +10,9 @@ import styles from './RoiEditor.module.css';
|
|
10
10
|
import ShapesList from './ShapesList';
|
11
11
|
import Toolbar from './Toolbar';
|
12
12
|
import { fabricShapeToOutputShape, validate } from './Utils';
|
13
|
+
import Header from './Header';
|
13
14
|
// https://github.com/n-mazaheri/image-editor
|
14
|
-
const RoiEditor = ({ imageUrl, configuration, onSubmit, initialData,
|
15
|
+
const RoiEditor = ({ imageUrl, configuration, onSubmit, initialData, editorId }) => {
|
15
16
|
var _a, _b, _c;
|
16
17
|
const { themeMode, enableLogs, pickerColors, strings, notify } = useContext(UiContext);
|
17
18
|
const { imageSize, canvasSize, wrapperRef, isReady } = useCanvasSize(imageUrl);
|
@@ -38,6 +39,7 @@ const RoiEditor = ({ imageUrl, configuration, onSubmit, initialData, id }) => {
|
|
38
39
|
setShapes(newShapes);
|
39
40
|
setMetadata(Object.assign(Object.assign({}, metadata), { rois: metadata.rois.filter((r) => r.id !== id) }));
|
40
41
|
}, [shapes, metadata]);
|
42
|
+
// TODO: go with percentage coordinates or pixel coordinates but relative to original image
|
41
43
|
const handleSubmit = useCallback(() => {
|
42
44
|
var _a, _b;
|
43
45
|
const [isValid, errors] = validate(configuration, shapes, metadata, strings);
|
@@ -45,10 +47,12 @@ const RoiEditor = ({ imageUrl, configuration, onSubmit, initialData, id }) => {
|
|
45
47
|
onSubmit({
|
46
48
|
parameters: (_b = (_a = metadata.parameters) === null || _a === void 0 ? void 0 : _a.map((p) => ({ codename: p.codename, value: p.value }))) !== null && _b !== void 0 ? _b : [],
|
47
49
|
rois: Object.keys(shapes).map((shapeId) => {
|
48
|
-
var _a, _b, _c;
|
50
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
49
51
|
return ({
|
50
52
|
parameters: (_c = (_b = (_a = metadata.rois
|
51
53
|
.find((r) => r.id === shapeId)) === null || _a === void 0 ? void 0 : _a.parameters) === null || _b === void 0 ? void 0 : _b.map((p) => ({ codename: p.codename, value: p.value }))) !== null && _c !== void 0 ? _c : [],
|
54
|
+
name: (_e = (_d = metadata.rois.find((r) => r.id === shapeId)) === null || _d === void 0 ? void 0 : _d.name) !== null && _e !== void 0 ? _e : '',
|
55
|
+
role: (_g = (_f = metadata.rois.find((r) => r.id === shapeId)) === null || _f === void 0 ? void 0 : _f.role) !== null && _g !== void 0 ? _g : '',
|
52
56
|
type: shapes[shapeId].type,
|
53
57
|
shape: fabricShapeToOutputShape(shapes[shapeId].shape, shapes[shapeId].shape.type),
|
54
58
|
});
|
@@ -65,7 +69,7 @@ const RoiEditor = ({ imageUrl, configuration, onSubmit, initialData, id }) => {
|
|
65
69
|
if (!isReady) {
|
66
70
|
return _jsx(Loader, {});
|
67
71
|
}
|
68
|
-
return (_jsx(EditorProvider, {
|
72
|
+
return (_jsx(EditorProvider, { editorId: editorId, hideForbiddenTools: (_c = (_b = configuration.options) === null || _b === void 0 ? void 0 : _b.hideForbiddenTools) !== null && _c !== void 0 ? _c : false, activeTool: activeTool, setActiveTool: setActiveTool, activeColor: activeColor, setActiveColor: setActiveColor, shapes: shapes, addShape: addShape, addShapes: addShapes, removeShape: removeShape, configuration: configuration, metadata: metadata, setMetadata: setMetadata, onSubmit: handleSubmit, children: _jsxs("div", { style: { maxWidth: '100%', width: `${imageSize.width}px` }, ref: wrapperRef, children: [_jsx(Header, {}), _jsx(Toolbar, {}), _jsx("div", { className: css('canvasWrapper', styles, themeMode), style: {
|
69
73
|
width: `${canvasSize.width}px`,
|
70
74
|
height: `${canvasSize.height}px`,
|
71
75
|
backgroundImage: `url(${imageUrl})`,
|
@@ -0,0 +1,7 @@
|
|
1
|
+
import { FieldProps } from '../Types';
|
2
|
+
import { ShapeType } from './RoiEditor/Types';
|
3
|
+
type RoleFieldProps = Omit<FieldProps<string>, 'readonly' | 'label'> & {
|
4
|
+
shapeType: ShapeType;
|
5
|
+
};
|
6
|
+
declare const RoleField: React.FC<RoleFieldProps>;
|
7
|
+
export default RoleField;
|
@@ -0,0 +1,35 @@
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
2
|
+
var t = {};
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
4
|
+
t[p] = s[p];
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
8
|
+
t[p[i]] = s[p[i]];
|
9
|
+
}
|
10
|
+
return t;
|
11
|
+
};
|
12
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
13
|
+
import { useContext, useEffect } from 'react';
|
14
|
+
import { useEditorContext } from '../Providers/EditorProvider';
|
15
|
+
import { UiContext } from '../Providers/UiProvider';
|
16
|
+
import EnumField from './EnumField';
|
17
|
+
import { humanize } from '../Utils';
|
18
|
+
const RoleField = (_a) => {
|
19
|
+
var { onChange, value, required, shapeType } = _a, props = __rest(_a, ["onChange", "value", "required", "shapeType"]);
|
20
|
+
const { strings } = useContext(UiContext);
|
21
|
+
const { configuration } = useEditorContext();
|
22
|
+
const options = [];
|
23
|
+
(configuration.rois || []).filter(r => r.type === shapeType).forEach((r) => {
|
24
|
+
if (!options.includes(r.role)) {
|
25
|
+
options.push(r.role);
|
26
|
+
}
|
27
|
+
});
|
28
|
+
useEffect(() => {
|
29
|
+
if (required) {
|
30
|
+
onChange(options[0]);
|
31
|
+
}
|
32
|
+
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
33
|
+
return (_jsx(EnumField, Object.assign({ required: required, label: strings.role, value: value, onChange: (value) => onChange(value), options: options.map((o) => ({ value: o, label: humanize(o) })) }, props)));
|
34
|
+
};
|
35
|
+
export default RoleField;
|
@@ -18,9 +18,9 @@ type EditorContextType = {
|
|
18
18
|
metadata: Metadata;
|
19
19
|
setMetadata: (data: Metadata) => void;
|
20
20
|
onSubmit: () => void;
|
21
|
-
|
21
|
+
editorId: string;
|
22
22
|
};
|
23
23
|
export declare const EditorContext: import("react").Context<EditorContextType | undefined>;
|
24
24
|
export declare function useEditorContext(): EditorContextType;
|
25
|
-
declare const EditorProvider: ({ children,
|
25
|
+
declare const EditorProvider: ({ children, editorId, hideForbiddenTools, activeTool, setActiveTool, activeColor, setActiveColor, shapes, addShape, addShapes, removeShape, configuration, metadata, setMetadata, onSubmit, }: PropsWithChildren<EditorContextType>) => import("react/jsx-runtime").JSX.Element;
|
26
26
|
export default EditorProvider;
|
@@ -8,9 +8,9 @@ export function useEditorContext() {
|
|
8
8
|
}
|
9
9
|
return context;
|
10
10
|
}
|
11
|
-
const EditorProvider = ({ children,
|
11
|
+
const EditorProvider = ({ children, editorId, hideForbiddenTools, activeTool, setActiveTool, activeColor, setActiveColor, shapes, addShape, addShapes, removeShape, configuration, metadata, setMetadata, onSubmit, }) => {
|
12
12
|
return (_jsx(EditorContext.Provider, { value: {
|
13
|
-
|
13
|
+
editorId,
|
14
14
|
hideForbiddenTools,
|
15
15
|
activeTool,
|
16
16
|
setActiveTool,
|
@@ -20,6 +20,7 @@ type UiContextType = {
|
|
20
20
|
enableLogs: boolean;
|
21
21
|
themeMode: 'light' | 'dark';
|
22
22
|
primaryColor: string;
|
23
|
+
primaryFgColor: string;
|
23
24
|
Typography: typeof Typography;
|
24
25
|
IconButton: typeof IconButton;
|
25
26
|
Modal: typeof Modal;
|
@@ -47,6 +48,13 @@ type UiContextType = {
|
|
47
48
|
mainParametersMetadata: string;
|
48
49
|
missingRequiredValuesInMainParameters: string;
|
49
50
|
missingRequiredValuesInShapeParameters: string;
|
51
|
+
roiMultiplicityEqRule: string;
|
52
|
+
roiMultiplicityGtRule: string;
|
53
|
+
roiMultiplicityGteRule: string;
|
54
|
+
roiMultiplicityLtRule: string;
|
55
|
+
roiMultiplicityLteRule: string;
|
56
|
+
roiMultiplicityNoRule: string;
|
57
|
+
name: string;
|
50
58
|
polygon: string;
|
51
59
|
polygonHelpText: string;
|
52
60
|
polyline: string;
|
@@ -55,8 +63,11 @@ type UiContextType = {
|
|
55
63
|
rectHelpText: string;
|
56
64
|
pointer: string;
|
57
65
|
pointerHelpText: string;
|
66
|
+
roisToBeDrawn: string;
|
67
|
+
role: string;
|
58
68
|
requiredField: string;
|
59
69
|
save: string;
|
70
|
+
shapeParametersMetadata: string;
|
60
71
|
shapesOfTypeShouldBeEqualToThreshold: string;
|
61
72
|
shapesOfTypeShouldBeGreaterThanThreshold: string;
|
62
73
|
shapesOfTypeShouldBeGreaterThanOrEqualToThreshold: string;
|
@@ -67,7 +78,7 @@ type UiContextType = {
|
|
67
78
|
};
|
68
79
|
export declare const DefaultUiContext: UiContextType;
|
69
80
|
export declare const UiContext: import("react").Context<UiContextType>;
|
70
|
-
declare const UiProvider: ({ children, enableLogs, themeMode, primaryColor, Typography, Modal, IconButton, DeleteIcon, EditIcon, SelectIcon, CopyIcon, AnnotateIcon, SaveIcon, CloseIcon, TextField, NumberField, BoolField, EnumField, Button, pickerColors, notify, strings, }: PropsWithChildren<Partial<Omit<UiContextType, "strings">> & {
|
81
|
+
declare const UiProvider: ({ children, enableLogs, themeMode, primaryColor, primaryFgColor, Typography, Modal, IconButton, DeleteIcon, EditIcon, SelectIcon, CopyIcon, AnnotateIcon, SaveIcon, CloseIcon, TextField, NumberField, BoolField, EnumField, Button, pickerColors, notify, strings, }: PropsWithChildren<Partial<Omit<UiContextType, "strings">> & {
|
71
82
|
strings?: Partial<UiContextType["strings"]>;
|
72
83
|
}>) => import("react/jsx-runtime").JSX.Element;
|
73
84
|
export default UiProvider;
|
@@ -20,6 +20,7 @@ export const DefaultUiContext = {
|
|
20
20
|
enableLogs: true,
|
21
21
|
themeMode: 'light',
|
22
22
|
primaryColor: '#1976d2',
|
23
|
+
primaryFgColor: '#fff',
|
23
24
|
Typography,
|
24
25
|
IconButton,
|
25
26
|
Modal,
|
@@ -47,6 +48,7 @@ export const DefaultUiContext = {
|
|
47
48
|
mainParametersMetadata: 'Main parameters',
|
48
49
|
missingRequiredValuesInMainParameters: 'Missing required values in main parameters',
|
49
50
|
missingRequiredValuesInShapeParameters: 'Missing required values in shape {id} parameters',
|
51
|
+
name: 'Name',
|
50
52
|
polygon: 'Polygon',
|
51
53
|
polygonHelpText: 'click to draw all the polygon points, double click on the last point to close the polygon',
|
52
54
|
polyline: 'Polyline',
|
@@ -56,7 +58,16 @@ export const DefaultUiContext = {
|
|
56
58
|
pointer: 'Selection',
|
57
59
|
pointerHelpText: 'click a shape to select it',
|
58
60
|
requiredField: 'This field is required',
|
61
|
+
roiMultiplicityEqRule: 'a number of {role} ({type}) equal to {threshold}',
|
62
|
+
roiMultiplicityGtRule: 'a number of {role} ({type}) greater than {threshold}',
|
63
|
+
roiMultiplicityGteRule: 'a number of {role} ({type}) greater than or equal to {threshold}',
|
64
|
+
roiMultiplicityLtRule: 'a number of {role} ({type}) less than {threshold}',
|
65
|
+
roiMultiplicityLteRule: 'a number of {role} ({type}) less than or equal to {threshold}',
|
66
|
+
roiMultiplicityNoRule: 'a number of {role} ({type})',
|
67
|
+
roisToBeDrawn: 'ROIs to be drawn',
|
68
|
+
role: 'Role',
|
59
69
|
save: 'Save',
|
70
|
+
shapeParametersMetadata: 'Shape parameters',
|
60
71
|
shapesOfTypeShouldBeEqualToThreshold: 'Shapes of type {type} should be equal to {threshold}',
|
61
72
|
shapesOfTypeShouldBeGreaterThanThreshold: 'Shapes of type {type} should be greater than {threshold}',
|
62
73
|
shapesOfTypeShouldBeGreaterThanOrEqualToThreshold: 'Shapes of type {type} should be greater than or equal to {threshold}',
|
@@ -66,7 +77,7 @@ export const DefaultUiContext = {
|
|
66
77
|
},
|
67
78
|
};
|
68
79
|
export const UiContext = createContext(DefaultUiContext);
|
69
|
-
const UiProvider = ({ children, enableLogs, themeMode, primaryColor, Typography, Modal, IconButton, DeleteIcon, EditIcon, SelectIcon, CopyIcon, AnnotateIcon, SaveIcon, CloseIcon, TextField, NumberField, BoolField, EnumField, Button, pickerColors, notify, strings, }) => {
|
80
|
+
const UiProvider = ({ children, enableLogs, themeMode, primaryColor, primaryFgColor, Typography, Modal, IconButton, DeleteIcon, EditIcon, SelectIcon, CopyIcon, AnnotateIcon, SaveIcon, CloseIcon, TextField, NumberField, BoolField, EnumField, Button, pickerColors, notify, strings, }) => {
|
70
81
|
const ctx = {
|
71
82
|
enableLogs: enableLogs !== null && enableLogs !== void 0 ? enableLogs : DefaultUiContext.enableLogs,
|
72
83
|
Typography: Typography !== null && Typography !== void 0 ? Typography : DefaultUiContext.Typography,
|
@@ -86,6 +97,7 @@ const UiProvider = ({ children, enableLogs, themeMode, primaryColor, Typography,
|
|
86
97
|
Button: Button !== null && Button !== void 0 ? Button : DefaultUiContext.Button,
|
87
98
|
themeMode: themeMode !== null && themeMode !== void 0 ? themeMode : DefaultUiContext.themeMode,
|
88
99
|
primaryColor: primaryColor !== null && primaryColor !== void 0 ? primaryColor : DefaultUiContext.primaryColor,
|
100
|
+
primaryFgColor: primaryFgColor !== null && primaryFgColor !== void 0 ? primaryFgColor : DefaultUiContext.primaryFgColor,
|
89
101
|
pickerColors: pickerColors !== null && pickerColors !== void 0 ? pickerColors : DefaultUiContext.pickerColors,
|
90
102
|
notify: notify !== null && notify !== void 0 ? notify : DefaultUiContext.notify,
|
91
103
|
strings: strings ? Object.assign(Object.assign({}, DefaultUiContext.strings), strings) : DefaultUiContext.strings,
|
package/dist/Utils/index.d.ts
CHANGED
@@ -1,2 +1,4 @@
|
|
1
1
|
export declare const log: (level: "log" | "info" | "warn" | "error", enable: boolean, ...args: unknown[]) => false | void;
|
2
2
|
export declare const css: (name: string, styles: Record<string, string>, themeMode: "light" | "dark" | null) => string;
|
3
|
+
export declare const humanize: (str: string) => string;
|
4
|
+
export declare const formatString: (str: string, placeholders: Record<string, string | number>) => string;
|
package/dist/Utils/index.js
CHANGED
@@ -1,2 +1,14 @@
|
|
1
1
|
export const log = (level, enable, ...args) => enable && console[level](...args);
|
2
2
|
export const css = (name, styles, themeMode) => `${styles[name]} ${styles[`${name}-${themeMode}`]} react-cam-roi-${name}${themeMode ? `react-cam-roi-${name}-${themeMode}` : ''}`;
|
3
|
+
export const humanize = (str) => {
|
4
|
+
return str
|
5
|
+
.replace(/([A-Z])/g, ' $1')
|
6
|
+
.replace('_', ' ')
|
7
|
+
.replace(/^./, (str) => str.toUpperCase());
|
8
|
+
};
|
9
|
+
export const formatString = (str, placeholders) => {
|
10
|
+
Object.keys(placeholders).forEach((key) => {
|
11
|
+
str = str.replace(`{${key}}`, placeholders[key].toString());
|
12
|
+
});
|
13
|
+
return str;
|
14
|
+
};
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@abidibo/react-cam-roi",
|
3
|
-
"version": "0.0.
|
3
|
+
"version": "0.0.13",
|
4
4
|
"description": "A react component for drawing ROI over images and managing metadata",
|
5
5
|
"repository": {
|
6
6
|
"type": "git",
|
@@ -31,7 +31,7 @@
|
|
31
31
|
"copy-files": "copyfiles -u 1 src/**/*.module.css dist/",
|
32
32
|
"test": "echo \"Error: no test specified\" && exit 1",
|
33
33
|
"storybook": "storybook dev -p 6006",
|
34
|
-
"build-storybook": "storybook build"
|
34
|
+
"build-storybook": "storybook build -o docs"
|
35
35
|
},
|
36
36
|
"author": "abidibo",
|
37
37
|
"license": "MIT",
|