datamint 2.2.1__py3-none-any.whl → 2.3.1__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/api/client.py +13 -5
- datamint/api/endpoints/__init__.py +3 -1
- datamint/api/endpoints/annotations_api.py +51 -5
- datamint/api/endpoints/models_api.py +47 -0
- datamint/entities/project.py +3 -1
- datamint/exceptions.py +22 -1
- {datamint-2.2.1.dist-info → datamint-2.3.1.dist-info}/METADATA +1 -1
- {datamint-2.2.1.dist-info → datamint-2.3.1.dist-info}/RECORD +10 -9
- {datamint-2.2.1.dist-info → datamint-2.3.1.dist-info}/WHEEL +0 -0
- {datamint-2.2.1.dist-info → datamint-2.3.1.dist-info}/entry_points.txt +0 -0
datamint/api/client.py
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
from typing import Optional
|
|
2
|
-
import httpx
|
|
3
2
|
from .base_api import ApiConfig
|
|
4
|
-
from .endpoints import ProjectsApi, ResourcesApi, AnnotationsApi, ChannelsApi, UsersApi, DatasetsInfoApi
|
|
3
|
+
from .endpoints import ProjectsApi, ResourcesApi, AnnotationsApi, ChannelsApi, UsersApi, DatasetsInfoApi, ModelsApi
|
|
5
4
|
import datamint.configs
|
|
6
5
|
from datamint.exceptions import DatamintException
|
|
7
|
-
import asyncio
|
|
8
6
|
|
|
9
7
|
|
|
10
8
|
class Api:
|
|
@@ -18,7 +16,8 @@ class Api:
|
|
|
18
16
|
'annotations': AnnotationsApi,
|
|
19
17
|
'channels': ChannelsApi,
|
|
20
18
|
'users': UsersApi,
|
|
21
|
-
'datasets': DatasetsInfoApi
|
|
19
|
+
'datasets': DatasetsInfoApi,
|
|
20
|
+
'models': ModelsApi
|
|
22
21
|
}
|
|
23
22
|
|
|
24
23
|
def __init__(self,
|
|
@@ -73,19 +72,28 @@ class Api:
|
|
|
73
72
|
@property
|
|
74
73
|
def projects(self) -> ProjectsApi:
|
|
75
74
|
return self._get_endpoint('projects')
|
|
75
|
+
|
|
76
76
|
@property
|
|
77
77
|
def resources(self) -> ResourcesApi:
|
|
78
78
|
return self._get_endpoint('resources')
|
|
79
|
+
|
|
79
80
|
@property
|
|
80
81
|
def annotations(self) -> AnnotationsApi:
|
|
81
82
|
return self._get_endpoint('annotations')
|
|
83
|
+
|
|
82
84
|
@property
|
|
83
85
|
def channels(self) -> ChannelsApi:
|
|
84
86
|
return self._get_endpoint('channels')
|
|
87
|
+
|
|
85
88
|
@property
|
|
86
89
|
def users(self) -> UsersApi:
|
|
87
90
|
return self._get_endpoint('users')
|
|
91
|
+
|
|
88
92
|
@property
|
|
89
93
|
def _datasetsinfo(self) -> DatasetsInfoApi:
|
|
90
94
|
"""Internal property to access DatasetsInfoApi."""
|
|
91
|
-
return self._get_endpoint('datasets')
|
|
95
|
+
return self._get_endpoint('datasets')
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def models(self) -> ModelsApi:
|
|
99
|
+
return self._get_endpoint('models')
|
|
@@ -6,6 +6,7 @@ from .projects_api import ProjectsApi
|
|
|
6
6
|
from .resources_api import ResourcesApi
|
|
7
7
|
from .users_api import UsersApi
|
|
8
8
|
from .datasetsinfo_api import DatasetsInfoApi
|
|
9
|
+
from .models_api import ModelsApi
|
|
9
10
|
|
|
10
11
|
__all__ = [
|
|
11
12
|
'AnnotationsApi',
|
|
@@ -13,5 +14,6 @@ __all__ = [
|
|
|
13
14
|
'ProjectsApi',
|
|
14
15
|
'ResourcesApi',
|
|
15
16
|
'UsersApi',
|
|
16
|
-
'DatasetsInfoApi'
|
|
17
|
+
'DatasetsInfoApi',
|
|
18
|
+
'ModelsApi',
|
|
17
19
|
]
|
|
@@ -3,6 +3,7 @@ import httpx
|
|
|
3
3
|
from datetime import date
|
|
4
4
|
import logging
|
|
5
5
|
from ..entity_base_api import ApiConfig, CreatableEntityApi, DeletableEntityApi
|
|
6
|
+
from .models_api import ModelsApi
|
|
6
7
|
from datamint.entities.annotation import Annotation
|
|
7
8
|
from datamint.entities.resource import Resource
|
|
8
9
|
from datamint.entities.project import Project
|
|
@@ -38,6 +39,7 @@ class AnnotationsApi(CreatableEntityApi[Annotation], DeletableEntityApi[Annotati
|
|
|
38
39
|
client: Optional HTTP client instance. If None, a new one will be created.
|
|
39
40
|
"""
|
|
40
41
|
super().__init__(config, Annotation, 'annotations', client)
|
|
42
|
+
self._models_api = ModelsApi(config, client=client)
|
|
41
43
|
|
|
42
44
|
def get_list(self,
|
|
43
45
|
resource: str | Resource | None = None,
|
|
@@ -69,7 +71,7 @@ class AnnotationsApi(CreatableEntityApi[Annotation], DeletableEntityApi[Annotati
|
|
|
69
71
|
|
|
70
72
|
async def _upload_segmentations_async(self,
|
|
71
73
|
resource: str | Resource,
|
|
72
|
-
frame_index: int | Sequence
|
|
74
|
+
frame_index: int | Sequence[int] | None,
|
|
73
75
|
file_path: str | np.ndarray,
|
|
74
76
|
name: dict[int, str] | dict[tuple, str],
|
|
75
77
|
imported_from: str | None = None,
|
|
@@ -78,7 +80,7 @@ class AnnotationsApi(CreatableEntityApi[Annotation], DeletableEntityApi[Annotati
|
|
|
78
80
|
worklist_id: str | None = None,
|
|
79
81
|
model_id: str | None = None,
|
|
80
82
|
transpose_segmentation: bool = False,
|
|
81
|
-
upload_volume: bool | str = 'auto'
|
|
83
|
+
upload_volume: bool | str = 'auto',
|
|
82
84
|
) -> Sequence[str]:
|
|
83
85
|
"""
|
|
84
86
|
Upload segmentations asynchronously.
|
|
@@ -397,6 +399,7 @@ class AnnotationsApi(CreatableEntityApi[Annotation], DeletableEntityApi[Annotati
|
|
|
397
399
|
worklist_id: str | None = None,
|
|
398
400
|
model_id: str | None = None,
|
|
399
401
|
transpose_segmentation: bool = False,
|
|
402
|
+
ai_model_name: str | None = None
|
|
400
403
|
) -> list[str]:
|
|
401
404
|
"""
|
|
402
405
|
Upload segmentations to a resource.
|
|
@@ -425,6 +428,7 @@ class AnnotationsApi(CreatableEntityApi[Annotation], DeletableEntityApi[Annotati
|
|
|
425
428
|
worklist_id: The annotation worklist unique id.
|
|
426
429
|
model_id: The model unique id.
|
|
427
430
|
transpose_segmentation: Whether to transpose the segmentation or not.
|
|
431
|
+
ai_model_name: Optional AI model name to associate with the segmentation.
|
|
428
432
|
|
|
429
433
|
Returns:
|
|
430
434
|
List of segmentation unique ids.
|
|
@@ -453,6 +457,18 @@ class AnnotationsApi(CreatableEntityApi[Annotation], DeletableEntityApi[Annotati
|
|
|
453
457
|
if isinstance(file_path, str) and not os.path.exists(file_path):
|
|
454
458
|
raise FileNotFoundError(f"File {file_path} not found.")
|
|
455
459
|
|
|
460
|
+
if ai_model_name is not None:
|
|
461
|
+
model_id = self._models_api.get_by_name(ai_model_name)
|
|
462
|
+
if model_id is None:
|
|
463
|
+
try:
|
|
464
|
+
available_models = [model['name'] for model in self._models_api.get_all()]
|
|
465
|
+
except Exception:
|
|
466
|
+
_LOGGER.warning("Could not fetch available AI models from the server.")
|
|
467
|
+
raise ValueError(f"AI model with name '{ai_model_name}' not found. ")
|
|
468
|
+
raise ValueError(f"AI model with name '{ai_model_name}' not found. " +
|
|
469
|
+
f"Available models: {available_models}")
|
|
470
|
+
model_id = model_id['id']
|
|
471
|
+
|
|
456
472
|
# Handle NIfTI files specially - upload as single volume
|
|
457
473
|
if isinstance(file_path, str) and (file_path.endswith('.nii') or file_path.endswith('.nii.gz')):
|
|
458
474
|
_LOGGER.info(f"Uploading NIfTI segmentation file: {file_path}")
|
|
@@ -472,7 +488,7 @@ class AnnotationsApi(CreatableEntityApi[Annotation], DeletableEntityApi[Annotati
|
|
|
472
488
|
worklist_id=worklist_id,
|
|
473
489
|
model_id=model_id,
|
|
474
490
|
transpose_segmentation=transpose_segmentation,
|
|
475
|
-
upload_volume=True
|
|
491
|
+
upload_volume=True,
|
|
476
492
|
)
|
|
477
493
|
return loop.run_until_complete(task)
|
|
478
494
|
|
|
@@ -486,8 +502,8 @@ class AnnotationsApi(CreatableEntityApi[Annotation], DeletableEntityApi[Annotati
|
|
|
486
502
|
raise ValueError("frame_index list contains duplicate values.")
|
|
487
503
|
|
|
488
504
|
if isinstance(frame_index, Sequence) and len(frame_index) == 1:
|
|
489
|
-
frame_index = frame_index[0]
|
|
490
|
-
|
|
505
|
+
frame_index = frame_index[0]
|
|
506
|
+
|
|
491
507
|
nest_asyncio.apply()
|
|
492
508
|
loop = asyncio.get_event_loop()
|
|
493
509
|
task = self._upload_segmentations_async(
|
|
@@ -982,3 +998,33 @@ class AnnotationsApi(CreatableEntityApi[Annotation], DeletableEntityApi[Annotati
|
|
|
982
998
|
) -> None:
|
|
983
999
|
"""Alias for :py:meth:`download_multiple_files`"""
|
|
984
1000
|
return self.download_multiple_files(annotations, save_paths)
|
|
1001
|
+
|
|
1002
|
+
def patch(self,
|
|
1003
|
+
annotation: str | Annotation,
|
|
1004
|
+
identifier: str) -> None:
|
|
1005
|
+
"""
|
|
1006
|
+
Update the project assignment for an annotation.
|
|
1007
|
+
|
|
1008
|
+
Args:
|
|
1009
|
+
annotation: The annotation unique id or Annotation instance.
|
|
1010
|
+
identifier: The new identifier/label for the annotation.
|
|
1011
|
+
|
|
1012
|
+
Raises:
|
|
1013
|
+
DatamintException: If the update fails.
|
|
1014
|
+
"""
|
|
1015
|
+
annotation_id = self._entid(annotation)
|
|
1016
|
+
|
|
1017
|
+
payload = {'identifier': identifier}
|
|
1018
|
+
# remove None values
|
|
1019
|
+
payload = {k: v for k, v in payload.items() if v is not None}
|
|
1020
|
+
if len(payload) == 0:
|
|
1021
|
+
_LOGGER.info("No fields to update for annotation patch.")
|
|
1022
|
+
return
|
|
1023
|
+
|
|
1024
|
+
resp = self._make_request('PATCH',
|
|
1025
|
+
f'{self.endpoint_base}/{annotation_id}',
|
|
1026
|
+
json=payload)
|
|
1027
|
+
|
|
1028
|
+
respdata = resp.json()
|
|
1029
|
+
if isinstance(respdata, dict) and 'error' in respdata:
|
|
1030
|
+
raise DatamintException(respdata['error'])
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from typing import Sequence
|
|
2
|
+
from ..entity_base_api import ApiConfig, BaseApi
|
|
3
|
+
import httpx
|
|
4
|
+
from datamint.exceptions import EntityAlreadyExistsError
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ModelsApi(BaseApi):
|
|
8
|
+
"""API handler for project-related endpoints."""
|
|
9
|
+
|
|
10
|
+
def __init__(self,
|
|
11
|
+
config: ApiConfig,
|
|
12
|
+
client: httpx.Client | None = None) -> None:
|
|
13
|
+
"""Initialize the projects API handler.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
config: API configuration containing base URL, API key, etc.
|
|
17
|
+
client: Optional HTTP client instance. If None, a new one will be created.
|
|
18
|
+
"""
|
|
19
|
+
super().__init__(config, client=client)
|
|
20
|
+
|
|
21
|
+
def create(self,
|
|
22
|
+
name: str) -> dict:
|
|
23
|
+
json = {
|
|
24
|
+
'name': name
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
response = self._make_request('POST',
|
|
29
|
+
'ai-models',
|
|
30
|
+
json=json)
|
|
31
|
+
return response.json()
|
|
32
|
+
except httpx.HTTPStatusError as e:
|
|
33
|
+
if e.response.status_code == 409:
|
|
34
|
+
raise EntityAlreadyExistsError('ai-model', {'name': name})
|
|
35
|
+
raise
|
|
36
|
+
|
|
37
|
+
def get_all(self) -> Sequence[dict]:
|
|
38
|
+
response = self._make_request('GET',
|
|
39
|
+
'ai-models')
|
|
40
|
+
return response.json()
|
|
41
|
+
|
|
42
|
+
def get_by_name(self, name: str) -> dict | None:
|
|
43
|
+
models = self.get_all()
|
|
44
|
+
for model in models:
|
|
45
|
+
if model['name'] == name:
|
|
46
|
+
return model
|
|
47
|
+
return None
|
datamint/entities/project.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from datetime import datetime
|
|
4
4
|
import logging
|
|
5
5
|
from .base_entity import BaseEntity, MISSING_FIELD
|
|
6
|
+
from typing import Any
|
|
6
7
|
|
|
7
8
|
logger = logging.getLogger(__name__)
|
|
8
9
|
|
|
@@ -43,9 +44,10 @@ class Project(BaseEntity):
|
|
|
43
44
|
resource_count: int
|
|
44
45
|
annotated_resource_count: int
|
|
45
46
|
description: str | None
|
|
46
|
-
ai_model_id: str | None
|
|
47
47
|
viewable_ai_segs: list | None
|
|
48
48
|
editable_ai_segs: list | None
|
|
49
|
+
registered_model: Any | None = MISSING_FIELD
|
|
50
|
+
ai_model_id: str | None = MISSING_FIELD
|
|
49
51
|
closed_resources_count: int = MISSING_FIELD
|
|
50
52
|
resources_to_annotate_count: int = MISSING_FIELD
|
|
51
53
|
most_recent_experiment: str | None = MISSING_FIELD # ISO timestamp string
|
datamint/exceptions.py
CHANGED
|
@@ -28,4 +28,25 @@ class ResourceNotFoundError(DatamintException):
|
|
|
28
28
|
self.params = params
|
|
29
29
|
|
|
30
30
|
def __str__(self):
|
|
31
|
-
return f"Resource '{self.resource_type}' not found for parameters: {self.params}"
|
|
31
|
+
return f"Resource '{self.resource_type}' not found for parameters: {self.params}"
|
|
32
|
+
|
|
33
|
+
# Already existing (e.g, creating a project with a name that already exists)
|
|
34
|
+
class EntityAlreadyExistsError(DatamintException):
|
|
35
|
+
"""
|
|
36
|
+
Exception raised when trying to create an entity that already exists.
|
|
37
|
+
For instance, when creating a project with a name that already exists.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__(self, entity_type: str, params: dict):
|
|
41
|
+
"""Constructor.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
entity_type: The type of entity that already exists.
|
|
45
|
+
params: Dict of params identifying the existing entity.
|
|
46
|
+
"""
|
|
47
|
+
super().__init__()
|
|
48
|
+
self.entity_type = entity_type
|
|
49
|
+
self.params = params
|
|
50
|
+
|
|
51
|
+
def __str__(self) -> str:
|
|
52
|
+
return f"Entity '{self.entity_type}' already exists for parameters: {self.params}"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: datamint
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.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
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
datamint/__init__.py,sha256=ucsnxrYClh6pdy7psRJXWam_9rjAQB4NXzvy7xLovmo,824
|
|
2
2
|
datamint/api/__init__.py,sha256=7QYkmDBXbKh8-zchV7k6Lpolaw6h-IK6ezfXROIWh2A,43
|
|
3
3
|
datamint/api/base_api.py,sha256=Iu9oJEZ8YlIF5xcbH_M0Lkb7t9ZDNFLzjJp9bPDHW1c,16628
|
|
4
|
-
datamint/api/client.py,sha256=
|
|
4
|
+
datamint/api/client.py,sha256=qL6kW1lUma98LieDEkhRdJAGRcsEFlaBSg2oQ3ZePYY,3466
|
|
5
5
|
datamint/api/dto/__init__.py,sha256=KOSNl1axDDE5eBt68MmsgkyE0Ds_1DDzWUg73iyoWvc,281
|
|
6
|
-
datamint/api/endpoints/__init__.py,sha256=
|
|
7
|
-
datamint/api/endpoints/annotations_api.py,sha256=
|
|
6
|
+
datamint/api/endpoints/__init__.py,sha256=uN2EYxSL6T5O5poVmQs8CQpyy3_MZzHfNyniORcvKUc,454
|
|
7
|
+
datamint/api/endpoints/annotations_api.py,sha256=jhaWkLd01zKw-lVQYhBX288o9Ew2lQ-jmRP-0fz2fx0,48185
|
|
8
8
|
datamint/api/endpoints/channels_api.py,sha256=oQqxSw9DJzAqtVQI7-tc1llTdnsm-URx8jwtXNXnhio,867
|
|
9
9
|
datamint/api/endpoints/datasetsinfo_api.py,sha256=WdzrUzK63w9gvAP6U--P65FbD-3X-jm9TPCcYnRNjas,597
|
|
10
|
+
datamint/api/endpoints/models_api.py,sha256=tbVuajc-mCsIp5AKSCoq3uQRDWgKnJaIA6tf_ck8-XY,1502
|
|
10
11
|
datamint/api/endpoints/projects_api.py,sha256=9tYIQsnMFOGTXrsoizweoWNqNue5907nbI6G9PAcYcA,7784
|
|
11
12
|
datamint/api/endpoints/resources_api.py,sha256=Hd8sObIarvAdATS6qmAvT9EdXzPfruyMHnlGXcYkeMg,48320
|
|
12
13
|
datamint/api/endpoints/users_api.py,sha256=pnkuTZ1B9Y0FtwwvXO8J64e02RSkRxnBmTl9UGSuC5I,1186
|
|
@@ -31,12 +32,12 @@ datamint/entities/annotation.py,sha256=ochAEh_JqxAe_FyYTNUfPT47KiIAG7CkBTim52bu7
|
|
|
31
32
|
datamint/entities/base_entity.py,sha256=DniakCgJ-gV7Hz8VKQA_dRYTp4DU5rcjLBVOuD1aZuA,1902
|
|
32
33
|
datamint/entities/channel.py,sha256=9fl22eSx_ng98NosfQGs18cdaRdbeC3wXL61KhSg4Zo,1601
|
|
33
34
|
datamint/entities/datasetinfo.py,sha256=O73Aq0tLflQomFzseful8a_cXqKdO9w2yP0p3zBcA-s,489
|
|
34
|
-
datamint/entities/project.py,sha256=
|
|
35
|
+
datamint/entities/project.py,sha256=FNweb1Q0ZQkWfbyijoV-UyqUXthzookdcyO-ophAl0U,2676
|
|
35
36
|
datamint/entities/resource.py,sha256=7YCVihswd-bH-2AH4aMPIddt5ejwRqRFQAszI_sTWaU,4882
|
|
36
37
|
datamint/entities/user.py,sha256=MREHDOsV9NOBEbXqiQ2ww6DmetN07CELId-ZQVpZCb8,620
|
|
37
38
|
datamint/examples/__init__.py,sha256=zcYnd5nLVme9GCTPYH-1JpGo8xXK2WEYvhzcy_2alZc,39
|
|
38
39
|
datamint/examples/example_projects.py,sha256=sU-Gxy7PPqA0WUfN-ZmXV-0YnwrnzpJ79lMXTJp2DzU,2804
|
|
39
|
-
datamint/exceptions.py,sha256=
|
|
40
|
+
datamint/exceptions.py,sha256=Or-NNj8pgChzAZNaWtkX1WyHJ2q5GziyvHdFLYymvX0,1661
|
|
40
41
|
datamint/experiment/__init__.py,sha256=5qQOMzoG17DEd1YnTF-vS0qiM-DGdbNh42EUo91CRhQ,34
|
|
41
42
|
datamint/experiment/_patcher.py,sha256=ZgbezoevAYhJsbiJTvWPALGTcUiMT371xddcTllt3H4,23296
|
|
42
43
|
datamint/experiment/experiment.py,sha256=aHK9dRFdQTi569xgUg1KqlCZLHZpDmSH3g3ndPIZvXw,44546
|
|
@@ -44,7 +45,7 @@ datamint/logging.yaml,sha256=tOMxtc2UmwlIMTK6ljtnBwTco1PNrPeq3mx2iMuSbiw,482
|
|
|
44
45
|
datamint/utils/logging_utils.py,sha256=9pRoaPrWu2jOdDCiAoUsjEdP5ZwaealWL3hjUqFvx9g,4022
|
|
45
46
|
datamint/utils/torchmetrics.py,sha256=lwU0nOtsSWfebyp7dvjlAggaqXtj5ohSEUXOg3L0hJE,2837
|
|
46
47
|
datamint/utils/visualization.py,sha256=yaUVAOHar59VrGUjpAWv5eVvQSfztFG0eP9p5Vt3l-M,4470
|
|
47
|
-
datamint-2.
|
|
48
|
-
datamint-2.
|
|
49
|
-
datamint-2.
|
|
50
|
-
datamint-2.
|
|
48
|
+
datamint-2.3.1.dist-info/METADATA,sha256=m77em4ywu5yOsk2pZV6dqnNo21UsxSaadPQPo2tNLSY,4203
|
|
49
|
+
datamint-2.3.1.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
50
|
+
datamint-2.3.1.dist-info/entry_points.txt,sha256=mn5H6jPjO-rY0W0CAZ6Z_KKWhMLvyVaSpoqk77jlTI4,145
|
|
51
|
+
datamint-2.3.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|