scale-nucleus 0.1.10__py3-none-any.whl → 0.1.24__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.
nucleus/annotation.py CHANGED
@@ -2,40 +2,38 @@ import json
2
2
  from dataclasses import dataclass
3
3
  from enum import Enum
4
4
  from typing import Dict, List, Optional, Sequence, Union
5
- from nucleus.dataset_item import is_local_path
5
+ from urllib.parse import urlparse
6
6
 
7
7
  from .constants import (
8
8
  ANNOTATION_ID_KEY,
9
9
  ANNOTATIONS_KEY,
10
10
  BOX_TYPE,
11
- DATASET_ITEM_ID_KEY,
11
+ CATEGORY_TYPE,
12
+ CUBOID_TYPE,
13
+ DIMENSIONS_KEY,
12
14
  GEOMETRY_KEY,
13
15
  HEIGHT_KEY,
14
16
  INDEX_KEY,
15
- ITEM_ID_KEY,
16
17
  LABEL_KEY,
17
18
  MASK_TYPE,
18
19
  MASK_URL_KEY,
19
20
  METADATA_KEY,
20
21
  POLYGON_TYPE,
22
+ POSITION_KEY,
21
23
  REFERENCE_ID_KEY,
24
+ TAXONOMY_NAME_KEY,
22
25
  TYPE_KEY,
23
26
  VERTICES_KEY,
24
27
  WIDTH_KEY,
25
28
  X_KEY,
29
+ YAW_KEY,
26
30
  Y_KEY,
31
+ Z_KEY,
27
32
  )
28
33
 
29
34
 
30
35
  class Annotation:
31
- reference_id: Optional[str] = None
32
- item_id: Optional[str] = None
33
-
34
- def _check_ids(self):
35
- if bool(self.reference_id) == bool(self.item_id):
36
- raise Exception(
37
- "You must specify either a reference_id or an item_id for an annotation."
38
- )
36
+ reference_id: str
39
37
 
40
38
  @classmethod
41
39
  def from_json(cls, payload: dict):
@@ -43,6 +41,10 @@ class Annotation:
43
41
  return BoxAnnotation.from_json(payload)
44
42
  elif payload.get(TYPE_KEY, None) == POLYGON_TYPE:
45
43
  return PolygonAnnotation.from_json(payload)
44
+ elif payload.get(TYPE_KEY, None) == CUBOID_TYPE:
45
+ return CuboidAnnotation.from_json(payload)
46
+ elif payload.get(TYPE_KEY, None) == CATEGORY_TYPE:
47
+ return CategoryAnnotation.from_json(payload)
46
48
  else:
47
49
  return SegmentationAnnotation.from_json(payload)
48
50
 
@@ -84,14 +86,12 @@ class Segment:
84
86
  class SegmentationAnnotation(Annotation):
85
87
  mask_url: str
86
88
  annotations: List[Segment]
89
+ reference_id: str
87
90
  annotation_id: Optional[str] = None
88
- reference_id: Optional[str] = None
89
- item_id: Optional[str] = None
90
91
 
91
92
  def __post_init__(self):
92
93
  if not self.mask_url:
93
94
  raise Exception("You must specify a mask_url.")
94
- self._check_ids()
95
95
 
96
96
  @classmethod
97
97
  def from_json(cls, payload: dict):
@@ -103,8 +103,7 @@ class SegmentationAnnotation(Annotation):
103
103
  Segment.from_json(ann)
104
104
  for ann in payload.get(ANNOTATIONS_KEY, [])
105
105
  ],
106
- reference_id=payload.get(REFERENCE_ID_KEY, None),
107
- item_id=payload.get(ITEM_ID_KEY, None),
106
+ reference_id=payload[REFERENCE_ID_KEY],
108
107
  annotation_id=payload.get(ANNOTATION_ID_KEY, None),
109
108
  )
110
109
 
@@ -115,16 +114,17 @@ class SegmentationAnnotation(Annotation):
115
114
  ANNOTATIONS_KEY: [ann.to_payload() for ann in self.annotations],
116
115
  ANNOTATION_ID_KEY: self.annotation_id,
117
116
  }
118
- if self.reference_id:
119
- payload[REFERENCE_ID_KEY] = self.reference_id
120
- else:
121
- payload[ITEM_ID_KEY] = self.item_id
117
+
118
+ payload[REFERENCE_ID_KEY] = self.reference_id
119
+
122
120
  return payload
123
121
 
124
122
 
125
123
  class AnnotationTypes(Enum):
126
124
  BOX = BOX_TYPE
127
125
  POLYGON = POLYGON_TYPE
126
+ CUBOID = CUBOID_TYPE
127
+ CATEGORY = CATEGORY_TYPE
128
128
 
129
129
 
130
130
  @dataclass # pylint: disable=R0902
@@ -134,14 +134,14 @@ class BoxAnnotation(Annotation): # pylint: disable=R0902
134
134
  y: Union[float, int]
135
135
  width: Union[float, int]
136
136
  height: Union[float, int]
137
- reference_id: Optional[str] = None
138
- item_id: Optional[str] = None
137
+ reference_id: str
139
138
  annotation_id: Optional[str] = None
140
139
  metadata: Optional[Dict] = None
141
140
 
142
141
  def __post_init__(self):
143
- self._check_ids()
144
142
  self.metadata = self.metadata if self.metadata else {}
143
+ if self.annotation_id is None:
144
+ self.annotation_id = f"{self.label}-{self.x}-{self.y}-{self.width}-{self.height}-{self.reference_id}"
145
145
 
146
146
  @classmethod
147
147
  def from_json(cls, payload: dict):
@@ -152,8 +152,7 @@ class BoxAnnotation(Annotation): # pylint: disable=R0902
152
152
  y=geometry.get(Y_KEY, 0),
153
153
  width=geometry.get(WIDTH_KEY, 0),
154
154
  height=geometry.get(HEIGHT_KEY, 0),
155
- reference_id=payload.get(REFERENCE_ID_KEY, None),
156
- item_id=payload.get(DATASET_ITEM_ID_KEY, None),
155
+ reference_id=payload[REFERENCE_ID_KEY],
157
156
  annotation_id=payload.get(ANNOTATION_ID_KEY, None),
158
157
  metadata=payload.get(METADATA_KEY, {}),
159
158
  )
@@ -187,17 +186,29 @@ class Point:
187
186
  return {X_KEY: self.x, Y_KEY: self.y}
188
187
 
189
188
 
189
+ @dataclass
190
+ class Point3D:
191
+ x: float
192
+ y: float
193
+ z: float
194
+
195
+ @classmethod
196
+ def from_json(cls, payload: Dict[str, float]):
197
+ return cls(payload[X_KEY], payload[Y_KEY], payload[Z_KEY])
198
+
199
+ def to_payload(self) -> dict:
200
+ return {X_KEY: self.x, Y_KEY: self.y, Z_KEY: self.z}
201
+
202
+
190
203
  @dataclass
191
204
  class PolygonAnnotation(Annotation):
192
205
  label: str
193
206
  vertices: List[Point]
194
- reference_id: Optional[str] = None
195
- item_id: Optional[str] = None
207
+ reference_id: str
196
208
  annotation_id: Optional[str] = None
197
209
  metadata: Optional[Dict] = None
198
210
 
199
211
  def __post_init__(self):
200
- self._check_ids()
201
212
  self.metadata = self.metadata if self.metadata else {}
202
213
  if len(self.vertices) > 0:
203
214
  if not hasattr(self.vertices[0], X_KEY) or not hasattr(
@@ -221,8 +232,7 @@ class PolygonAnnotation(Annotation):
221
232
  vertices=[
222
233
  Point.from_json(_) for _ in geometry.get(VERTICES_KEY, [])
223
234
  ],
224
- reference_id=payload.get(REFERENCE_ID_KEY, None),
225
- item_id=payload.get(DATASET_ITEM_ID_KEY, None),
235
+ reference_id=payload[REFERENCE_ID_KEY],
226
236
  annotation_id=payload.get(ANNOTATION_ID_KEY, None),
227
237
  metadata=payload.get(METADATA_KEY, {}),
228
238
  )
@@ -241,12 +251,91 @@ class PolygonAnnotation(Annotation):
241
251
  return payload
242
252
 
243
253
 
244
- def check_all_annotation_paths_remote(
254
+ @dataclass # pylint: disable=R0902
255
+ class CuboidAnnotation(Annotation): # pylint: disable=R0902
256
+ label: str
257
+ position: Point3D
258
+ dimensions: Point3D
259
+ yaw: float
260
+ reference_id: str
261
+ annotation_id: Optional[str] = None
262
+ metadata: Optional[Dict] = None
263
+
264
+ def __post_init__(self):
265
+ self.metadata = self.metadata if self.metadata else {}
266
+
267
+ @classmethod
268
+ def from_json(cls, payload: dict):
269
+ geometry = payload.get(GEOMETRY_KEY, {})
270
+ return cls(
271
+ label=payload.get(LABEL_KEY, 0),
272
+ position=Point3D.from_json(geometry.get(POSITION_KEY, {})),
273
+ dimensions=Point3D.from_json(geometry.get(DIMENSIONS_KEY, {})),
274
+ yaw=geometry.get(YAW_KEY, 0),
275
+ reference_id=payload[REFERENCE_ID_KEY],
276
+ annotation_id=payload.get(ANNOTATION_ID_KEY, None),
277
+ metadata=payload.get(METADATA_KEY, {}),
278
+ )
279
+
280
+ def to_payload(self) -> dict:
281
+ payload = {
282
+ LABEL_KEY: self.label,
283
+ TYPE_KEY: CUBOID_TYPE,
284
+ GEOMETRY_KEY: {
285
+ POSITION_KEY: self.position.to_payload(),
286
+ DIMENSIONS_KEY: self.dimensions.to_payload(),
287
+ YAW_KEY: self.yaw,
288
+ },
289
+ }
290
+ payload[REFERENCE_ID_KEY] = self.reference_id
291
+ if self.annotation_id:
292
+ payload[ANNOTATION_ID_KEY] = self.annotation_id
293
+ if self.metadata:
294
+ payload[METADATA_KEY] = self.metadata
295
+
296
+ return payload
297
+
298
+
299
+ @dataclass
300
+ class CategoryAnnotation(Annotation):
301
+ label: str
302
+ taxonomy_name: str
303
+ reference_id: str
304
+ metadata: Optional[Dict] = None
305
+
306
+ def __post_init__(self):
307
+ self.metadata = self.metadata if self.metadata else {}
308
+
309
+ @classmethod
310
+ def from_json(cls, payload: dict):
311
+ return cls(
312
+ label=payload[LABEL_KEY],
313
+ taxonomy_name=payload[TAXONOMY_NAME_KEY],
314
+ reference_id=payload[REFERENCE_ID_KEY],
315
+ metadata=payload.get(METADATA_KEY, {}),
316
+ )
317
+
318
+ def to_payload(self) -> dict:
319
+ return {
320
+ LABEL_KEY: self.label,
321
+ TAXONOMY_NAME_KEY: self.taxonomy_name,
322
+ TYPE_KEY: CATEGORY_TYPE,
323
+ GEOMETRY_KEY: {},
324
+ REFERENCE_ID_KEY: self.reference_id,
325
+ METADATA_KEY: self.metadata,
326
+ }
327
+
328
+
329
+ def is_local_path(path: str) -> bool:
330
+ return urlparse(path).scheme not in {"https", "http", "s3", "gs"}
331
+
332
+
333
+ def check_all_mask_paths_remote(
245
334
  annotations: Sequence[Union[Annotation]],
246
335
  ):
247
336
  for annotation in annotations:
248
337
  if hasattr(annotation, MASK_URL_KEY):
249
338
  if is_local_path(getattr(annotation, MASK_URL_KEY)):
250
339
  raise ValueError(
251
- f"Found an annotation with a local path, which cannot be uploaded asynchronously. Use a remote path instead. {annotation}"
340
+ f"Found an annotation with a local path, which is not currently supported. Use a remote path instead. {annotation}"
252
341
  )
nucleus/autocurate.py ADDED
@@ -0,0 +1,26 @@
1
+ import datetime
2
+ import requests
3
+ from nucleus.constants import (
4
+ JOB_CREATION_TIME_KEY,
5
+ JOB_LAST_KNOWN_STATUS_KEY,
6
+ JOB_TYPE_KEY,
7
+ )
8
+ from nucleus.job import AsyncJob
9
+
10
+
11
+ def entropy(name, model_run, client):
12
+ model_run_ids = [model_run.model_run_id]
13
+ dataset_id = model_run.dataset_id
14
+ response = client.make_request(
15
+ payload={"modelRunIds": model_run_ids},
16
+ route=f"autocurate/{dataset_id}/single_model_entropy/{name}",
17
+ requests_command=requests.post,
18
+ )
19
+ # TODO: the response should already have the below three fields populated
20
+ response[JOB_LAST_KNOWN_STATUS_KEY] = "Started"
21
+ response[JOB_TYPE_KEY] = "autocurateEntropy"
22
+ response[JOB_CREATION_TIME_KEY] = (
23
+ datetime.datetime.now().isoformat("T", "milliseconds") + "Z"
24
+ )
25
+ job = AsyncJob.from_json(response, client)
26
+ return job
nucleus/constants.py CHANGED
@@ -7,38 +7,64 @@ BOX_TYPE = "box"
7
7
  POLYGON_TYPE = "polygon"
8
8
  MASK_TYPE = "mask"
9
9
  SEGMENTATION_TYPE = "segmentation"
10
- ANNOTATION_TYPES = (BOX_TYPE, POLYGON_TYPE, SEGMENTATION_TYPE)
10
+ CUBOID_TYPE = "cuboid"
11
+ CATEGORY_TYPE = "category"
12
+ MULTICATEGORY_TYPE = "multicategory"
13
+ ANNOTATION_TYPES = (
14
+ BOX_TYPE,
15
+ POLYGON_TYPE,
16
+ SEGMENTATION_TYPE,
17
+ CUBOID_TYPE,
18
+ CATEGORY_TYPE,
19
+ )
11
20
  ANNOTATION_UPDATE_KEY = "update"
12
21
  AUTOTAGS_KEY = "autotags"
22
+ AUTOTAG_SCORE_THRESHOLD = "score_threshold"
13
23
  EXPORTED_ROWS = "exportedRows"
24
+ CAMERA_PARAMS_KEY = "camera_params"
14
25
  CLASS_PDF_KEY = "class_pdf"
15
26
  CONFIDENCE_KEY = "confidence"
27
+ CX_KEY = "cx"
28
+ CY_KEY = "cy"
16
29
  DATASET_ID_KEY = "dataset_id"
17
- DATASET_ITEM_IDS_KEY = "dataset_item_ids"
18
- DATASET_ITEM_ID_KEY = "dataset_item_id"
19
30
  DATASET_LENGTH_KEY = "length"
20
31
  DATASET_MODEL_RUNS_KEY = "model_run_ids"
21
32
  DATASET_NAME_KEY = "name"
22
33
  DATASET_SLICES_KEY = "slice_ids"
23
34
  DEFAULT_ANNOTATION_UPDATE_MODE = False
24
35
  DEFAULT_NETWORK_TIMEOUT_SEC = 120
25
- EMBEDDINGS_URL_KEY = "embeddings_url"
36
+ DIMENSIONS_KEY = "dimensions"
37
+ EMBEDDINGS_URL_KEY = "embeddings_urls"
38
+ EMBEDDING_DIMENSION_KEY = "embedding_dimension"
26
39
  ERRORS_KEY = "errors"
27
40
  ERROR_CODES = "error_codes"
28
41
  ERROR_ITEMS = "upload_errors"
29
42
  ERROR_PAYLOAD = "error_payload"
43
+ FRAMES_KEY = "frames"
44
+ FX_KEY = "fx"
45
+ FY_KEY = "fy"
30
46
  GEOMETRY_KEY = "geometry"
47
+ HEADING_KEY = "heading"
31
48
  HEIGHT_KEY = "height"
32
49
  IGNORED_ITEMS = "ignored_items"
33
50
  IMAGE_KEY = "image"
51
+ IMAGE_LOCATION_KEY = "image_location"
34
52
  IMAGE_URL_KEY = "image_url"
35
53
  INDEX_KEY = "index"
54
+ INDEX_CONTINUOUS_ENABLE_KEY = "enable"
36
55
  ITEMS_KEY = "items"
37
- ITEM_ID_KEY = "item_id"
38
56
  ITEM_KEY = "item"
57
+ ITEMS_KEY = "items"
39
58
  ITEM_METADATA_SCHEMA_KEY = "item_metadata_schema"
40
59
  JOB_ID_KEY = "job_id"
60
+ KEEP_HISTORY_KEY = "keep_history"
61
+ LENGTH_KEY = "length"
62
+ JOB_STATUS_KEY = "job_status"
63
+ JOB_LAST_KNOWN_STATUS_KEY = "job_last_known_status"
64
+ JOB_TYPE_KEY = "job_type"
65
+ JOB_CREATION_TIME_KEY = "job_creation_time"
41
66
  LABEL_KEY = "label"
67
+ LABELS_KEY = "labels"
42
68
  MASK_URL_KEY = "mask_url"
43
69
  MESSAGE_KEY = "message"
44
70
  METADATA_KEY = "metadata"
@@ -47,21 +73,33 @@ MODEL_RUN_ID_KEY = "model_run_id"
47
73
  NAME_KEY = "name"
48
74
  NEW_ITEMS = "new_items"
49
75
  NUCLEUS_ENDPOINT = "https://api.scale.com/v1/nucleus"
76
+ NUM_SENSORS_KEY = "num_sensors"
50
77
  ORIGINAL_IMAGE_URL_KEY = "original_image_url"
78
+ POINTCLOUD_KEY = "pointcloud"
79
+ POINTCLOUD_LOCATION_KEY = "pointcloud_location"
80
+ POINTCLOUD_URL_KEY = "pointcloud_url"
81
+ POSITION_KEY = "position"
51
82
  PREDICTIONS_IGNORED_KEY = "predictions_ignored"
52
83
  PREDICTIONS_PROCESSED_KEY = "predictions_processed"
53
84
  REFERENCE_IDS_KEY = "reference_ids"
54
85
  REFERENCE_ID_KEY = "reference_id"
55
86
  REQUEST_ID_KEY = "requestId"
87
+ SCENES_KEY = "scenes"
56
88
  SEGMENTATIONS_KEY = "segmentations"
57
89
  SLICE_ID_KEY = "slice_id"
58
90
  STATUS_CODE_KEY = "status_code"
59
91
  STATUS_KEY = "status"
60
92
  SUCCESS_STATUS_CODES = [200, 201, 202]
93
+ TAXONOMY_NAME_KEY = "taxonomy_name"
61
94
  TYPE_KEY = "type"
62
95
  UPDATED_ITEMS = "updated_items"
63
96
  UPDATE_KEY = "update"
97
+ UPLOAD_TO_SCALE_KEY = "upload_to_scale"
98
+ URL_KEY = "url"
64
99
  VERTICES_KEY = "vertices"
65
100
  WIDTH_KEY = "width"
101
+ YAW_KEY = "yaw"
102
+ W_KEY = "w"
66
103
  X_KEY = "x"
67
104
  Y_KEY = "y"
105
+ Z_KEY = "z"