scale-nucleus 0.15.10b0__tar.gz → 0.16.2__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.
Files changed (89) hide show
  1. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/PKG-INFO +4 -7
  2. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/__init__.py +15 -6
  3. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/async_job.py +70 -2
  4. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/connection.py +2 -1
  5. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/dataset.py +26 -6
  6. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/dataset_item.py +1 -1
  7. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/job.py +1 -0
  8. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/logger.py +1 -1
  9. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/metrics/cuboid_metrics.py +0 -8
  10. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/metrics/cuboid_utils.py +9 -12
  11. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/metrics/segmentation_loader.py +1 -0
  12. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/metrics/segmentation_to_poly_metrics.py +1 -1
  13. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/metrics/segmentation_utils.py +1 -1
  14. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/scene.py +4 -1
  15. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/slice.py +50 -4
  16. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/test_launch_integration.py +8 -8
  17. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/utils.py +49 -15
  18. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/pyproject.toml +25 -19
  19. scale_nucleus-0.15.10b0/setup.py +0 -64
  20. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/LICENSE +0 -0
  21. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/README.md +0 -0
  22. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/cli/client.py +0 -0
  23. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/cli/datasets.py +0 -0
  24. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/cli/helpers/__init__.py +0 -0
  25. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/cli/helpers/nucleus_url.py +0 -0
  26. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/cli/helpers/web_helper.py +0 -0
  27. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/cli/install_completion.py +0 -0
  28. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/cli/jobs.py +0 -0
  29. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/cli/models.py +0 -0
  30. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/cli/nu.py +0 -0
  31. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/cli/reference.py +0 -0
  32. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/cli/slices.py +0 -0
  33. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/cli/tests.py +0 -0
  34. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/annotation.py +0 -0
  35. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/annotation_uploader.py +0 -0
  36. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/async_utils.py +0 -0
  37. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/autocurate.py +0 -0
  38. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/camera_params.py +0 -0
  39. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/constants.py +0 -0
  40. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/data_transfer_object/__init__.py +0 -0
  41. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/data_transfer_object/dataset_details.py +0 -0
  42. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/data_transfer_object/dataset_info.py +0 -0
  43. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/data_transfer_object/dataset_size.py +0 -0
  44. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/data_transfer_object/job_status.py +0 -0
  45. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/data_transfer_object/scenes_list.py +0 -0
  46. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/dataset_item_uploader.py +0 -0
  47. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/deprecation_warning.py +0 -0
  48. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/errors.py +0 -0
  49. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/metadata_manager.py +0 -0
  50. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/metrics/__init__.py +0 -0
  51. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/metrics/base.py +0 -0
  52. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/metrics/categorization_metrics.py +0 -0
  53. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/metrics/custom_types.py +0 -0
  54. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/metrics/errors.py +0 -0
  55. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/metrics/filtering.py +0 -0
  56. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/metrics/filters.py +0 -0
  57. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/metrics/metric_utils.py +0 -0
  58. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/metrics/polygon_metrics.py +0 -0
  59. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/metrics/polygon_utils.py +0 -0
  60. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/metrics/segmentation_metrics.py +0 -0
  61. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/model.py +0 -0
  62. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/model_run.py +0 -0
  63. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/package_not_installed.py +0 -0
  64. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/payload_constructor.py +0 -0
  65. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/prediction.py +0 -0
  66. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/pydantic_base.py +0 -0
  67. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/quaternion.py +0 -0
  68. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/retry_strategy.py +0 -0
  69. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/track.py +0 -0
  70. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/upload_response.py +0 -0
  71. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/url_utils.py +0 -0
  72. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/validate/__init__.py +0 -0
  73. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/validate/client.py +0 -0
  74. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/validate/constants.py +0 -0
  75. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/validate/data_transfer_objects/__init__.py +0 -0
  76. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/validate/data_transfer_objects/eval_function.py +0 -0
  77. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/validate/data_transfer_objects/scenario_test.py +0 -0
  78. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/validate/data_transfer_objects/scenario_test_evaluations.py +0 -0
  79. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/validate/data_transfer_objects/scenario_test_metric.py +0 -0
  80. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/validate/errors.py +0 -0
  81. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/validate/eval_functions/__init__.py +0 -0
  82. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/validate/eval_functions/available_eval_functions.py +0 -0
  83. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/validate/eval_functions/base_eval_function.py +0 -0
  84. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/validate/eval_functions/config_classes/__init__.py +0 -0
  85. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/validate/eval_functions/config_classes/segmentation.py +0 -0
  86. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/validate/scenario_test.py +0 -0
  87. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/validate/scenario_test_evaluation.py +0 -0
  88. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/validate/scenario_test_metric.py +0 -0
  89. {scale_nucleus-0.15.10b0 → scale_nucleus-0.16.2}/nucleus/validate/utils.py +0 -0
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: scale-nucleus
3
- Version: 0.15.10b0
3
+ Version: 0.16.2
4
4
  Summary: The official Python client library for Nucleus, the Data Platform for AI
5
5
  Home-page: https://scale.com/nucleus
6
6
  License: MIT
7
7
  Author: Scale AI Nucleus Team
8
8
  Author-email: nucleusapi@scaleapi.com
9
- Requires-Python: >=3.6.2,<4.0
9
+ Requires-Python: >=3.7,<4.0
10
10
  Classifier: License :: OSI Approved :: MIT License
11
11
  Classifier: Programming Language :: Python :: 3
12
12
  Classifier: Programming Language :: Python :: 3.7
@@ -19,11 +19,8 @@ Provides-Extra: metrics
19
19
  Requires-Dist: Pillow (>=7.1.2)
20
20
  Requires-Dist: Shapely (>=1.8.0) ; extra == "metrics"
21
21
  Requires-Dist: aiohttp (>=3.7.4,<4.0.0)
22
- Requires-Dist: astroid (<=2.12) ; python_full_version <= "3.7.0"
23
22
  Requires-Dist: click (>=7.1.2,<9.0)
24
- Requires-Dist: dataclasses (>=0.7,<0.8) ; python_full_version >= "3.6.1" and python_version < "3.7"
25
23
  Requires-Dist: nest-asyncio (>=1.5.1,<2.0.0)
26
- Requires-Dist: numpy (>=1.19.5) ; python_version >= "3.6" and python_version < "4.0"
27
24
  Requires-Dist: numpy (>=1.19.5) ; python_version >= "3.7" and python_version < "3.10"
28
25
  Requires-Dist: numpy (>=1.22.0) ; python_version >= "3.10"
29
26
  Requires-Dist: pydantic (>=1.8.2,<2.0.0)
@@ -33,8 +30,8 @@ Requires-Dist: rasterio (>=1.2.0) ; extra == "metrics"
33
30
  Requires-Dist: requests (>=2.23.0,<3.0.0)
34
31
  Requires-Dist: rich (>=10.15.2)
35
32
  Requires-Dist: scale-launch (>=0.1.0) ; (python_version >= "3.7" and python_version < "4.0") and (extra == "launch")
36
- Requires-Dist: scikit-learn (>=0.24.0)
37
- Requires-Dist: scipy (>=1.4.1)
33
+ Requires-Dist: scikit-learn (>=0.24.0) ; extra == "metrics"
34
+ Requires-Dist: scipy (>=1.4.1) ; extra == "metrics"
38
35
  Requires-Dist: shellingham (>=1.4.0,<2.0.0)
39
36
  Requires-Dist: tqdm (>=4.41.0,<5.0.0)
40
37
  Project-URL: Documentation, https://dashboard.scale.com/nucleus/docs/api
@@ -2,6 +2,7 @@
2
2
 
3
3
  __all__ = [
4
4
  "AsyncJob",
5
+ "EmbeddingsExportJob",
5
6
  "BoxAnnotation",
6
7
  "BoxPrediction",
7
8
  "CameraParams",
@@ -68,7 +69,7 @@ from .annotation import (
68
69
  Segment,
69
70
  SegmentationAnnotation,
70
71
  )
71
- from .async_job import AsyncJob
72
+ from .async_job import AsyncJob, EmbeddingsExportJob
72
73
  from .camera_params import CameraParams
73
74
  from .connection import Connection
74
75
  from .constants import (
@@ -170,7 +171,7 @@ class NucleusClient:
170
171
  self,
171
172
  api_key: Optional[str] = None,
172
173
  use_notebook: bool = False,
173
- endpoint: str = None,
174
+ endpoint: Optional[str] = None,
174
175
  ):
175
176
  self.api_key = self._set_api_key(api_key)
176
177
  self.tqdm_bar = tqdm.tqdm
@@ -236,7 +237,7 @@ class NucleusClient:
236
237
  def jobs(
237
238
  self,
238
239
  ) -> List[AsyncJob]:
239
- """Lists all jobs, see NucleusClinet.list_jobs(...) for advanced options
240
+ """Lists all jobs, see NucleusClient.list_jobs(...) for advanced options
240
241
 
241
242
  Returns:
242
243
  List of all AsyncJobs
@@ -343,7 +344,9 @@ class NucleusClient:
343
344
  return AsyncJob.from_json(payload=payload, client=self)
344
345
 
345
346
  def get_model(
346
- self, model_id: str = None, model_run_id: str = None
347
+ self,
348
+ model_id: Optional[str] = None,
349
+ model_run_id: Optional[str] = None,
347
350
  ) -> Model:
348
351
  """Fetches a model by its ID.
349
352
 
@@ -388,7 +391,10 @@ class NucleusClient:
388
391
  )
389
392
 
390
393
  def create_dataset_from_project(
391
- self, project_id: str, last_n_tasks: int = None, name: str = None
394
+ self,
395
+ project_id: str,
396
+ last_n_tasks: Optional[int] = None,
397
+ name: Optional[str] = None,
392
398
  ) -> Dataset:
393
399
  """Create a new dataset from an existing Scale or Rapid project.
394
400
 
@@ -922,7 +928,10 @@ class NucleusClient:
922
928
 
923
929
  @deprecated("Prefer calling Dataset.delete_annotations instead.")
924
930
  def delete_annotations(
925
- self, dataset_id: str, reference_ids: list = None, keep_history=True
931
+ self,
932
+ dataset_id: str,
933
+ reference_ids: Optional[list] = None,
934
+ keep_history=True,
926
935
  ) -> AsyncJob:
927
936
  dataset = self.get_dataset(dataset_id)
928
937
  return dataset.delete_annotations(reference_ids, keep_history)
@@ -1,6 +1,7 @@
1
1
  import time
2
2
  from dataclasses import dataclass
3
- from typing import Dict, List
3
+ from enum import Enum
4
+ from typing import Dict, List, Set
4
5
 
5
6
  import requests
6
7
 
@@ -16,6 +17,29 @@ from nucleus.utils import replace_double_slashes
16
17
  JOB_POLLING_INTERVAL = 5
17
18
 
18
19
 
20
+ class JobStatus(str, Enum):
21
+ QUEUED = "Queued"
22
+ RUNNING = "Running"
23
+ COMPLETED = "Completed"
24
+ ERRORED_DEPRECATED = "Errored"
25
+ ERRORED_SERVER = "Errored_Server" # Server Error
26
+ ERRORED_USER = "Errored_User" # User Error
27
+ ERRORED_PARTIAL = "Errored_Partial" # Partially Completed
28
+ ERRORED_HANGING = "Errored_Hanging" # Hanging
29
+ CANCELLED = "Cancelled"
30
+ RETRIED = "Retried"
31
+
32
+
33
+ JOB_ERROR_PREFIX = JobStatus.ERRORED_DEPRECATED
34
+ JOB_ERROR_STATES: Set[JobStatus] = {
35
+ JobStatus.ERRORED_DEPRECATED,
36
+ JobStatus.ERRORED_SERVER,
37
+ JobStatus.ERRORED_USER,
38
+ JobStatus.ERRORED_PARTIAL,
39
+ JobStatus.ERRORED_HANGING,
40
+ }
41
+
42
+
19
43
  @dataclass
20
44
  class AsyncJob:
21
45
  """Object used to check the status or errors of a long running asynchronous operation.
@@ -116,9 +140,25 @@ class AsyncJob:
116
140
  f"Finished at {time.perf_counter() - start_time} s: {status}"
117
141
  )
118
142
  final_status = status
119
- if final_status["status"] == "Errored":
143
+ if final_status["status"] in JOB_ERROR_STATES or final_status[
144
+ "status"
145
+ ].startswith(JOB_ERROR_PREFIX):
120
146
  raise JobError(final_status, self)
121
147
 
148
+ @classmethod
149
+ def from_id(cls, job_id: str, client: "NucleusClient"): # type: ignore # noqa: F821
150
+ """Creates a job instance from a specific job Id.
151
+
152
+ Parameters:
153
+ job_id: Defines the job Id
154
+ client: The client to use for the request.
155
+
156
+ Returns:
157
+ The specific AsyncMethod (or inherited) instance.
158
+ """
159
+ job = client.get_job(job_id)
160
+ return cls.from_json(job.__dict__, client)
161
+
122
162
  @classmethod
123
163
  def from_json(cls, payload: dict, client):
124
164
  # TODO: make private
@@ -131,6 +171,34 @@ class AsyncJob:
131
171
  )
132
172
 
133
173
 
174
+ class EmbeddingsExportJob(AsyncJob):
175
+ def result_urls(self, wait_for_completion=True) -> List[str]:
176
+ """Gets a list of signed Scale URLs for each embedding batch.
177
+
178
+ Parameters:
179
+ wait_for_completion: Defines whether the call shall wait for
180
+ the job to complete. Defaults to True
181
+
182
+ Returns:
183
+ A list of signed Scale URLs which contain batches of embeddings.
184
+
185
+ The files contain a JSON array of embedding records with the following schema:
186
+ [{
187
+ "reference_id": str,
188
+ "embedding_vector": List[float]
189
+ }]
190
+ """
191
+ if wait_for_completion:
192
+ self.sleep_until_complete(verbose_std_out=False)
193
+
194
+ status = self.status()
195
+
196
+ if status["status"] != "Completed":
197
+ raise JobError(status, self)
198
+
199
+ return status["message"]["result"] # type: ignore
200
+
201
+
134
202
  class JobError(Exception):
135
203
  def __init__(self, job_status: Dict[str, str], job: AsyncJob):
136
204
  final_status_message = job_status["message"]
@@ -1,4 +1,5 @@
1
1
  import time
2
+ from typing import Optional
2
3
 
3
4
  import requests
4
5
 
@@ -11,7 +12,7 @@ from .retry_strategy import RetryStrategy
11
12
  class Connection:
12
13
  """Wrapper of HTTP requests to the Nucleus endpoint."""
13
14
 
14
- def __init__(self, api_key: str, endpoint: str = None):
15
+ def __init__(self, api_key: str, endpoint: Optional[str] = None):
15
16
  self.api_key = api_key
16
17
  self.endpoint = endpoint
17
18
 
@@ -15,7 +15,7 @@ from typing import (
15
15
  import requests
16
16
 
17
17
  from nucleus.annotation_uploader import AnnotationUploader, PredictionUploader
18
- from nucleus.async_job import AsyncJob
18
+ from nucleus.async_job import AsyncJob, EmbeddingsExportJob
19
19
  from nucleus.prediction import Prediction, from_json
20
20
  from nucleus.track import Track
21
21
  from nucleus.url_utils import sanitize_string_args
@@ -1230,7 +1230,9 @@ class Dataset:
1230
1230
  return AsyncJob.from_json(response, self._client)
1231
1231
 
1232
1232
  def create_object_index(
1233
- self, model_run_id: str = None, gt_only: bool = None
1233
+ self,
1234
+ model_run_id: Optional[str] = None,
1235
+ gt_only: Optional[bool] = None,
1234
1236
  ):
1235
1237
  """Creates or updates object index by generating embeddings for objects that do not already have embeddings.
1236
1238
 
@@ -1419,18 +1421,34 @@ class Dataset:
1419
1421
 
1420
1422
  def export_embeddings(
1421
1423
  self,
1422
- ) -> List[Dict[str, Union[str, List[float]]]]:
1424
+ asynchronous: bool = True,
1425
+ ) -> Union[List[Dict[str, Union[str, List[float]]]], EmbeddingsExportJob]:
1423
1426
  """Fetches a pd.DataFrame-ready list of dataset embeddings.
1424
1427
 
1428
+ Parameters:
1429
+ asynchronous: Whether or not to process the export asynchronously (and
1430
+ return an :class:`EmbeddingsExportJob` object). Default is True.
1431
+
1425
1432
  Returns:
1426
- A list, where each item is a dict with two keys representing a row
1433
+ If synchronous, a list where each item is a dict with two keys representing a row
1427
1434
  in the dataset::
1428
1435
 
1429
1436
  List[{
1430
1437
  "reference_id": str,
1431
1438
  "embedding_vector": List[float]
1432
1439
  }]
1440
+
1441
+ Otherwise, returns an :class:`EmbeddingsExportJob` object.
1433
1442
  """
1443
+ if asynchronous:
1444
+ api_payload = self._client.make_request(
1445
+ payload=None,
1446
+ route=f"dataset/{self.id}/async_export_embeddings",
1447
+ requests_command=requests.post,
1448
+ )
1449
+
1450
+ return EmbeddingsExportJob.from_json(api_payload, self._client)
1451
+
1434
1452
  api_payload = self._client.make_request(
1435
1453
  payload=None,
1436
1454
  route=f"dataset/{self.id}/embeddings",
@@ -1439,7 +1457,7 @@ class Dataset:
1439
1457
  return api_payload # type: ignore
1440
1458
 
1441
1459
  def delete_annotations(
1442
- self, reference_ids: list = None, keep_history: bool = True
1460
+ self, reference_ids: Optional[list] = None, keep_history: bool = True
1443
1461
  ) -> AsyncJob:
1444
1462
  """Deletes all annotations associated with the specified item reference IDs.
1445
1463
 
@@ -1555,7 +1573,9 @@ class Dataset:
1555
1573
  )
1556
1574
  return format_scale_task_info_response(response)
1557
1575
 
1558
- def calculate_evaluation_metrics(self, model, options: dict = None):
1576
+ def calculate_evaluation_metrics(
1577
+ self, model, options: Optional[dict] = None
1578
+ ):
1559
1579
  """Starts computation of evaluation metrics for a model on the dataset.
1560
1580
 
1561
1581
  To update matches and metrics calculated for a model on a given dataset you
@@ -1,5 +1,5 @@
1
1
  import json
2
- import os.path
2
+ import os
3
3
  from collections import Counter
4
4
  from dataclasses import dataclass
5
5
  from enum import Enum
@@ -27,6 +27,7 @@ class CustomerJobTypes(str, Enum):
27
27
  CLONE_DATASET = "cloneDataset"
28
28
  METADATA_UPDATE = "metadataUpdate"
29
29
  TRIGGER_EVALUATE = "triggerEvaluate"
30
+ EXPORT_EMBEDDINGS = "exportEmbeddings"
30
31
 
31
32
  def __contains__(self, item):
32
33
  try:
@@ -5,5 +5,5 @@ import requests
5
5
  logger = logging.getLogger(__name__)
6
6
  logging.basicConfig()
7
7
  logging.getLogger(
8
- requests.packages.urllib3.__package__ # pylint: disable=no-member
8
+ requests.packages.urllib3.__package__ # type: ignore # pylint: disable=no-member
9
9
  ).setLevel(logging.ERROR)
@@ -174,7 +174,6 @@ class CuboidPrecision(CuboidMetric):
174
174
  prediction_filters: Optional[
175
175
  Union[ListOfOrAndFilters, ListOfAndFilters]
176
176
  ] = None,
177
- use_2d_iou: bool = False,
178
177
  ):
179
178
  """Initializes CuboidIOU object.
180
179
 
@@ -192,13 +191,11 @@ class CuboidPrecision(CuboidMetric):
192
191
  predicates. The innermost structures each describe a single column predicate. The list of inner predicates is
193
192
  interpreted as a conjunction (AND), forming a more selective and multiple column predicate.
194
193
  Finally, the most outer list combines these filters as a disjunction (OR).
195
- use_2d_iou: whether to use 2D or 3D IOU for precision calculation.
196
194
  """
197
195
  assert (
198
196
  0 <= iou_threshold <= 1
199
197
  ), "IoU threshold must be between 0 and 1."
200
198
  self.iou_threshold = iou_threshold
201
- self.use_2d_iou = use_2d_iou
202
199
  super().__init__(
203
200
  enforce_label_match=enforce_label_match,
204
201
  confidence_threshold=confidence_threshold,
@@ -215,7 +212,6 @@ class CuboidPrecision(CuboidMetric):
215
212
  predictions,
216
213
  annotations,
217
214
  threshold_in_overlap_ratio=self.iou_threshold,
218
- use_2d=self.use_2d_iou,
219
215
  )
220
216
  weight = stats["tp_sum"] + stats["fp_sum"]
221
217
  precision = stats["tp_sum"] / max(weight, sys.float_info.epsilon)
@@ -237,7 +233,6 @@ class CuboidRecall(CuboidMetric):
237
233
  prediction_filters: Optional[
238
234
  Union[ListOfOrAndFilters, ListOfAndFilters]
239
235
  ] = None,
240
- use_2d_iou: bool = False,
241
236
  ):
242
237
  """Initializes CuboidIOU object.
243
238
 
@@ -245,13 +240,11 @@ class CuboidRecall(CuboidMetric):
245
240
  enforce_label_match: whether to enforce that annotation and prediction labels must match. Defaults to True
246
241
  iou_threshold: IOU threshold to consider detection as valid. Must be in [0, 1]. Default 0.0
247
242
  confidence_threshold: minimum confidence threshold for predictions. Must be in [0, 1]. Default 0.0
248
- use_2d_iou: whether to use 2D or 3D IOU for calculation.
249
243
  """
250
244
  assert (
251
245
  0 <= iou_threshold <= 1
252
246
  ), "IoU threshold must be between 0 and 1."
253
247
  self.iou_threshold = iou_threshold
254
- self.use_2d_iou = use_2d_iou
255
248
  super().__init__(
256
249
  enforce_label_match=enforce_label_match,
257
250
  confidence_threshold=confidence_threshold,
@@ -268,7 +261,6 @@ class CuboidRecall(CuboidMetric):
268
261
  predictions,
269
262
  annotations,
270
263
  threshold_in_overlap_ratio=self.iou_threshold,
271
- use_2d_iou=self.use_2d_iou
272
264
  )
273
265
  weight = stats["tp_sum"] + stats["fn_sum"]
274
266
  recall = stats["tp_sum"] / max(weight, sys.float_info.epsilon)
@@ -1,5 +1,5 @@
1
1
  from functools import wraps
2
- from typing import Dict, List, Tuple
2
+ from typing import Dict, List, Optional, Tuple
3
3
 
4
4
  import numpy as np
5
5
 
@@ -176,8 +176,8 @@ def get_batch_cuboid_corners(
176
176
  xyz: "np.ndarray",
177
177
  wlh: "np.ndarray",
178
178
  yaw: "np.ndarray",
179
- pitch: "np.ndarray" = None,
180
- roll: "np.ndarray" = None,
179
+ pitch: Optional["np.ndarray"] = None,
180
+ roll: Optional["np.ndarray"] = None,
181
181
  scale_convention: bool = True,
182
182
  ) -> "np.ndarray":
183
183
  """
@@ -211,7 +211,9 @@ def get_batch_cuboid_corners(
211
211
 
212
212
 
213
213
  def get_batch_rotation_matrices(
214
- yaw: "np.ndarray", pitch: "np.ndarray" = None, roll: "np.ndarray" = None
214
+ yaw: "np.ndarray",
215
+ pitch: Optional["np.ndarray"] = None,
216
+ roll: Optional["np.ndarray"] = None,
215
217
  ) -> "np.ndarray":
216
218
  if pitch is None:
217
219
  pitch = np.zeros_like(yaw)
@@ -245,16 +247,14 @@ def associate_cuboids_on_iou(
245
247
  wlh_1: "np.ndarray",
246
248
  yaw_1: "np.ndarray",
247
249
  threshold_in_overlap_ratio: float = 0.1,
248
- use_2d_iou: bool = False,
249
250
  ) -> List[Tuple[int, int]]:
250
251
  if xyz_0.shape[0] < 1 or xyz_1.shape[0] < 1:
251
252
  return []
252
- iou_3d, iou_2d = compute_outer_iou(xyz_0, wlh_0, yaw_0, xyz_1, wlh_1, yaw_1)
253
- iou = iou_2d if use_2d_iou else iou_3d
253
+ iou_matrix, _ = compute_outer_iou(xyz_0, wlh_0, yaw_0, xyz_1, wlh_1, yaw_1)
254
254
  mapping = []
255
- for i, m in enumerate(iou.max(axis=1)):
255
+ for i, m in enumerate(iou_matrix.max(axis=1)):
256
256
  if m >= threshold_in_overlap_ratio:
257
- mapping.append((i, iou[i].argmax()))
257
+ mapping.append((i, iou_matrix[i].argmax()))
258
258
  return mapping
259
259
 
260
260
 
@@ -262,7 +262,6 @@ def recall_precision(
262
262
  prediction: List[CuboidPrediction],
263
263
  groundtruth: List[CuboidAnnotation],
264
264
  threshold_in_overlap_ratio: float,
265
- use_2d_iou: bool = False,
266
265
  ) -> Dict[str, float]:
267
266
  """
268
267
  Calculates the precision and recall of each lidar frame.
@@ -271,7 +270,6 @@ def recall_precision(
271
270
  :param predictions: list of cuboid annotation predictions.
272
271
  :param ground_truth: list of cuboid annotation groundtruths.
273
272
  :param threshold: IOU threshold to consider detection as valid. Must be in [0, 1].
274
- :param use_2d_iou: flag whether to use 2d or 3d iou for evaluation.
275
273
  """
276
274
 
277
275
  tp_sum = 0
@@ -298,7 +296,6 @@ def recall_precision(
298
296
  gt_items["wlh"],
299
297
  gt_items["yaw"] + np.pi / 2,
300
298
  threshold_in_overlap_ratio=threshold_in_overlap_ratio,
301
- use_2d_iou=use_2d_iou,
302
299
  )
303
300
 
304
301
  for pred_id, gt_id in mapping:
@@ -25,6 +25,7 @@ class InMemoryLoader(SegmentationMaskLoader):
25
25
 
26
26
  def __init__(self, url_to_array: Dict[str, "np.ndarray"]):
27
27
  self.url_to_array = url_to_array
28
+ super().__init__()
28
29
 
29
30
  def fetch(self, url: str):
30
31
  array = self.url_to_array[url]
@@ -115,7 +115,7 @@ class SegmentationMaskToPolyMetric(Metric):
115
115
  if prediction:
116
116
  if self.mode == SegToPolyMode.GENERATE_GT_FROM_POLY:
117
117
  pred_img = self.loader.fetch(prediction.mask_url)
118
- ann_img, segments = rasterize_polygons_to_segmentation_mask(
118
+ ann_img, segments = rasterize_polygons_to_segmentation_mask( # type: ignore
119
119
  annotations.polygon_annotations
120
120
  + annotations.box_annotations, # type:ignore
121
121
  pred_img.shape,
@@ -103,7 +103,7 @@ def fast_confusion_matrix(
103
103
  mask = (label_true >= 0) & (label_true < n_class)
104
104
  hist = np.bincount(
105
105
  n_class * label_true[mask].astype(int) + label_pred[mask],
106
- minlength=n_class ** 2,
106
+ minlength=n_class**2,
107
107
  ).reshape(n_class, n_class)
108
108
  return hist
109
109
 
@@ -596,7 +596,10 @@ class VideoScene(ABC):
596
596
  ), "No list of items is accepted when uploading a video_location unless you are using privacy mode"
597
597
 
598
598
  def add_item(
599
- self, item: DatasetItem, index: int = None, update: bool = False
599
+ self,
600
+ item: DatasetItem,
601
+ index: Optional[int] = None,
602
+ update: bool = False,
600
603
  ) -> None:
601
604
  """Adds DatasetItem to the specified index for videos uploaded as an array of images.
602
605
 
@@ -7,10 +7,11 @@ from typing import Dict, Iterable, List, Optional, Set, Tuple, Union
7
7
  import requests
8
8
 
9
9
  from nucleus.annotation import Annotation
10
- from nucleus.async_job import AsyncJob
10
+ from nucleus.async_job import AsyncJob, EmbeddingsExportJob
11
11
  from nucleus.constants import EXPORT_FOR_TRAINING_KEY, EXPORTED_ROWS, ITEMS_KEY
12
12
  from nucleus.dataset_item import DatasetItem
13
13
  from nucleus.errors import NucleusAPIError
14
+ from nucleus.prediction import Prediction
14
15
  from nucleus.prediction import from_json as prediction_from_json
15
16
  from nucleus.scene import Scene
16
17
  from nucleus.utils import (
@@ -335,7 +336,7 @@ class Slice:
335
336
 
336
337
  def append(
337
338
  self,
338
- reference_ids: List[str] = None,
339
+ reference_ids: Optional[List[str]] = None,
339
340
  ) -> dict:
340
341
  """Appends existing DatasetItems from a Dataset to a Slice.
341
342
 
@@ -458,6 +459,35 @@ class Slice:
458
459
  )
459
460
  return convert_export_payload(api_payload[EXPORTED_ROWS], True)
460
461
 
462
+ def export_raw_json(
463
+ self,
464
+ ) -> List[Union[DatasetItem, Annotation, Prediction, Scene]]:
465
+ """Exports object slices in a raw JSON format. Note that it currently does not support item-level slices.
466
+
467
+ For each object or match in an object slice, this method exports the following information:
468
+ - The item that contains the object.
469
+ - The prediction and/or annotation (both, if the slice is based on IOU matches).
470
+ - If the object is part of a scene, it includes scene-level attributes in the export.
471
+
472
+ Returns:
473
+ An iterable where each element is a dictionary containing JSON-formatted data.
474
+ ::
475
+
476
+ List[{
477
+ "item": DatasetItem (as JSON),
478
+ "annotation": BoxAnnotation/CuboidAnnotation (as JSON)
479
+ "prediction": BoxPrediction/CuboidPrediction (as JSON)
480
+ "scene": Scene (as JSON)
481
+ }
482
+ }]
483
+ """
484
+ api_payload = self._client.make_request(
485
+ payload=None,
486
+ route=f"slice/{self.id}/export_raw_json",
487
+ requests_command=requests.get,
488
+ )
489
+ return api_payload
490
+
461
491
  def export_predictions_generator(
462
492
  self, model
463
493
  ) -> Iterable[Dict[str, Union[DatasetItem, Dict[str, List[Annotation]]]]]:
@@ -570,17 +600,33 @@ class Slice:
570
600
 
571
601
  def export_embeddings(
572
602
  self,
573
- ) -> List[Dict[str, Union[str, List[float]]]]:
603
+ asynchronous: bool = True,
604
+ ) -> Union[List[Dict[str, Union[str, List[float]]]], EmbeddingsExportJob]:
574
605
  """Fetches a pd.DataFrame-ready list of slice embeddings.
575
606
 
607
+ Parameters:
608
+ asynchronous: Whether or not to process the export asynchronously (and
609
+ return an :class:`EmbeddingsExportJob` object). Default is True.
610
+
576
611
  Returns:
577
- A list where each element is a columnar mapping::
612
+ If synchronous, a list where each element is a columnar mapping::
578
613
 
579
614
  List[{
580
615
  "reference_id": str,
581
616
  "embedding_vector": List[float]
582
617
  }]
618
+
619
+ Otherwise, returns an :class:`EmbeddingsExportJob` object.
583
620
  """
621
+ if asynchronous:
622
+ api_payload = self._client.make_request(
623
+ payload=None,
624
+ route=f"dataset/{self.id}/async_export_embeddings",
625
+ requests_command=requests.post,
626
+ )
627
+
628
+ return EmbeddingsExportJob.from_json(api_payload, self._client)
629
+
584
630
  api_payload = self._client.make_request(
585
631
  payload=None,
586
632
  route=f"slice/{self.id}/embeddings",
@@ -144,8 +144,8 @@ _OUTLINE_COLOR = (0, 255, 0, 255)
144
144
  def visualize_box_launch_bundle(
145
145
  img_file: str,
146
146
  load_predict_fn: Callable,
147
- load_model_fn: Callable = None,
148
- model: Any = None,
147
+ load_model_fn: Optional[Callable] = None,
148
+ model: Optional[Any] = None,
149
149
  show_image: bool = False,
150
150
  max_annotations: int = 5,
151
151
  ) -> Image:
@@ -194,8 +194,8 @@ def visualize_box_launch_bundle(
194
194
  def run_category_launch_bundle(
195
195
  img_file: str,
196
196
  load_predict_fn: Callable,
197
- load_model_fn: Callable = None,
198
- model: Any = None,
197
+ load_model_fn: Optional[Callable] = None,
198
+ model: Optional[Any] = None,
199
199
  ):
200
200
  """
201
201
  Run this function locally to test if your image categorization model returns a format consumable by Launch + Nucleus
@@ -218,8 +218,8 @@ def run_category_launch_bundle(
218
218
  def visualize_line_launch_bundle(
219
219
  img_file: str,
220
220
  load_predict_fn: Callable,
221
- load_model_fn: Callable = None,
222
- model: Any = None,
221
+ load_model_fn: Optional[Callable] = None,
222
+ model: Optional[Any] = None,
223
223
  show_image: bool = False,
224
224
  max_annotations: int = 5,
225
225
  ) -> Image:
@@ -266,8 +266,8 @@ def visualize_line_launch_bundle(
266
266
  def visualize_polygon_launch_bundle(
267
267
  img_file: str,
268
268
  load_predict_fn: Callable,
269
- load_model_fn: Callable = None,
270
- model: Any = None,
269
+ load_model_fn: Optional[Callable] = None,
270
+ model: Optional[Any] = None,
271
271
  show_image: bool = False,
272
272
  max_annotations: int = 5,
273
273
  ) -> Image:
@@ -221,6 +221,7 @@ def format_scale_task_info_response(response: dict) -> Union[Dict, List[Dict]]:
221
221
  return ret
222
222
 
223
223
 
224
+ # pylint: disable=too-many-branches
224
225
  def convert_export_payload(api_payload, has_predictions: bool = False):
225
226
  """Helper function to convert raw JSON to API objects
226
227
 
@@ -239,33 +240,66 @@ def convert_export_payload(api_payload, has_predictions: bool = False):
239
240
  if row.get(SEGMENTATION_TYPE) is not None:
240
241
  segmentation = row[SEGMENTATION_TYPE]
241
242
  segmentation[REFERENCE_ID_KEY] = row[ITEM_KEY][REFERENCE_ID_KEY]
242
- annotations[SEGMENTATION_TYPE] = SegmentationAnnotation.from_json(
243
- segmentation
244
- )
243
+ if not has_predictions:
244
+ annotations[
245
+ SEGMENTATION_TYPE
246
+ ] = SegmentationAnnotation.from_json(segmentation)
247
+ else:
248
+ annotations[
249
+ SEGMENTATION_TYPE
250
+ ] = SegmentationPrediction.from_json(segmentation)
245
251
  for polygon in row[POLYGON_TYPE]:
246
252
  polygon[REFERENCE_ID_KEY] = row[ITEM_KEY][REFERENCE_ID_KEY]
247
- annotations[POLYGON_TYPE].append(
248
- PolygonAnnotation.from_json(polygon)
249
- )
253
+ if not has_predictions:
254
+ annotations[POLYGON_TYPE].append(
255
+ PolygonAnnotation.from_json(polygon)
256
+ )
257
+ else:
258
+ annotations[POLYGON_TYPE].append(
259
+ PolygonPrediction.from_json(polygon)
260
+ )
250
261
  for line in row[LINE_TYPE]:
251
262
  line[REFERENCE_ID_KEY] = row[ITEM_KEY][REFERENCE_ID_KEY]
252
- annotations[LINE_TYPE].append(LineAnnotation.from_json(line))
263
+ if not has_predictions:
264
+ annotations[LINE_TYPE].append(LineAnnotation.from_json(line))
265
+ else:
266
+ annotations[LINE_TYPE].append(LinePrediction.from_json(line))
253
267
  for keypoints in row[KEYPOINTS_TYPE]:
254
268
  keypoints[REFERENCE_ID_KEY] = row[ITEM_KEY][REFERENCE_ID_KEY]
255
- annotations[KEYPOINTS_TYPE].append(
256
- KeypointsAnnotation.from_json(keypoints)
257
- )
269
+ if not has_predictions:
270
+ annotations[KEYPOINTS_TYPE].append(
271
+ KeypointsAnnotation.from_json(keypoints)
272
+ )
273
+ else:
274
+ annotations[KEYPOINTS_TYPE].append(
275
+ KeypointsPrediction.from_json(keypoints)
276
+ )
258
277
  for box in row[BOX_TYPE]:
259
278
  box[REFERENCE_ID_KEY] = row[ITEM_KEY][REFERENCE_ID_KEY]
260
- annotations[BOX_TYPE].append(BoxAnnotation.from_json(box))
279
+ if not has_predictions:
280
+ annotations[BOX_TYPE].append(BoxAnnotation.from_json(box))
281
+ else:
282
+ annotations[BOX_TYPE].append(BoxPrediction.from_json(box))
261
283
  for cuboid in row[CUBOID_TYPE]:
262
284
  cuboid[REFERENCE_ID_KEY] = row[ITEM_KEY][REFERENCE_ID_KEY]
263
- annotations[CUBOID_TYPE].append(CuboidAnnotation.from_json(cuboid))
285
+ if not has_predictions:
286
+ annotations[CUBOID_TYPE].append(
287
+ CuboidAnnotation.from_json(cuboid)
288
+ )
289
+ else:
290
+ annotations[CUBOID_TYPE].append(
291
+ CuboidPrediction.from_json(cuboid)
292
+ )
264
293
  for category in row[CATEGORY_TYPE]:
265
294
  category[REFERENCE_ID_KEY] = row[ITEM_KEY][REFERENCE_ID_KEY]
266
- annotations[CATEGORY_TYPE].append(
267
- CategoryAnnotation.from_json(category)
268
- )
295
+ if not has_predictions:
296
+ annotations[CATEGORY_TYPE].append(
297
+ CategoryAnnotation.from_json(category)
298
+ )
299
+ else:
300
+ annotations[CATEGORY_TYPE].append(
301
+ CategoryPrediction.from_json(category)
302
+ )
269
303
  for multicategory in row[MULTICATEGORY_TYPE]:
270
304
  multicategory[REFERENCE_ID_KEY] = row[ITEM_KEY][REFERENCE_ID_KEY]
271
305
  annotations[MULTICATEGORY_TYPE].append(
@@ -1,6 +1,6 @@
1
1
  [tool.black]
2
2
  line-length = 79
3
- target-version = ['py37']
3
+ target-version = ['py38']
4
4
  include = '\.pyi?$'
5
5
  exclude = '''
6
6
  (
@@ -19,9 +19,13 @@ exclude = '''
19
19
  )
20
20
  '''
21
21
 
22
+ [tool.ruff]
23
+ line-length = 79
24
+ ignore = ["E501", "E741", "E731", "F401"] # Easy ignore for getting it running - can be reevaluated later
25
+
22
26
  [tool.poetry]
23
27
  name = "scale-nucleus"
24
- version = "0.15.10b0"
28
+ version = "0.16.2"
25
29
  description = "The official Python client library for Nucleus, the Data Platform for AI"
26
30
  license = "MIT"
27
31
  authors = ["Scale AI Nucleus Team <nucleusapi@scaleapi.com>"]
@@ -32,42 +36,38 @@ documentation = "https://dashboard.scale.com/nucleus/docs/api"
32
36
  packages = [{include="nucleus"}, {include="cli"}]
33
37
 
34
38
  [tool.poetry.dependencies]
35
- python = ">=3.6.2,<4.0"
39
+ python = ">=3.7,<4.0"
36
40
  requests = "^2.23.0"
37
41
  tqdm = "^4.41.0"
38
- dataclasses = { version = "^0.7", python = "^3.6.1, <3.7" }
39
42
  aiohttp = "^3.7.4"
40
43
  nest-asyncio = "^1.5.1"
41
44
  pydantic = "^1.8.2"
42
- numpy = [{ version = ">=1.19.5", python = ">=3.7,<3.10" }, { version = ">=1.19.5", python = "^3.6" }, { version = ">=1.22.0", python = ">=3.10"}]
43
- scipy = ">=1.4.1" # NOTE: COLAB has 1.4.1 and has problems updating
45
+ numpy = [{ version = ">=1.19.5", python = ">=3.7,<3.10" }, { version = ">=1.22.0", python = ">=3.10"}]
46
+ scipy = { version=">=1.4.1", optional = true } # NOTE: COLAB has 1.4.1 and has problems updating
44
47
  click = ">=7.1.2,<9.0" # NOTE: COLAB has 7.1.2 and has problems updating
45
48
  rich = ">=10.15.2"
46
49
  shellingham = "^1.4.0"
47
- scikit-learn = ">=0.24.0"
50
+ scikit-learn = { version =">=0.24.0", optional = true }
48
51
  Shapely = { version = ">=1.8.0", optional = true }
49
52
  rasterio = { version = ">=1.2.0", optional = true }
50
53
  Pillow = ">=7.1.2"
51
54
  scale-launch = { version = ">=0.1.0", python = ">=3.7,<4.0", optional = true}
52
- astroid = { version = "<=2.12", python = "<=3.7"} # This hint is vital for dependency resolution ¯\_(ツ)_/¯
53
55
  questionary = "^1.10.0"
54
56
  python-dateutil = "^2.8.2"
55
57
 
56
58
  [tool.poetry.dev-dependencies]
57
59
  pytest = [
58
- { version = ">=6.1.3", python = ">=3.6.2,<3.7" },
59
60
  { version = ">=7.1.1", python = ">=3.7,<4.0" }
60
61
  ]
61
- pylint = "^2.7.4"
62
- black = "^20.8b1"
63
- flake8 = "^3.9.1"
64
- mypy = "^0.812"
62
+ pylint = ">=2.7.4"
63
+ black = "^23.3.0"
64
+ mypy = ">=0.812"
65
65
  coverage = "^5.5"
66
- pre-commit = "^2.12.1"
67
- jupyterlab = "^3.1.10"
68
- isort = "^5.10.1"
69
- absl-py = "^0.13.0"
70
- Sphinx = "^4.2.0"
66
+ pre-commit = ">=2.12.1"
67
+ jupyterlab = ">=3.1.10,<4.0"
68
+ isort = ">=5.10.1"
69
+ absl-py = ">=0.13.0"
70
+ Sphinx = ">=4.2.0,<5"
71
71
  sphinx-autobuild = "^2021.3.14"
72
72
  furo = ">=2021.10.9"
73
73
  sphinx-autoapi = "^1.8.4"
@@ -77,10 +77,16 @@ python-dateutil = "^2.8.2"
77
77
  nu = "cli.nu:nu"
78
78
 
79
79
  [tool.poetry.extras]
80
- metrics = ["Shapely", "rasterio"]
80
+ metrics = ["Shapely", "rasterio", "scipy", "scikit-learn"]
81
81
  launch = ["scale-launch"]
82
82
 
83
83
 
84
+ [tool.poetry.group.dev.dependencies]
85
+ ruff = "^0.0.290"
86
+ types-setuptools = "^68.2.0.0"
87
+ types-requests = "^2.31.0.2"
88
+ types-python-dateutil = "^2.8.19.14"
89
+
84
90
  [tool.pytest.ini_options]
85
91
  markers = [
86
92
  "integration: marks tests as slow (deselect with '-m \"not integration\"')",
@@ -1,64 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- from setuptools import setup
3
-
4
- packages = \
5
- ['cli',
6
- 'cli.helpers',
7
- 'nucleus',
8
- 'nucleus.data_transfer_object',
9
- 'nucleus.metrics',
10
- 'nucleus.validate',
11
- 'nucleus.validate.data_transfer_objects',
12
- 'nucleus.validate.eval_functions',
13
- 'nucleus.validate.eval_functions.config_classes']
14
-
15
- package_data = \
16
- {'': ['*']}
17
-
18
- install_requires = \
19
- ['Pillow>=7.1.2',
20
- 'aiohttp>=3.7.4,<4.0.0',
21
- 'click>=7.1.2,<9.0',
22
- 'nest-asyncio>=1.5.1,<2.0.0',
23
- 'pydantic>=1.8.2,<2.0.0',
24
- 'python-dateutil>=2.8.2,<3.0.0',
25
- 'questionary>=1.10.0,<2.0.0',
26
- 'requests>=2.23.0,<3.0.0',
27
- 'rich>=10.15.2',
28
- 'scikit-learn>=0.24.0',
29
- 'scipy>=1.4.1',
30
- 'shellingham>=1.4.0,<2.0.0',
31
- 'tqdm>=4.41.0,<5.0.0']
32
-
33
- extras_require = \
34
- {':python_full_version <= "3.7.0"': ['astroid<=2.12'],
35
- ':python_full_version >= "3.6.1" and python_version < "3.7"': ['dataclasses>=0.7,<0.8'],
36
- ':python_version >= "3.10"': ['numpy>=1.22.0'],
37
- ':python_version >= "3.6" and python_version < "4.0"': ['numpy>=1.19.5'],
38
- ':python_version >= "3.7" and python_version < "3.10"': ['numpy>=1.19.5'],
39
- 'launch:python_version >= "3.7" and python_version < "4.0"': ['scale-launch>=0.1.0'],
40
- 'metrics': ['Shapely>=1.8.0', 'rasterio>=1.2.0']}
41
-
42
- entry_points = \
43
- {'console_scripts': ['nu = cli.nu:nu']}
44
-
45
- setup_kwargs = {
46
- 'name': 'scale-nucleus',
47
- 'version': '0.15.10b0',
48
- 'description': 'The official Python client library for Nucleus, the Data Platform for AI',
49
- 'long_description': '# Nucleus\n\nhttps://dashboard.scale.com/nucleus\n\nAggregate metrics in ML are not good enough. To improve production ML, you need to understand their qualitative failure modes, fix them by gathering more data, and curate diverse scenarios.\n\nScale Nucleus helps you:\n\n- Visualize your data\n- Curate interesting slices within your dataset\n- Review and manage annotations\n- Measure and debug your model performance\n\nNucleus is a new way—the right way—to develop ML models, helping us move away from the concept of one dataset and towards a paradigm of collections of scenarios.\n\n## Installation\n\n`$ pip install scale-nucleus`\n\n## CLI installation\n\nWe recommend installing the CLI via `pipx` (https://pypa.github.io/pipx/installation/). This makes sure that\nthe CLI does not interfere with you system packages and is accessible from your favorite terminal.\n\nFor MacOS:\n\n```bash\nbrew install pipx\npipx ensurepath\npipx install scale-nucleus\n# Optional installation of shell completion (for bash, zsh or fish)\nnu install-completions\n```\n\nOtherwise, install via pip (requires pip 19.0 or later):\n\n```bash\npython3 -m pip install --user pipx\npython3 -m pipx ensurepath\npython3 -m pipx install scale-nucleus\n# Optional installation of shell completion (for bash, zsh or fish)\nnu install-completions\n```\n\n## Common issues/FAQ\n\n### Outdated Client\n\nNucleus is iterating rapidly and as a result we do not always perfectly preserve backwards compatibility with older versions of the client. If you run into any unexpected error, it\'s a good idea to upgrade your version of the client by running\n\n```\npip install --upgrade scale-nucleus\n```\n\n## Usage\n\nFor the most up to date documentation, reference: https://dashboard.scale.com/nucleus/docs/api?language=python.\n\n## For Developers\n\nClone from github and install as editable\n\n```\ngit clone git@github.com:scaleapi/nucleus-python-client.git\ncd nucleus-python-client\npip3 install poetry\npoetry install\n```\n\nPlease install the pre-commit hooks by running the following command:\n\n```python\npoetry run pre-commit install\n```\n\nWhen releasing a new version please add release notes to the changelog in `CHANGELOG.md`.\n\n**Best practices for testing:**\n(1). Please run pytest from the root directory of the repo, i.e.\n\n```\npoetry run pytest tests/test_dataset.py\n```\n\n(2) To skip slow integration tests that have to wait for an async job to start.\n\n```\npoetry run pytest -m "not integration"\n```\n\n## Pydantic Models\n\nPrefer using [Pydantic](https://pydantic-docs.helpmanual.io/usage/models/) models rather than creating raw dictionaries\nor dataclasses to send or receive over the wire as JSONs. Pydantic is created with data validation in mind and provides very clear error\nmessages when it encounters a problem with the payload.\n\nThe Pydantic model(s) should mirror the payload to send. To represent a JSON payload that looks like this:\n\n```json\n{\n "example_json_with_info": {\n "metadata": {\n "frame": 0\n },\n "reference_id": "frame0",\n "url": "s3://example/scale_nucleus/2021/lidar/0038711321865000.json",\n "type": "pointcloud"\n },\n "example_image_with_info": {\n "metadata": {\n "author": "Picasso"\n },\n "reference_id": "frame0",\n "url": "s3://bucket/0038711321865000.jpg",\n "type": "image"\n }\n}\n```\n\nCould be represented as the following structure. Note that the field names map to the JSON keys and the usage of field\nvalidators (`@validator`).\n\n```python\nimport os.path\nfrom pydantic import BaseModel, validator\nfrom typing import Literal\n\n\nclass JsonWithInfo(BaseModel):\n metadata: dict # any dict is valid\n reference_id: str\n url: str\n type: Literal["pointcloud", "recipe"]\n\n @validator("url")\n def has_json_extension(cls, v):\n if not v.endswith(".json"):\n raise ValueError(f"Expected \'.json\' extension got {v}")\n return v\n\n\nclass ImageWithInfo(BaseModel):\n metadata: dict # any dict is valid\n reference_id: str\n url: str\n type: Literal["image", "mask"]\n\n @validator("url")\n def has_valid_extension(cls, v):\n valid_extensions = {".jpg", ".jpeg", ".png", ".tiff"}\n _, extension = os.path.splitext(v)\n if extension not in valid_extensions:\n raise ValueError(f"Expected extension in {valid_extensions} got {v}")\n return v\n\n\nclass ExampleNestedModel(BaseModel):\n example_json_with_info: JsonWithInfo\n example_image_with_info: ImageWithInfo\n\n# Usage:\nimport requests\npayload = requests.get("/example")\nparsed_model = ExampleNestedModel.parse_obj(payload.json())\nrequests.post("example/post_to", json=parsed_model.dict())\n```\n\n### Migrating to Pydantic\n\n- When migrating an interface from a dictionary use `nucleus.pydantic_base.DictCompatibleModel`. That allows you to get\n the benefits of Pydantic but maintaints backwards compatibility with a Python dictionary by delegating `__getitem__` to\n fields.\n- When migrating a frozen dataclass use `nucleus.pydantic_base.ImmutableModel`. That is a base class set up to be\n immutable after initialization.\n\n**Updating documentation:**\nWe use [Sphinx](https://www.sphinx-doc.org/en/master/) to autogenerate our API Reference from docstrings.\n\nTo test your local docstring changes, run the following commands from the repository\'s root directory:\n\n```\npoetry shell\ncd docs\nsphinx-autobuild . ./_build/html --watch ../nucleus\n```\n\n`sphinx-autobuild` will spin up a server on localhost (port 8000 by default) that will watch for and automatically rebuild a version of the API reference based on your local docstring changes.\n\n## Custom Metrics using Shapely in scale-validate\n\nCertain metrics use `Shapely` and `rasterio` which is added as optional dependencies.\n\n```bash\npip install scale-nucleus[metrics]\n```\n\nNote that you might need to install a local GEOS package since Shapely doesn\'t provide binaries bundled with GEOS for every platform.\n\n```bash\n#Mac OS\nbrew install geos\n# Ubuntu/Debian flavors\napt-get install libgeos-dev\n```\n\nTo develop it locally use\n\n`poetry install --extras metrics`\n',
50
- 'author': 'Scale AI Nucleus Team',
51
- 'author_email': 'nucleusapi@scaleapi.com',
52
- 'maintainer': 'None',
53
- 'maintainer_email': 'None',
54
- 'url': 'https://scale.com/nucleus',
55
- 'packages': packages,
56
- 'package_data': package_data,
57
- 'install_requires': install_requires,
58
- 'extras_require': extras_require,
59
- 'entry_points': entry_points,
60
- 'python_requires': '>=3.6.2,<4.0',
61
- }
62
-
63
-
64
- setup(**setup_kwargs)