@servantcdh/ez-planet-labeling 0.4.0 → 0.4.1

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 (2) hide show
  1. package/README.md +299 -121
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,10 +1,8 @@
1
1
  # @servantcdh/ez-planet-labeling
2
2
 
3
- Fabric.js v6 기반의 이미지 라벨링 워크스페이스 라이브러리.
3
+ Fabric.js 기반 이미지 라벨링 워크스페이스 라이브러리.
4
4
 
5
- 3-Level API유연한 통합을 지원합니다.
6
-
7
- [![StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/servantcdh/ez-planet/tree/master/examples/labeling-demo?file=src/App.tsx)
5
+ 호스트 앱이 데이터와 뮤테이션을 Context주입하면, 라이브러리는 순수 프레젠테이션 레이어로 동작합니다. CSS는 JS 번들에 자동 주입되어 별도 import가 필요 없습니다.
8
6
 
9
7
  ## 설치
10
8
 
@@ -20,160 +18,340 @@ npm install @servantcdh/ez-planet-labeling
20
18
  |---------|---------|------|
21
19
  | `react` | `^18.0.0` | UI 런타임 |
22
20
  | `react-dom` | `^18.0.0` | DOM 렌더링 |
23
- | `fabric` | `^6.0.0` | 캔버스 엔진 |
21
+ | `fabric` | `^5.0.0` | 캔버스 엔진 |
24
22
 
25
23
  ### Bundled Dependencies (라이브러리에 포함)
26
24
 
27
25
  | Package | 용도 |
28
26
  |---------|------|
29
- | `zustand` | 내부 상태 관리 (호스트 앱 store와 격리) |
30
- | `zundo` | Temporal history (Undo/Redo) |
31
- | `@erase2d/fabric` | Fabric v6 EraserBrush 대체 |
27
+ | `zustand` | 내부 상태 관리 |
28
+ | `zundo` | Undo/Redo |
29
+ | `@erase2d/fabric` | EraserBrush |
30
+ | `@tanstack/react-table` | 가상화 테이블 |
31
+ | `@tanstack/react-virtual` | 가상화 스크롤 |
32
+ | `echarts` | 차트 라벨 렌더링 |
32
33
 
33
- ## 3-Level API
34
+ ## 기본 사용법
34
35
 
35
- ### Level 1 — All-in-one
36
+ ```tsx
37
+ import {
38
+ LabelingProviders,
39
+ LabelingWorkspace,
40
+ staticData,
41
+ loadingData,
42
+ } from '@servantcdh/ez-planet-labeling';
43
+ // CSS import 불필요 — JS 번들에 자동 포함
44
+
45
+ function App() {
46
+ const dataCtx = useMyLabelingData(); // 호스트가 구현
47
+ const mutationCtx = useMyLabelingMutations(); // 호스트가 구현
48
+ const datasetCtx = useMyDatasetData(); // 호스트가 구현
49
+
50
+ return (
51
+ <LabelingProviders
52
+ data={dataCtx}
53
+ mutations={mutationCtx}
54
+ dataset={datasetCtx}
55
+ >
56
+ <LabelingWorkspace />
57
+ </LabelingProviders>
58
+ );
59
+ }
60
+ ```
36
61
 
37
- 단일 컴포넌트로 완성된 라벨링 워크스페이스를 렌더링합니다.
62
+ ## Architecture
38
63
 
39
- ```tsx
40
- import { LabelingWorkspace } from '@servantcdh/ez-planet-labeling'
41
- import '@servantcdh/ez-planet-labeling/dist/style.css'
42
-
43
- <LabelingWorkspace
44
- image={{ url: '/sample.png', width: 1920, height: 1080 }}
45
- annotations={annotations}
46
- onChange={(e) => setAnnotations(e.annotations)}
47
- records={records}
48
- activeRecordId={currentId}
49
- onRecordSelect={handleSelect}
50
- classes={classes}
51
- onSave={handleSave}
52
- tools={['selection', 'blankRect', 'polygon', 'brush', 'eraser']}
53
- mode="labeling"
54
- />
64
+ ### Provider 구조
65
+
66
+ 라이브러리는 3개의 Context를 통해 호스트로부터 데이터를 주입받습니다. 변경 빈도별로 분리하여 불필요한 리렌더를 방지합니다.
67
+
68
+ ```
69
+ LabelingProviders
70
+ ├── LabelingDataProvider — 라벨/정책/컨텍스트 데이터 (자주 변경)
71
+ ├── LabelingMutationProvider Mutation 콜백 함수 (참조 안정적)
72
+ └── LabelingDatasetProvider — 데이터셋/콘텐츠 (레코드 선택 시만 변경)
55
73
  ```
56
74
 
57
- ### Level 2 — Composable
75
+ ### AsyncData\<T\>
58
76
 
59
- Provider와 개별 컴포넌트를 조합하여 커스텀 레이아웃을 구성합니다.
77
+ 모든 쿼리 데이터는 `AsyncData<T>` 래퍼로 전달합니다. react-query의 반환 타입과 동일한 인터페이스입니다.
60
78
 
61
- ```tsx
62
- import {
63
- LabelingProvider,
64
- LabelingCanvas,
65
- LabelingToolbar,
66
- LabelingNavigation,
67
- LabelingInfoPanel,
68
- } from '@servantcdh/ez-planet-labeling'
69
- import '@servantcdh/ez-planet-labeling/dist/style.css'
70
-
71
- <LabelingProvider image={image} annotations={annotations} onChange={onChange} ...>
72
- <LabelingNavigation records={records} activeRecordId={id} onRecordSelect={onSelect} />
73
- <LabelingCanvas image={image} annotations={annotations} onChange={onChange} />
74
- <LabelingToolbar tools={['selection', 'brush', 'eraser']} />
75
- <LabelingInfoPanel classes={classes} annotations={annotations} />
76
- </LabelingProvider>
79
+ ```ts
80
+ interface AsyncData<T> {
81
+ data: T | undefined;
82
+ isLoading: boolean;
83
+ isFetching: boolean;
84
+ isError: boolean;
85
+ error: unknown;
86
+ refetch: () => void;
87
+ dataUpdatedAt: number;
88
+ }
77
89
  ```
78
90
 
79
- ### Level 3 — Headless Hooks
91
+ 헬퍼 함수:
80
92
 
81
- UI 없이 캔버스 로직만 사용합니다.
93
+ | 함수 | 용도 |
94
+ |------|------|
95
+ | `staticData(value)` | 이미 로드된 값을 래핑 |
96
+ | `loadingData()` | 로딩 상태 플레이스홀더 |
97
+ | `errorData(error)` | 에러 상태 플레이스홀더 |
82
98
 
83
- ```tsx
84
- import {
85
- useLabelingTools,
86
- useLabelingCanvas,
87
- useLabelingHistory,
88
- } from '@servantcdh/ez-planet-labeling'
89
-
90
- const { activeToolId, setTool, colorCode, setColorCode } = useLabelingTools()
91
- const { addAnnotation, removeAnnotation, exportState } = useLabelingCanvas()
92
- const { canUndo, canRedo, undo, redo } = useLabelingHistory()
99
+ ### LabelingDataContextValue
100
+
101
+ ```ts
102
+ interface LabelingDataContextValue {
103
+ policiesBatch: AsyncData<PolicyDetail[]>;
104
+ labelContext: AsyncData<LabelContextResponse>;
105
+ labelContextStatus: AsyncData<ContentsetStatusResponse>;
106
+ labelContextInLabeling: AsyncData<InLabelingStatusResponse>;
107
+ labelContextEnable: AsyncData<LabelContextEnableResponse>;
108
+ labelSearch: AsyncData<LabelSearchResult>;
109
+ previousLabelContexts: AsyncData<PreviousLabelContextWithLabelsResponse[]>;
110
+ validResultSearch: AsyncData<ValidResultSearchResult>;
111
+ }
93
112
  ```
94
113
 
95
- ## Props Reference
96
-
97
- ### LabelingWorkspaceProps
98
-
99
- | Prop | Type | Required | Description |
100
- |------|------|----------|-------------|
101
- | `image` | `string \| { url, width, height }` | Yes | 라벨링 대상 이미지 |
102
- | `annotations` | `Annotation[]` | Yes | 현재 어노테이션 목록 |
103
- | `onChange` | `(event: CanvasChangeEvent) => void` | Yes | 어노테이션 변경 콜백 |
104
- | `records` | `WorkspaceRecord[]` | Yes | 레코드 목록 (Navigation) |
105
- | `activeRecordId` | `string` | Yes | 현재 활성 레코드 ID |
106
- | `onRecordSelect` | `(record: WorkspaceRecord) => void` | Yes | 레코드 선택 콜백 |
107
- | `classes` | `LabelingClass[]` | Yes | 라벨 클래스 목록 |
108
- | `onSave` | `(state: CanvasState) => void` | Yes | 저장 콜백 |
109
- | `tools` | `ToolType[]` | No | 사용할 도구 목록 |
110
- | `mode` | `'labeling' \| 'validation' \| 'readonly'` | No | 워크스페이스 모드 |
111
- | `theme` | `Partial<LabelingTheme>` | No | 테마 커스터마이징 |
112
- | `layout` | `WorkspaceLayout` | No | 레이아웃 설정 |
113
- | `extensions` | `LabelingExtension[]` | No | 확장 기능 |
114
- | `validationResults` | `ValidationResult[]` | No | 검증 결과 (validation 모드) |
115
- | `indicator` | `WorkspaceIndicator` | No | 진행 표시기 |
116
-
117
- ### Annotation
114
+ ### LabelingMutationContextValue
115
+
116
+ 뮤테이션은 `async function` + `MutationState` 쌍으로 구성됩니다.
118
117
 
119
118
  ```ts
120
- interface Annotation {
121
- id: string // 호스트 앱에서 자유롭게 할당 (DB PK, UUID 등)
122
- type: AnnotationType // 'box' | 'segmentation' | 'polygon' | 'brush' | ...
123
- label: { name: string; index: number }
124
- style: { color: string; opacity: number; lineColor?: string; lineWidth?: number }
125
- geometry: AnnotationGeometry | null // BoxGeometry | PolygonGeometry | ...
119
+ interface LabelingMutationContextValue {
120
+ batchUpdateLabels: (vars: LabelBatchUpdateVariables) => Promise<LabelBatchUpdateResponse>;
121
+ batchUpdateLabelsState: MutationState;
122
+
123
+ bulkCreateLabels: (vars: LabelBulkCreateVariables) => Promise<BulkLabelCreateResponse>;
124
+ bulkCreateLabelsState: MutationState;
125
+
126
+ createLabelContext: (body: LabelContextCreateRequest) => Promise<LabelContextResponse>;
127
+ createLabelContextState: MutationState;
128
+
129
+ updateLabelContext: (vars: LabelContextUpdateVariables) => Promise<LabelContextResponse>;
130
+ updateLabelContextState: MutationState;
131
+
132
+ createLabelStatus: (body: LabelStatusCreateRequest) => Promise<ApiResponse<LabelStatusResponse>>;
133
+ createLabelStatusState: MutationState;
134
+
135
+ uploadFileLabel: (vars: FileLabelUploadVariables) => Promise<LabelResponse>;
136
+ uploadFileLabelState: MutationState;
137
+
138
+ copyLabels: (body: LabelCopyRequest) => Promise<LabelCopyResponse>;
139
+ copyLabelsState: MutationState;
140
+
141
+ createValidResult: (body: ValidResultCreateRequest) => Promise<ValidResultResponse>;
142
+ createValidResultState: MutationState;
143
+
144
+ updateValidResult: (vars: ValidResultUpdateVariables) => Promise<ValidResultResponse>;
145
+ updateValidResultState: MutationState;
146
+
147
+ bulkDeleteValidResults: (body: ValidResultBulkDeleteRequest) => Promise<ValidResultBulkDeleteResponse>;
148
+ bulkDeleteValidResultsState: MutationState;
149
+
150
+ onMutationSuccess: (hint: MutationSuccessHint) => void;
126
151
  }
127
152
  ```
128
153
 
129
- ## 주요 기능
154
+ `onMutationSuccess`는 라이브러리가 뮤테이션 성공 후 호출합니다. 호스트는 `hint.type`에 따라 적절한 캐시 무효화/리페치를 수행하면 됩니다.
130
155
 
131
- - **도구**: Selection, Bounding Box, Polygon, Brush, Eraser, Magic Brush, Superpixel
132
- - **Extension System**: `ToolExtension`으로 커스텀 도구 등록 (SAM, AutoLabeling 등)
133
- - **Validation Mode**: `mode="validation"`으로 검증 워크플로우 지원
134
- - **SSR Safe**: `loadFabric()` 비동기 동적 import로 Next.js 호환
135
- - **CSS Modules**: `lc-` prefix 스코핑, CSS 변수 기반 theming
136
- - **Undo/Redo**: zundo 기반 temporal history
156
+ ```ts
157
+ type MutationSuccessHint =
158
+ | { type: "labels-saved"; labelContextId: string | null }
159
+ | { type: "labels-copied" }
160
+ | { type: "labels-bulk-created"; labelContextId: string }
161
+ | { type: "label-context-created"; labelContextId: string }
162
+ | { type: "label-context-updated"; labelContextId: string }
163
+ | { type: "label-status-created" }
164
+ | { type: "valid-result-created" }
165
+ | { type: "valid-result-updated" }
166
+ | { type: "valid-results-deleted" }
167
+ | { type: "file-uploaded"; labelContextId: string };
168
+ ```
169
+
170
+ ### LabelingDatasetContextValue
171
+
172
+ ```ts
173
+ interface LabelingDatasetContextValue {
174
+ datasetDetail: AsyncData<DatasetDTO>;
175
+ datasetContents: AsyncData<DatasetApiResponse<DatasetContentSearchResponse>>;
176
+ datasetContentDetail: AsyncData<DatasetContentRecord | null>;
177
+ }
178
+ ```
137
179
 
138
180
  ## Extension System
139
181
 
182
+ `LabelingWorkspace`는 `extensions` prop을 통해 호스트가 확장 기능을 등록할 수 있습니다. 라이브러리는 렌더 슬롯만 제공하며, 모든 UI와 로직은 호스트가 소유합니다.
183
+
140
184
  ```tsx
141
- import type { ToolExtension } from '@servantcdh/ez-planet-labeling'
142
-
143
- const samExtension: ToolExtension = {
144
- id: 'sam-tool',
145
- slot: 'tool',
146
- icon: <SAMIcon />,
147
- label: 'SAM',
148
- shortcut: 'S',
149
- canvasHandlers: {
150
- onMouseDown: (e) => { /* SAM point prompt */ },
151
- onMouseUp: (e) => { /* Generate mask */ },
152
- },
153
- render: (ctx) => <SAMPanel context={ctx} />,
185
+ <LabelingWorkspace extensions={[samExtension, autoLabelingExtension]} />
186
+ ```
187
+
188
+ ### LabelingExtension
189
+
190
+ ```ts
191
+ interface LabelingExtension {
192
+ id: string;
193
+ name: string;
194
+ enabled?: boolean;
195
+ renderInfoPanelAction?: (ctx: ExtensionRenderContext) => ReactNode;
196
+ renderOverlay?: (ctx: ExtensionRenderContext) => ReactNode;
197
+ renderToolbarAction?: (ctx: ExtensionRenderContext) => ReactNode;
198
+ }
199
+ ```
200
+
201
+ ### Render Slots
202
+
203
+ | Slot | 위치 | 용도 |
204
+ |------|------|------|
205
+ | `renderInfoPanelAction` | InfoPanel 하단 | 액션 버튼 (예: Auto Labeling 실행) |
206
+ | `renderOverlay` | 워크스페이스 루트 위 | 모달, 패널 등 오버레이 UI |
207
+ | `renderToolbarAction` | 플로팅 툴바 끝 | 캔버스 도구 버튼 (예: SAM) |
208
+
209
+ ### ExtensionRenderContext
210
+
211
+ 각 렌더 함수는 현재 워크스페이스 상태와 캔버스 접근 API를 포함한 컨텍스트를 받습니다.
212
+
213
+ ```ts
214
+ interface ExtensionRenderContext {
215
+ // 워크스페이스 상태
216
+ contentSetId: string | null;
217
+ labelContextId: string | null;
218
+ policyIds: string[];
219
+ datasetId: string;
220
+ datasetVersion: string;
221
+ requestDataRefresh: (hint: MutationSuccessHint) => void;
222
+
223
+ // 캔버스 접근
224
+ canvasRef: RefObject<unknown | null>; // fabric.Canvas 인스턴스
225
+ imageInfo: WorkspaceImageInfo | null; // { url, width, height }
226
+ addCanvasObjects: (objects: unknown[]) => void;
227
+ removeCanvasObjects: (predicate: (obj: unknown) => boolean) => void;
154
228
  }
229
+ ```
230
+
231
+ ### Canvas Access
155
232
 
156
- <LabelingWorkspace extensions={[samExtension]} ... />
233
+ 캔버스와 직접 상호작용해야 하는 확장은 다음 export를 사용할 수 있습니다:
234
+
235
+ ```ts
236
+ import {
237
+ getCanvasInstance, // fabric.Canvas 싱글턴 반환
238
+ addCanvasObjects, // fabric 오브젝트 추가
239
+ removeCanvasObjects, // 조건에 맞는 오브젝트 제거
240
+ } from '@servantcdh/ez-planet-labeling';
157
241
  ```
158
242
 
159
- ## Theming
243
+ ### Extension 구현 예시 (SAM)
160
244
 
161
245
  ```tsx
162
- <LabelingWorkspace
163
- theme={{
164
- primary: '#3b82f6',
165
- background: '#1a1a2e',
166
- surface: '#16213e',
167
- border: '#334155',
168
- text: '#f1f5f9',
169
- textSecondary: '#94a3b8',
170
- fontFamily: 'Pretendard, sans-serif',
171
- radius: 8,
172
- }}
173
- ...
174
- />
246
+ import type { LabelingExtension } from '@servantcdh/ez-planet-labeling';
247
+
248
+ function createSAMExtension(api: SAMApi): LabelingExtension {
249
+ return {
250
+ id: 'sam',
251
+ name: 'Segment Anything',
252
+ renderToolbarAction: (ctx) => (
253
+ <SAMToolButton
254
+ canvasRef={ctx.canvasRef}
255
+ imageInfo={ctx.imageInfo}
256
+ onResult={(polygons) => ctx.addCanvasObjects(polygons)}
257
+ />
258
+ ),
259
+ renderOverlay: (ctx) => (
260
+ <SAMResultPanel
261
+ api={api}
262
+ contentSetId={ctx.contentSetId}
263
+ onApply={() => ctx.requestDataRefresh({
264
+ type: 'labels-saved',
265
+ labelContextId: ctx.labelContextId,
266
+ })}
267
+ />
268
+ ),
269
+ };
270
+ }
175
271
  ```
176
272
 
273
+ ### Extension 구현 예시 (Automated Labeling)
274
+
275
+ ```tsx
276
+ function createAutoLabelingExtension(api: AutoLabelingApi): LabelingExtension {
277
+ return {
278
+ id: 'auto-labeling',
279
+ name: 'Automated Labeling',
280
+ renderInfoPanelAction: (ctx) => (
281
+ <AutoLabelingButton
282
+ contentSetId={ctx.contentSetId}
283
+ policyIds={ctx.policyIds}
284
+ />
285
+ ),
286
+ renderOverlay: (ctx) => (
287
+ <AutoLabelingModal
288
+ api={api}
289
+ datasetId={ctx.datasetId}
290
+ onComplete={() => ctx.requestDataRefresh({
291
+ type: 'labels-saved',
292
+ labelContextId: ctx.labelContextId,
293
+ })}
294
+ />
295
+ ),
296
+ };
297
+ }
298
+ ```
299
+
300
+ ## 주요 기능
301
+
302
+ - **도구**: Selection, Bounding Box, Polygon, Brush, Eraser, Magic Brush, Superpixel
303
+ - **Extension System**: 렌더 슬롯 + 캔버스 접근 기반 플러그인 아키텍처
304
+ - **Validation Mode**: 검증 워크플로우 지원
305
+ - **Undo/Redo**: zundo 기반 temporal history
306
+ - **CSS Scoping**: `.lc-root` 컨테이너 스코핑, `--lc-*` 네임스페이스 CSS 변수
307
+ - **CSS-in-JS 번들**: `vite-plugin-css-injected-by-js`로 별도 CSS import 불필요
308
+
309
+ ## Exports
310
+
311
+ ### Components
312
+
313
+ | Export | 설명 |
314
+ |--------|------|
315
+ | `LabelingWorkspace` | 메인 워크스페이스 컴포넌트 |
316
+ | `LabelingProviders` | 3개 Provider 합성 래퍼 |
317
+
318
+ ### Types
319
+
320
+ | Export | 설명 |
321
+ |--------|------|
322
+ | `LabelingWorkspaceProps` | 워크스페이스 props |
323
+ | `LabelingProvidersProps` | Provider props |
324
+ | `LabelingDataContextValue` | 데이터 Context 인터페이스 |
325
+ | `LabelingMutationContextValue` | 뮤테이션 Context 인터페이스 |
326
+ | `LabelingDatasetContextValue` | 데이터셋 Context 인터페이스 |
327
+ | `AsyncData<T>` | 비동기 데이터 래퍼 |
328
+ | `MutationState` | 뮤테이션 상태 |
329
+ | `MutationSuccessHint` | 뮤테이션 성공 힌트 |
330
+ | `LabelingExtension` | 확장 인터페이스 |
331
+ | `ExtensionRenderContext` | 확장 렌더 컨텍스트 |
332
+ | `WorkspaceImageInfo` | 이미지 정보 |
333
+
334
+ ### Helpers
335
+
336
+ | Export | 설명 |
337
+ |--------|------|
338
+ | `staticData(value)` | 로드 완료 상태의 `AsyncData` 생성 |
339
+ | `loadingData()` | 로딩 상태의 `AsyncData` 생성 |
340
+ | `errorData(error)` | 에러 상태의 `AsyncData` 생성 |
341
+ | `IDLE_MUTATION` | 대기 상태의 `MutationState` |
342
+
343
+ ### Canvas Access
344
+
345
+ | Export | 설명 |
346
+ |--------|------|
347
+ | `getCanvasInstance()` | fabric.Canvas 싱글턴 반환 |
348
+ | `addCanvasObjects(objects)` | 캔버스에 fabric 오브젝트 추가 |
349
+ | `removeCanvasObjects(predicate)` | 조건에 맞는 오브젝트 제거 |
350
+
351
+ ### Domain Types
352
+
353
+ 라벨, 정책, 데이터셋 관련 도메인 타입은 `index.ts`에서 re-export됩니다. 전체 목록은 소스를 참조하세요.
354
+
177
355
  ## License
178
356
 
179
357
  MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@servantcdh/ez-planet-labeling",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",