돌아가기 공통 어노테이터 스키마
## 공통 어노테이터 스키마

### 적용 범위

본 문서는 Datamaker의 모든 어노테이터(이미지, 비디오, 텍스트, 프롬프트, PCD 등)가 **공통적으로 따라야 하는 데이터 모델**을 정의합니다.

각 어노테이터별 스펙은 이 스키마를 상속하고, 도구별(툴별) 고유 필드를 추가로 확장합니다.

### 핵심 원칙

- **classification 구조는 tool(도구)와 무관**하며, **관리자(운영자) 페이지에서 직접 정의**한 class, attributes, options로 구성됩니다.

- **attributes, options 등 모든 하위 구조 역시 사용자가 직접 정의**합니다.
- 실제 어노테이션 데이터의 classification 필드는 **트리 구조를 flatten(평탄화)한 key-value 쌍**으로 저장됩니다.
- 특정 class가 선택된 경우, **필수 하위 속성(종속성)은 프로젝트별 dm-schema 확장 JSON Schema**로 관리됩니다.

### 전체 구조 개요 (Mermaid)

```mermaid
flowchart TD
    Schema["관리자 정의 분류 스키마 (트리)"]
    Data["실제 어노테이션 데이터 (Flatten)"]
    Schema -->|Flatten 규칙| Data
    Schema -->|종속성 규칙| JSONSchema["dm-schema 확장 JSON Schema"]
    Data -->|유효성 검증| JSONSchema
```

### 데이터 흐름
```
┌─────────────────────────────────────────────────────────────┐
│                    annotatorData (최상위)                    │
├─────────────────────────────────────────────────────────────┤
│  extra          → 에셋별 추가 메타데이터                      │
│  annotations    → 어노테이션 메타 정보 (id, tool, 분류 등)   │
│  annotationsData → 어노테이션 실제 좌표/데이터                │
│  relations      → 어노테이션 간 관계                         │
│  annotationGroups → 어노테이션 그룹 정보                     │
│  assignmentId   → 작업 식별자                                │
└─────────────────────────────────────────────────────────────┘
```

### 공통 데이터 모델

#### 1. 최상위 구조

모든 어노테이션 작업은 하나의 JSON 객체로 저장되며, 최상위 키는 어노테이터 종류와 관계없이 동일합니다.

| 키 | 타입 | 설명 |
| --- | --- | --- |
| `extra` | `Record<string, unknown>` | 에셋별 메타데이터 |
| `relations` | `Record<string, RelationItem[]>` | 어노테이션 간 관계 |
| `annotations` | `Record<string, AnnotationBase[]>` | 에셋 단위의 어노테이션 목록 |
| `annotationsData` | `Record<string, AnnotationDataItem[]>` | 어노테이션 실제 좌표/데이터 |
| `annotationGroups` | `Record<string, AnnotationGroupItem[]>` | 어노테이션 그룹화 |
| `assignmentId` | `string` | 작업 식별자 |

#### 2. 공통 스키마 구조

#### 2.1 최상위 구조

```typescript
type AnnotatorData = {
  extra: Record<AssetId, unknown>
  annotations: Record<AssetId, AnnotationBase[]>
  annotationsData: Record<AssetId, AnnotationDataItem[]>
  relations: Record<AssetId, RelationItem[]>
  annotationGroups: Record<AssetId, AnnotationGroupItem[]>
  assignmentId: number | string
}

// AssetId 예시: "image_1", "video_1", "text_1", "pcd"
```

#### 2.2 AnnotationBase (공통 메타 정보)

소스코드 `src/app/core/lib/data.js`에서 확인된 구조:

```typescript
type AnnotationBase = {
  id: string // 10자 랜덤 문자열 (예: "Cd1qfFQFI4")
  tool: string // 사용된 도구 코드
  isLocked: boolean // 편집 잠금 여부 (기본값: false)
  isVisible: boolean // 화면 표시 여부 (기본값: true)
  isValid?: boolean // 유효성 여부 (기본값: false)
  isDrawCompleted?: boolean // 그리기 완료 여부
  classification: ClassificationObject | null
  label?: string[] // 분류 기반 생성된 라벨 배열

  // Sequential Data 전용
  sequenceIndex?: number // 시퀀스 인덱스
  instanceId?: string // 인스턴스 ID (자동 생성)
}
```

#### 2.3 RelationItem (관계 객체)

```typescript
type RelationItem = {
  id: string // 소스ID + 타겟ID 조합 (예: "Cd1qfFQFI4AUjPgaMzQa")
  tool: 'relation' // 항상 "relation" 고정
  isLocked: boolean
  isVisible: boolean
  isValid?: boolean
  annotationId: string // 출발(소스) 어노테이션 ID
  targetAnnotationId: string // 도착(타겟) 어노테이션 ID
  classification: ClassificationObject | null
  label?: string[]
}
```

#### 2.4 AnnotationGroupItem (그룹 객체)

```typescript
type AnnotationGroupItem = {
  id: string
  tool: 'annotationGroup' // 항상 "annotationGroup" 고정
  isLocked: boolean
  isValid?: boolean
  annotationList: GroupMemberItem[]
  classification: ClassificationObject | null
}

type GroupMemberItem = {
  annotationId: string
  children: GroupMemberItem[] // 계층 구조 지원
}
```

#### 2.5 AnnotationDataBase (공통 데이터 필드)

`annotationsData` 배열 내 각 항목이 가질 수 있는 공통 필드입니다:

```typescript
type AnnotationDataBase = {
  id: string // AnnotationBase.id와 1:1 매칭

  // 데이터 압축 (전역 공통 필드)
  isCompressed?: boolean // 압축 여부
  compressionFormat?: CompressionFormat // 압축 포맷
}

type CompressionFormat =
  | 'rle' // Run-Length Encoding (현재 지원)
```

#### 2.6 데이터 압축 공통 규격

> ⚠️ **적용 범위**: 현재 Image Annotator의 `segmentation` 도구에서 사용 중이며, 향후 모든 어노테이터에서 공통적으로 적용될 예정입니다.

##### 압축 필드 명세

| 필드                | 타입      | 필수   | 설명                                                     |
| ------------------- | --------- | ------ | -------------------------------------------------------- |
| `isCompressed`      | `boolean` | 조건부 | 데이터 압축 여부. `true`일 경우 `compressionFormat` 필수 |
| `compressionFormat` | `string`  | 조건부 | 압축 알고리즘 식별자. `isCompressed: true`일 때 필수     |

##### 압축 포맷 종류

| 포맷   | 상태       | 설명                  | 적용 대상                 |
| ------ | ---------- | --------------------- | ------------------------- |
| `rle`  | ✅ 사용 중 | Run-Length Encoding   | 연속된 인덱스/픽셀 데이터 |

##### 압축 데이터 처리 흐름

```
┌────────────────┐      ┌─────────────┐      ┌─────────────┐
│   원본 데이터  │ ──▶ │  인코딩     │ ──▶ │  저장/전송  │
│ (pixel_indices)│      │ (RLE 등)    │      │ (압축 상태) │
└────────────────┘      └─────────────┘      └─────────────┘
                                                 │
┌───────────────┐      ┌─────────────┐           │
│   사용 가능   │ ◀── │  디코딩     │ ◀─────────┘
│ (복원된 배열) │      │ (압축 해제) │
└───────────────┘      └─────────────┘
```

##### 압축 적용 예시

**압축 전 (Raw):**

```json
{
  "id": "seg_001",
  "pixel_indices": [100, 101, 102, 103, 104, 200, 201, 202]
}
```

**압축 후 (RLE):**

```json
{
  "id": "seg_001",
  "pixel_indices": [100, 5, 200, 3],
  "isCompressed": true,
  "compressionFormat": "rle"
}
```

> RLE 형식: `[시작인덱스, 연속개수, 시작인덱스, 연속개수, ...]`


### Classification 및 Label 처리

#### Classification 구조

관리자가 정의한 트리 구조의 분류 스키마는 **1-depth key-value 쌍**으로 평탄화됩니다:

```json
{
  "class": "boundingbox",
  "text": "설명 텍스트",
  "multiple": ["option1", "option2"],
  "single_radio": "selected_option",
  "single_dropdown": "dropdown_value"
}
```

#### Label 자동 생성

`classification` 기반으로 `label` 배열이 자동 생성됩니다:

```javascript
if (annotation.classification) {
  annotation.label = getLabel(variables, annotation.id, annotation.tool, annotation.classification)
}
```

### 데이터 전처리 및 후처리

#### beforeAction Hook

모든 어노테이터는 `definedHooks.beforeAction`을 통해 저장 전 데이터 정리를 수행합니다:

```javascript
definedHooks: {
  beforeAction: (annotatorData, variables) => {
    return dataGrooming(annotatorData, variables)
  }
}
```

#### 어노테이터별 특수 처리

| 어노테이터 | 전처리 내용                                                            |
| ---------- | ---------------------------------------------------------------------- |
| Image      | `dataGrooming` 실행                                                    |
| Video      | `bakeInterpolatedFrames` (bounding_box 키프레임 보간) + `dataGrooming` |
| Text       | `dataGrooming` 실행                                                    |
| PCD        | 빈 `points` 배열 가진 `3d_segmentation` 삭제 + `dataGrooming`          |
| Prompt     | `dataGrooming` 실행                                                    |
| Audio      | `dataGrooming` 실행                                                    |