modelstudio-sdk 0.0.0.dev0__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.
- modelstudio/__init__.py +25 -0
- modelstudio/_http.py +116 -0
- modelstudio/_pandas.py +33 -0
- modelstudio/_polling.py +59 -0
- modelstudio/_version.py +1 -0
- modelstudio/client.py +108 -0
- modelstudio/exceptions.py +80 -0
- modelstudio/models/__init__.py +81 -0
- modelstudio/models/annotations.py +80 -0
- modelstudio/models/categories.py +106 -0
- modelstudio/models/common.py +18 -0
- modelstudio/models/datasets.py +55 -0
- modelstudio/models/deletion.py +29 -0
- modelstudio/models/exports.py +32 -0
- modelstudio/models/few_shot.py +41 -0
- modelstudio/models/filters.py +81 -0
- modelstudio/models/history.py +58 -0
- modelstudio/models/images.py +91 -0
- modelstudio/models/imports.py +141 -0
- modelstudio/models/media.py +37 -0
- modelstudio/models/merge.py +51 -0
- modelstudio/models/metrics.py +180 -0
- modelstudio/models/oversample.py +41 -0
- modelstudio/models/splits.py +112 -0
- modelstudio/models/validation.py +89 -0
- modelstudio/resources/__init__.py +1 -0
- modelstudio/resources/dataset.py +732 -0
- modelstudio/resources/split.py +140 -0
- modelstudio_sdk-0.0.0.dev0.dist-info/METADATA +513 -0
- modelstudio_sdk-0.0.0.dev0.dist-info/RECORD +32 -0
- modelstudio_sdk-0.0.0.dev0.dist-info/WHEEL +4 -0
- modelstudio_sdk-0.0.0.dev0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Common/shared model types."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PagedResponse(BaseModel):
|
|
11
|
+
"""Generic paged response wrapper matching the API's paginated response."""
|
|
12
|
+
|
|
13
|
+
content: list[dict[str, Any]] = []
|
|
14
|
+
total_elements: int = 0
|
|
15
|
+
total_pages: int = 0
|
|
16
|
+
page: int = 0
|
|
17
|
+
size: int = 0
|
|
18
|
+
facets: dict[str, Any] | None = None
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Dataset-related models."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from uuid import UUID
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
|
|
10
|
+
from modelstudio.models.splits import SplitModel
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DatasetModel(BaseModel):
|
|
14
|
+
"""Represents a dataset from GET /api/v1/datasets."""
|
|
15
|
+
|
|
16
|
+
id: UUID
|
|
17
|
+
parent_dataset_id: UUID | None = None
|
|
18
|
+
name: str
|
|
19
|
+
dataset_type: str
|
|
20
|
+
format: str
|
|
21
|
+
description: str | None = None
|
|
22
|
+
created_on: datetime | None = None
|
|
23
|
+
locked: bool = False
|
|
24
|
+
uploaded: bool = False
|
|
25
|
+
splits: list[SplitModel] = []
|
|
26
|
+
clone_status: str | None = None
|
|
27
|
+
clone_progress: int = 0
|
|
28
|
+
clone_error: str | None = None
|
|
29
|
+
clone_started_at: datetime | None = None
|
|
30
|
+
clone_finished_at: datetime | None = None
|
|
31
|
+
finalize_status: str | None = None
|
|
32
|
+
finalize_progress: int = 0
|
|
33
|
+
finalize_message: str | None = None
|
|
34
|
+
finalize_started_at: datetime | None = None
|
|
35
|
+
finalize_finished_at: datetime | None = None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class CloneStatusModel(BaseModel):
|
|
39
|
+
"""Clone operation status."""
|
|
40
|
+
|
|
41
|
+
clone_status: str | None = None
|
|
42
|
+
clone_progress: int = 0
|
|
43
|
+
clone_error: str | None = None
|
|
44
|
+
clone_started_at: datetime | None = None
|
|
45
|
+
clone_finished_at: datetime | None = None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class FinalizeStatusModel(BaseModel):
|
|
49
|
+
"""Finalize operation status."""
|
|
50
|
+
|
|
51
|
+
finalize_status: str | None = None
|
|
52
|
+
finalize_progress: int = 0
|
|
53
|
+
finalize_message: str | None = None
|
|
54
|
+
finalize_started_at: datetime | None = None
|
|
55
|
+
finalize_finished_at: datetime | None = None
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Deletion check models."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from uuid import UUID
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ExperimentRefModel(BaseModel):
|
|
11
|
+
"""An experiment referencing a dataset."""
|
|
12
|
+
|
|
13
|
+
experiment_id: UUID | None = None
|
|
14
|
+
experiment_name: str | None = None
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class DatasetRefModel(BaseModel):
|
|
18
|
+
"""A child dataset reference."""
|
|
19
|
+
|
|
20
|
+
dataset_id: UUID | None = None
|
|
21
|
+
dataset_name: str | None = None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class DeletionCheckModel(BaseModel):
|
|
25
|
+
"""Response from GET /datasets/{id}/can-delete."""
|
|
26
|
+
|
|
27
|
+
can_delete: bool = False
|
|
28
|
+
referencing_experiments: list[ExperimentRefModel] = []
|
|
29
|
+
child_datasets: list[DatasetRefModel] = []
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Export-related models."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ExportCocoModel(BaseModel):
|
|
11
|
+
"""Response from POST /datasets/{id}/export/coco."""
|
|
12
|
+
|
|
13
|
+
coco_json: dict[str, Any] | None = None
|
|
14
|
+
image_count: int = 0
|
|
15
|
+
annotation_count: int = 0
|
|
16
|
+
category_count: int = 0
|
|
17
|
+
image_id_mapping: dict[str, int] | None = None
|
|
18
|
+
annotation_id_mapping: dict[str, int] | None = None
|
|
19
|
+
category_id_mapping: dict[str, int] | None = None
|
|
20
|
+
reindexed: bool = False
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class SegmentationExportModel(BaseModel):
|
|
24
|
+
"""Response from POST /datasets/{id}/splits/{sid}/export/segmentation."""
|
|
25
|
+
|
|
26
|
+
split_id: str | None = None
|
|
27
|
+
images_archive_uri: str | None = None
|
|
28
|
+
masks_archive_uri: str | None = None
|
|
29
|
+
categories_uri: str | None = None
|
|
30
|
+
image_count: int = 0
|
|
31
|
+
mask_count: int = 0
|
|
32
|
+
images_without_masks: list[str] = []
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Few-shot dataset creation models."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
from uuid import UUID
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class FewShotRequest(BaseModel):
|
|
12
|
+
"""Request for POST /datasets/{id}/few-shot."""
|
|
13
|
+
|
|
14
|
+
num_images: int
|
|
15
|
+
method: str = "RANDOM"
|
|
16
|
+
specific_image_ids: list[UUID] | None = None
|
|
17
|
+
specific_file_names: list[str] | None = None
|
|
18
|
+
new_dataset_name: str | None = None
|
|
19
|
+
new_dataset_description: str | None = None
|
|
20
|
+
seed: int | None = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class FewShotPreviewModel(BaseModel):
|
|
24
|
+
"""Response from POST /datasets/{id}/few-shot/preview."""
|
|
25
|
+
|
|
26
|
+
selected_image_count: int = 0
|
|
27
|
+
total_annotations: int = 0
|
|
28
|
+
selected_images: list[dict[str, Any]] = []
|
|
29
|
+
class_coverage: dict[str, Any] = {}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class FewShotResponseModel(BaseModel):
|
|
33
|
+
"""Response from POST /datasets/{id}/few-shot."""
|
|
34
|
+
|
|
35
|
+
success: bool = False
|
|
36
|
+
message: str | None = None
|
|
37
|
+
new_dataset_id: str | None = None
|
|
38
|
+
new_dataset_name: str | None = None
|
|
39
|
+
images_included: int = 0
|
|
40
|
+
annotations_included: int = 0
|
|
41
|
+
categories_included: int = 0
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Filter-related models."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CategoryFilter(BaseModel):
|
|
11
|
+
"""Category filter options."""
|
|
12
|
+
|
|
13
|
+
keep_categories: list[str] | None = None
|
|
14
|
+
keep_category_ids: list[int] | None = None
|
|
15
|
+
exclude_mode: bool = False
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class BboxFilter(BaseModel):
|
|
19
|
+
"""Bounding box filter options."""
|
|
20
|
+
|
|
21
|
+
min_width: float | None = None
|
|
22
|
+
max_width: float | None = None
|
|
23
|
+
min_height: float | None = None
|
|
24
|
+
max_height: float | None = None
|
|
25
|
+
min_area: float | None = None
|
|
26
|
+
max_area: float | None = None
|
|
27
|
+
min_aspect_ratio: float | None = None
|
|
28
|
+
max_aspect_ratio: float | None = None
|
|
29
|
+
min_width_meters: float | None = None
|
|
30
|
+
max_width_meters: float | None = None
|
|
31
|
+
min_height_meters: float | None = None
|
|
32
|
+
max_height_meters: float | None = None
|
|
33
|
+
min_area_m2: float | None = None
|
|
34
|
+
max_area_m2: float | None = None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class AnnotationCountFilter(BaseModel):
|
|
38
|
+
"""Annotation count filter options."""
|
|
39
|
+
|
|
40
|
+
min_annotations_per_image: int | None = None
|
|
41
|
+
max_annotations_per_image: int | None = None
|
|
42
|
+
count_only_categories: list[str] | None = None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class DatasetFilterRequest(BaseModel):
|
|
46
|
+
"""Request for POST /datasets/{id}/filter."""
|
|
47
|
+
|
|
48
|
+
category_filter: CategoryFilter | None = None
|
|
49
|
+
bbox_filter: BboxFilter | None = None
|
|
50
|
+
annotation_count_filter: AnnotationCountFilter | None = None
|
|
51
|
+
consolidate_labels: dict[str, str] | None = None
|
|
52
|
+
output_mode: str | None = None
|
|
53
|
+
new_dataset_name: str | None = None
|
|
54
|
+
new_dataset_description: str | None = None
|
|
55
|
+
dry_run: bool = False
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class FilterStatisticsModel(BaseModel):
|
|
59
|
+
"""Filter operation statistics."""
|
|
60
|
+
|
|
61
|
+
original_images: int = 0
|
|
62
|
+
original_annotations: int = 0
|
|
63
|
+
original_categories: int = 0
|
|
64
|
+
filtered_images: int = 0
|
|
65
|
+
filtered_annotations: int = 0
|
|
66
|
+
filtered_categories: int = 0
|
|
67
|
+
removed_by_filter: dict[str, int] = {}
|
|
68
|
+
category_distribution: dict[str, Any] = {}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class DatasetFilterResponse(BaseModel):
|
|
72
|
+
"""Response from POST /datasets/{id}/filter."""
|
|
73
|
+
|
|
74
|
+
success: bool = False
|
|
75
|
+
message: str | None = None
|
|
76
|
+
mode: str | None = None
|
|
77
|
+
coco_json: dict[str, Any] | None = None
|
|
78
|
+
new_dataset_id: str | None = None
|
|
79
|
+
new_dataset_name: str | None = None
|
|
80
|
+
statistics: FilterStatisticsModel | None = None
|
|
81
|
+
dry_run: bool = False
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""History and undo/redo models."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from uuid import UUID
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class HistoryEntryModel(BaseModel):
|
|
12
|
+
"""A single change in the dataset history."""
|
|
13
|
+
|
|
14
|
+
change_id: UUID | None = None
|
|
15
|
+
sequence_number: int | None = None
|
|
16
|
+
operation_type: str | None = None
|
|
17
|
+
short_description: str | None = None
|
|
18
|
+
detailed_description: str | None = None
|
|
19
|
+
changed_on: datetime | None = None
|
|
20
|
+
changed_by: str | None = None
|
|
21
|
+
is_undone: bool = False
|
|
22
|
+
is_reversible: bool = True
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class HistoryModel(BaseModel):
|
|
26
|
+
"""Response from GET /datasets/{id}/history."""
|
|
27
|
+
|
|
28
|
+
dataset_id: UUID | None = None
|
|
29
|
+
current_position: int = 0
|
|
30
|
+
total_changes: int = 0
|
|
31
|
+
can_undo: bool = False
|
|
32
|
+
can_redo: bool = False
|
|
33
|
+
changes: list[HistoryEntryModel] = []
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class PreviewModel(BaseModel):
|
|
37
|
+
"""Response from GET /datasets/{id}/history/preview-undo or preview-redo."""
|
|
38
|
+
|
|
39
|
+
can_perform: bool = False
|
|
40
|
+
description: str | None = None
|
|
41
|
+
operation_type: str | None = None
|
|
42
|
+
changed_on: datetime | None = None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class UndoRedoResponse(BaseModel):
|
|
46
|
+
"""Response from POST /datasets/{id}/history/undo or redo."""
|
|
47
|
+
|
|
48
|
+
success: bool = False
|
|
49
|
+
description: str | None = None
|
|
50
|
+
new_position: int = 0
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class RewindResponse(BaseModel):
|
|
54
|
+
"""Response from POST /datasets/{id}/history/rewind."""
|
|
55
|
+
|
|
56
|
+
success: bool = False
|
|
57
|
+
operations_applied: int = 0
|
|
58
|
+
new_position: int = 0
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Image-related models."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from uuid import UUID
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ImageModel(BaseModel):
|
|
12
|
+
"""Core image metadata."""
|
|
13
|
+
|
|
14
|
+
image_id: UUID
|
|
15
|
+
file_name: str | None = None
|
|
16
|
+
hash: str | None = None
|
|
17
|
+
width: int = 0
|
|
18
|
+
height: int = 0
|
|
19
|
+
file_type: str | None = None
|
|
20
|
+
source_info: str | None = None
|
|
21
|
+
path: str | None = None
|
|
22
|
+
uri: str | None = None
|
|
23
|
+
created_on: datetime | None = None
|
|
24
|
+
coco_id: int | None = None
|
|
25
|
+
storage_connection_id: UUID | None = None
|
|
26
|
+
full_tile_id: str | None = None
|
|
27
|
+
tile_spec: str | None = None
|
|
28
|
+
gsd_meters: float | None = None
|
|
29
|
+
is_synthetic: bool | None = None
|
|
30
|
+
mask_uri: str | None = None
|
|
31
|
+
mask_format: str | None = None
|
|
32
|
+
band_count: int | None = None
|
|
33
|
+
band_names: str | None = None
|
|
34
|
+
pixel_data_type: str | None = None
|
|
35
|
+
preview_uri: str | None = None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class DatasetImageModel(BaseModel):
|
|
39
|
+
"""Image as part of a dataset split."""
|
|
40
|
+
|
|
41
|
+
dataset_image_id: UUID | None = None
|
|
42
|
+
split_id: UUID | None = None
|
|
43
|
+
image: ImageModel | None = None
|
|
44
|
+
added_on: datetime | None = None
|
|
45
|
+
thumb_url: str | None = None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class SyntheticStatsModel(BaseModel):
|
|
49
|
+
"""Response from GET /datasets/{id}/images/synthetic/stats."""
|
|
50
|
+
|
|
51
|
+
total_images: int = 0
|
|
52
|
+
real_images: int = 0
|
|
53
|
+
synthetic_images: int = 0
|
|
54
|
+
synthetic_ratio: float = 0.0
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class RemoveEmptyImagesResponse(BaseModel):
|
|
58
|
+
"""Response from DELETE /datasets/{id}/images/empty."""
|
|
59
|
+
|
|
60
|
+
success: bool = False
|
|
61
|
+
message: str | None = None
|
|
62
|
+
images_removed: int = 0
|
|
63
|
+
removed_images: list[dict[str, object]] = []
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class MarkSyntheticResponse(BaseModel):
|
|
67
|
+
"""Response from PUT /datasets/{id}/images/synthetic."""
|
|
68
|
+
|
|
69
|
+
success: bool = False
|
|
70
|
+
message: str | None = None
|
|
71
|
+
images_updated: int = 0
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class PaginatedImageModel(BaseModel):
|
|
75
|
+
"""An image in the paginated dataset-level image listing."""
|
|
76
|
+
|
|
77
|
+
dataset_image_id: UUID | None = None
|
|
78
|
+
image: ImageModel | None = None
|
|
79
|
+
added_on: datetime | None = None
|
|
80
|
+
thumb_url: str | None = None
|
|
81
|
+
mask_uri: str | None = None
|
|
82
|
+
mask_format: str | None = None
|
|
83
|
+
mask_version: int | None = None
|
|
84
|
+
annotation_count: int = 0
|
|
85
|
+
tags: list[str] = []
|
|
86
|
+
file_size: int | None = None
|
|
87
|
+
cloud_cover: float | None = None
|
|
88
|
+
off_nadir_angle: float | None = None
|
|
89
|
+
sensor_type: str | None = None
|
|
90
|
+
provider: str | None = None
|
|
91
|
+
constellation: str | None = None
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""Import-related models."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from typing import Any
|
|
7
|
+
from uuid import UUID
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ImportQueuedModel(BaseModel):
|
|
13
|
+
"""Response from POST /datasets/{id}/splits/{sid}/import/{source} (202)."""
|
|
14
|
+
|
|
15
|
+
split_id: UUID | None = None
|
|
16
|
+
status: str | None = None
|
|
17
|
+
progress: int = 0
|
|
18
|
+
source: str | None = None
|
|
19
|
+
poll_url: str | None = None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ImportStatusModel(BaseModel):
|
|
23
|
+
"""Response from GET /datasets/{id}/splits/{sid}/import/status."""
|
|
24
|
+
|
|
25
|
+
split_id: UUID | None = None
|
|
26
|
+
status: str | None = None
|
|
27
|
+
progress: int = 0
|
|
28
|
+
error: str | None = None
|
|
29
|
+
started_at: datetime | None = None
|
|
30
|
+
finished_at: datetime | None = None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ValidationIssueModel(BaseModel):
|
|
34
|
+
"""A single validation issue from pre-import validation."""
|
|
35
|
+
|
|
36
|
+
check: str | None = None
|
|
37
|
+
severity: str | None = None
|
|
38
|
+
message: str | None = None
|
|
39
|
+
entity_type: str | None = None
|
|
40
|
+
entity_id: str | None = None
|
|
41
|
+
details: dict[str, Any] | None = None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ImportConflictModel(BaseModel):
|
|
45
|
+
"""A conflict found during pre-import validation."""
|
|
46
|
+
|
|
47
|
+
type: str | None = None
|
|
48
|
+
severity: str | None = None
|
|
49
|
+
message: str | None = None
|
|
50
|
+
details: dict[str, Any] | None = None
|
|
51
|
+
options: list[str] = []
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class PreImportSummaryModel(BaseModel):
|
|
55
|
+
"""Summary statistics from pre-import validation."""
|
|
56
|
+
|
|
57
|
+
total_images: int = 0
|
|
58
|
+
total_annotations: int = 0
|
|
59
|
+
total_categories: int = 0
|
|
60
|
+
empty_images: int = 0
|
|
61
|
+
small_annotations: int = 0
|
|
62
|
+
orphan_annotations: int = 0
|
|
63
|
+
duplicate_annotations: int = 0
|
|
64
|
+
unused_categories: int = 0
|
|
65
|
+
invalid_bbox_count: int = 0
|
|
66
|
+
zero_area_bbox_count: int = 0
|
|
67
|
+
fixes_to_apply: list[str] = []
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class PreImportValidationModel(BaseModel):
|
|
71
|
+
"""Response from GET /datasets/{id}/splits/{sid}/import/validate/{vid}."""
|
|
72
|
+
|
|
73
|
+
validation_id: str | None = None
|
|
74
|
+
status: str | None = None
|
|
75
|
+
progress: int = 0
|
|
76
|
+
can_proceed: bool = False
|
|
77
|
+
error_count: int = 0
|
|
78
|
+
warning_count: int = 0
|
|
79
|
+
conflict_count: int = 0
|
|
80
|
+
issues: list[ValidationIssueModel] = []
|
|
81
|
+
conflicts: list[ImportConflictModel] = []
|
|
82
|
+
summary: PreImportSummaryModel | None = None
|
|
83
|
+
started_at: datetime | None = None
|
|
84
|
+
finished_at: datetime | None = None
|
|
85
|
+
error_message: str | None = None
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class ValidationStartedModel(BaseModel):
|
|
89
|
+
"""Response from POST /datasets/{id}/splits/{sid}/import/s3/validate (202)."""
|
|
90
|
+
|
|
91
|
+
validation_id: str | None = None
|
|
92
|
+
status: str | None = None
|
|
93
|
+
progress: int = 0
|
|
94
|
+
poll_url: str | None = None
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# ── Dataset-level import job models ──────────────────────────────────────
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class ImportLogEntryModel(BaseModel):
|
|
101
|
+
"""A single log entry from an import job."""
|
|
102
|
+
|
|
103
|
+
ts: datetime | None = None
|
|
104
|
+
level: str | None = None
|
|
105
|
+
message: str | None = None
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class ImportJobModel(BaseModel):
|
|
109
|
+
"""A dataset-level import job."""
|
|
110
|
+
|
|
111
|
+
job_id: UUID | None = None
|
|
112
|
+
dataset_id: UUID | None = None
|
|
113
|
+
split_id: UUID | None = None
|
|
114
|
+
source: str | None = None
|
|
115
|
+
dataset_type: str | None = None
|
|
116
|
+
status: str | None = None
|
|
117
|
+
progress: int = 0
|
|
118
|
+
error_summary: str | None = None
|
|
119
|
+
import_log: list[ImportLogEntryModel] | None = None
|
|
120
|
+
images_imported: int | None = None
|
|
121
|
+
source_details: dict[str, Any] | None = None
|
|
122
|
+
canceled: bool = False
|
|
123
|
+
created_at: datetime | None = None
|
|
124
|
+
started_at: datetime | None = None
|
|
125
|
+
finished_at: datetime | None = None
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class ImportJobListModel(BaseModel):
|
|
129
|
+
"""Response from GET /datasets/{id}/imports."""
|
|
130
|
+
|
|
131
|
+
jobs: list[ImportJobModel] = []
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class ImportJobQueuedModel(BaseModel):
|
|
135
|
+
"""Response from POST /datasets/{id}/imports/{jobId}/retry (202)."""
|
|
136
|
+
|
|
137
|
+
job_id: UUID | None = None
|
|
138
|
+
split_id: UUID | None = None
|
|
139
|
+
status: str | None = None
|
|
140
|
+
source: str | None = None
|
|
141
|
+
poll_url: str | None = None
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Media job models."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from uuid import UUID
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class MediaJobErrorModel(BaseModel):
|
|
12
|
+
"""A single error from a media processing job."""
|
|
13
|
+
|
|
14
|
+
job_id: UUID | None = None
|
|
15
|
+
image_id: UUID | None = None
|
|
16
|
+
image_name: str | None = None
|
|
17
|
+
error: str | None = None
|
|
18
|
+
updated_at: datetime | None = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class MediaJobStatusModel(BaseModel):
|
|
22
|
+
"""Aggregate status of media processing jobs for a dataset."""
|
|
23
|
+
|
|
24
|
+
total: int = 0
|
|
25
|
+
queued: int = 0
|
|
26
|
+
processing: int = 0
|
|
27
|
+
done: int = 0
|
|
28
|
+
error: int = 0
|
|
29
|
+
recent_errors: list[MediaJobErrorModel] = []
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class MediaJobActionModel(BaseModel):
|
|
33
|
+
"""Response from a media job action (retry, cancel)."""
|
|
34
|
+
|
|
35
|
+
action: str | None = None
|
|
36
|
+
count: int = 0
|
|
37
|
+
message: str | None = None
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Dataset merge models."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MergeDatasetRequest(BaseModel):
|
|
11
|
+
"""Request for POST /datasets/merge."""
|
|
12
|
+
|
|
13
|
+
source_dataset_ids: list[str]
|
|
14
|
+
target_name: str
|
|
15
|
+
target_description: str | None = None
|
|
16
|
+
include_categories: list[str] | None = None
|
|
17
|
+
keep_images_with_no_annotations: bool = True
|
|
18
|
+
conflict_strategy: str | None = None
|
|
19
|
+
category_mapping: dict[str, str] | None = None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class MergePreviewModel(BaseModel):
|
|
23
|
+
"""Response from POST /datasets/merge/preview."""
|
|
24
|
+
|
|
25
|
+
total_images: int = 0
|
|
26
|
+
total_annotations: int = 0
|
|
27
|
+
total_categories: int = 0
|
|
28
|
+
merged_categories: list[str] = []
|
|
29
|
+
warnings: list[str] = []
|
|
30
|
+
source_contributions: dict[str, Any] = {}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class MergeAnalysisModel(BaseModel):
|
|
34
|
+
"""Response from POST /datasets/merge/analyze."""
|
|
35
|
+
|
|
36
|
+
all_categories: list[dict[str, Any]] = []
|
|
37
|
+
conflicts: list[dict[str, Any]] = []
|
|
38
|
+
suggested_mapping: dict[str, str] = {}
|
|
39
|
+
has_conflicts: bool = False
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class MergeResponseModel(BaseModel):
|
|
43
|
+
"""Response from POST /datasets/merge."""
|
|
44
|
+
|
|
45
|
+
new_dataset_id: str | None = None
|
|
46
|
+
new_dataset_name: str | None = None
|
|
47
|
+
status: str | None = None
|
|
48
|
+
images_created: int = 0
|
|
49
|
+
annotations_created: int = 0
|
|
50
|
+
categories_created: int = 0
|
|
51
|
+
category_id_mapping: dict[str, int] = {}
|