datamint 2.3.2__py3-none-any.whl → 2.3.3__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.
@@ -1,9 +1,15 @@
1
1
  """Project entity module for DataMint API."""
2
-
3
2
  from datetime import datetime
4
3
  import logging
4
+ from typing import Sequence, Literal, TYPE_CHECKING
5
5
  from .base_entity import BaseEntity, MISSING_FIELD
6
6
  from typing import Any
7
+ import webbrowser
8
+ from pydantic import PrivateAttr
9
+
10
+ if TYPE_CHECKING:
11
+ from datamint.api.endpoints.projects_api import ProjectsApi
12
+ from .resource import Resource
7
13
 
8
14
  logger = logging.getLogger(__name__)
9
15
 
@@ -36,7 +42,7 @@ class Project(BaseEntity):
36
42
  """
37
43
  id: str
38
44
  name: str
39
- created_at: str # ISO timestamp string
45
+ created_at: str
40
46
  created_by: str
41
47
  dataset_id: str
42
48
  worklist_id: str
@@ -50,17 +56,52 @@ class Project(BaseEntity):
50
56
  ai_model_id: str | None = MISSING_FIELD
51
57
  closed_resources_count: int = MISSING_FIELD
52
58
  resources_to_annotate_count: int = MISSING_FIELD
53
- most_recent_experiment: str | None = MISSING_FIELD # ISO timestamp string
59
+ most_recent_experiment: str | None = MISSING_FIELD
54
60
  annotators: list[dict] = MISSING_FIELD
55
- customer_id: str | None = MISSING_FIELD
56
61
  archived_on: str | None = MISSING_FIELD
57
62
  archived_by: str | None = MISSING_FIELD
58
63
  is_active_learning: bool = MISSING_FIELD
59
64
  two_up_display: bool = MISSING_FIELD
60
65
  require_review: bool = MISSING_FIELD
61
66
 
67
+ _api: 'ProjectsApi' = PrivateAttr()
68
+
69
+ def fetch_resources(self) -> Sequence['Resource']:
70
+ """Fetch resources associated with this project from the API,
71
+ IMPORTANT: It always fetches fresh data from the server.
72
+
73
+ Returns:
74
+ List of Resource instances associated with the project.
75
+ """
76
+ return self._api.get_project_resources(self.id)
77
+
78
+ def set_work_status(self, resource: 'Resource', status: Literal['opened', 'annotated', 'closed']) -> None:
79
+ """Set the status of a resource.
80
+
81
+ Args:
82
+ resource: The resource unique id or a resource object.
83
+ status: The new status to set.
84
+ """
85
+
86
+ return self._api.set_work_status(self, resource, status)
87
+
62
88
  @property
63
89
  def url(self) -> str:
64
90
  """Get the URL to access this project in the DataMint web application."""
65
- base_url = "https://app.datamint.io/projects/edit"
66
- return f"{base_url}/{self.id}"
91
+ base_url = self._api.config.web_app_url
92
+ return f'{base_url}/projects/edit/{self.id}'
93
+
94
+ def show(self) -> None:
95
+ """Open the project in the default web browser."""
96
+ webbrowser.open(self.url)
97
+
98
+ def as_torch_dataset(self,
99
+ root_dir: str | None = None,
100
+ auto_update: bool = True,
101
+ return_as_semantic_segmentation: bool = False):
102
+ from datamint.dataset import Dataset
103
+ return Dataset(project_name=self.name,
104
+ root=root_dir,
105
+ auto_update=auto_update,
106
+ return_as_semantic_segmentation=return_as_semantic_segmentation,
107
+ all_annotations=True)
@@ -1,19 +1,33 @@
1
1
  """Resource entity module for DataMint API."""
2
2
 
3
3
  from datetime import datetime
4
- from typing import Optional, Any
4
+ from typing import TYPE_CHECKING, Optional, Any, Sequence
5
5
  import logging
6
+
6
7
  from .base_entity import BaseEntity, MISSING_FIELD
7
- from pydantic import Field
8
+ from .cache_manager import CacheManager
9
+ from pydantic import PrivateAttr
10
+ from datamint.api.dto import AnnotationType
11
+ import webbrowser
12
+ from datamint.types import ImagingData
13
+
14
+ if TYPE_CHECKING:
15
+ from datamint.api.endpoints.resources_api import ResourcesApi
16
+ from .project import Project
17
+ from .annotation import Annotation
8
18
 
9
19
  logger = logging.getLogger(__name__)
10
20
 
21
+
22
+ _IMAGE_CACHEKEY = "image_data"
23
+
24
+
11
25
  class Resource(BaseEntity):
12
26
  """Represents a DataMint resource with all its properties and metadata.
13
-
27
+
14
28
  This class models a resource entity from the DataMint API, containing
15
29
  information about uploaded files, their metadata, and associated projects.
16
-
30
+
17
31
  Attributes:
18
32
  id: Unique identifier for the resource
19
33
  resource_uri: URI path to access the resource file
@@ -84,42 +98,145 @@ class Resource(BaseEntity):
84
98
  categories: Optional[Any] = None # TODO: Define proper type when spec available
85
99
  user_info: Optional[dict] = None
86
100
 
101
+ _api: 'ResourcesApi' = PrivateAttr()
102
+
103
+ def __init__(self, **data):
104
+ """Initialize the resource entity."""
105
+ super().__init__(**data)
106
+ self._cache: CacheManager[bytes] = CacheManager[bytes]('resources')
107
+
108
+ def fetch_file_data(
109
+ self,
110
+ auto_convert: bool = True,
111
+ save_path: str | None = None,
112
+ use_cache: bool = False,
113
+ ) -> bytes | ImagingData:
114
+ """Get the file data for this resource.
115
+
116
+ This method automatically caches the file data locally. On subsequent
117
+ calls, it checks the server for changes and uses cached data if unchanged.
118
+
119
+ Args:
120
+ use_cache: If True, uses cached data when available and valid
121
+ auto_convert: If True, automatically converts to appropriate format (pydicom.Dataset, PIL Image, etc.)
122
+ save_path: Optional path to save the file locally
123
+
124
+ Returns:
125
+ File data (format depends on auto_convert and file type)
126
+ """
127
+ # Version info for cache validation
128
+ version_info = self._generate_version_info()
129
+
130
+ # Try to get from cache
131
+ img_data = None
132
+ if use_cache:
133
+ img_data = self._cache.get(self.id, _IMAGE_CACHEKEY, version_info)
134
+ if img_data is not None:
135
+ logger.debug(f"Using cached image data for resource {self.id}")
136
+
137
+ if img_data is None:
138
+ # Fetch from server using download_resource_file
139
+ logger.debug(f"Fetching image data from server for resource {self.id}")
140
+ img_data = self._api.download_resource_file(
141
+ self,
142
+ save_path=save_path,
143
+ auto_convert=False
144
+ )
145
+ # Cache the data
146
+ if use_cache:
147
+ self._cache.set(self.id, _IMAGE_CACHEKEY, img_data, version_info)
148
+
149
+ if auto_convert:
150
+ try:
151
+ mimetype, ext = self._api._determine_mimetype(img_data, self)
152
+ img_data = self._api.convert_format(img_data,
153
+ mimetype=mimetype,
154
+ file_path=save_path)
155
+ except Exception as e:
156
+ logger.error(f"Failed to auto-convert resource {self.id}: {e}")
157
+
158
+ return img_data
159
+
160
+ def _generate_version_info(self) -> dict:
161
+ """Helper to generate version info for caching."""
162
+ return {
163
+ 'created_at': self.created_at,
164
+ 'deleted_at': self.deleted_at,
165
+ 'size': self.size,
166
+ }
167
+
168
+ def _save_into_cache(self, data: bytes) -> None:
169
+ """Helper to save raw data into cache."""
170
+ version_info = self._generate_version_info()
171
+ self._cache.set(self.id, _IMAGE_CACHEKEY, data, version_info)
172
+
173
+ def fetch_annotations(
174
+ self,
175
+ annotation_type: AnnotationType | str | None = None
176
+ ) -> Sequence['Annotation']:
177
+ """Get annotations associated with this resource."""
178
+
179
+ annotations = self._api.get_annotations(self)
180
+
181
+ if annotation_type:
182
+ annotation_type = AnnotationType(annotation_type)
183
+ annotations = [a for a in annotations if a.annotation_type == annotation_type]
184
+ return annotations
185
+
186
+ # def get_projects(
187
+ # self,
188
+ # ) -> Sequence['Project']:
189
+ # """Get all projects this resource belongs to.
190
+
191
+ # Returns:
192
+ # List of Project instances
193
+ # """
194
+ # return self._api.get_projects(self)
195
+
196
+
197
+ def invalidate_cache(self) -> None:
198
+ """Invalidate cached data for this resource.
199
+ """
200
+ # Invalidate all
201
+ self._cache.invalidate(self.id)
202
+ logger.debug(f"Invalidated all cache for resource {self.id}")
203
+
87
204
  @property
88
205
  def size_mb(self) -> float:
89
206
  """Get file size in megabytes.
90
-
207
+
91
208
  Returns:
92
209
  File size in MB rounded to 2 decimal places
93
210
  """
94
211
  return round(self.size / (1024 * 1024), 2)
95
-
212
+
96
213
  def is_dicom(self) -> bool:
97
214
  """Check if the resource is a DICOM file.
98
-
215
+
99
216
  Returns:
100
217
  True if the resource is a DICOM file, False otherwise
101
218
  """
102
219
  return self.mimetype == 'application/dicom' or self.storage == 'DicomResource'
103
-
104
- def get_project_names(self) -> list[str]:
105
- """Get list of project names this resource belongs to.
106
-
107
- Returns:
108
- List of project names
109
- """
110
- return [proj['name'] for proj in self.projects]
111
-
220
+
221
+ # def get_project_names(self) -> list[str]:
222
+ # """Get list of project names this resource belongs to.
223
+
224
+ # Returns:
225
+ # List of project names
226
+ # """
227
+ # return [proj['name'] for proj in self.projects] if self.projects != MISSING_FIELD else []
228
+
112
229
  def __str__(self) -> str:
113
230
  """String representation of the resource.
114
-
231
+
115
232
  Returns:
116
233
  Human-readable string describing the resource
117
234
  """
118
235
  return f"Resource(id='{self.id}', filename='{self.filename}', size={self.size_mb}MB)"
119
-
236
+
120
237
  def __repr__(self) -> str:
121
238
  """Detailed string representation of the resource.
122
-
239
+
123
240
  Returns:
124
241
  Detailed string representation for debugging
125
242
  """
@@ -128,3 +245,13 @@ class Resource(BaseEntity):
128
245
  f"modality='{self.modality}', status='{self.status}', "
129
246
  f"published={self.published})"
130
247
  )
248
+
249
+ @property
250
+ def url(self) -> str:
251
+ """Get the URL to access this resource in the DataMint web application."""
252
+ base_url = self._api.config.web_app_url
253
+ return f'{base_url}/resource/{self.id}'
254
+
255
+ def show(self) -> None:
256
+ """Open the resource in the default web browser."""
257
+ webbrowser.open(self.url)
datamint/types.py ADDED
@@ -0,0 +1,17 @@
1
+ from typing import TypeAlias, TYPE_CHECKING, Union
2
+
3
+ if TYPE_CHECKING:
4
+ import pydicom.dataset
5
+ from PIL import Image
6
+ import cv2
7
+ from nibabel.filebasedimages import FileBasedImage as nib_FileBasedImage
8
+
9
+ # Type alias for imaging formats
10
+ ImagingData: TypeAlias = (
11
+ Union[
12
+ 'pydicom.dataset.Dataset',
13
+ 'Image.Image',
14
+ 'cv2.VideoCapture',
15
+ 'nib_FileBasedImage'
16
+ ]
17
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datamint
3
- Version: 2.3.2
3
+ Version: 2.3.3
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
@@ -15,6 +15,7 @@ Requires-Dist: Deprecated (>=1.2.0)
15
15
  Requires-Dist: aiohttp (>=3.0.0,<4.0.0)
16
16
  Requires-Dist: aioresponses (>=0.7.8,<0.8.0) ; extra == "dev"
17
17
  Requires-Dist: albumentations (>=2.0.0)
18
+ Requires-Dist: backports-strenum ; python_version < "3.11"
18
19
  Requires-Dist: datamintapi (==0.0.*)
19
20
  Requires-Dist: httpx
20
21
  Requires-Dist: humanize (>=4.0.0,<5.0.0)
@@ -1,40 +1,41 @@
1
1
  datamint/__init__.py,sha256=ucsnxrYClh6pdy7psRJXWam_9rjAQB4NXzvy7xLovmo,824
2
2
  datamint/api/__init__.py,sha256=7QYkmDBXbKh8-zchV7k6Lpolaw6h-IK6ezfXROIWh2A,43
3
- datamint/api/base_api.py,sha256=Iu9oJEZ8YlIF5xcbH_M0Lkb7t9ZDNFLzjJp9bPDHW1c,16628
4
- datamint/api/client.py,sha256=WfcCpvH3tl0nQ9SgRdQG0qy8A7tD_2dytRxQUln3G9Y,3724
5
- datamint/api/dto/__init__.py,sha256=KOSNl1axDDE5eBt68MmsgkyE0Ds_1DDzWUg73iyoWvc,281
3
+ datamint/api/base_api.py,sha256=UmNEgcFt1czkqyVCM3t5P9UvA1F9AEZITPqdakojvJs,18810
4
+ datamint/api/client.py,sha256=ndKSj2QnveRNOtaQhE9qM4tCGtgrTxfInDy9FhdReCo,3922
5
+ datamint/api/dto/__init__.py,sha256=fUi901Zs-q5XHyWwZ4dMi2fEO8-CUEVEdYbpd17lahc,416
6
6
  datamint/api/endpoints/__init__.py,sha256=wi4liAb5-wOohwyzKUD6TxHGeZmUPaZerFUGa2IUju4,529
7
- datamint/api/endpoints/annotations_api.py,sha256=jhaWkLd01zKw-lVQYhBX288o9Ew2lQ-jmRP-0fz2fx0,48185
7
+ datamint/api/endpoints/annotations_api.py,sha256=B21oGgNXO7xoir3ynNvhnbo1JyRvWZ9sbsc7VVjghhM,49713
8
8
  datamint/api/endpoints/annotationsets_api.py,sha256=NIsPIjGGptiUBxHft-EhOMRG-DsQAthheVqd7ph0id4,409
9
9
  datamint/api/endpoints/channels_api.py,sha256=oQqxSw9DJzAqtVQI7-tc1llTdnsm-URx8jwtXNXnhio,867
10
10
  datamint/api/endpoints/datasetsinfo_api.py,sha256=WdzrUzK63w9gvAP6U--P65FbD-3X-jm9TPCcYnRNjas,597
11
11
  datamint/api/endpoints/models_api.py,sha256=tbVuajc-mCsIp5AKSCoq3uQRDWgKnJaIA6tf_ck8-XY,1502
12
- datamint/api/endpoints/projects_api.py,sha256=9tYIQsnMFOGTXrsoizweoWNqNue5907nbI6G9PAcYcA,7784
13
- datamint/api/endpoints/resources_api.py,sha256=Hd8sObIarvAdATS6qmAvT9EdXzPfruyMHnlGXcYkeMg,48320
12
+ datamint/api/endpoints/projects_api.py,sha256=C5IqKXaYRrg5LDBjIRSo4NBCvbN2619X3SSgKqGe8pc,7929
13
+ datamint/api/endpoints/resources_api.py,sha256=Ez-mGYElCpwHZ8T8NlqXH188mCFBZGfs8b320xa43tc,50142
14
14
  datamint/api/endpoints/users_api.py,sha256=pnkuTZ1B9Y0FtwwvXO8J64e02RSkRxnBmTl9UGSuC5I,1186
15
- datamint/api/entity_base_api.py,sha256=gPE28bwv7B6JngMk9szD2XwaVhB8OwB1HJjaMYD354k,12935
15
+ datamint/api/entity_base_api.py,sha256=-8SIt4M8P9G2b8SQznuWpFuFE8zEQjQxkRkw0s_w0Y4,11692
16
16
  datamint/apihandler/annotation_api_handler.py,sha256=W3vV4z3BqX1OQe1r7zr8dI-IVu4zUDxED4QttdiWV-E,57098
17
17
  datamint/apihandler/api_handler.py,sha256=mL0gMaWePYa7zwkw92E-VMK2WjpcPt7au0KqnmsWSYw,439
18
18
  datamint/apihandler/base_api_handler.py,sha256=Hqt3oUvXfEqF25DJkk0WOWAtNLnKaZRGtnCchKFA1ag,11669
19
19
  datamint/apihandler/dto/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
- datamint/apihandler/dto/annotation_dto.py,sha256=KUeHbxLYols16q-ANNxC48eH4EA8Tc-nKmW_8xrqhy4,7119
20
+ datamint/apihandler/dto/annotation_dto.py,sha256=8gcTFsvbXQQU4QjdW6lcUI_De1fyNyF49_a2uIyBOpg,7220
21
21
  datamint/apihandler/exp_api_handler.py,sha256=hFUgUgBc5rL7odK7gTW3MnrvMY1pVfJUpUdzRNobMQE,6226
22
22
  datamint/apihandler/root_api_handler.py,sha256=jBof_XPTeq4o41CW-l-I5GHQKVa76kaX75RovS_qAM4,63384
23
23
  datamint/client_cmd_tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
24
  datamint/client_cmd_tools/datamint_config.py,sha256=MpR5UHv_xpElOOYyEESBkDg2n3JjP_PNLI2jqmZgYQ8,16222
25
25
  datamint/client_cmd_tools/datamint_upload.py,sha256=rZEqBRZZDBFSlwMvIg_DbipqgBtQD31v7DC1C6tysqA,36058
26
- datamint/configs.py,sha256=Bdp6NydYwyCJ2dk19_gf_o3M2ZyQOmMHpLi8wEWNHUk,1426
26
+ datamint/configs.py,sha256=ArVD5QxuohLcM6NB0dMxcEfrD1-x5lPRDLcBdY8jhMU,1625
27
27
  datamint/dataset/__init__.py,sha256=4PlUKSvVhdfQvvuq8jQXrkdqnot-iTTizM3aM1vgSwg,47
28
28
  datamint/dataset/annotation.py,sha256=qN1IMjdfLD2ceQ6va3l76jOXA8Vb_c-eBk1oWQu6hW0,7994
29
- datamint/dataset/base_dataset.py,sha256=pJXU_nY9_D67E7G3cjnVnwdzjNNJgOhMt-i687oHgtw,49869
29
+ datamint/dataset/base_dataset.py,sha256=2m_5nBayFCzXSjuPLb6mM9NLQ43wIUqeFUfm-6WVg_U,49827
30
30
  datamint/dataset/dataset.py,sha256=c0887PcI6fEWG3FjM3gz_EYb9maH8v4ormZhm7vf4gE,28929
31
- datamint/entities/__init__.py,sha256=tbHE7rZb0R9Hm-Dc8VWEq3PlRl7BYOzffumrV0ZdsMs,444
32
- datamint/entities/annotation.py,sha256=ochAEh_JqxAe_FyYTNUfPT47KiIAG7CkBTim52bu7M8,6636
33
- datamint/entities/base_entity.py,sha256=DniakCgJ-gV7Hz8VKQA_dRYTp4DU5rcjLBVOuD1aZuA,1902
31
+ datamint/entities/__init__.py,sha256=ePkFeNATE5tJ9Lulsn2VTGWBJGwPtoRT1zsfbg1bkPw,504
32
+ datamint/entities/annotation.py,sha256=4qsm3yO8bTgu5SNxY91P0LGXx3d7mxF1OGWpMNir1CA,8956
33
+ datamint/entities/base_entity.py,sha256=QX381VdnrisQeQKg2kkHNDUvczYm03VsDHlHZQXr78U,3307
34
+ datamint/entities/cache_manager.py,sha256=ZN5_HeyM6c0ocWFsJuJEF4CkuOHr6RPErwhZNYYq7WU,10282
34
35
  datamint/entities/channel.py,sha256=9fl22eSx_ng98NosfQGs18cdaRdbeC3wXL61KhSg4Zo,1601
35
- datamint/entities/datasetinfo.py,sha256=O73Aq0tLflQomFzseful8a_cXqKdO9w2yP0p3zBcA-s,489
36
- datamint/entities/project.py,sha256=FNweb1Q0ZQkWfbyijoV-UyqUXthzookdcyO-ophAl0U,2676
37
- datamint/entities/resource.py,sha256=7YCVihswd-bH-2AH4aMPIddt5ejwRqRFQAszI_sTWaU,4882
36
+ datamint/entities/datasetinfo.py,sha256=yeGy5CVxqc7h0SFBJROr0UObeIMkOVZKwrOBsZ-iCEg,4151
37
+ datamint/entities/project.py,sha256=QQXBOe6FzupPrCunBcqOlY6d3uqxvnYvGeCq8hZy_U8,4192
38
+ datamint/entities/resource.py,sha256=Ai36-dJ9PhrRYho8QLRnySfsAPWPW8QpKMiVra_jCgA,9316
38
39
  datamint/entities/user.py,sha256=MREHDOsV9NOBEbXqiQ2ww6DmetN07CELId-ZQVpZCb8,620
39
40
  datamint/examples/__init__.py,sha256=zcYnd5nLVme9GCTPYH-1JpGo8xXK2WEYvhzcy_2alZc,39
40
41
  datamint/examples/example_projects.py,sha256=sU-Gxy7PPqA0WUfN-ZmXV-0YnwrnzpJ79lMXTJp2DzU,2804
@@ -43,10 +44,11 @@ datamint/experiment/__init__.py,sha256=5qQOMzoG17DEd1YnTF-vS0qiM-DGdbNh42EUo91CR
43
44
  datamint/experiment/_patcher.py,sha256=ZgbezoevAYhJsbiJTvWPALGTcUiMT371xddcTllt3H4,23296
44
45
  datamint/experiment/experiment.py,sha256=aHK9dRFdQTi569xgUg1KqlCZLHZpDmSH3g3ndPIZvXw,44546
45
46
  datamint/logging.yaml,sha256=tOMxtc2UmwlIMTK6ljtnBwTco1PNrPeq3mx2iMuSbiw,482
47
+ datamint/types.py,sha256=2OaY5QJvQIJKxyMNJYzxBksKCa9ZS2gb_ayJrByvu2Y,410
46
48
  datamint/utils/logging_utils.py,sha256=9pRoaPrWu2jOdDCiAoUsjEdP5ZwaealWL3hjUqFvx9g,4022
47
49
  datamint/utils/torchmetrics.py,sha256=lwU0nOtsSWfebyp7dvjlAggaqXtj5ohSEUXOg3L0hJE,2837
48
50
  datamint/utils/visualization.py,sha256=yaUVAOHar59VrGUjpAWv5eVvQSfztFG0eP9p5Vt3l-M,4470
49
- datamint-2.3.2.dist-info/METADATA,sha256=wybrUAOXVw_v1HzHAamP9uGMO7mAemu87lmDf7-0mbE,4203
50
- datamint-2.3.2.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
51
- datamint-2.3.2.dist-info/entry_points.txt,sha256=mn5H6jPjO-rY0W0CAZ6Z_KKWhMLvyVaSpoqk77jlTI4,145
52
- datamint-2.3.2.dist-info/RECORD,,
51
+ datamint-2.3.3.dist-info/METADATA,sha256=Cl4QTN2lWp65r0Sot2NP-n5h0A-1B-PqH0l_gT94XUY,4262
52
+ datamint-2.3.3.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
53
+ datamint-2.3.3.dist-info/entry_points.txt,sha256=mn5H6jPjO-rY0W0CAZ6Z_KKWhMLvyVaSpoqk77jlTI4,145
54
+ datamint-2.3.3.dist-info/RECORD,,