synapse-sdk 1.0.0b17__py3-none-any.whl → 1.0.0b19__py3-none-any.whl

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.

Potentially problematic release.


This version of synapse-sdk might be problematic. Click here for more details.

Files changed (25) hide show
  1. synapse_sdk/clients/backend/data_collection.py +2 -2
  2. synapse_sdk/devtools/docs/docs/contributing.md +1 -1
  3. synapse_sdk/devtools/docs/docs/features/index.md +4 -4
  4. synapse_sdk/devtools/docs/docs/plugins/export-plugins.md +786 -0
  5. synapse_sdk/devtools/docs/docs/{features/plugins/index.md → plugins/plugins.md} +352 -21
  6. synapse_sdk/devtools/docs/docusaurus.config.ts +8 -0
  7. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/export-plugins.md +788 -0
  8. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/plugins.md +71 -0
  9. synapse_sdk/devtools/docs/package-lock.json +1366 -37
  10. synapse_sdk/devtools/docs/package.json +2 -1
  11. synapse_sdk/devtools/docs/sidebars.ts +8 -1
  12. synapse_sdk/plugins/categories/export/actions/export.py +2 -1
  13. synapse_sdk/plugins/categories/export/templates/config.yaml +1 -1
  14. synapse_sdk/plugins/categories/export/templates/plugin/__init__.py +376 -0
  15. synapse_sdk/plugins/categories/export/templates/plugin/export.py +56 -190
  16. synapse_sdk/plugins/categories/upload/actions/upload.py +181 -22
  17. synapse_sdk/plugins/categories/upload/templates/config.yaml +24 -2
  18. synapse_sdk/plugins/categories/upload/templates/plugin/upload.py +9 -2
  19. {synapse_sdk-1.0.0b17.dist-info → synapse_sdk-1.0.0b19.dist-info}/METADATA +1 -1
  20. {synapse_sdk-1.0.0b17.dist-info → synapse_sdk-1.0.0b19.dist-info}/RECORD +24 -22
  21. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/features/plugins/index.md +0 -30
  22. {synapse_sdk-1.0.0b17.dist-info → synapse_sdk-1.0.0b19.dist-info}/WHEEL +0 -0
  23. {synapse_sdk-1.0.0b17.dist-info → synapse_sdk-1.0.0b19.dist-info}/entry_points.txt +0 -0
  24. {synapse_sdk-1.0.0b17.dist-info → synapse_sdk-1.0.0b19.dist-info}/licenses/LICENSE +0 -0
  25. {synapse_sdk-1.0.0b17.dist-info → synapse_sdk-1.0.0b19.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,788 @@
1
+ ---
2
+ id: export-plugins
3
+ title: Export 플러그인
4
+ sidebar_position: 2
5
+ ---
6
+
7
+ # Export 플러그인
8
+
9
+ Export 플러그인은 Synapse 플랫폼에서 주석이 달린 데이터, 그라운드 트루스 데이터셋, 할당 및 작업을 내보내기 위한 데이터 내보내기 및 변환 작업을 제공합니다.
10
+
11
+ ## 개요
12
+
13
+ **사용 가능한 액션:**
14
+
15
+ - `export` - 다양한 소스(할당, 그라운드 트루스, 작업)에서 사용자 정의 처리와 함께 데이터 내보내기
16
+
17
+ **사용 사례:**
18
+
19
+ - 훈련용 주석 데이터셋 내보내기
20
+ - 그라운드 트루스 데이터를 사용자 정의 형식으로 변환
21
+ - 배포용 데이터 패키지 생성
22
+ - 할당 결과의 배치 처리
23
+ - 외부 도구용 주석 데이터 변환
24
+
25
+ **지원되는 내보내기 대상:**
26
+
27
+ - `assignment` - 주석이 있는 할당 데이터 내보내기
28
+ - `ground_truth` - 그라운드 트루스 데이터셋 버전 내보내기
29
+ - `task` - 관련 주석이 있는 작업 데이터 내보내기
30
+
31
+ ## BaseExporter와 Exporter 클래스 구조도
32
+
33
+ 다음 다이어그램은 BaseExporter 클래스와 Exporter 클래스 간의 관계와 메서드 구현을 보여줍니다:
34
+
35
+ ```mermaid
36
+ classDiagram
37
+ %% Light/Dark mode compatible colors using CSS variables
38
+ classDef baseClass fill:#f0f8ff,stroke:#4169e1,stroke-width:2px,color:#000
39
+ classDef childClass fill:#f0fff0,stroke:#228b22,stroke-width:2px,color:#000
40
+ classDef method fill:#fff8dc,stroke:#daa520,stroke-width:1px,color:#000
41
+ classDef abstractMethod fill:#ffe4e1,stroke:#dc143c,stroke-width:1px,color:#000
42
+ classDef helperMethod fill:#f5f5f5,stroke:#696969,stroke-width:1px,color:#000
43
+
44
+ class BaseExporter {
45
+ %% Core attributes
46
+ +run: object
47
+ +export_items: Generator
48
+ +path_root: Path
49
+ +params: dict
50
+
51
+ %% Main workflow methods
52
+ +export(export_items, results, **kwargs) dict
53
+ +process_data_conversion(export_item) object
54
+ +process_file_saving(...) bool
55
+ +setup_output_directories(path, flag) dict
56
+
57
+ %% Abstract methods (to be implemented)
58
+ +convert_data(data)* object
59
+ +before_convert(data)* object
60
+ +after_convert(data)* object
61
+
62
+ %% File operations
63
+ +save_original_file(result, path, errors) ExportStatus
64
+ +save_as_json(result, path, errors) ExportStatus
65
+ +get_original_file_name(files) str
66
+
67
+ %% Helper methods
68
+ -_create_unique_export_path(name) Path
69
+ -_save_error_list(path, json_errors, file_errors)
70
+ -_process_original_file_saving(...) bool
71
+ -_process_json_file_saving(...) bool
72
+ }
73
+
74
+ class Exporter {
75
+ %% Inherited from BaseExporter
76
+ +export(export_items, results, **kwargs) dict
77
+
78
+ %% Implemented abstract methods
79
+ +convert_data(data) object
80
+ +before_convert(data) object
81
+ +after_convert(data) object
82
+ }
83
+
84
+ %% Inheritance relationship
85
+ BaseExporter <|-- Exporter
86
+
87
+ %% Apply styles
88
+ class BaseExporter baseClass
89
+ class Exporter childClass
90
+ ```
91
+
92
+ ### 메서드 실행 플로우
93
+
94
+ Export 작업의 전체 실행 흐름을 보여주는 플로우차트입니다:
95
+
96
+ ```mermaid
97
+ flowchart TD
98
+ %% Start
99
+ A[export 메서드 호출] --> B[경로와 메트릭 초기화]
100
+ B --> C[출력 디렉토리 설정]
101
+ C --> D[export_items 순환]
102
+
103
+ %% Data processing pipeline
104
+ D --> E[process_data_conversion]
105
+ E --> F[before_convert]
106
+ F --> G[convert_data]
107
+ G --> H[after_convert]
108
+
109
+ %% File saving pipeline
110
+ H --> I[process_file_saving]
111
+ I --> J{원본 파일 저장?}
112
+ J -->|예| K[_process_original_file_saving]
113
+ J -->|아니오| L[_process_json_file_saving]
114
+ K --> L
115
+
116
+ %% Continue or finish
117
+ L --> M{더 많은 항목?}
118
+ M -->|예| D
119
+ M -->|아니오| N[오류 목록 저장]
120
+ N --> O[내보내기 경로 반환]
121
+
122
+ %% Error handling
123
+ K --> P{원본 파일 실패?}
124
+ P -->|예| Q[다음 항목으로 건너뛰기]
125
+ P -->|아니오| L
126
+
127
+ L --> R{JSON 파일 실패?}
128
+ R -->|예| Q
129
+ R -->|아니오| S[메트릭 업데이트]
130
+ S --> M
131
+ Q --> M
132
+
133
+ %% Styling for light/dark compatibility
134
+ classDef startEnd fill:#e1f5fe,stroke:#01579b,color:#000
135
+ classDef process fill:#f3e5f5,stroke:#4a148c,color:#000
136
+ classDef decision fill:#fff3e0,stroke:#e65100,color:#000
137
+ classDef data fill:#e8f5e8,stroke:#2e7d32,color:#000
138
+ classDef error fill:#ffebee,stroke:#c62828,color:#000
139
+
140
+ class A,O startEnd
141
+ class B,C,E,F,G,H,I,K,L,N,S process
142
+ class J,M,P,R decision
143
+ class D data
144
+ class Q error
145
+ ```
146
+
147
+ ### 주요 관계 및 책임
148
+
149
+ **BaseExporter (추상 기본 클래스)**
150
+ - **핵심 기능**: 완전한 내보내기 워크플로우 인프라 제공
151
+ - **템플릿 메서드**: `export()` 메서드가 전체 프로세스 조율
152
+ - **훅 메서드**: 커스터마이징을 위한 `convert_data()`, `before_convert()`, `after_convert()`
153
+ - **유틸리티**: 파일 작업, 디렉토리 설정, 오류 처리, 진행률 추적
154
+
155
+ **Exporter (구체적 구현)**
156
+ - **상속**: `BaseExporter` 확장
157
+ - **최소 구현**: 추상 메서드들의 기본 구현 제공
158
+ - **위임 동작**: 대부분의 메서드가 부모 클래스에 위임
159
+ - **커스터마이징 지점**: 특정 로직을 위해 변환 메서드 오버라이드
160
+
161
+ ### 메서드 카테고리
162
+ - **🔵 핵심 워크플로우**: 주요 내보내기 조율 메서드
163
+ - **🟢 템플릿/훅**: 서브클래스에서 오버라이드하도록 설계된 메서드
164
+ - **🟡 파일 작업**: 구체적인 파일 저장 및 처리 메서드
165
+ - **🔸 헬퍼/유틸리티**: 내부 작업을 위한 프라이빗 메서드
166
+
167
+ 이 설계는 **템플릿 메서드 패턴**을 따르며, `BaseExporter.export()`가 알고리즘 골격을 정의하고 서브클래스가 훅 메서드를 통해 특정 단계를 커스터마이징합니다.
168
+
169
+ ## BaseExporter 클래스 구조
170
+
171
+ 새로운 BaseExporter 클래스는 export 플러그인을 위한 객체지향적 접근 방식을 제공합니다:
172
+
173
+ ```python
174
+ from synapse_sdk.plugins.categories.export.templates.plugin import BaseExporter
175
+
176
+ class Exporter(BaseExporter):
177
+ """플러그인 export 액션 인터페이스."""
178
+
179
+ def __init__(self, run, export_items, path_root, **params):
180
+ """플러그인 export 액션 클래스를 초기화합니다."""
181
+ super().__init__(run, export_items, path_root, **params)
182
+
183
+ def convert_data(self, data):
184
+ """데이터 변환 로직을 구현하세요."""
185
+ return data
186
+
187
+ def before_convert(self, data):
188
+ """변환 전 데이터 전처리를 수행합니다."""
189
+ return data
190
+
191
+ def after_convert(self, data):
192
+ """변환 후 데이터 후처리를 수행합니다."""
193
+ return data
194
+ ```
195
+
196
+ ## BaseExporter의 핵심 기능
197
+
198
+ ### 자동 제공 유틸리티
199
+
200
+ - **완전한 export 워크플로우**: `export()` 메서드가 전체 export 프로세스를 관리
201
+ - **데이터 변환 파이프라인**: `process_data_conversion()` 메서드로 before_convert → convert_data → after_convert 처리
202
+ - **파일 저장 관리**: `process_file_saving()` 메서드로 원본 파일과 JSON 파일 저장 처리 (오버라이드 가능)
203
+ - **디렉토리 설정**: `setup_output_directories()` 메서드로 출력 디렉토리 구조 생성 (오버라이드 가능)
204
+
205
+ ### 필수 메서드 (서브클래스에서 구현해야 함)
206
+
207
+ - **convert_data()**: export 중 데이터 변환
208
+
209
+ ### 선택적 메서드 (서브클래스에서 오버라이드 가능)
210
+
211
+ - **save_original_file()**: export 항목의 원본 파일 저장
212
+ - **save_as_json()**: 데이터를 JSON 파일로 저장
213
+ - **before_convert()**: 변환 전 데이터 전처리
214
+ - **after_convert()**: 변환 후 데이터 후처리
215
+ - **process_file_saving()**: 사용자 정의 파일 저장 로직
216
+
217
+ ### 헬퍼 메서드
218
+
219
+ - **\_process_original_file_saving()**: 메트릭과 함께 원본 파일 저장 처리
220
+ - **\_process_json_file_saving()**: 메트릭과 함께 JSON 파일 저장 처리
221
+
222
+ ### 자동 제공 유틸리티
223
+
224
+ - `self.run.set_progress()`를 통한 진행률 추적
225
+ - `self.run.log_message()` 및 기타 run 메서드를 통한 로깅
226
+ - run 메서드를 통한 오류 처리 및 메트릭 수집
227
+
228
+ ## 주요 특징
229
+
230
+ - **진행률 추적**: `run.set_progress()`로 내장 진행률 모니터링
231
+ - **오류 처리**: 자동 오류 수집 및 보고
232
+ - **메트릭 로깅**: `run.log_metrics()`로 성공/실패율 추적
233
+ - **파일 관리**: 원본 파일과 처리된 JSON 데이터 모두 처리
234
+ - **로깅**: `run.log_message()` 및 사용자 정의 이벤트로 포괄적인 로깅
235
+
236
+ ## 실용적인 예시
237
+
238
+ ### YOLO 형식 Exporter with 커스텀 디렉토리 구조
239
+
240
+ 다음은 YOLO 형식으로 데이터를 내보내면서 `setup_output_directories`와 `process_file_saving`을 활용하는 완전한 예시입니다:
241
+
242
+ ```python
243
+ from synapse_sdk.plugins.categories.export.templates.plugin import BaseExporter
244
+ import os
245
+ import json
246
+
247
+ class YOLOExporter(BaseExporter):
248
+ """YOLO 형식으로 데이터를 내보내는 플러그인."""
249
+
250
+ def __init__(self, run, export_items, path_root, **params):
251
+ super().__init__(run, export_items, path_root, **params)
252
+ self.class_mapping = {}
253
+
254
+ def setup_output_directories(self, unique_export_path, save_original_file_flag):
255
+ """YOLO 프로젝트 구조에 맞는 디렉토리 생성."""
256
+ directories = ['images', 'labels', 'data']
257
+
258
+ for directory in directories:
259
+ dir_path = os.path.join(unique_export_path, directory)
260
+ os.makedirs(dir_path, exist_ok=True)
261
+ self.run.log_message(f"YOLO 디렉토리 생성: {dir_path}")
262
+
263
+ return unique_export_path
264
+
265
+ def convert_data(self, data):
266
+ """주석 데이터를 YOLO 형식으로 변환."""
267
+ converted_annotations = []
268
+
269
+ for annotation in data.get('annotations', []):
270
+ # 바운딩 박스를 YOLO 형식으로 변환
271
+ bbox = annotation['geometry']['bbox']
272
+ image_width = data['image']['width']
273
+ image_height = data['image']['height']
274
+
275
+ # YOLO 형식: center_x, center_y, width, height (정규화)
276
+ center_x = (bbox['x'] + bbox['width'] / 2) / image_width
277
+ center_y = (bbox['y'] + bbox['height'] / 2) / image_height
278
+ width = bbox['width'] / image_width
279
+ height = bbox['height'] / image_height
280
+
281
+ # 클래스 ID 매핑
282
+ class_name = annotation['class_name']
283
+ if class_name not in self.class_mapping:
284
+ self.class_mapping[class_name] = len(self.class_mapping)
285
+
286
+ class_id = self.class_mapping[class_name]
287
+
288
+ converted_annotations.append({
289
+ 'class_id': class_id,
290
+ 'center_x': center_x,
291
+ 'center_y': center_y,
292
+ 'width': width,
293
+ 'height': height
294
+ })
295
+
296
+ return {
297
+ 'yolo_annotations': converted_annotations,
298
+ 'class_mapping': self.class_mapping,
299
+ 'image_info': data['image']
300
+ }
301
+
302
+ def process_file_saving(
303
+ self,
304
+ final_data,
305
+ unique_export_path,
306
+ save_original_file_flag,
307
+ errors_json_file_list,
308
+ errors_original_file_list,
309
+ original_file_metrics_record,
310
+ data_file_metrics_record,
311
+ current_index,
312
+ ):
313
+ """YOLO 형식으로 파일 저장 처리."""
314
+ try:
315
+ export_item = self.export_items[current_index - 1]
316
+ base_name = os.path.splitext(export_item.original_file.name)[0]
317
+
318
+ # 1. 이미지 파일을 images 폴더에 저장
319
+ if save_original_file_flag:
320
+ images_dir = os.path.join(unique_export_path, 'images')
321
+ image_path = os.path.join(images_dir, export_item.original_file.name)
322
+ import shutil
323
+ shutil.copy2(export_item.original_file.path, image_path)
324
+ self.run.log_message(f"이미지 저장: {image_path}")
325
+
326
+ # 2. YOLO 라벨 파일을 labels 폴더에 저장
327
+ labels_dir = os.path.join(unique_export_path, 'labels')
328
+ label_path = os.path.join(labels_dir, f"{base_name}.txt")
329
+
330
+ with open(label_path, 'w') as f:
331
+ for ann in final_data.get('yolo_annotations', []):
332
+ line = f"{ann['class_id']} {ann['center_x']} {ann['center_y']} {ann['width']} {ann['height']}\n"
333
+ f.write(line)
334
+
335
+ self.run.log_message(f"YOLO 라벨 저장: {label_path}")
336
+
337
+ # 3. 클래스 매핑 파일 저장 (한 번만)
338
+ if current_index == 1: # 첫 번째 파일 처리 시에만
339
+ classes_path = os.path.join(unique_export_path, 'data', 'classes.txt')
340
+ with open(classes_path, 'w') as f:
341
+ for class_name, class_id in sorted(final_data['class_mapping'].items(), key=lambda x: x[1]):
342
+ f.write(f"{class_name}\n")
343
+ self.run.log_message(f"클래스 파일 저장: {classes_path}")
344
+
345
+ return True
346
+
347
+ except Exception as e:
348
+ self.run.log_message(f"파일 저장 중 오류: {str(e)}", level="error")
349
+ errors_json_file_list.append(f"Export item {current_index}: {str(e)}")
350
+ return True # 다른 파일 처리를 계속하기 위해 True 반환
351
+ ```
352
+
353
+ 이 예시는 BaseExporter의 핵심 확장 포인트인 `setup_output_directories`와 `process_file_saving`을 활용하여:
354
+
355
+ - YOLO 프로젝트 구조 (`images/`, `labels/`, `data/`) 생성
356
+ - 이미지 파일과 YOLO 라벨 파일을 적절한 위치에 저장
357
+ - 클래스 매핑 파일 관리
358
+ - 진행률 추적과 오류 처리
359
+
360
+ 이를 보여줍니다.
361
+
362
+ ## 빠른 시작 가이드
363
+
364
+ BaseExporter를 사용하여 간단한 플러그인을 만드는 단계별 가이드입니다:
365
+
366
+ ### 1단계: 기본 클래스 상속
367
+
368
+ ```python
369
+ from synapse_sdk.plugins.categories.export.templates.plugin import BaseExporter
370
+
371
+ class MyExporter(BaseExporter):
372
+ def convert_data(self, data):
373
+ # 필수: 데이터 변환 로직 구현
374
+ return data # 또는 변환된 데이터 반환
375
+ ```
376
+
377
+ ### 2단계: 필요에 따라 추가 메서드 오버라이드
378
+
379
+ ```python
380
+ def before_convert(self, data):
381
+ # 선택적: 변환 전 전처리
382
+ return data
383
+
384
+ def after_convert(self, converted_data):
385
+ # 선택적: 변환 후 후처리
386
+ return converted_data
387
+
388
+ def save_as_json(self, converted_data, output_path):
389
+ # 선택적: 사용자 정의 저장 형식
390
+ # 기본적으로는 JSON 형식으로 저장됨
391
+ pass
392
+ ```
393
+
394
+ ### 3단계: 플러그인 등록
395
+
396
+ 플러그인 디렉토리 구조:
397
+
398
+ ```
399
+ my_plugin/
400
+ ├── __init__.py
401
+ ├── plugin.py # MyExporter 클래스 정의
402
+ └── manifest.yaml # 플러그인 메타데이터
403
+ ```
404
+
405
+ ## Export 플러그인 생성
406
+
407
+ Export 플러그인은 더 나은 구성과 재사용성을 위해 BaseExporter 클래스 기반 접근 방식을 사용합니다. 커스텀 export 플러그인을 생성하는 방법은 다음과 같습니다:
408
+
409
+ ### 1단계: Export 플러그인 템플릿 생성
410
+
411
+ ```bash
412
+ synapse plugin create
413
+ # 카테고리로 'export' 선택
414
+ # export 템플릿으로 플러그인이 생성됩니다
415
+ ```
416
+
417
+ ### 2단계: Export 매개변수 커스터마이징
418
+
419
+ `ExportParams` 모델이 필요한 매개변수를 정의합니다:
420
+
421
+ ```python
422
+ from synapse_sdk.plugins.categories.export.actions.export import ExportParams
423
+ from pydantic import BaseModel
424
+ from typing import Literal
425
+
426
+ class CustomExportParams(ExportParams):
427
+ # 커스텀 매개변수 추가
428
+ output_format: Literal['json', 'csv', 'xml'] = 'json'
429
+ include_metadata: bool = True
430
+ compression: bool = False
431
+ ```
432
+
433
+ ### 3단계: 데이터 변환 구현
434
+
435
+ `plugin/export.py`의 `Exporter` 클래스에서 필요한 메서드를 구현합니다:
436
+
437
+ ```python
438
+ from datetime import datetime
439
+ from synapse_sdk.plugins.categories.export.templates.plugin import BaseExporter
440
+
441
+ class Exporter(BaseExporter):
442
+ """COCO 형식 변환을 포함한 커스텀 export 플러그인."""
443
+
444
+ def convert_data(self, data):
445
+ """주석 데이터를 원하는 형식으로 변환합니다."""
446
+ # 예시: COCO 형식으로 변환
447
+ if data.get('data_type') == 'image_detection':
448
+ return self.convert_to_coco_format(data)
449
+ elif data.get('data_type') == 'image_classification':
450
+ return self.convert_to_classification_format(data)
451
+ return data
452
+
453
+ def before_convert(self, export_item):
454
+ """변환 전 데이터 전처리."""
455
+ # 검증, 필터링 또는 전처리 추가
456
+ if not export_item.get('data'):
457
+ return None # 빈 항목 건너뛰기
458
+
459
+ # 커스텀 메타데이터 추가
460
+ export_item['processed_at'] = datetime.now().isoformat()
461
+ return export_item
462
+
463
+ def after_convert(self, converted_data):
464
+ """변환된 데이터 후처리."""
465
+ # 최종 마무리, 검증 또는 형식 지정 추가
466
+ if 'annotations' in converted_data:
467
+ converted_data['annotation_count'] = len(converted_data['annotations'])
468
+ return converted_data
469
+
470
+ def convert_to_coco_format(self, data):
471
+ """예시: COCO 검출 형식으로 변환."""
472
+ coco_data = {
473
+ "images": [],
474
+ "annotations": [],
475
+ "categories": []
476
+ }
477
+
478
+ # 주석 데이터를 COCO 형식으로 변환
479
+ for annotation in data.get('annotations', []):
480
+ coco_annotation = {
481
+ "id": annotation['id'],
482
+ "image_id": annotation['image_id'],
483
+ "category_id": annotation['category_id'],
484
+ "bbox": annotation['bbox'],
485
+ "area": annotation.get('area', 0),
486
+ "iscrowd": 0
487
+ }
488
+ coco_data["annotations"].append(coco_annotation)
489
+
490
+ return coco_data
491
+ ```
492
+
493
+ ### 4단계: Export 대상 구성
494
+
495
+ Export 액션은 다양한 데이터 소스를 지원합니다:
496
+
497
+ ```python
498
+ # 다양한 대상에 대한 필터 예시
499
+ filters = {
500
+ # 그라운드 트루스 내보내기용
501
+ "ground_truth": {
502
+ "ground_truth_dataset_version": 123,
503
+ "expand": ["data"]
504
+ },
505
+
506
+ # 할당 내보내기용
507
+ "assignment": {
508
+ "project": 456,
509
+ "status": "completed",
510
+ "expand": ["data"]
511
+ },
512
+
513
+ # 작업 내보내기용
514
+ "task": {
515
+ "project": 456,
516
+ "assignment": 789,
517
+ "expand": ["data_unit", "assignment"]
518
+ }
519
+ }
520
+ ```
521
+
522
+ ### 5단계: 파일 작업 처리
523
+
524
+ BaseExporter 메서드를 오버라이드하여 파일 저장 및 구성을 커스터마이징합니다:
525
+
526
+ ```python
527
+ import json
528
+ from pathlib import Path
529
+ from synapse_sdk.plugins.categories.export.enums import ExportStatus
530
+
531
+ class Exporter(BaseExporter):
532
+ """다중 형식 지원을 포함한 커스텀 export 플러그인."""
533
+
534
+ def save_as_json(self, result, base_path, error_file_list):
535
+ """다양한 형식으로 커스텀 JSON 저장."""
536
+ file_name = Path(self.get_original_file_name(result['files'])).stem
537
+
538
+ # 매개변수에 따른 출력 형식 선택
539
+ if self.params.get('output_format') == 'csv':
540
+ return self.save_as_csv(result, base_path, error_file_list)
541
+ elif self.params.get('output_format') == 'xml':
542
+ return self.save_as_xml(result, base_path, error_file_list)
543
+
544
+ # 기본 JSON 처리
545
+ json_data = result['data']
546
+ file_info = {'file_name': f'{file_name}.json'}
547
+
548
+ try:
549
+ with (base_path / f'{file_name}.json').open('w', encoding='utf-8') as f:
550
+ json.dump(json_data, f, indent=4, ensure_ascii=False)
551
+ status = ExportStatus.SUCCESS
552
+ except Exception as e:
553
+ error_file_list.append([f'{file_name}.json', str(e)])
554
+ status = ExportStatus.FAILED
555
+
556
+ self.run.export_log_json_file(result['id'], file_info, status)
557
+ return status
558
+
559
+ def setup_output_directories(self, unique_export_path, save_original_file_flag):
560
+ """커스텀 디렉토리 구조."""
561
+ # 형식별 디렉토리 생성
562
+ output_paths = super().setup_output_directories(unique_export_path, save_original_file_flag)
563
+
564
+ # 출력 형식에 따른 커스텀 디렉토리 추가
565
+ format_dir = unique_export_path / self.params.get('output_format', 'json')
566
+ format_dir.mkdir(parents=True, exist_ok=True)
567
+ output_paths['format_output_path'] = format_dir
568
+
569
+ return output_paths
570
+ ```
571
+
572
+ ### 6단계: 사용 예시
573
+
574
+ 다양한 구성으로 export 플러그인 실행:
575
+
576
+ ```bash
577
+ # 그라운드 트루스 데이터 기본 내보내기
578
+ synapse plugin run export '{
579
+ "name": "my_export",
580
+ "storage": 1,
581
+ "target": "ground_truth",
582
+ "filter": {"ground_truth_dataset_version": 123},
583
+ "path": "exports/ground_truth",
584
+ "save_original_file": true
585
+ }' --plugin my-export-plugin
586
+
587
+ # 커스텀 매개변수로 할당 내보내기
588
+ synapse plugin run export '{
589
+ "name": "assignment_export",
590
+ "storage": 1,
591
+ "target": "assignment",
592
+ "filter": {"project": 456, "status": "completed"},
593
+ "path": "exports/assignments",
594
+ "save_original_file": false,
595
+ "extra_params": {
596
+ "output_format": "coco",
597
+ "include_metadata": true
598
+ }
599
+ }' --plugin custom-coco-export
600
+ ```
601
+
602
+ ## 일반적인 Export 패턴
603
+
604
+ ```python
605
+ # 패턴 1: 형식별 변환
606
+ class Exporter(BaseExporter):
607
+ def convert_data(self, data):
608
+ """YOLO 형식으로 변환."""
609
+ if data.get('task_type') == 'object_detection':
610
+ return self.convert_to_yolo_format(data)
611
+ return data
612
+
613
+ # 패턴 2: 조건부 파일 구성
614
+ class Exporter(BaseExporter):
615
+ def setup_output_directories(self, unique_export_path, save_original_file_flag):
616
+ # 부모 메서드 호출
617
+ output_paths = super().setup_output_directories(unique_export_path, save_original_file_flag)
618
+
619
+ # 카테고리별 별도 폴더 생성
620
+ for category in ['train', 'val', 'test']:
621
+ category_path = unique_export_path / category
622
+ category_path.mkdir(parents=True, exist_ok=True)
623
+ output_paths[f'{category}_path'] = category_path
624
+
625
+ return output_paths
626
+
627
+ # 패턴 3: 검증을 포함한 배치 처리
628
+ class Exporter(BaseExporter):
629
+ def before_convert(self, export_item):
630
+ # 필수 필드 검증
631
+ required_fields = ['data', 'files', 'id']
632
+ for field in required_fields:
633
+ if field not in export_item:
634
+ raise ValueError(f"필수 필드가 누락됨: {field}")
635
+ return export_item
636
+ ```
637
+
638
+ ## 개발 팁 및 모범 사례
639
+
640
+ ### 1. 오류 처리
641
+
642
+ ```python
643
+ def convert_data(self, data):
644
+ try:
645
+ # 변환 로직
646
+ result = self.process_annotations(data)
647
+ return result
648
+ except Exception as e:
649
+ self.run.log_message(f"변환 중 오류 발생: {str(e)}", level="error")
650
+ raise # BaseExporter가 오류를 자동으로 처리
651
+ ```
652
+
653
+ ### 2. 진행률 추적
654
+
655
+ ```python
656
+ def convert_data(self, data):
657
+ annotations = data.get('annotations', [])
658
+ total = len(annotations)
659
+
660
+ for i, annotation in enumerate(annotations):
661
+ # 진행률 업데이트 (0-100 사이의 값)
662
+ progress = int((i / total) * 100)
663
+ self.run.set_progress(progress)
664
+
665
+ # 변환 로직...
666
+
667
+ return converted_data
668
+ ```
669
+
670
+ ### 3. 메트릭 수집
671
+
672
+ ```python
673
+ def after_convert(self, converted_data):
674
+ # 유용한 메트릭 수집
675
+ metrics = {
676
+ 'total_exported': len(converted_data.get('annotations', [])),
677
+ 'processing_time': time.time() - self.start_time,
678
+ 'success_rate': self.calculate_success_rate(),
679
+ }
680
+
681
+ self.run.log_metrics(metrics)
682
+ return converted_data
683
+ ```
684
+
685
+ ### 4. 로깅 활용
686
+
687
+ ```python
688
+ def convert_data(self, data):
689
+ self.run.log_message("데이터 변환 시작", level="info")
690
+
691
+ if not data.get('annotations'):
692
+ self.run.log_message("주석 데이터가 없습니다", level="warning")
693
+ return data
694
+
695
+ # 변환 로직...
696
+
697
+ self.run.log_message(f"변환 완료: {len(result)} 항목 처리됨", level="success")
698
+ return result
699
+ ```
700
+
701
+ ### 5. 매개변수 처리
702
+
703
+ ```python
704
+ def __init__(self, run, export_items, path_root, **params):
705
+ super().__init__(run, export_items, path_root, **params)
706
+
707
+ # 사용자 정의 매개변수 처리
708
+ self.output_format = params.get('output_format', 'json')
709
+ self.include_metadata = params.get('include_metadata', True)
710
+ self.compression = params.get('compression', False)
711
+ ```
712
+
713
+ ## 모범 사례
714
+
715
+ ### 데이터 처리
716
+
717
+ - **메모리 효율성**: 대용량 데이터셋 처리를 위해 제너레이터 사용
718
+ - **오류 복구**: 개별 항목에 대한 우아한 오류 처리 구현
719
+ - **진행률 보고**: 장시간 실행되는 내보내기의 진행률을 정기적으로 업데이트
720
+ - **데이터 검증**: 변환 전 데이터 구조 검증
721
+
722
+ ```python
723
+ class Exporter(BaseExporter):
724
+ def export(self, export_items=None, results=None, **kwargs):
725
+ """커스텀 처리를 위한 주 export 메서드 오버라이드."""
726
+ # 제너레이터를 소비하지 않고 항목 수를 카운트하기 위해 tee 사용
727
+ items_to_process = export_items if export_items is not None else self.export_items
728
+ export_items_count, export_items_process = tee(items_to_process)
729
+ total = sum(1 for _ in export_items_count)
730
+
731
+ # 오류 처리가 포함된 커스텀 처리
732
+ for no, export_item in enumerate(export_items_process, start=1):
733
+ try:
734
+ # 내장 데이터 변환 파이프라인 사용
735
+ processed_item = self.process_data_conversion(export_item)
736
+ self.run.set_progress(no, total, category='dataset_conversion')
737
+ except Exception as e:
738
+ self.run.log_message(f"항목 {no} 처리 중 오류: {str(e)}", "ERROR")
739
+ continue
740
+
741
+ # 표준 처리를 위해 부모의 export 메서드 호출
742
+ # 또는 자체 완전한 워크플로우 구현
743
+ return super().export(export_items, results, **kwargs)
744
+ ```
745
+
746
+ ### 파일 관리
747
+
748
+ - **고유 경로**: 타임스탬프나 카운터 접미사로 파일 충돌 방지
749
+ - **디렉토리 구조**: 출력 파일을 논리적으로 구성
750
+ - **오류 로깅**: 디버깅을 위해 실패한 파일 추적
751
+ - **정리**: 완료 시 임시 파일 제거
752
+
753
+ ```python
754
+ class Exporter(BaseExporter):
755
+ def setup_output_directories(self, unique_export_path, save_original_file_flag):
756
+ """고유한 export 디렉토리 구조 생성."""
757
+ # BaseExporter는 이미 _create_unique_export_path를 통해 고유 경로 생성을 처리함
758
+ # 이 메서드는 내부 디렉토리 구조를 설정함
759
+ output_paths = super().setup_output_directories(unique_export_path, save_original_file_flag)
760
+
761
+ # 필요에 따라 커스텀 서브디렉토리 추가
762
+ custom_dir = unique_export_path / 'custom_output'
763
+ custom_dir.mkdir(parents=True, exist_ok=True)
764
+ output_paths['custom_output_path'] = custom_dir
765
+
766
+ return output_paths
767
+ ```
768
+
769
+ ### 형식 변환
770
+
771
+ - **유연한 템플릿**: 여러 데이터 타입과 함께 작동하는 템플릿 설계
772
+ - **스키마 검증**: 예상 스키마에 대한 출력 검증
773
+ - **메타데이터 보존**: 변환 중 중요한 메타데이터 유지
774
+ - **버전 호환성**: 다양한 데이터 스키마 버전 처리
775
+
776
+ ## 자주 묻는 질문
777
+
778
+ **Q: BaseExporter를 사용하지 않고 직접 구현할 수 있나요?**
779
+ A: 가능하지만 권장하지 않습니다. BaseExporter는 진행률 추적, 오류 처리, 메트릭 수집 등의 기본 기능을 자동으로 제공합니다.
780
+
781
+ **Q: 여러 파일 형식으로 동시에 내보낼 수 있나요?**
782
+ A: `process_file_saving()` 메서드를 오버라이드하여 여러 형식으로 저장할 수 있습니다.
783
+
784
+ **Q: 대용량 데이터셋을 처리할 때 메모리 사용량을 최적화하려면?**
785
+ A: `convert_data()`에서 한 번에 모든 데이터를 로드하지 말고, 스트리밍 방식으로 처리하는 것을 고려해보세요.
786
+
787
+ **Q: 진행률이 올바르게 표시되지 않는다면?**
788
+ A: `self.run.set_progress()`를 적절한 간격으로 호출하고 있는지 확인하세요. 0-100 사이의 정수 값을 사용해야 합니다.