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,786 @@
1
+ ---
2
+ id: export-plugins
3
+ title: Export Plugins
4
+ sidebar_position: 2
5
+ ---
6
+
7
+ # Export Plugins
8
+
9
+ Export plugins provide data export and transformation operations for exporting annotated data, ground truth datasets, assignments, and tasks from the Synapse platform.
10
+
11
+ ## Overview
12
+
13
+ **Available Actions:**
14
+
15
+ - `export` - Export data from various sources (assignments, ground truth, tasks) with customizable processing
16
+
17
+ **Use Cases:**
18
+
19
+ - Exporting annotated datasets for training
20
+ - Converting ground truth data to custom formats
21
+ - Creating data packages for distribution
22
+ - Batch processing of assignment results
23
+ - Transforming annotation data for external tools
24
+
25
+ **Supported Export Targets:**
26
+
27
+ - `assignment` - Export assignment data with annotations
28
+ - `ground_truth` - Export ground truth dataset versions
29
+ - `task` - Export task data with associated annotations
30
+
31
+ ## BaseExporter and Exporter Class Architecture
32
+
33
+ The following diagrams illustrate the relationship between BaseExporter and Exporter classes and their method implementations:
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
+ ### Method Execution Flow
93
+
94
+ This flowchart shows the complete execution flow of export operations:
95
+
96
+ ```mermaid
97
+ flowchart TD
98
+ %% Start
99
+ A[export method called] --> B[Initialize paths and metrics]
100
+ B --> C[Setup output directories]
101
+ C --> D[Loop through 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{save_original_file?}
112
+ J -->|Yes| K[_process_original_file_saving]
113
+ J -->|No| L[_process_json_file_saving]
114
+ K --> L
115
+
116
+ %% Continue or finish
117
+ L --> M{More items?}
118
+ M -->|Yes| D
119
+ M -->|No| N[Save error lists]
120
+ N --> O[Return export path]
121
+
122
+ %% Error handling
123
+ K --> P{Original file failed?}
124
+ P -->|Yes| Q[Skip to next item]
125
+ P -->|No| L
126
+
127
+ L --> R{JSON file failed?}
128
+ R -->|Yes| Q
129
+ R -->|No| S[Update metrics]
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
+ ### Key Relationships and Responsibilities
148
+
149
+ **BaseExporter (Abstract Base Class)**
150
+ - **Core Functionality**: Provides complete export workflow infrastructure
151
+ - **Template Methods**: `export()` method orchestrates the entire process
152
+ - **Hook Methods**: `convert_data()`, `before_convert()`, `after_convert()` for customization
153
+ - **Utilities**: File operations, directory setup, error handling, progress tracking
154
+
155
+ **Exporter (Concrete Implementation)**
156
+ - **Inheritance**: Extends `BaseExporter`
157
+ - **Minimal Implementation**: Provides basic implementations of abstract methods
158
+ - **Pass-through Behavior**: Most methods delegate to parent class
159
+ - **Customization Points**: Override conversion methods for specific logic
160
+
161
+ ### Method Categories
162
+ - **🔵 Core Workflow**: Main export orchestration methods
163
+ - **🟢 Template/Hook**: Methods designed to be overridden by subclasses
164
+ - **🟡 File Operations**: Concrete file saving and handling methods
165
+ - **🔸 Helper/Utility**: Private methods for internal operations
166
+
167
+ The design follows the **Template Method Pattern** where `BaseExporter.export()` defines the algorithm skeleton, and subclasses customize specific steps through the hook methods.
168
+
169
+ ## BaseExporter Class Structure
170
+
171
+ The new BaseExporter class provides an object-oriented approach for export plugins:
172
+
173
+ ```python
174
+ from synapse_sdk.plugins.categories.export.templates.plugin import BaseExporter
175
+
176
+ class Exporter(BaseExporter):
177
+ """Plugin export action interface for organizing files."""
178
+
179
+ def __init__(self, run, export_items, path_root, **params):
180
+ """Initialize the plugin export action class."""
181
+ super().__init__(run, export_items, path_root, **params)
182
+
183
+ def convert_data(self, data):
184
+ """Converts the data."""
185
+ return data
186
+
187
+ def before_convert(self, data):
188
+ """Preprocesses the data before conversion."""
189
+ return data
190
+
191
+ def after_convert(self, data):
192
+ """Post-processes the data after conversion."""
193
+ return data
194
+ ```
195
+
196
+ ## BaseExporter Core Functionality
197
+
198
+ ### Auto-provided Utilities
199
+
200
+ - **Complete Export Workflow**: `export()` method handles the complete export process
201
+ - **Data Conversion Pipeline**: `process_data_conversion()` handles before_convert → convert_data → after_convert processing
202
+ - **File Saving Management**: `process_file_saving()` handles original file and JSON file saving operations (can be overridden)
203
+ - **Output Directory Setup**: `setup_output_directories()` creates output directory structure (can be overridden)
204
+
205
+ ### Required Methods (should be implemented by subclasses)
206
+
207
+ - **convert_data()**: Transform data during export
208
+
209
+ ### Optional Methods (can be overridden by subclasses)
210
+
211
+ - **save_original_file()**: Save original files from export items
212
+ - **save_as_json()**: Save data as JSON files
213
+ - **before_convert()**: Pre-process data before conversion
214
+ - **after_convert()**: Post-process data after conversion
215
+ - **process_file_saving()**: Custom file saving logic
216
+
217
+ ### Helper Methods
218
+
219
+ - **\_process_original_file_saving()**: Handle original file saving with metrics
220
+ - **\_process_json_file_saving()**: Handle JSON file saving with metrics
221
+
222
+ ### Auto-provided Utilities
223
+
224
+ - Progress tracking via `self.run.set_progress()`
225
+ - Logging via `self.run.log_message()` and other run methods
226
+ - Error handling and metrics collection via self.run methods
227
+
228
+ ## Key Features
229
+
230
+ - **Progress Tracking**: Built-in progress monitoring with `run.set_progress()`
231
+ - **Error Handling**: Automatic error collection and reporting
232
+ - **Metrics Logging**: Track success/failure rates with `run.log_metrics()`
233
+ - **File Management**: Handles both original files and processed JSON data
234
+ - **Logging**: Comprehensive logging with `run.log_message()` and custom events
235
+
236
+ ## Practical Examples
237
+
238
+ ### YOLO Format Exporter with Custom Directory Structure
239
+
240
+ Here's a complete example that exports data in YOLO format while leveraging `setup_output_directories` and `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
+ """Plugin to export data in YOLO format."""
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
+ """Create directories matching YOLO project structure."""
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"Created YOLO directory: {dir_path}")
262
+
263
+ return unique_export_path
264
+
265
+ def convert_data(self, data):
266
+ """Convert annotation data to YOLO format."""
267
+ converted_annotations = []
268
+
269
+ for annotation in data.get('annotations', []):
270
+ # Convert bounding box to YOLO format
271
+ bbox = annotation['geometry']['bbox']
272
+ image_width = data['image']['width']
273
+ image_height = data['image']['height']
274
+
275
+ # YOLO format: center_x, center_y, width, height (normalized)
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
+ # Class ID mapping
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
+ """Handle file saving in YOLO format."""
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. Save image file to images folder
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"Saved image: {image_path}")
325
+
326
+ # 2. Save YOLO label file to labels folder
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"Saved YOLO label: {label_path}")
336
+
337
+ # 3. Save class mapping file (only once)
338
+ if current_index == 1: # Only when processing first file
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"Saved classes file: {classes_path}")
344
+
345
+ return True
346
+
347
+ except Exception as e:
348
+ self.run.log_message(f"Error during file saving: {str(e)}", level="error")
349
+ errors_json_file_list.append(f"Export item {current_index}: {str(e)}")
350
+ return True # Return True to continue processing other files
351
+ ```
352
+
353
+ This example demonstrates how to leverage BaseExporter's key extension points `setup_output_directories` and `process_file_saving` to:
354
+
355
+ - Create YOLO project structure (`images/`, `labels/`, `data/`)
356
+ - Save image files and YOLO label files in appropriate locations
357
+ - Manage class mapping files
358
+ - Track progress and handle errors
359
+
360
+ ## Quick Start Guide
361
+
362
+ Step-by-step guide to create a simple plugin using BaseExporter:
363
+
364
+ ### Step 1: Inherit Base Class
365
+
366
+ ```python
367
+ from synapse_sdk.plugins.categories.export.templates.plugin import BaseExporter
368
+
369
+ class MyExporter(BaseExporter):
370
+ def convert_data(self, data):
371
+ # Required: Implement data transformation logic
372
+ return data # or return transformed data
373
+ ```
374
+
375
+ ### Step 2: Override Additional Methods as Needed
376
+
377
+ ```python
378
+ def before_convert(self, data):
379
+ # Optional: Pre-processing before conversion
380
+ return data
381
+
382
+ def after_convert(self, converted_data):
383
+ # Optional: Post-processing after conversion
384
+ return converted_data
385
+
386
+ def save_as_json(self, converted_data, output_path):
387
+ # Optional: Custom save format
388
+ # By default saves as JSON format
389
+ pass
390
+ ```
391
+
392
+ ### Step 3: Register Plugin
393
+
394
+ Plugin directory structure:
395
+
396
+ ```
397
+ my_plugin/
398
+ ├── __init__.py
399
+ ├── plugin.py # MyExporter class definition
400
+ └── manifest.yaml # Plugin metadata
401
+ ```
402
+
403
+ ## Creating Export Plugins
404
+
405
+ Export plugins now use the BaseExporter class-based approach for better organization and reusability. Here's how to create a custom export plugin:
406
+
407
+ ### Step 1: Generate Export Plugin Template
408
+
409
+ ```bash
410
+ synapse plugin create
411
+ # Select 'export' as category
412
+ # Plugin will be created with export template
413
+ ```
414
+
415
+ ### Step 2: Customize Export Parameters
416
+
417
+ The `ExportParams` model defines the required parameters:
418
+
419
+ ```python
420
+ from synapse_sdk.plugins.categories.export.actions.export import ExportParams
421
+ from pydantic import BaseModel
422
+ from typing import Literal
423
+
424
+ class CustomExportParams(ExportParams):
425
+ # Add custom parameters
426
+ output_format: Literal['json', 'csv', 'xml'] = 'json'
427
+ include_metadata: bool = True
428
+ compression: bool = False
429
+ ```
430
+
431
+ ### Step 3: Implement Data Transformation
432
+
433
+ Implement the required methods in your `Exporter` class in `plugin/export.py`:
434
+
435
+ ```python
436
+ from datetime import datetime
437
+ from synapse_sdk.plugins.categories.export.templates.plugin import BaseExporter
438
+
439
+ class Exporter(BaseExporter):
440
+ """Custom export plugin with COCO format conversion."""
441
+
442
+ def convert_data(self, data):
443
+ """Convert annotation data to your desired format."""
444
+ # Example: Convert to COCO format
445
+ if data.get('data_type') == 'image_detection':
446
+ return self.convert_to_coco_format(data)
447
+ elif data.get('data_type') == 'image_classification':
448
+ return self.convert_to_classification_format(data)
449
+ return data
450
+
451
+ def before_convert(self, export_item):
452
+ """Preprocess data before conversion."""
453
+ # Add validation, filtering, or preprocessing
454
+ if not export_item.get('data'):
455
+ return None # Skip empty items
456
+
457
+ # Add custom metadata
458
+ export_item['processed_at'] = datetime.now().isoformat()
459
+ return export_item
460
+
461
+ def after_convert(self, converted_data):
462
+ """Post-process converted data."""
463
+ # Add final touches, validation, or formatting
464
+ if 'annotations' in converted_data:
465
+ converted_data['annotation_count'] = len(converted_data['annotations'])
466
+ return converted_data
467
+
468
+ def convert_to_coco_format(self, data):
469
+ """Example: Convert to COCO detection format."""
470
+ coco_data = {
471
+ "images": [],
472
+ "annotations": [],
473
+ "categories": []
474
+ }
475
+
476
+ # Transform annotation data to COCO format
477
+ for annotation in data.get('annotations', []):
478
+ coco_annotation = {
479
+ "id": annotation['id'],
480
+ "image_id": annotation['image_id'],
481
+ "category_id": annotation['category_id'],
482
+ "bbox": annotation['bbox'],
483
+ "area": annotation.get('area', 0),
484
+ "iscrowd": 0
485
+ }
486
+ coco_data["annotations"].append(coco_annotation)
487
+
488
+ return coco_data
489
+ ```
490
+
491
+ ### Step 4: Configure Export Targets
492
+
493
+ The export action supports different data sources:
494
+
495
+ ```python
496
+ # Filter examples for different targets
497
+ filters = {
498
+ # For ground truth export
499
+ "ground_truth": {
500
+ "ground_truth_dataset_version": 123,
501
+ "expand": ["data"]
502
+ },
503
+
504
+ # For assignment export
505
+ "assignment": {
506
+ "project": 456,
507
+ "status": "completed",
508
+ "expand": ["data"]
509
+ },
510
+
511
+ # For task export
512
+ "task": {
513
+ "project": 456,
514
+ "assignment": 789,
515
+ "expand": ["data_unit", "assignment"]
516
+ }
517
+ }
518
+ ```
519
+
520
+ ### Step 5: Handle File Operations
521
+
522
+ Customize file saving and organization by overriding BaseExporter methods:
523
+
524
+ ```python
525
+ import json
526
+ from pathlib import Path
527
+ from synapse_sdk.plugins.categories.export.enums import ExportStatus
528
+
529
+ class Exporter(BaseExporter):
530
+ """Custom export plugin with multiple format support."""
531
+
532
+ def save_as_json(self, result, base_path, error_file_list):
533
+ """Custom JSON saving with different formats."""
534
+ file_name = Path(self.get_original_file_name(result['files'])).stem
535
+
536
+ # Choose output format based on params
537
+ if self.params.get('output_format') == 'csv':
538
+ return self.save_as_csv(result, base_path, error_file_list)
539
+ elif self.params.get('output_format') == 'xml':
540
+ return self.save_as_xml(result, base_path, error_file_list)
541
+
542
+ # Default JSON handling
543
+ json_data = result['data']
544
+ file_info = {'file_name': f'{file_name}.json'}
545
+
546
+ try:
547
+ with (base_path / f'{file_name}.json').open('w', encoding='utf-8') as f:
548
+ json.dump(json_data, f, indent=4, ensure_ascii=False)
549
+ status = ExportStatus.SUCCESS
550
+ except Exception as e:
551
+ error_file_list.append([f'{file_name}.json', str(e)])
552
+ status = ExportStatus.FAILED
553
+
554
+ self.run.export_log_json_file(result['id'], file_info, status)
555
+ return status
556
+
557
+ def setup_output_directories(self, unique_export_path, save_original_file_flag):
558
+ """Custom directory structure."""
559
+ # Create format-specific directories
560
+ output_paths = super().setup_output_directories(unique_export_path, save_original_file_flag)
561
+
562
+ # Add custom directories based on output format
563
+ format_dir = unique_export_path / self.params.get('output_format', 'json')
564
+ format_dir.mkdir(parents=True, exist_ok=True)
565
+ output_paths['format_output_path'] = format_dir
566
+
567
+ return output_paths
568
+ ```
569
+
570
+ ### Step 6: Usage Examples
571
+
572
+ Running export plugins with different configurations:
573
+
574
+ ```bash
575
+ # Basic export of ground truth data
576
+ synapse plugin run export '{
577
+ "name": "my_export",
578
+ "storage": 1,
579
+ "target": "ground_truth",
580
+ "filter": {"ground_truth_dataset_version": 123},
581
+ "path": "exports/ground_truth",
582
+ "save_original_file": true
583
+ }' --plugin my-export-plugin
584
+
585
+ # Export assignments with custom parameters
586
+ synapse plugin run export '{
587
+ "name": "assignment_export",
588
+ "storage": 1,
589
+ "target": "assignment",
590
+ "filter": {"project": 456, "status": "completed"},
591
+ "path": "exports/assignments",
592
+ "save_original_file": false,
593
+ "extra_params": {
594
+ "output_format": "coco",
595
+ "include_metadata": true
596
+ }
597
+ }' --plugin custom-coco-export
598
+ ```
599
+
600
+ ## Common Export Patterns
601
+
602
+ ```python
603
+ # Pattern 1: Format-specific conversion
604
+ class Exporter(BaseExporter):
605
+ def convert_data(self, data):
606
+ """Convert to YOLO format."""
607
+ if data.get('task_type') == 'object_detection':
608
+ return self.convert_to_yolo_format(data)
609
+ return data
610
+
611
+ # Pattern 2: Conditional file organization
612
+ class Exporter(BaseExporter):
613
+ def setup_output_directories(self, unique_export_path, save_original_file_flag):
614
+ # Call parent method
615
+ output_paths = super().setup_output_directories(unique_export_path, save_original_file_flag)
616
+
617
+ # Create separate folders by category
618
+ for category in ['train', 'val', 'test']:
619
+ category_path = unique_export_path / category
620
+ category_path.mkdir(parents=True, exist_ok=True)
621
+ output_paths[f'{category}_path'] = category_path
622
+
623
+ return output_paths
624
+
625
+ # Pattern 3: Batch processing with validation
626
+ class Exporter(BaseExporter):
627
+ def before_convert(self, export_item):
628
+ # Validate required fields
629
+ required_fields = ['data', 'files', 'id']
630
+ for field in required_fields:
631
+ if field not in export_item:
632
+ raise ValueError(f"Missing required field: {field}")
633
+ return export_item
634
+ ```
635
+
636
+ ## Development Tips and Best Practices
637
+
638
+ ### 1. Error Handling
639
+
640
+ ```python
641
+ def convert_data(self, data):
642
+ try:
643
+ # Conversion logic
644
+ result = self.process_annotations(data)
645
+ return result
646
+ except Exception as e:
647
+ self.run.log_message(f"Error during conversion: {str(e)}", level="error")
648
+ raise # BaseExporter handles errors automatically
649
+ ```
650
+
651
+ ### 2. Progress Tracking
652
+
653
+ ```python
654
+ def convert_data(self, data):
655
+ annotations = data.get('annotations', [])
656
+ total = len(annotations)
657
+
658
+ for i, annotation in enumerate(annotations):
659
+ # Update progress (value between 0-100)
660
+ progress = int((i / total) * 100)
661
+ self.run.set_progress(progress)
662
+
663
+ # Conversion logic...
664
+
665
+ return converted_data
666
+ ```
667
+
668
+ ### 3. Metrics Collection
669
+
670
+ ```python
671
+ def after_convert(self, converted_data):
672
+ # Collect useful metrics
673
+ metrics = {
674
+ 'total_exported': len(converted_data.get('annotations', [])),
675
+ 'processing_time': time.time() - self.start_time,
676
+ 'success_rate': self.calculate_success_rate(),
677
+ }
678
+
679
+ self.run.log_metrics(metrics)
680
+ return converted_data
681
+ ```
682
+
683
+ ### 4. Logging Usage
684
+
685
+ ```python
686
+ def convert_data(self, data):
687
+ self.run.log_message("Starting data conversion", level="info")
688
+
689
+ if not data.get('annotations'):
690
+ self.run.log_message("No annotation data found", level="warning")
691
+ return data
692
+
693
+ # Conversion logic...
694
+
695
+ self.run.log_message(f"Conversion complete: processed {len(result)} items", level="success")
696
+ return result
697
+ ```
698
+
699
+ ### 5. Parameter Handling
700
+
701
+ ```python
702
+ def __init__(self, run, export_items, path_root, **params):
703
+ super().__init__(run, export_items, path_root, **params)
704
+
705
+ # Handle custom parameters
706
+ self.output_format = params.get('output_format', 'json')
707
+ self.include_metadata = params.get('include_metadata', True)
708
+ self.compression = params.get('compression', False)
709
+ ```
710
+
711
+ ## Best Practices
712
+
713
+ ### Data Processing
714
+
715
+ - **Memory Efficiency**: Use generators for processing large datasets
716
+ - **Error Recovery**: Implement graceful error handling for individual items
717
+ - **Progress Reporting**: Update progress regularly for long-running exports
718
+ - **Data Validation**: Validate data structure before conversion
719
+
720
+ ```python
721
+ class Exporter(BaseExporter):
722
+ def export(self, export_items=None, results=None, **kwargs):
723
+ """Override the main export method for custom processing."""
724
+ # Use tee to count items without consuming generator
725
+ items_to_process = export_items if export_items is not None else self.export_items
726
+ export_items_count, export_items_process = tee(items_to_process)
727
+ total = sum(1 for _ in export_items_count)
728
+
729
+ # Custom processing with error handling
730
+ for no, export_item in enumerate(export_items_process, start=1):
731
+ try:
732
+ # Use the built-in data conversion pipeline
733
+ processed_item = self.process_data_conversion(export_item)
734
+ self.run.set_progress(no, total, category='dataset_conversion')
735
+ except Exception as e:
736
+ self.run.log_message(f"Error processing item {no}: {str(e)}", "ERROR")
737
+ continue
738
+
739
+ # Call parent's export method for standard processing
740
+ # or implement your own complete workflow
741
+ return super().export(export_items, results, **kwargs)
742
+ ```
743
+
744
+ ### File Management
745
+
746
+ - **Unique Paths**: Prevent file collisions with timestamp or counter suffixes
747
+ - **Directory Structure**: Organize output files logically
748
+ - **Error Logging**: Track failed files for debugging
749
+ - **Cleanup**: Remove temporary files on completion
750
+
751
+ ```python
752
+ class Exporter(BaseExporter):
753
+ def setup_output_directories(self, unique_export_path, save_original_file_flag):
754
+ """Create unique export directory structure."""
755
+ # BaseExporter already handles unique path creation via _create_unique_export_path
756
+ # This method sets up the internal directory structure
757
+ output_paths = super().setup_output_directories(unique_export_path, save_original_file_flag)
758
+
759
+ # Add custom subdirectories as needed
760
+ custom_dir = unique_export_path / 'custom_output'
761
+ custom_dir.mkdir(parents=True, exist_ok=True)
762
+ output_paths['custom_output_path'] = custom_dir
763
+
764
+ return output_paths
765
+ ```
766
+
767
+ ### Format Conversion
768
+
769
+ - **Flexible Templates**: Design templates that work with multiple data types
770
+ - **Schema Validation**: Validate output against expected schemas
771
+ - **Metadata Preservation**: Maintain important metadata during conversion
772
+ - **Version Compatibility**: Handle different data schema versions
773
+
774
+ ## Frequently Asked Questions
775
+
776
+ **Q: Can I implement plugins directly without using BaseExporter?**
777
+ A: Yes, but it's not recommended. BaseExporter provides essential features like progress tracking, error handling, and metrics collection automatically.
778
+
779
+ **Q: Can I export to multiple file formats simultaneously?**
780
+ A: Yes, you can override the `process_file_saving()` method to save in multiple formats.
781
+
782
+ **Q: How can I optimize memory usage when processing large datasets?**
783
+ A: Consider using streaming processing in `convert_data()` rather than loading all data at once.
784
+
785
+ **Q: What should I do if progress is not displaying correctly?**
786
+ A: Make sure you're calling `self.run.set_progress()` at appropriate intervals and using integer values between 0-100.