Skip to content

Commit bc6ae80

Browse files
Merge remote-tracking branch 'origin/feature/region-classlist'
2 parents 8781aa4 + 9749868 commit bc6ae80

File tree

15 files changed

+281
-179
lines changed

15 files changed

+281
-179
lines changed

.npmignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.idea
2+
.github
3+
node_modules
4+
src

package.json

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
"name": "react-image-annotate",
33
"version": "2.0.0",
44
"type": "module",
5-
"main": "dist/main.js",
6-
"types": "dist/main.d.ts",
5+
"main": "dist/react-image-annotate.js",
6+
"types": "dist/lib.d.ts",
77
"files": [
88
"dist"
99
],
@@ -23,8 +23,8 @@
2323
"material-survey": "^2.1.0",
2424
"moment": "^2.30.0",
2525
"monaco-editor": "^0.47.0",
26-
"react": "^18.0.0",
27-
"react-dom": "^18.0.2",
26+
"react": "18.2.0",
27+
"react-dom": "18.2.0",
2828
"react-full-screen": "^1.1.1",
2929
"react-hotkeys": "^2.0.0",
3030
"react-markdown": "^9.0.0",
@@ -63,10 +63,9 @@
6363
"vite-tsconfig-paths": "^4.3.2"
6464
},
6565
"peerDependencies": {
66-
"react": "^18.0.0",
67-
"react-dom": "^18.0.0"
66+
"react": "18.2.0",
67+
"react-dom": "18.2.0"
6868
},
69-
"homepage": "/react-image-annotate",
7069
"repository": {
7170
"type": "git",
7271
"url": "https://github.com/UniversalDataTool/react-image-annotate.git"

src/Annotator/index.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// @flow
22

3-
import type { Action, Image, MainLayoutState } from "../MainLayout/types";
3+
import { Action, Image, MainLayoutState, ToolEnum } from "../MainLayout/types";
44
import { ComponentType, FunctionComponent, useEffect, useReducer } from "react";
55
import Immutable, { ImmutableObject } from "seamless-immutable";
66

@@ -20,10 +20,10 @@ export type AnnotatorProps = {
2020
taskDescription?: string;
2121
allowedArea?: { x: number; y: number; w: number; h: number };
2222
regionTagList?: Array<string>;
23-
regionClsList?: Array<string>;
23+
regionClsList?: Array<string | { id: string; label: string }>;
2424
imageTagList?: Array<string>;
2525
imageClsList?: Array<string>;
26-
enabledTools?: Array<string>;
26+
enabledTools?: Array<ToolEnum>;
2727
selectedTool?: String;
2828
showTags?: boolean;
2929
selectedImage?: string | number;
@@ -191,7 +191,7 @@ export const Annotator = ({
191191
}, [selectedImage, state.annotationType, state.images]);
192192

193193
if (!images && !videoSrc)
194-
return 'Missing required prop "images" or "videoSrc"';
194+
return <div>Missing required prop "images" or "videoSrc"</div>;
195195

196196
return (
197197
<SettingsProvider>

src/Annotator/reducers/general-reducer.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,13 @@ export default <T extends ImmutableObject<MainLayoutState>>(
3535

3636
if (action.type === "ON_CLS_ADDED" && !!action.cls) {
3737
const oldRegionClsList = state.regionClsList;
38+
const isStringRegionClsList = oldRegionClsList?.every(
39+
(cls) => typeof cls === "string"
40+
);
41+
if (!isStringRegionClsList) return state;
3842
return {
3943
...state,
40-
regionClsList: oldRegionClsList?.concat(action.cls),
44+
regionClsList: ((oldRegionClsList || []) as string[]).concat(action.cls),
4145
};
4246
}
4347

@@ -148,7 +152,11 @@ export default <T extends ImmutableObject<MainLayoutState>>(
148152
if (oldRegion?.cls !== action.region.cls) {
149153
state = saveToHistory(state, "Change Region Classification") as T;
150154
const clsIndex = action.region.cls
151-
? state.regionClsList?.indexOf(action.region.cls)
155+
? state.regionClsList?.findIndex((cls) =>
156+
typeof cls === "string"
157+
? cls === action.region.cls
158+
: cls.id === action.region.cls
159+
)
152160
: undefined;
153161
if (clsIndex !== undefined && clsIndex !== -1) {
154162
state = Immutable(state).setIn(
@@ -631,10 +639,13 @@ export default <T extends ImmutableObject<MainLayoutState>>(
631639
let newRegion;
632640
let defaultRegionCls = state.selectedCls,
633641
defaultRegionColor = "#ff0000";
634-
635642
const clsIndex =
636643
defaultRegionCls && state.regionClsList
637-
? state.regionClsList.indexOf(defaultRegionCls)
644+
? state.regionClsList.findIndex((cls) =>
645+
typeof cls === "string"
646+
? cls === defaultRegionCls
647+
: cls.id === defaultRegionCls
648+
)
638649
: -1;
639650
if (clsIndex !== -1) {
640651
defaultRegionColor = colors[clsIndex % colors.length];
@@ -745,7 +756,6 @@ export default <T extends ImmutableObject<MainLayoutState>>(
745756
return state;
746757
}
747758
state = saveToHistory(state, "Create Keypoints") as T;
748-
console.log(state.keypointDefinitions);
749759
const [[keypointsDefinitionId, { landmarks }]] = Object.entries(
750760
state.keypointDefinitions
751761
);

src/ClassSelectionMenu/index.tsx

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,14 @@ const Number = styled("div")(() => ({
5252
color: muiColors.grey[700],
5353
}));
5454

55+
const getRegionValue = (item: string | { id: string; label: string }) => {
56+
return typeof item === "string" ? item : item.id;
57+
};
58+
5559
interface ClassSelectionMenuProps {
5660
selectedCls?: string;
57-
regionClsList: string[];
58-
onSelectCls: (label?: string) => void;
61+
regionClsList: (string | { id: string; label: string })[];
62+
onSelectCls: (value: string) => void;
5963
}
6064

6165
export const ClassSelectionMenu = ({
@@ -64,9 +68,15 @@ export const ClassSelectionMenu = ({
6468
onSelectCls,
6569
}: ClassSelectionMenuProps) => {
6670
useEffect(() => {
67-
const keyMapping: Record<string, (label?: string) => void> = {};
71+
const keyMapping: Record<
72+
string,
73+
(item?: string | { id: string; label: string }) => void
74+
> = {};
6875
for (let i = 0; i < 9 && i < regionClsList.length; i++) {
69-
keyMapping[i + 1] = () => onSelectCls(regionClsList[i]);
76+
keyMapping[i + 1] = () => {
77+
const item = regionClsList[i];
78+
onSelectCls(getRegionValue(item));
79+
};
7080
}
7181
const onKeyDown = (e: KeyboardEvent) => {
7282
if (keyMapping[e.key]) {
@@ -86,19 +96,29 @@ export const ClassSelectionMenu = ({
8696
icon={<BallotIcon style={{ color: muiColors.grey[700] }} />}
8797
expandedByDefault
8898
>
89-
{regionClsList.map((label, index) => (
99+
{regionClsList.map((item, index) => (
90100
<LabelContainer
91-
className={classnames({ selected: label === selectedCls })}
92-
onClick={() => onSelectCls(label)}
101+
className={classnames({
102+
selected: getRegionValue(item) === selectedCls,
103+
})}
104+
onClick={() => onSelectCls(getRegionValue(item))}
93105
>
94106
<Circle
95107
style={{ backgroundColor: colors[index % colors.length] }}
96108
/>
97-
<Label className={classnames({ selected: label === selectedCls })}>
98-
{capitalize(label)}
109+
<Label
110+
className={classnames({
111+
selected: getRegionValue(item) === selectedCls,
112+
})}
113+
>
114+
{capitalize(typeof item === "string" ? item : item.label)}
99115
</Label>
100116
<DashSep />
101-
<Number className={classnames({ selected: label === selectedCls })}>
117+
<Number
118+
className={classnames({
119+
selected: getRegionValue(item) === selectedCls,
120+
})}
121+
>
102122
{index < 9 ? `Key [${index + 1}]` : ""}
103123
</Number>
104124
</LabelContainer>

src/DemoSite/Editor.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@ export const examples: Record<string, () => Omit<AnnotatorProps, "onExit">> = {
4545
taskDescription:
4646
"Annotate each image according to this _markdown_ specification.",
4747
regionTagList: ["has-bun"],
48-
regionClsList: ["hotdog", "not-hotdog"],
48+
regionClsList: [
49+
{ id: "1", label: "hotdog" },
50+
{ id: "2", label: "not-hotdog" },
51+
],
4952
enabledTools: [
5053
"select",
5154
"create-point",

src/ImageCanvas/index.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ type Props = {
6262
showHighlightBox?: boolean;
6363
showPointDistances?: boolean;
6464
pointDistancePrecision?: number;
65-
regionClsList?: Array<string>;
65+
regionClsList?: Array<string> | Array<{ id: string; label: string }>;
6666
regionTagList?: Array<string>;
6767
allowedArea?: { x: number; y: number; w: number; h: number };
6868
RegionEditLabel?: ComponentType<any> | FunctionComponent<any> | null;
@@ -72,10 +72,10 @@ type Props = {
7272
autoSegmentationOptions?: AutosegOptions;
7373
modifyingAllowedArea?: boolean;
7474
allowComments?: boolean;
75-
onChangeRegion: (region: Region) => any;
76-
onBeginRegionEdit: (region: Region) => any;
77-
onCloseRegionEdit: (region: Region) => any;
78-
onDeleteRegion: (region: Region) => any;
75+
onChangeRegion: (region: Region) => void;
76+
onBeginRegionEdit: (region: Region) => void;
77+
onCloseRegionEdit: (region: Region) => void;
78+
onDeleteRegion: (region: Region) => void;
7979
onBeginBoxTransform: (region: Box, point: [number, number]) => void;
8080
onBeginMovePolygonPoint: (region: Polygon, index: number) => void;
8181
onBeginMoveKeypoint: (region: Keypoints, keypointId: string) => void;
@@ -481,7 +481,9 @@ export const ImageCanvas = ({
481481
hide={!showMask}
482482
autoSegmentationOptions={autoSegmentationOptions}
483483
imagePosition={imagePosition}
484-
regionClsList={regionClsList}
484+
regionClsList={regionClsList?.map((c) =>
485+
typeof c === "string" ? c : c.id
486+
)}
485487
imageSrc={imageSrc}
486488
regions={regions}
487489
/>

src/MainLayout/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,7 @@ export const MainLayout = ({
339339
/>
340340
),
341341
<RegionSelector
342+
regionClsList={state.regionClsList}
342343
regions={activeImage ? activeImage.regions : []}
343344
onSelectRegion={action("SELECT_REGION", "region")}
344345
onDeleteRegion={action("DELETE_REGION", "region")}

src/MainLayout/types.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ export type ToolEnum =
2121
| "create-pixel"
2222
| "create-expanding-line"
2323
| "create-keypoints"
24-
// TODO: check, added new types
2524
| "modify-allowed-area"
2625
| "create-line"
2726
| "show-tags"
@@ -101,11 +100,11 @@ export type MainLayoutStateBase = {
101100
mode: Mode;
102101
taskDescription: string;
103102
allowedArea?: { x: number; y: number; w: number; h: number };
104-
regionClsList?: Array<string>;
103+
regionClsList?: Array<string> | Array<{ id: string; label: string }>;
105104
regionTagList?: Array<string>;
106105
imageClsList?: Array<string>;
107106
imageTagList?: Array<string>;
108-
enabledTools: Array<string>;
107+
enabledTools: Array<ToolEnum>;
109108
history: Array<{ time: Date; state: MainLayoutState; name: string }>;
110109
keypointDefinitions: KeypointsDefinition;
111110
allowComments?: boolean;

src/RegionLabel/index.tsx

Lines changed: 64 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const useStyles = makeStyles(styles);
2323
type Props = {
2424
region: Region;
2525
editing?: boolean;
26-
allowedClasses?: Array<string>;
26+
allowedClasses?: Array<string> | Array<{ id: string; label: string }>;
2727
allowedTags?: Array<string>;
2828
cls?: string;
2929
tags?: Array<string>;
@@ -57,6 +57,21 @@ export const RegionLabel = ({
5757
if (commentInput) return commentInput.focus();
5858
};
5959

60+
const isCreatableAllowedClasses = typeof allowedClasses?.[0] === "string";
61+
const selectedRegionClass =
62+
allowedClasses?.find((c) => typeof c === "object" && c.id === region.cls) ||
63+
region.cls;
64+
const selectedLabel =
65+
selectedRegionClass && typeof selectedRegionClass === "object"
66+
? selectedRegionClass.label
67+
: region.cls;
68+
const selectedValue =
69+
selectedRegionClass && typeof selectedRegionClass === "object"
70+
? { label: selectedRegionClass.label, value: selectedRegionClass.id }
71+
: region.cls
72+
? { label: region.cls, value: region.cls }
73+
: null;
74+
6075
return (
6176
<ThemeProvider theme={theme}>
6277
<Paper
@@ -73,7 +88,7 @@ export const RegionLabel = ({
7388
className="circle"
7489
style={{ backgroundColor: region.color }}
7590
/>
76-
{region.cls}
91+
{selectedLabel}
7792
</div>
7893
)}
7994
{region.tags && (
@@ -116,36 +131,59 @@ export const RegionLabel = ({
116131
</div>
117132
{(allowedClasses || []).length > 0 && (
118133
<div style={{ marginTop: 6 }}>
119-
<CreatableSelect
120-
placeholder="Classification"
121-
onChange={(o, actionMeta) => {
122-
if (!o) return;
123-
if (
124-
actionMeta.action == "create-option" &&
125-
onRegionClassAdded
126-
) {
127-
onRegionClassAdded(o.value);
128-
}
129-
// TODO: check if this is correct after update source
130-
onChange({
131-
...region,
132-
cls: o.value,
133-
});
134-
}}
135-
value={
136-
region.cls ? { label: region.cls, value: region.cls } : null
137-
}
138-
options={asMutable(
139-
allowedClasses?.map((c) => ({ value: c, label: c }))
140-
)}
141-
/>
134+
{isCreatableAllowedClasses ? (
135+
<CreatableSelect
136+
placeholder="Classification"
137+
onChange={(o, actionMeta) => {
138+
if (!o) return;
139+
if (
140+
actionMeta.action == "create-option" &&
141+
onRegionClassAdded
142+
) {
143+
onRegionClassAdded(o.value);
144+
}
145+
onChange({
146+
...region,
147+
cls: o.value,
148+
});
149+
}}
150+
value={selectedValue}
151+
options={asMutable(
152+
allowedClasses?.map((c) => {
153+
if (typeof c === "string") {
154+
return { value: c, label: c };
155+
}
156+
return { value: c.id, label: c.label };
157+
})
158+
)}
159+
/>
160+
) : (
161+
<Select
162+
placeholder="Classification"
163+
onChange={(o) => {
164+
if (!o) return;
165+
onChange({
166+
...region,
167+
cls: o.value,
168+
});
169+
}}
170+
value={selectedValue}
171+
options={asMutable(
172+
allowedClasses?.map((c) => {
173+
if (typeof c === "string") {
174+
return { value: c, label: c };
175+
}
176+
return { value: c.id, label: c.label };
177+
})
178+
)}
179+
/>
180+
)}
142181
</div>
143182
)}
144183
{(allowedTags || []).length > 0 && (
145184
<div style={{ marginTop: 4 }}>
146185
<Select
147186
onChange={(newTags) =>
148-
// TODO: check if this is correct after update source
149187
onChange({
150188
...region,
151189
tags: newTags.map((t) => t.value),

0 commit comments

Comments
 (0)