Feat: 레이블 카테고리 수동 변경 기능 추가 - S11P21S002-236
This commit is contained in:
parent
4a71a25a23
commit
c58a6a5fd4
@ -11,34 +11,29 @@ import { Label } from '@/types';
|
||||
import useLabelJson from '@/hooks/useLabelJson';
|
||||
import { saveImageLabels } from '@/api/lablingApi';
|
||||
import useProjectStore from '@/stores/useProjectStore';
|
||||
import { LABEL_CATEGORY } from '@/constants';
|
||||
|
||||
export default function ImageCanvas() {
|
||||
const project = useProjectStore((state) => state.project)!;
|
||||
const { id: imageId, imagePath, dataPath } = useCanvasStore((state) => state.image)!;
|
||||
const { data: labelData } = useLabelJson(dataPath, project);
|
||||
const { labels, drawState, setDrawState, addLabel, setLabels, selectedLabelId, setSelectedLabelId, sidebarSize } =
|
||||
useCanvasStore();
|
||||
const { shapes } = labelData || [];
|
||||
const selectedLabelId = useCanvasStore((state) => state.selectedLabelId);
|
||||
const setSelectedLabelId = useCanvasStore((state) => state.setSelectedLabelId);
|
||||
const sidebarSize = useCanvasStore((state) => state.sidebarSize);
|
||||
const stageWidth = window.innerWidth * ((100 - sidebarSize) / 100) - 201;
|
||||
const stageHeight = window.innerHeight - 64;
|
||||
const stageRef = useRef<Konva.Stage>(null);
|
||||
const dragLayerRef = useRef<Konva.Layer>(null);
|
||||
const scale = useRef<number>(0);
|
||||
const labels = useCanvasStore((state) => state.labels) ?? [];
|
||||
const [image] = useImage(imagePath);
|
||||
const drawState = useCanvasStore((state) => state.drawState);
|
||||
const setDrawState = useCanvasStore((state) => state.setDrawState);
|
||||
const addLabel = useCanvasStore((state) => state.addLabel);
|
||||
const setLabels = useCanvasStore((state) => state.setLabels);
|
||||
const [rectPoints, setRectPoints] = useState<[number, number][]>([]);
|
||||
const [polygonPoints, setPolygonPoints] = useState<[number, number][]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
setLabels(
|
||||
shapes.map<Label>(({ label, color, points, shape_type }, index) => ({
|
||||
shapes.map<Label>(({ group_id, color, points, shape_type }, index) => ({
|
||||
id: index,
|
||||
name: label,
|
||||
categoryId: group_id,
|
||||
color,
|
||||
type: shape_type === 'polygon' ? 'polygon' : 'rect',
|
||||
coordinates: points,
|
||||
@ -58,11 +53,13 @@ export default function ImageCanvas() {
|
||||
const saveJson = () => {
|
||||
const json = JSON.stringify({
|
||||
...labelData,
|
||||
shapes: labels.map(({ name, color, coordinates, type }) => ({
|
||||
label: name,
|
||||
shapes: labels.map(({ categoryId, color, coordinates, type }) => ({
|
||||
label: LABEL_CATEGORY[categoryId],
|
||||
color,
|
||||
shape_type: type === 'polygon' ? 'polygon' : 'rectangle',
|
||||
points: coordinates,
|
||||
group_id: categoryId,
|
||||
shape_type: type === 'polygon' ? 'polygon' : 'rectangle',
|
||||
flags: {},
|
||||
})),
|
||||
imageWidth: image!.width,
|
||||
imageHeight: image!.height,
|
||||
@ -128,7 +125,7 @@ export default function ImageCanvas() {
|
||||
const id = labels.length;
|
||||
addLabel({
|
||||
id: id,
|
||||
name: 'label',
|
||||
categoryId: 0,
|
||||
type: 'polygon',
|
||||
color: `#${color}`,
|
||||
coordinates: polygonPoints.slice(0, -1),
|
||||
@ -155,7 +152,7 @@ export default function ImageCanvas() {
|
||||
const id = labels.length;
|
||||
addLabel({
|
||||
id: id,
|
||||
name: 'label',
|
||||
categoryId: 0,
|
||||
type: 'rect',
|
||||
color: `#${color}`,
|
||||
coordinates: rectPoints,
|
||||
|
@ -1,14 +1,7 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Image, Layer, Stage, Line, Rect } from 'react-konva';
|
||||
import useImage from 'use-image';
|
||||
import { Label } from '@/types';
|
||||
|
||||
interface Shape {
|
||||
label: string;
|
||||
color: string;
|
||||
points: [number, number][];
|
||||
shape_type: 'polygon' | 'rectangle';
|
||||
}
|
||||
import { Label, Shape } from '@/types';
|
||||
|
||||
interface ImageWithLabelsProps {
|
||||
imagePath: string;
|
||||
@ -27,7 +20,7 @@ export default function ImageWithLabels({ imagePath, labelData }: ImageWithLabel
|
||||
const json: { shapes: Shape[] } = await response.json();
|
||||
const shapes = json.shapes.map((shape, index) => ({
|
||||
id: index,
|
||||
name: shape.label,
|
||||
categoryId: shape.categoryId,
|
||||
color: shape.color,
|
||||
type: shape.shape_type === 'polygon' ? 'polygon' : 'rect',
|
||||
coordinates: shape.points,
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { LABEL_CATEGORY } from '@/constants';
|
||||
import useCanvasStore from '@/stores/useCanvasStore';
|
||||
import { Label } from '@/types';
|
||||
import { Trash2 } from 'lucide-react';
|
||||
@ -5,7 +6,7 @@ import { MouseEventHandler } from 'react';
|
||||
|
||||
export default function LabelButton({
|
||||
id,
|
||||
name,
|
||||
categoryId,
|
||||
color,
|
||||
selected,
|
||||
setSelectedLabelId,
|
||||
@ -23,10 +24,10 @@ export default function LabelButton({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex items-center gap-2.5 rounded-lg transition-colors ${selected ? 'bg-gray-200' : 'bg-gray-50 hover:bg-gray-100'}`}
|
||||
className={`flex items-center rounded-lg transition-colors ${selected ? 'bg-gray-200' : 'bg-gray-50 hover:bg-gray-100'}`}
|
||||
>
|
||||
<button
|
||||
className="flex grow items-center gap-2.5 p-2.5 text-left"
|
||||
<div
|
||||
className="flex grow cursor-pointer items-center gap-2.5 p-2.5 text-left"
|
||||
onClick={handleClick}
|
||||
>
|
||||
<div
|
||||
@ -35,8 +36,24 @@ export default function LabelButton({
|
||||
backgroundColor: color,
|
||||
}}
|
||||
/>
|
||||
<span className="body grow text-gray-900">{name}</span>
|
||||
</button>
|
||||
<select
|
||||
className="body-small w-[97.2px] cursor-pointer rounded bg-transparent"
|
||||
value={categoryId.toString()}
|
||||
onChange={(event) => {
|
||||
const newCategoryId = Number(event.target.value);
|
||||
setLabels(labels.map((label) => (label.id === id ? { ...label, categoryId: newCategoryId } : label)));
|
||||
}}
|
||||
>
|
||||
{LABEL_CATEGORY.map((category, index) => (
|
||||
<option
|
||||
value={index.toString()}
|
||||
key={index}
|
||||
>
|
||||
{category}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<button
|
||||
className="p-2.5"
|
||||
onClick={handleDelete}
|
||||
|
82
frontend/src/constants.ts
Normal file
82
frontend/src/constants.ts
Normal file
@ -0,0 +1,82 @@
|
||||
export const LABEL_CATEGORY = [
|
||||
'person',
|
||||
'bicycle',
|
||||
'car',
|
||||
'motorcycle',
|
||||
'airplane',
|
||||
'bus',
|
||||
'train',
|
||||
'truck',
|
||||
'boat',
|
||||
'traffic light',
|
||||
'fire hydrant',
|
||||
'stop sign',
|
||||
'parking meter',
|
||||
'bench',
|
||||
'bird',
|
||||
'cat',
|
||||
'dog',
|
||||
'horse',
|
||||
'sheep',
|
||||
'cow',
|
||||
'elephant',
|
||||
'bear',
|
||||
'zebra',
|
||||
'giraffe',
|
||||
'backpack',
|
||||
'umbrella',
|
||||
'handbag',
|
||||
'tie',
|
||||
'suitcase',
|
||||
'frisbee',
|
||||
'skis',
|
||||
'snowboard',
|
||||
'sports ball',
|
||||
'kite',
|
||||
'baseball bat',
|
||||
'baseball glove',
|
||||
'skateboard',
|
||||
'surfboard',
|
||||
'tennis racket',
|
||||
'bottle',
|
||||
'wine glass',
|
||||
'cup',
|
||||
'fork',
|
||||
'knife',
|
||||
'spoon',
|
||||
'bowl',
|
||||
'banana',
|
||||
'apple',
|
||||
'sandwich',
|
||||
'orange',
|
||||
'broccoli',
|
||||
'carrot',
|
||||
'hot dog',
|
||||
'pizza',
|
||||
'donut',
|
||||
'cake',
|
||||
'chair',
|
||||
'couch',
|
||||
'potted plant',
|
||||
'bed',
|
||||
'dining table',
|
||||
'toilet',
|
||||
'tv',
|
||||
'laptop',
|
||||
'mouse',
|
||||
'remote',
|
||||
'keyboard',
|
||||
'cell phone',
|
||||
'microwave',
|
||||
'oven',
|
||||
'toaster',
|
||||
'sink',
|
||||
'refrigerator',
|
||||
'book',
|
||||
'clock',
|
||||
'vase',
|
||||
'scissors',
|
||||
'teddy bear',
|
||||
'hair drier',
|
||||
'toothbrush',
|
||||
];
|
@ -32,7 +32,7 @@ export type Workspace = {
|
||||
// 레이블 관련 타입
|
||||
export type Label = {
|
||||
id: number;
|
||||
name: string;
|
||||
categoryId: number;
|
||||
color: string;
|
||||
type: 'polygon' | 'rect';
|
||||
coordinates: Array<[number, number]>;
|
||||
@ -248,7 +248,7 @@ export interface RefreshTokenResponse {
|
||||
}
|
||||
|
||||
export interface Shape {
|
||||
label: string;
|
||||
categoryId: number;
|
||||
color: string;
|
||||
points: [number, number][];
|
||||
group_id: number;
|
||||
|
@ -4,7 +4,7 @@ describe('createLabelJson', () => {
|
||||
it('should create a label JSON for classification', () => {
|
||||
const result = createLabelJson('classification');
|
||||
expect(result).toEqual({
|
||||
version: '0.1.0',
|
||||
version: '0.0.0',
|
||||
task_type: 'cls',
|
||||
shapes: [],
|
||||
split: 'none',
|
||||
@ -17,7 +17,7 @@ describe('createLabelJson', () => {
|
||||
it('should create a label JSON for detection', () => {
|
||||
const result = createLabelJson('detection');
|
||||
expect(result).toEqual({
|
||||
version: '0.1.0',
|
||||
version: '0.0.0',
|
||||
task_type: 'det',
|
||||
shapes: [],
|
||||
split: 'none',
|
||||
@ -30,7 +30,7 @@ describe('createLabelJson', () => {
|
||||
it('should create a label JSON for segmentation', () => {
|
||||
const result = createLabelJson('segmentation');
|
||||
expect(result).toEqual({
|
||||
version: '0.1.0',
|
||||
version: '0.0.0',
|
||||
task_type: 'seg',
|
||||
shapes: [],
|
||||
split: 'none',
|
||||
|
@ -2,7 +2,7 @@ import { LabelJson } from '@/types';
|
||||
|
||||
export default function createLabelJson(type: 'classification' | 'detection' | 'segmentation'): LabelJson {
|
||||
return {
|
||||
version: '0.1.0',
|
||||
version: '0.0.0',
|
||||
task_type: type === 'classification' ? 'cls' : type === 'detection' ? 'det' : 'seg',
|
||||
shapes: [],
|
||||
split: 'none',
|
||||
|
Loading…
Reference in New Issue
Block a user