# 🌐 Label Studio 외부 데이터 소싱 가이드

Label Studio 프로젝트에 외부 데이터를 import하는 다양한 방법을 설명합니다.

## 📋 목차

1. [Dataset 모듈 활용](#1-dataset-모듈-활용-추천)
2. [외부 REST API 연동](#2-외부-rest-api-연동)
3. [JSON/CSV 파일 Import](#3-jsoncsv-파일-import)
4. [클라우드 스토리지 연동](#4-클라우드-스토리지-연동)

---

## 1. Dataset 모듈 활용 (추천)

Things Factory의 Dataset 모듈에서 DataSample을 Label Studio로 자동 export합니다.

### 장점
- ✅ 데이터 구조 자동 변환
- ✅ 메타데이터 자동 매핑
- ✅ AI 예측 자동 생성
- ✅ 양방향 동기화 지원

### GraphQL API

```graphql
mutation {
  createLabelingTasksFromDataset(input: {
    projectId: 2
    dataSetId: "your-dataset-id"
    imageField: "image"
    autoGeneratePredictions: true
    confidenceThreshold: 0.5
    limit: 100
  }) {
    totalSamples
    tasksCreated
    tasksFailed
    predictionsCreated
    taskIds
  }
}
```

### 사용 방법

1. **DataSet 생성**
   ```graphql
   mutation {
     createDataSet(dataSet: {
       name: "Product Images"
       description: "Quality inspection images"
       active: true
     }) {
       id
       name
     }
   }
   ```

2. **DataSample 추가**
   ```graphql
   mutation {
     createDataSample(dataSample: {
       dataSetId: "dataset-id"
       name: "Sample-001"
       data: {
         image: "https://example.com/image1.jpg"
         product_id: "P123"
         batch: "B456"
       }
       collectedAt: "2025-01-13T10:00:00Z"
     }) {
       id
     }
   }
   ```

3. **Label Studio로 Export**
   ```graphql
   mutation {
     createLabelingTasksFromDataset(input: {
       projectId: 2
       dataSetId: "dataset-id"
       autoGeneratePredictions: true
     }) {
       tasksCreated
     }
   }
   ```

---

## 2. 외부 REST API 연동

REST API 엔드포인트에서 직접 데이터를 가져와 Label Studio에 import합니다.

### 미리보기 (Preview)

먼저 데이터 구조를 확인합니다:

```graphql
query {
  previewExternalDataSource(source: {
    sourceType: "api"
    sourceUrl: "https://api.example.com/images"
    authHeader: "Bearer YOUR_TOKEN"
    httpMethod: "GET"
    dataPath: "data.items"
  }) {
    itemCount
    sampleData
    schema
  }
}
```

**응답 예시:**
```json
{
  "itemCount": 1,
  "sampleData": "{\n  \"id\": \"123\",\n  \"imageUrl\": \"https://...\",\n  \"category\": \"defect\"\n}",
  "schema": "{\n  \"id\": \"string\",\n  \"imageUrl\": \"string\",\n  \"category\": \"string\"\n}"
}
```

### Import 실행

```graphql
mutation {
  importFromExternalSource(input: {
    projectId: 2
    source: {
      sourceType: "api"
      sourceUrl: "https://api.example.com/images"
      authHeader: "Bearer YOUR_TOKEN"
      httpMethod: "GET"
      dataPath: "data.items"
    }
    imageField: "imageUrl"
    fieldMapping: "{\"id\":\"task_id\", \"category\":\"label\"}"
    autoGeneratePredictions: true
    limit: 100
  }) {
    totalFetched
    tasksImported
    tasksFailed
    taskIds
  }
}
```

### API 응답 형식

외부 API는 다음 형식으로 응답해야 합니다:

```json
{
  "data": {
    "items": [
      {
        "id": "img-001",
        "imageUrl": "https://storage.example.com/image1.jpg",
        "category": "electronics",
        "metadata": {
          "width": 1920,
          "height": 1080
        }
      }
    ]
  }
}
```

또는 배열 직접 반환:

```json
[
  {
    "id": "img-001",
    "imageUrl": "https://storage.example.com/image1.jpg"
  }
]
```

---

## 3. JSON/CSV 파일 Import

파일 URL에서 직접 데이터를 가져옵니다.

### JSON 파일

```graphql
mutation {
  importFromExternalSource(input: {
    projectId: 2
    source: {
      sourceType: "json"
      sourceUrl: "https://example.com/data/images.json"
      dataPath: "images"
    }
    imageField: "url"
    limit: 50
  }) {
    totalFetched
    tasksImported
  }
}
```

**images.json 예시:**
```json
{
  "images": [
    {
      "url": "https://example.com/img1.jpg",
      "label": "cat",
      "confidence": 0.95
    },
    {
      "url": "https://example.com/img2.jpg",
      "label": "dog",
      "confidence": 0.87
    }
  ]
}
```

### CSV 파일

```graphql
mutation {
  importFromExternalSource(input: {
    projectId: 2
    source: {
      sourceType: "csv"
      sourceUrl: "https://example.com/data/images.csv"
    }
    imageField: "image_url"
    limit: 50
  }) {
    totalFetched
    tasksImported
  }
}
```

**images.csv 예시:**
```csv
id,image_url,category,status
1,https://example.com/img1.jpg,electronics,new
2,https://example.com/img2.jpg,furniture,new
3,https://example.com/img3.jpg,clothing,new
```

---

## 4. 클라우드 스토리지 연동

### S3/GCS/Azure Blob (Presigned URLs)

클라우드 스토리지의 경우 presigned URL을 사용합니다:

```graphql
mutation {
  importFromExternalSource(input: {
    projectId: 2
    source: {
      sourceType: "url"
      sourceUrl: "https://my-bucket.s3.amazonaws.com/manifest.json?X-Amz-..."
    }
    imageField: "s3_url"
  }) {
    tasksImported
  }
}
```

**manifest.json 예시:**
```json
[
  {
    "s3_url": "https://my-bucket.s3.amazonaws.com/image1.jpg?X-Amz-...",
    "object_key": "images/2025/01/image1.jpg",
    "size": 245678,
    "uploaded_at": "2025-01-13T10:00:00Z"
  }
]
```

### Label Studio Cloud Storage 직접 연동

Label Studio는 클라우드 스토리지 직접 연동도 지원합니다:

1. **Label Studio UI에서 Storage 설정**
   - Project Settings → Cloud Storage
   - Add Source Storage (S3/GCS/Azure)

2. **Sync 버튼으로 자동 import**
   - Storage에서 자동으로 task 생성
   - Label Studio가 직접 스토리지 접근

---

## 📊 Field Mapping (필드 매핑)

소스 데이터의 필드명과 Label Studio 필드명이 다를 때 매핑을 지정합니다.

### 예시

**소스 데이터:**
```json
{
  "product_image": "https://...",
  "sku": "SKU-123",
  "inspection_date": "2025-01-13"
}
```

**Field Mapping:**
```json
{
  "product_image": "image",
  "sku": "product_id",
  "inspection_date": "date"
}
```

**GraphQL:**
```graphql
mutation {
  importFromExternalSource(input: {
    projectId: 2
    source: { ... }
    imageField: "product_image"
    fieldMapping: "{\"product_image\":\"image\",\"sku\":\"product_id\",\"inspection_date\":\"date\"}"
  }) { ... }
}
```

---

## 🔄 데이터 흐름 다이어그램

```
┌─────────────────┐
│  External API   │
│  JSON/CSV File  │
│  Cloud Storage  │
└────────┬────────┘
         │ HTTP Request
         ▼
┌─────────────────────────┐
│ ExternalDataSourceService│
│ - fetchFromSource()     │
│ - transformData()       │
└────────┬────────────────┘
         │ Label Studio API
         ▼
┌─────────────────┐
│  Label Studio   │
│     Project     │
│   (Tasks 생성)   │
└─────────────────┘
```

---

## 🛠️ 고급 사용법

### 1. 인증 헤더 사용

```graphql
source: {
  sourceType: "api"
  sourceUrl: "https://api.example.com/data"
  authHeader: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
```

### 2. POST 요청으로 데이터 가져오기

```graphql
source: {
  sourceType: "api"
  sourceUrl: "https://api.example.com/search"
  httpMethod: "POST"
  requestBody: "{\"query\":\"defect images\",\"limit\":100}"
  dataPath: "results"
}
```

### 3. 중첩된 데이터 추출

```graphql
source: {
  sourceType: "url"
  sourceUrl: "https://example.com/data.json"
  dataPath: "response.data.items"  # response.data.items 배열 추출
}
```

---

## 📝 Best Practices

### 1. 미리보기 먼저 실행
항상 `previewExternalDataSource`로 데이터 구조를 확인한 후 import를 실행하세요.

### 2. 배치 크기 조절
대량 데이터는 `limit` 파라미터로 나누어 import합니다:
```graphql
# 첫 번째 배치
mutation { importFromExternalSource(input: { limit: 100 }) { ... } }

# 두 번째 배치 (API에서 offset/page 지원 필요)
mutation { importFromExternalSource(input: { limit: 100 }) { ... } }
```

### 3. 오류 처리
Import 결과를 확인하고 실패한 항목은 재시도합니다:
```graphql
mutation {
  importFromExternalSource(input: { ... }) {
    totalFetched
    tasksImported
    tasksFailed  # 이 값 확인
    error
  }
}
```

### 4. 이미지 URL 검증
Import 전에 이미지 URL이 유효한지 확인하세요:
- URL이 공개 접근 가능한지
- CORS 설정이 올바른지
- Presigned URL의 만료 시간 확인

---

## 🚨 문제 해결

### "No image URL found"
- `imageField` 파라미터를 소스 데이터의 실제 필드명으로 설정
- Preview로 데이터 구조 확인

### "401 Unauthorized"
- `authHeader`에 유효한 인증 토큰 설정
- API 키나 Bearer 토큰 확인

### "Path not found in data"
- `dataPath`가 올바른지 확인
- Preview로 실제 응답 구조 확인

### Import는 성공했지만 이미지가 안 보임
- Label Studio에서 이미지 URL 접근 가능한지 확인
- CORS 설정 확인
- Presigned URL 만료 확인

---

## 📚 참고 자료

- [Label Studio API Documentation](https://labelstud.io/api)
- [Label Studio Cloud Storage](https://labelstud.io/guide/storage.html)
- [Things Factory Dataset Module](../dataset/README.md)

---

## 🎯 요약

| 방법 | 용도 | 장점 | 단점 |
|------|------|------|------|
| **Dataset 모듈** | Things Factory 내부 데이터 | 자동 변환, 양방향 동기화 | Things Factory 전용 |
| **REST API** | 외부 시스템 연동 | 실시간 데이터, 동적 조회 | API 개발 필요 |
| **JSON/CSV 파일** | 정적 데이터셋 | 간단함, 표준 형식 | 수동 업데이트 필요 |
| **Cloud Storage** | 대용량 이미지 | 확장성, 비용 효율 | 설정 복잡 |

**추천 순서:**
1. Things Factory 내부 데이터 → **Dataset 모듈**
2. 외부 API 있음 → **REST API 연동**
3. 파일 형태 데이터 → **JSON/CSV Import**
4. 대용량 이미지 → **Cloud Storage**
