datamint 1.4.1__tar.gz → 1.5.1__tar.gz
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-1.4.1 → datamint-1.5.1}/PKG-INFO +1 -1
- {datamint-1.4.1 → datamint-1.5.1}/datamint/apihandler/annotation_api_handler.py +246 -66
- {datamint-1.4.1 → datamint-1.5.1}/datamint/apihandler/root_api_handler.py +227 -101
- datamint-1.5.1/datamint/client_cmd_tools/datamint_config.py +211 -0
- {datamint-1.4.1 → datamint-1.5.1}/datamint/client_cmd_tools/datamint_upload.py +26 -18
- {datamint-1.4.1 → datamint-1.5.1}/datamint/utils/dicom_utils.py +12 -12
- {datamint-1.4.1 → datamint-1.5.1}/pyproject.toml +1 -1
- datamint-1.4.1/datamint/client_cmd_tools/datamint_config.py +0 -168
- {datamint-1.4.1 → datamint-1.5.1}/README.md +0 -0
- {datamint-1.4.1 → datamint-1.5.1}/datamint/__init__.py +0 -0
- {datamint-1.4.1 → datamint-1.5.1}/datamint/apihandler/api_handler.py +0 -0
- {datamint-1.4.1 → datamint-1.5.1}/datamint/apihandler/base_api_handler.py +0 -0
- {datamint-1.4.1 → datamint-1.5.1}/datamint/apihandler/dto/annotation_dto.py +0 -0
- {datamint-1.4.1 → datamint-1.5.1}/datamint/apihandler/exp_api_handler.py +0 -0
- {datamint-1.4.1 → datamint-1.5.1}/datamint/client_cmd_tools/__init__.py +0 -0
- {datamint-1.4.1 → datamint-1.5.1}/datamint/configs.py +0 -0
- {datamint-1.4.1 → datamint-1.5.1}/datamint/dataset/__init__.py +0 -0
- {datamint-1.4.1 → datamint-1.5.1}/datamint/dataset/base_dataset.py +0 -0
- {datamint-1.4.1 → datamint-1.5.1}/datamint/dataset/dataset.py +0 -0
- {datamint-1.4.1 → datamint-1.5.1}/datamint/examples/__init__.py +0 -0
- {datamint-1.4.1 → datamint-1.5.1}/datamint/examples/example_projects.py +0 -0
- {datamint-1.4.1 → datamint-1.5.1}/datamint/experiment/__init__.py +0 -0
- {datamint-1.4.1 → datamint-1.5.1}/datamint/experiment/_patcher.py +0 -0
- {datamint-1.4.1 → datamint-1.5.1}/datamint/experiment/experiment.py +0 -0
- {datamint-1.4.1 → datamint-1.5.1}/datamint/logging.yaml +0 -0
- {datamint-1.4.1 → datamint-1.5.1}/datamint/utils/io_utils.py +0 -0
- {datamint-1.4.1 → datamint-1.5.1}/datamint/utils/logging_utils.py +0 -0
- {datamint-1.4.1 → datamint-1.5.1}/datamint/utils/torchmetrics.py +0 -0
- {datamint-1.4.1 → datamint-1.5.1}/datamint/utils/visualization.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: datamint
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5.1
|
|
4
4
|
Summary: A library for interacting with the Datamint API, designed for efficient data management, processing and Deep Learning workflows.
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -13,6 +13,7 @@ from requests.exceptions import HTTPError
|
|
|
13
13
|
from deprecated.sphinx import deprecated
|
|
14
14
|
from .dto.annotation_dto import CreateAnnotationDto, LineGeometry, BoxGeometry, CoordinateSystem, AnnotationType
|
|
15
15
|
import pydicom
|
|
16
|
+
import json
|
|
16
17
|
|
|
17
18
|
_LOGGER = logging.getLogger(__name__)
|
|
18
19
|
_USER_LOGGER = logging.getLogger('user_logger')
|
|
@@ -88,41 +89,39 @@ class AnnotationAPIHandler(BaseAPIHandler):
|
|
|
88
89
|
raise DatamintException(r['error'])
|
|
89
90
|
return resp
|
|
90
91
|
|
|
91
|
-
async def
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
model_id=model_id)
|
|
120
|
-
annotids.extend(reti)
|
|
121
|
-
return annotids
|
|
92
|
+
async def _upload_single_frame_segmentation_async(self,
|
|
93
|
+
resource_id: str,
|
|
94
|
+
frame_index: int,
|
|
95
|
+
fio: IO,
|
|
96
|
+
name: Optional[str | dict[int, str]] = None,
|
|
97
|
+
imported_from: Optional[str] = None,
|
|
98
|
+
author_email: Optional[str] = None,
|
|
99
|
+
discard_empty_segmentations: bool = True,
|
|
100
|
+
worklist_id: Optional[str] = None,
|
|
101
|
+
model_id: Optional[str] = None
|
|
102
|
+
) -> list[str]:
|
|
103
|
+
"""
|
|
104
|
+
Upload a single frame segmentation asynchronously.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
resource_id: The resource unique id.
|
|
108
|
+
frame_index: The frame index for the segmentation.
|
|
109
|
+
fio: File-like object containing the segmentation image.
|
|
110
|
+
name: The name of the segmentation or a dictionary mapping pixel values to names.
|
|
111
|
+
imported_from: The imported from value.
|
|
112
|
+
author_email: The author email.
|
|
113
|
+
discard_empty_segmentations: Whether to discard empty segmentations.
|
|
114
|
+
worklist_id: The annotation worklist unique id.
|
|
115
|
+
model_id: The model unique id.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
List of annotation IDs created.
|
|
119
|
+
"""
|
|
122
120
|
try:
|
|
123
121
|
try:
|
|
124
122
|
img = np.array(Image.open(fio))
|
|
125
|
-
|
|
123
|
+
|
|
124
|
+
# Check that frame is not empty
|
|
126
125
|
uniq_vals = np.unique(img)
|
|
127
126
|
if discard_empty_segmentations:
|
|
128
127
|
if len(uniq_vals) == 1 and uniq_vals[0] == 0:
|
|
@@ -135,31 +134,38 @@ class AnnotationAPIHandler(BaseAPIHandler):
|
|
|
135
134
|
|
|
136
135
|
segnames = AnnotationAPIHandler._get_segmentation_names(uniq_vals, names=name)
|
|
137
136
|
segs_generator = AnnotationAPIHandler._split_segmentations(img, uniq_vals, fio)
|
|
137
|
+
|
|
138
|
+
# Create annotations
|
|
138
139
|
annotations: list[CreateAnnotationDto] = []
|
|
139
140
|
for segname in segnames:
|
|
140
|
-
ann = CreateAnnotationDto(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
141
|
+
ann = CreateAnnotationDto(
|
|
142
|
+
type='segmentation',
|
|
143
|
+
identifier=segname,
|
|
144
|
+
scope='frame',
|
|
145
|
+
frame_index=frame_index,
|
|
146
|
+
imported_from=imported_from,
|
|
147
|
+
import_author=author_email,
|
|
148
|
+
model_id=model_id,
|
|
149
|
+
annotation_worklist_id=worklist_id
|
|
150
|
+
)
|
|
148
151
|
annotations.append(ann)
|
|
149
|
-
|
|
152
|
+
|
|
153
|
+
# Validate unique identifiers
|
|
150
154
|
if len(annotations) != len(set([a.identifier for a in annotations])):
|
|
151
155
|
raise ValueError(
|
|
152
|
-
"Multiple annotations with the same identifier, frame_index, scope and author is not supported yet."
|
|
156
|
+
"Multiple annotations with the same identifier, frame_index, scope and author is not supported yet."
|
|
157
|
+
)
|
|
153
158
|
|
|
154
159
|
annotids = await self._upload_annotations_async(resource_id, annotations)
|
|
155
160
|
|
|
156
|
-
|
|
161
|
+
# Upload segmentation files
|
|
157
162
|
if len(annotids) != len(segnames):
|
|
158
163
|
_LOGGER.warning(f"Number of uploaded annotations ({len(annotids)})" +
|
|
159
164
|
f" does not match the number of annotations ({len(segnames)})")
|
|
160
|
-
|
|
165
|
+
|
|
166
|
+
for annotid, segname, fio_seg in zip(annotids, segnames, segs_generator):
|
|
161
167
|
form = aiohttp.FormData()
|
|
162
|
-
form.add_field('file',
|
|
168
|
+
form.add_field('file', fio_seg, filename=segname, content_type='image/png')
|
|
163
169
|
request_params = dict(
|
|
164
170
|
method='POST',
|
|
165
171
|
url=f'{self.root_url}/annotations/{resource_id}/annotations/{annotid}/file',
|
|
@@ -168,19 +174,182 @@ class AnnotationAPIHandler(BaseAPIHandler):
|
|
|
168
174
|
resp = await self._run_request_async(request_params)
|
|
169
175
|
if 'error' in resp:
|
|
170
176
|
raise DatamintException(resp['error'])
|
|
171
|
-
|
|
177
|
+
|
|
178
|
+
return annotids
|
|
172
179
|
finally:
|
|
173
180
|
fio.close()
|
|
174
|
-
_USER_LOGGER.info(f'Segmentations uploaded for resource {resource_id}')
|
|
175
|
-
return annotids
|
|
176
181
|
except ResourceNotFoundError:
|
|
177
182
|
raise ResourceNotFoundError('resource', {'resource_id': resource_id})
|
|
178
183
|
|
|
184
|
+
async def _upload_volume_segmentation_async(self,
|
|
185
|
+
resource_id: str,
|
|
186
|
+
file_path: str | np.ndarray,
|
|
187
|
+
name: str | dict[int, str] | None = None,
|
|
188
|
+
imported_from: Optional[str] = None,
|
|
189
|
+
author_email: Optional[str] = None,
|
|
190
|
+
worklist_id: Optional[str] = None,
|
|
191
|
+
model_id: Optional[str] = None,
|
|
192
|
+
transpose_segmentation: bool = False
|
|
193
|
+
) -> list[str]:
|
|
194
|
+
"""
|
|
195
|
+
Upload a volume segmentation as a single file asynchronously.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
resource_id: The resource unique id.
|
|
199
|
+
file_path: Path to segmentation file or numpy array.
|
|
200
|
+
name: The name of the segmentation (string only for volumes).
|
|
201
|
+
imported_from: The imported from value.
|
|
202
|
+
author_email: The author email.
|
|
203
|
+
worklist_id: The annotation worklist unique id.
|
|
204
|
+
model_id: The model unique id.
|
|
205
|
+
transpose_segmentation: Whether to transpose the segmentation.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
List of annotation IDs created.
|
|
209
|
+
|
|
210
|
+
Raises:
|
|
211
|
+
ValueError: If name is not a string or file format is unsupported for volume upload.
|
|
212
|
+
"""
|
|
213
|
+
if name is None:
|
|
214
|
+
name = 'volume_segmentation'
|
|
215
|
+
|
|
216
|
+
# Prepare file for upload
|
|
217
|
+
if isinstance(file_path, str):
|
|
218
|
+
if file_path.endswith('.nii') or file_path.endswith('.nii.gz'):
|
|
219
|
+
# Upload NIfTI file directly
|
|
220
|
+
with open(file_path, 'rb') as f:
|
|
221
|
+
filename = os.path.basename(file_path)
|
|
222
|
+
form = aiohttp.FormData()
|
|
223
|
+
form.add_field('file', f, filename=filename, content_type='application/x-nifti')
|
|
224
|
+
model_id = 'c9daf156-5335-4cb3-b374-5b3a776e0025'
|
|
225
|
+
if model_id is not None:
|
|
226
|
+
form.add_field('model_id', model_id) # Add model_id if provided
|
|
227
|
+
if worklist_id is not None:
|
|
228
|
+
form.add_field('annotation_worklist_id', worklist_id)
|
|
229
|
+
form.add_field('segmentation_map', json.dumps(name), content_type='application/json')
|
|
230
|
+
|
|
231
|
+
request_params = dict(
|
|
232
|
+
method='POST',
|
|
233
|
+
url=f'{self.root_url}/annotations/{resource_id}/segmentations/file',
|
|
234
|
+
data=form,
|
|
235
|
+
)
|
|
236
|
+
resp = await self._run_request_async(request_params)
|
|
237
|
+
if 'error' in resp:
|
|
238
|
+
raise DatamintException(resp['error'])
|
|
239
|
+
return resp
|
|
240
|
+
else:
|
|
241
|
+
raise ValueError(f"Volume upload not supported for file format: {file_path}")
|
|
242
|
+
elif isinstance(file_path, np.ndarray):
|
|
243
|
+
raise NotImplementedError
|
|
244
|
+
else:
|
|
245
|
+
raise ValueError(f"Unsupported file_path type for volume upload: {type(file_path)}")
|
|
246
|
+
|
|
247
|
+
_USER_LOGGER.info(f'Volume segmentation uploaded for resource {resource_id}')
|
|
248
|
+
|
|
249
|
+
async def _upload_segmentations_async(self,
|
|
250
|
+
resource_id: str,
|
|
251
|
+
frame_index: int | None,
|
|
252
|
+
file_path: str | np.ndarray | None = None,
|
|
253
|
+
fio: IO | None = None,
|
|
254
|
+
name: Optional[str | dict[int, str]] = None,
|
|
255
|
+
imported_from: Optional[str] = None,
|
|
256
|
+
author_email: Optional[str] = None,
|
|
257
|
+
discard_empty_segmentations: bool = True,
|
|
258
|
+
worklist_id: Optional[str] = None,
|
|
259
|
+
model_id: Optional[str] = None,
|
|
260
|
+
transpose_segmentation: bool = False,
|
|
261
|
+
upload_volume: bool | str = 'auto'
|
|
262
|
+
) -> list[str]:
|
|
263
|
+
"""
|
|
264
|
+
Upload segmentations asynchronously.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
resource_id: The resource unique id.
|
|
268
|
+
frame_index: The frame index or None for multiple frames.
|
|
269
|
+
file_path: Path to segmentation file or numpy array.
|
|
270
|
+
fio: File-like object containing segmentation data.
|
|
271
|
+
name: The name of the segmentation or mapping of pixel values to names.
|
|
272
|
+
imported_from: The imported from value.
|
|
273
|
+
author_email: The author email.
|
|
274
|
+
discard_empty_segmentations: Whether to discard empty segmentations.
|
|
275
|
+
worklist_id: The annotation worklist unique id.
|
|
276
|
+
model_id: The model unique id.
|
|
277
|
+
transpose_segmentation: Whether to transpose the segmentation.
|
|
278
|
+
upload_volume: Whether to upload the volume as a single file or split into frames.
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
List of annotation IDs created.
|
|
282
|
+
"""
|
|
283
|
+
if upload_volume == 'auto':
|
|
284
|
+
if file_path is not None and (file_path.endswith('.nii') or file_path.endswith('.nii.gz')):
|
|
285
|
+
upload_volume = True
|
|
286
|
+
else:
|
|
287
|
+
upload_volume = False
|
|
288
|
+
|
|
289
|
+
if file_path is not None:
|
|
290
|
+
# Handle volume upload
|
|
291
|
+
if upload_volume:
|
|
292
|
+
if frame_index is not None:
|
|
293
|
+
_LOGGER.warning("frame_index parameter ignored when upload_volume=True")
|
|
294
|
+
|
|
295
|
+
return await self._upload_volume_segmentation_async(
|
|
296
|
+
resource_id=resource_id,
|
|
297
|
+
file_path=file_path,
|
|
298
|
+
name=name,
|
|
299
|
+
imported_from=imported_from,
|
|
300
|
+
author_email=author_email,
|
|
301
|
+
worklist_id=worklist_id,
|
|
302
|
+
model_id=model_id,
|
|
303
|
+
transpose_segmentation=transpose_segmentation
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
# Handle frame-by-frame upload (existing logic)
|
|
307
|
+
nframes, fios = AnnotationAPIHandler._generate_segmentations_ios(
|
|
308
|
+
file_path, transpose_segmentation=transpose_segmentation
|
|
309
|
+
)
|
|
310
|
+
if frame_index is None:
|
|
311
|
+
frame_index = list(range(nframes))
|
|
312
|
+
|
|
313
|
+
annotids = []
|
|
314
|
+
for fidx, f in zip(frame_index, fios):
|
|
315
|
+
frame_annotids = await self._upload_single_frame_segmentation_async(
|
|
316
|
+
resource_id=resource_id,
|
|
317
|
+
frame_index=fidx,
|
|
318
|
+
fio=f,
|
|
319
|
+
name=name,
|
|
320
|
+
imported_from=imported_from,
|
|
321
|
+
author_email=author_email,
|
|
322
|
+
discard_empty_segmentations=discard_empty_segmentations,
|
|
323
|
+
worklist_id=worklist_id,
|
|
324
|
+
model_id=model_id
|
|
325
|
+
)
|
|
326
|
+
annotids.extend(frame_annotids)
|
|
327
|
+
return annotids
|
|
328
|
+
|
|
329
|
+
# Handle single file-like object
|
|
330
|
+
if fio is not None:
|
|
331
|
+
if upload_volume:
|
|
332
|
+
raise ValueError("upload_volume=True is not supported when providing fio parameter")
|
|
333
|
+
|
|
334
|
+
return await self._upload_single_frame_segmentation_async(
|
|
335
|
+
resource_id=resource_id,
|
|
336
|
+
frame_index=frame_index,
|
|
337
|
+
fio=fio,
|
|
338
|
+
name=name,
|
|
339
|
+
imported_from=imported_from,
|
|
340
|
+
author_email=author_email,
|
|
341
|
+
discard_empty_segmentations=discard_empty_segmentations,
|
|
342
|
+
worklist_id=worklist_id,
|
|
343
|
+
model_id=model_id
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
raise ValueError("Either file_path or fio must be provided")
|
|
347
|
+
|
|
179
348
|
def upload_segmentations(self,
|
|
180
349
|
resource_id: str,
|
|
181
350
|
file_path: str | np.ndarray,
|
|
182
351
|
name: Optional[str | dict[int, str]] = None,
|
|
183
|
-
frame_index: int | list[int] = None,
|
|
352
|
+
frame_index: int | list[int] | None = None,
|
|
184
353
|
imported_from: Optional[str] = None,
|
|
185
354
|
author_email: Optional[str] = None,
|
|
186
355
|
discard_empty_segmentations: bool = True,
|
|
@@ -195,13 +364,15 @@ class AnnotationAPIHandler(BaseAPIHandler):
|
|
|
195
364
|
resource_id (str): The resource unique id.
|
|
196
365
|
file_path (str|np.ndarray): The path to the segmentation file or a numpy array.
|
|
197
366
|
If a numpy array is provided, it must have the shape (height, width, #frames) or (height, width).
|
|
367
|
+
For NIfTI files (.nii/.nii.gz), the entire volume is uploaded as a single segmentation.
|
|
198
368
|
name (Optional[Union[str, Dict[int, str]]]): The name of the segmentation or a dictionary mapping pixel values to names.
|
|
199
|
-
example: {1: 'Femur', 2: 'Tibia'}.
|
|
369
|
+
example: {1: 'Femur', 2: 'Tibia'}. For NIfTI files, only string names are supported.
|
|
200
370
|
frame_index (int | list[int]): The frame index of the segmentation.
|
|
201
371
|
If a list, it must have the same length as the number of frames in the segmentation.
|
|
202
372
|
If None, it is assumed that the segmentations are in sequential order starting from 0.
|
|
203
|
-
|
|
373
|
+
This parameter is ignored for NIfTI files as they are treated as volume segmentations.
|
|
204
374
|
discard_empty_segmentations (bool): Whether to discard empty segmentations or not.
|
|
375
|
+
This is ignored for NIfTI files.
|
|
205
376
|
|
|
206
377
|
Returns:
|
|
207
378
|
str: The segmentation unique id.
|
|
@@ -211,9 +382,32 @@ class AnnotationAPIHandler(BaseAPIHandler):
|
|
|
211
382
|
|
|
212
383
|
Example:
|
|
213
384
|
>>> api_handler.upload_segmentation(resource_id, 'path/to/segmentation.png', 'SegmentationName')
|
|
385
|
+
>>> api_handler.upload_segmentation(resource_id, 'path/to/segmentation.nii.gz', 'VolumeSegmentation')
|
|
214
386
|
"""
|
|
215
387
|
if isinstance(file_path, str) and not os.path.exists(file_path):
|
|
216
388
|
raise FileNotFoundError(f"File {file_path} not found.")
|
|
389
|
+
|
|
390
|
+
# Handle NIfTI files specially - upload as single volume
|
|
391
|
+
if isinstance(file_path, str) and (file_path.endswith('.nii') or file_path.endswith('.nii.gz')):
|
|
392
|
+
_LOGGER.info(f"Uploading NIfTI segmentation file: {file_path}")
|
|
393
|
+
if frame_index is not None:
|
|
394
|
+
raise ValueError("Do not provide frame_index for NIfTI segmentations.")
|
|
395
|
+
loop = asyncio.get_event_loop()
|
|
396
|
+
task = self._upload_segmentations_async(
|
|
397
|
+
resource_id=resource_id,
|
|
398
|
+
frame_index=None,
|
|
399
|
+
file_path=file_path,
|
|
400
|
+
name=name,
|
|
401
|
+
imported_from=imported_from,
|
|
402
|
+
author_email=author_email,
|
|
403
|
+
discard_empty_segmentations=False,
|
|
404
|
+
worklist_id=worklist_id,
|
|
405
|
+
model_id=model_id,
|
|
406
|
+
transpose_segmentation=transpose_segmentation,
|
|
407
|
+
upload_volume=True
|
|
408
|
+
)
|
|
409
|
+
return loop.run_until_complete(task)
|
|
410
|
+
# All other file types are converted to multiple PNGs and uploaded frame by frame.
|
|
217
411
|
if isinstance(frame_index, int):
|
|
218
412
|
frame_index = [frame_index]
|
|
219
413
|
|
|
@@ -429,7 +623,7 @@ class AnnotationAPIHandler(BaseAPIHandler):
|
|
|
429
623
|
model_id: Optional[str] = None) -> list[str]:
|
|
430
624
|
"""
|
|
431
625
|
Common method for creating geometry-based annotations.
|
|
432
|
-
|
|
626
|
+
|
|
433
627
|
Args:
|
|
434
628
|
geometry: The geometry object (LineGeometry or BoxGeometry)
|
|
435
629
|
resource_id: The resource unique id
|
|
@@ -622,20 +816,6 @@ class AnnotationAPIHandler(BaseAPIHandler):
|
|
|
622
816
|
model_id=model_id
|
|
623
817
|
)
|
|
624
818
|
|
|
625
|
-
@deprecated(version='0.12.1', reason='Use :meth:`~get_annotations` instead with `resource_id` parameter.')
|
|
626
|
-
def get_resource_annotations(self,
|
|
627
|
-
resource_id: str,
|
|
628
|
-
annotation_type: Optional[str] = None,
|
|
629
|
-
annotator_email: Optional[str] = None,
|
|
630
|
-
date_from: Optional[date] = None,
|
|
631
|
-
date_to: Optional[date] = None) -> Generator[dict, None, None]:
|
|
632
|
-
|
|
633
|
-
return self.get_annotations(resource_id=resource_id,
|
|
634
|
-
annotation_type=annotation_type,
|
|
635
|
-
annotator_email=annotator_email,
|
|
636
|
-
date_from=date_from,
|
|
637
|
-
date_to=date_to)
|
|
638
|
-
|
|
639
819
|
def get_annotations(self,
|
|
640
820
|
resource_id: Optional[str] = None,
|
|
641
821
|
annotation_type: AnnotationType | str | None = None,
|