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/__init__.py +259 -162
- nucleus/annotation.py +121 -32
- nucleus/autocurate.py +26 -0
- nucleus/constants.py +43 -5
- nucleus/dataset.py +213 -52
- nucleus/dataset_item.py +139 -26
- nucleus/errors.py +21 -3
- nucleus/job.py +27 -6
- nucleus/model.py +23 -2
- nucleus/model_run.py +56 -14
- nucleus/payload_constructor.py +39 -2
- nucleus/prediction.py +75 -14
- nucleus/scene.py +241 -0
- nucleus/slice.py +24 -15
- nucleus/url_utils.py +22 -0
- nucleus/utils.py +26 -5
- {scale_nucleus-0.1.10.dist-info → scale_nucleus-0.1.24.dist-info}/LICENSE +0 -0
- scale_nucleus-0.1.24.dist-info/METADATA +85 -0
- scale_nucleus-0.1.24.dist-info/RECORD +21 -0
- {scale_nucleus-0.1.10.dist-info → scale_nucleus-0.1.24.dist-info}/WHEEL +1 -1
- scale_nucleus-0.1.10.dist-info/METADATA +0 -236
- scale_nucleus-0.1.10.dist-info/RECORD +0 -18
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
|
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
|
-
|
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:
|
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
|
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
|
-
|
119
|
-
|
120
|
-
|
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:
|
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
|
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:
|
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
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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"
|