datamint 1.6.3.post1__py3-none-any.whl → 1.7.0__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 datamint might be problematic. Click here for more details.
- datamint/apihandler/annotation_api_handler.py +125 -3
- datamint/apihandler/base_api_handler.py +30 -26
- datamint/apihandler/root_api_handler.py +121 -35
- datamint/dataset/annotation.py +221 -0
- datamint/dataset/base_dataset.py +735 -483
- datamint/dataset/dataset.py +33 -16
- {datamint-1.6.3.post1.dist-info → datamint-1.7.0.dist-info}/METADATA +1 -1
- {datamint-1.6.3.post1.dist-info → datamint-1.7.0.dist-info}/RECORD +10 -9
- {datamint-1.6.3.post1.dist-info → datamint-1.7.0.dist-info}/WHEEL +0 -0
- {datamint-1.6.3.post1.dist-info → datamint-1.7.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from dataclasses import dataclass, field, asdict
|
|
3
|
+
from typing import Optional, Any, TYPE_CHECKING
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import logging
|
|
7
|
+
import numpy as np
|
|
8
|
+
from PIL import Image
|
|
9
|
+
import json
|
|
10
|
+
|
|
11
|
+
# if TYPE_CHECKING:
|
|
12
|
+
# from datamint.apihandler.annotation_api_handler import AnnotationAPIHandler
|
|
13
|
+
|
|
14
|
+
_LOGGER = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# Map API field names to class attributes
|
|
18
|
+
_FIELD_MAPPING = {
|
|
19
|
+
'type':'annotation_type',
|
|
20
|
+
'name': 'identifier',
|
|
21
|
+
'added_by': 'created_by',
|
|
22
|
+
'index': 'frame_index',
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class Annotation:
|
|
27
|
+
"""
|
|
28
|
+
Class representing an annotation from the Datamint API.
|
|
29
|
+
|
|
30
|
+
This class stores annotation data and provides methods for loading
|
|
31
|
+
and saving annotations through the API handler.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
id: Unique identifier for the annotation
|
|
35
|
+
identifier: The annotation identifier/label name
|
|
36
|
+
scope: Whether annotation applies to 'frame' or 'image'
|
|
37
|
+
annotation_type: Type of annotation ('segmentation', 'label', 'category', etc.)
|
|
38
|
+
resource_id: ID of the resource this annotation belongs to
|
|
39
|
+
annotation_worklist_id: ID of the annotation worklist
|
|
40
|
+
created_by: Email of the user who created the annotation
|
|
41
|
+
status: Status of the annotation ('published', 'new', etc.)
|
|
42
|
+
frame_index: Frame index for frame-scoped annotations
|
|
43
|
+
text_value: Text value for category annotations
|
|
44
|
+
numeric_value: Numeric value for numeric annotations
|
|
45
|
+
units: Units for numeric annotations
|
|
46
|
+
geometry: Geometry data for geometric annotations
|
|
47
|
+
created_at: When the annotation was created
|
|
48
|
+
approved_at: When the annotation was approved
|
|
49
|
+
approved_by: Who approved the annotation
|
|
50
|
+
associated_file: Path to associated file (for segmentations)
|
|
51
|
+
deleted: Whether the annotation is deleted
|
|
52
|
+
deleted_at: When the annotation was deleted
|
|
53
|
+
deleted_by: Who deleted the annotation
|
|
54
|
+
created_by_model: Model ID if created by AI
|
|
55
|
+
old_geometry: Previous geometry data
|
|
56
|
+
set_name: Set name for grouped annotations
|
|
57
|
+
resource_filename: Filename of the associated resource
|
|
58
|
+
resource_modality: Modality of the associated resource
|
|
59
|
+
annotation_worklist_name: Name of the annotation worklist
|
|
60
|
+
user_info: Information about the user who created the annotation
|
|
61
|
+
values: Additional values
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
id: str
|
|
65
|
+
identifier: str
|
|
66
|
+
scope: str
|
|
67
|
+
annotation_type: str
|
|
68
|
+
resource_id: str
|
|
69
|
+
created_by: str
|
|
70
|
+
annotation_worklist_id: Optional[str] = None
|
|
71
|
+
status: Optional[str] = None
|
|
72
|
+
frame_index: Optional[int] = None
|
|
73
|
+
text_value: Optional[str] = None
|
|
74
|
+
numeric_value: Optional[float] = None
|
|
75
|
+
units: Optional[str] = None
|
|
76
|
+
geometry: list[Any] = field(default_factory=list)
|
|
77
|
+
created_at: Optional[str] = None
|
|
78
|
+
approved_at: Optional[str] = None
|
|
79
|
+
approved_by: Optional[str] = None
|
|
80
|
+
associated_file: Optional[str] = None
|
|
81
|
+
file: Optional[str] = None
|
|
82
|
+
deleted: bool = False
|
|
83
|
+
deleted_at: Optional[str] = None
|
|
84
|
+
deleted_by: Optional[str] = None
|
|
85
|
+
created_by_model: Optional[str] = None
|
|
86
|
+
old_geometry: Optional[Any] = None
|
|
87
|
+
set_name: Optional[str] = None
|
|
88
|
+
resource_filename: Optional[str] = None
|
|
89
|
+
resource_modality: Optional[str] = None
|
|
90
|
+
annotation_worklist_name: Optional[str] = None
|
|
91
|
+
user_info: Optional[dict[str, str]] = None
|
|
92
|
+
values: Optional[Any] = None
|
|
93
|
+
|
|
94
|
+
@classmethod
|
|
95
|
+
def from_dict(cls, data: dict[str, Any]) -> Annotation:
|
|
96
|
+
"""
|
|
97
|
+
Create an Annotation instance from a dictionary.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
data: Dictionary containing annotation data from API
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Annotation instance
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# Convert field names and filter valid fields
|
|
108
|
+
converted_data = {}
|
|
109
|
+
for key, value in data.items():
|
|
110
|
+
# Map field names if needed
|
|
111
|
+
mapped_key = _FIELD_MAPPING.get(key, key)
|
|
112
|
+
converted_data[mapped_key] = value
|
|
113
|
+
|
|
114
|
+
if 'scope' not in converted_data:
|
|
115
|
+
converted_data['scope'] = 'image' if converted_data.get('frame_index') is None else 'frame'
|
|
116
|
+
|
|
117
|
+
if converted_data['annotation_type'] in ['segmentation']:
|
|
118
|
+
if converted_data.get('file') is None:
|
|
119
|
+
raise ValueError(f"Segmentation annotations must have an associated file. {data}")
|
|
120
|
+
|
|
121
|
+
# Create instance with only valid fields
|
|
122
|
+
valid_fields = {f.name for f in cls.__dataclass_fields__.values()}
|
|
123
|
+
filtered_data = {k: v for k, v in converted_data.items() if k in valid_fields}
|
|
124
|
+
|
|
125
|
+
return cls(**filtered_data)
|
|
126
|
+
|
|
127
|
+
def to_dict(self) -> dict[str, Any]:
|
|
128
|
+
"""
|
|
129
|
+
Convert the annotation to a dictionary format.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
Dictionary representation of the annotation
|
|
133
|
+
"""
|
|
134
|
+
result = {}
|
|
135
|
+
for key, value in self.__dict__.items():
|
|
136
|
+
# Handle special serialization cases
|
|
137
|
+
if isinstance(value, (np.ndarray, np.generic)):
|
|
138
|
+
value = value.tolist()
|
|
139
|
+
elif isinstance(value, datetime):
|
|
140
|
+
value = value.isoformat()
|
|
141
|
+
elif isinstance(value, Path):
|
|
142
|
+
value = str(value)
|
|
143
|
+
|
|
144
|
+
result[key] = value
|
|
145
|
+
if self.annotation_type == 'segmentation' and 'file' not in result:
|
|
146
|
+
raise ValueError(f"Segmentation annotations must have an associated file. {self}")
|
|
147
|
+
return result
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def name(self) -> str:
|
|
151
|
+
"""Get the annotation name (alias for identifier)."""
|
|
152
|
+
return self.identifier
|
|
153
|
+
|
|
154
|
+
@property
|
|
155
|
+
def type(self) -> str:
|
|
156
|
+
"""Get the annotation type."""
|
|
157
|
+
return self.annotation_type
|
|
158
|
+
|
|
159
|
+
@property
|
|
160
|
+
def value(self) -> Optional[str]:
|
|
161
|
+
"""Get the annotation value (for category annotations)."""
|
|
162
|
+
return self.text_value
|
|
163
|
+
|
|
164
|
+
@property
|
|
165
|
+
def index(self) -> Optional[int]:
|
|
166
|
+
"""Get the frame index (alias for frame_index)."""
|
|
167
|
+
return self.frame_index
|
|
168
|
+
|
|
169
|
+
@property
|
|
170
|
+
def added_by(self) -> str:
|
|
171
|
+
"""Get the creator email (alias for created_by)."""
|
|
172
|
+
return self.created_by
|
|
173
|
+
|
|
174
|
+
# @property
|
|
175
|
+
# def file(self) -> Optional[str]:
|
|
176
|
+
# """Get the associated file path."""
|
|
177
|
+
# return self.associated_file
|
|
178
|
+
|
|
179
|
+
# @file.setter
|
|
180
|
+
# def file(self, value: Optional[str]) -> None:
|
|
181
|
+
# """Set the associated file path."""
|
|
182
|
+
# self.associated_file = value
|
|
183
|
+
|
|
184
|
+
def is_segmentation(self) -> bool:
|
|
185
|
+
"""Check if this is a segmentation annotation."""
|
|
186
|
+
return self.annotation_type == 'segmentation'
|
|
187
|
+
|
|
188
|
+
def is_label(self) -> bool:
|
|
189
|
+
"""Check if this is a label annotation."""
|
|
190
|
+
return self.annotation_type == 'label'
|
|
191
|
+
|
|
192
|
+
def is_category(self) -> bool:
|
|
193
|
+
"""Check if this is a category annotation."""
|
|
194
|
+
return self.annotation_type == 'category'
|
|
195
|
+
|
|
196
|
+
def is_frame_scoped(self) -> bool:
|
|
197
|
+
"""Check if this annotation is frame-scoped."""
|
|
198
|
+
return self.scope == 'frame'
|
|
199
|
+
|
|
200
|
+
def is_image_scoped(self) -> bool:
|
|
201
|
+
"""Check if this annotation is image-scoped."""
|
|
202
|
+
return self.scope == 'image'
|
|
203
|
+
|
|
204
|
+
def get_created_datetime(self) -> Optional[datetime]:
|
|
205
|
+
"""
|
|
206
|
+
Get the creation datetime as a datetime object.
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
datetime object or None if created_at is not set
|
|
210
|
+
"""
|
|
211
|
+
if self.created_at:
|
|
212
|
+
try:
|
|
213
|
+
return datetime.fromisoformat(self.created_at.replace('Z', '+00:00'))
|
|
214
|
+
except ValueError:
|
|
215
|
+
_LOGGER.warning(f"Could not parse created_at datetime: {self.created_at}")
|
|
216
|
+
return None
|
|
217
|
+
|
|
218
|
+
def __repr__(self) -> str:
|
|
219
|
+
"""String representation of the annotation."""
|
|
220
|
+
return (f"Annotation(id='{self.id}', identifier='{self.identifier}', "
|
|
221
|
+
f"type='{self.annotation_type}', scope='{self.scope}', resource_id='{self.resource_id}')")
|