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/job.py CHANGED
@@ -1,47 +1,68 @@
1
1
  from dataclasses import dataclass
2
2
  import time
3
3
  from typing import Dict, List
4
-
5
4
  import requests
5
+ from nucleus.constants import (
6
+ JOB_CREATION_TIME_KEY,
7
+ JOB_ID_KEY,
8
+ JOB_LAST_KNOWN_STATUS_KEY,
9
+ JOB_TYPE_KEY,
10
+ STATUS_KEY,
11
+ )
6
12
 
7
13
  JOB_POLLING_INTERVAL = 5
8
14
 
9
15
 
10
16
  @dataclass
11
17
  class AsyncJob:
12
- id: str
18
+ job_id: str
19
+ job_last_known_status: str
20
+ job_type: str
21
+ job_creation_time: str
13
22
  client: "NucleusClient" # type: ignore # noqa: F821
14
23
 
15
24
  def status(self) -> Dict[str, str]:
16
- return self.client.make_request(
25
+ response = self.client.make_request(
17
26
  payload={},
18
- route=f"job/{self.id}",
27
+ route=f"job/{self.job_id}",
19
28
  requests_command=requests.get,
20
29
  )
30
+ self.job_last_known_status = response[STATUS_KEY]
31
+ return response
21
32
 
22
33
  def errors(self) -> List[str]:
23
34
  return self.client.make_request(
24
35
  payload={},
25
- route=f"job/{self.id}/errors",
36
+ route=f"job/{self.job_id}/errors",
26
37
  requests_command=requests.get,
27
38
  )
28
39
 
29
40
  def sleep_until_complete(self, verbose_std_out=True):
30
41
  while 1:
31
42
  status = self.status()
32
-
33
43
  time.sleep(JOB_POLLING_INTERVAL)
34
44
 
35
45
  if verbose_std_out:
36
46
  print(f"Status at {time.ctime()}: {status}")
37
47
  if status["status"] == "Running":
38
48
  continue
49
+
39
50
  break
40
51
 
41
52
  final_status = status
42
53
  if final_status["status"] == "Errored":
43
54
  raise JobError(final_status, self)
44
55
 
56
+ @classmethod
57
+ def from_json(cls, payload: dict, client):
58
+ return cls(
59
+ job_id=payload[JOB_ID_KEY],
60
+ job_last_known_status=payload[JOB_LAST_KNOWN_STATUS_KEY],
61
+ job_type=payload[JOB_TYPE_KEY],
62
+ job_creation_time=payload[JOB_CREATION_TIME_KEY],
63
+ client=client,
64
+ )
65
+
45
66
 
46
67
  class JobError(Exception):
47
68
  def __init__(self, job_status: Dict[str, str], job: AsyncJob):
nucleus/model.py CHANGED
@@ -2,6 +2,7 @@ from typing import List, Optional, Dict, Union
2
2
  from .dataset import Dataset
3
3
  from .prediction import (
4
4
  BoxPrediction,
5
+ CuboidPrediction,
5
6
  PolygonPrediction,
6
7
  SegmentationPrediction,
7
8
  )
@@ -32,17 +33,37 @@ class Model:
32
33
  return f"Model(model_id='{self.id}', name='{self.name}', reference_id='{self.reference_id}', metadata={self.metadata}, client={self._client})"
33
34
 
34
35
  def __eq__(self, other):
35
- return self.id == other.id
36
+ return (
37
+ (self.id == other.id)
38
+ and (self.name == other.name)
39
+ and (self.metadata == other.metadata)
40
+ and (self._client == other._client)
41
+ )
36
42
 
37
43
  def __hash__(self):
38
44
  return hash(self.id)
39
45
 
46
+ @classmethod
47
+ def from_json(cls, payload: dict, client):
48
+ return cls(
49
+ model_id=payload["id"],
50
+ name=payload["name"],
51
+ reference_id=payload["ref_id"],
52
+ metadata=payload["metadata"] or None,
53
+ client=client,
54
+ )
55
+
40
56
  def create_run(
41
57
  self,
42
58
  name: str,
43
59
  dataset: Dataset,
44
60
  predictions: List[
45
- Union[BoxPrediction, PolygonPrediction, SegmentationPrediction]
61
+ Union[
62
+ BoxPrediction,
63
+ PolygonPrediction,
64
+ CuboidPrediction,
65
+ SegmentationPrediction,
66
+ ]
46
67
  ],
47
68
  metadata: Optional[Dict] = None,
48
69
  asynchronous: bool = False,
nucleus/model_run.py CHANGED
@@ -1,14 +1,14 @@
1
1
  from typing import Dict, List, Optional, Type, Union
2
-
3
- from nucleus.annotation import check_all_annotation_paths_remote
2
+ import requests
3
+ from nucleus.annotation import check_all_mask_paths_remote
4
4
  from nucleus.job import AsyncJob
5
5
  from nucleus.utils import serialize_and_write_to_presigned_url
6
6
 
7
7
  from .constants import (
8
8
  ANNOTATIONS_KEY,
9
9
  BOX_TYPE,
10
+ CUBOID_TYPE,
10
11
  DEFAULT_ANNOTATION_UPDATE_MODE,
11
- JOB_ID_KEY,
12
12
  POLYGON_TYPE,
13
13
  REQUEST_ID_KEY,
14
14
  SEGMENTATION_TYPE,
@@ -16,8 +16,10 @@ from .constants import (
16
16
  )
17
17
  from .prediction import (
18
18
  BoxPrediction,
19
+ CuboidPrediction,
19
20
  PolygonPrediction,
20
21
  SegmentationPrediction,
22
+ from_json,
21
23
  )
22
24
 
23
25
 
@@ -30,10 +32,10 @@ class ModelRun:
30
32
  def __init__(self, model_run_id: str, dataset_id: str, client):
31
33
  self.model_run_id = model_run_id
32
34
  self._client = client
33
- self._dataset_id = dataset_id
35
+ self.dataset_id = dataset_id
34
36
 
35
37
  def __repr__(self):
36
- return f"ModelRun(model_run_id='{self.model_run_id}', dataset_id='{self._dataset_id}', client={self._client})"
38
+ return f"ModelRun(model_run_id='{self.model_run_id}', dataset_id='{self.dataset_id}', client={self._client})"
37
39
 
38
40
  def __eq__(self, other):
39
41
  if self.model_run_id == other.model_run_id:
@@ -90,14 +92,19 @@ class ModelRun:
90
92
  def predict(
91
93
  self,
92
94
  annotations: List[
93
- Union[BoxPrediction, PolygonPrediction, SegmentationPrediction]
95
+ Union[
96
+ BoxPrediction,
97
+ PolygonPrediction,
98
+ CuboidPrediction,
99
+ SegmentationPrediction,
100
+ ]
94
101
  ],
95
102
  update: Optional[bool] = DEFAULT_ANNOTATION_UPDATE_MODE,
96
103
  asynchronous: bool = False,
97
104
  ) -> Union[dict, AsyncJob]:
98
105
  """
99
106
  Uploads model outputs as predictions for a model_run. Returns info about the upload.
100
- :param annotations: List[Union[BoxPrediction, PolygonPrediction]],
107
+ :param annotations: List[Union[BoxPrediction, PolygonPrediction, CuboidPrediction, SegmentationPrediction]],
101
108
  :return:
102
109
  {
103
110
  "model_run_id": str,
@@ -106,17 +113,16 @@ class ModelRun:
106
113
  }
107
114
  """
108
115
  if asynchronous:
109
- check_all_annotation_paths_remote(annotations)
116
+ check_all_mask_paths_remote(annotations)
110
117
 
111
118
  request_id = serialize_and_write_to_presigned_url(
112
- annotations, self._dataset_id, self._client
119
+ annotations, self.dataset_id, self._client
113
120
  )
114
121
  response = self._client.make_request(
115
122
  payload={REQUEST_ID_KEY: request_id, UPDATE_KEY: update},
116
123
  route=f"modelRun/{self.model_run_id}/predict?async=1",
117
124
  )
118
-
119
- return AsyncJob(response[JOB_ID_KEY], self._client)
125
+ return AsyncJob.from_json(response, self._client)
120
126
  else:
121
127
  return self._client.predict(self.model_run_id, annotations, update)
122
128
 
@@ -124,7 +130,7 @@ class ModelRun:
124
130
  """
125
131
  Returns Model Run Info For Dataset Item by its number.
126
132
  :param i: absolute number of Dataset Item for a dataset corresponding to the model run.
127
- :return: List[Union[BoxPrediction, PolygonPrediction]],
133
+ :return: List[Union[BoxPrediction, PolygonPrediction, CuboidPrediction, SegmentationPrediction]],
128
134
  }
129
135
  """
130
136
  response = self._client.predictions_iloc(self.model_run_id, i)
@@ -134,7 +140,7 @@ class ModelRun:
134
140
  """
135
141
  Returns Model Run Info For Dataset Item by its reference_id.
136
142
  :param reference_id: reference_id of a dataset item.
137
- :return: List[Union[BoxPrediction, PolygonPrediction]],
143
+ :return: List[Union[BoxPrediction, PolygonPrediction, CuboidPrediction, SegmentationPrediction]],
138
144
  """
139
145
  response = self._client.predictions_ref_id(
140
146
  self.model_run_id, reference_id
@@ -155,11 +161,45 @@ class ModelRun:
155
161
  )
156
162
  return self._format_prediction_response(response)
157
163
 
164
+ def prediction_loc(self, reference_id: str, annotation_id: str):
165
+ """
166
+ Returns info for single Prediction by its reference id and annotation id.
167
+ :param reference_id: the user specified id for the image
168
+ :param annotation_id: the user specified id for the prediction, or if one was not provided, the Scale internally generated id for the prediction
169
+ :return:
170
+ BoxPrediction | PolygonPrediction | CuboidPrediction
171
+ """
172
+
173
+ response = self._client.make_request(
174
+ {},
175
+ f"modelRun/{self.model_run_id}/prediction/loc/{reference_id}/{annotation_id}",
176
+ requests.get,
177
+ )
178
+
179
+ return from_json(response)
180
+
181
+ def ungrouped_export(self):
182
+ json_response = self._client.make_request(
183
+ payload={},
184
+ route=f"modelRun/{self.model_run_id}/ungrouped",
185
+ requests_command=requests.get,
186
+ )
187
+ return self._format_prediction_response(
188
+ {ANNOTATIONS_KEY: json_response}
189
+ )
190
+
158
191
  def _format_prediction_response(
159
192
  self, response: dict
160
193
  ) -> Union[
161
194
  dict,
162
- List[Union[BoxPrediction, PolygonPrediction, SegmentationPrediction]],
195
+ List[
196
+ Union[
197
+ BoxPrediction,
198
+ PolygonPrediction,
199
+ CuboidPrediction,
200
+ SegmentationPrediction,
201
+ ]
202
+ ],
163
203
  ]:
164
204
  annotation_payload = response.get(ANNOTATIONS_KEY, None)
165
205
  if not annotation_payload:
@@ -171,11 +211,13 @@ class ModelRun:
171
211
  Union[
172
212
  Type[BoxPrediction],
173
213
  Type[PolygonPrediction],
214
+ Type[CuboidPrediction],
174
215
  Type[SegmentationPrediction],
175
216
  ],
176
217
  ] = {
177
218
  BOX_TYPE: BoxPrediction,
178
219
  POLYGON_TYPE: PolygonPrediction,
220
+ CUBOID_TYPE: CuboidPrediction,
179
221
  SEGMENTATION_TYPE: SegmentationPrediction,
180
222
  }
181
223
  for type_key in annotation_payload:
@@ -1,12 +1,16 @@
1
1
  from typing import List, Optional, Dict, Union
2
2
  from .dataset_item import DatasetItem
3
+ from .scene import LidarScene
3
4
  from .annotation import (
4
5
  BoxAnnotation,
6
+ CuboidAnnotation,
5
7
  PolygonAnnotation,
8
+ CategoryAnnotation,
6
9
  SegmentationAnnotation,
7
10
  )
8
11
  from .prediction import (
9
12
  BoxPrediction,
13
+ CuboidPrediction,
10
14
  PolygonPrediction,
11
15
  SegmentationPrediction,
12
16
  )
@@ -17,10 +21,14 @@ from .constants import (
17
21
  REFERENCE_ID_KEY,
18
22
  ANNOTATIONS_KEY,
19
23
  ITEMS_KEY,
24
+ SCENES_KEY,
20
25
  UPDATE_KEY,
21
26
  MODEL_ID_KEY,
22
27
  ANNOTATION_METADATA_SCHEMA_KEY,
23
28
  SEGMENTATIONS_KEY,
29
+ TAXONOMY_NAME_KEY,
30
+ TYPE_KEY,
31
+ LABELS_KEY,
24
32
  )
25
33
 
26
34
 
@@ -38,8 +46,25 @@ def construct_append_payload(
38
46
  )
39
47
 
40
48
 
49
+ def construct_append_scenes_payload(
50
+ scene_list: List[LidarScene], update: Optional[bool] = False
51
+ ) -> dict:
52
+ scenes = []
53
+ for scene in scene_list:
54
+ scenes.append(scene.to_payload())
55
+ return {SCENES_KEY: scenes, UPDATE_KEY: update}
56
+
57
+
41
58
  def construct_annotation_payload(
42
- annotation_items: List[Union[BoxAnnotation, PolygonAnnotation]],
59
+ annotation_items: List[
60
+ Union[
61
+ BoxAnnotation,
62
+ PolygonAnnotation,
63
+ CuboidAnnotation,
64
+ CategoryAnnotation,
65
+ SegmentationAnnotation,
66
+ ]
67
+ ],
43
68
  update: bool,
44
69
  ) -> dict:
45
70
  annotations = []
@@ -63,7 +88,9 @@ def construct_segmentation_payload(
63
88
 
64
89
 
65
90
  def construct_box_predictions_payload(
66
- box_predictions: List[Union[BoxPrediction, PolygonPrediction]],
91
+ box_predictions: List[
92
+ Union[BoxPrediction, PolygonPrediction, CuboidPrediction]
93
+ ],
67
94
  update: bool,
68
95
  ) -> dict:
69
96
  predictions = []
@@ -104,3 +131,13 @@ def construct_model_run_creation_payload(
104
131
  METADATA_KEY: metadata if metadata else {},
105
132
  ANNOTATION_METADATA_SCHEMA_KEY: annotation_metadata_schema,
106
133
  }
134
+
135
+
136
+ def construct_taxonomy_payload(
137
+ taxonomy_name: str, taxonomy_type: str, labels: List[str]
138
+ ) -> dict:
139
+ return {
140
+ TAXONOMY_NAME_KEY: taxonomy_name,
141
+ TYPE_KEY: taxonomy_type,
142
+ LABELS_KEY: labels,
143
+ }
nucleus/prediction.py CHANGED
@@ -5,14 +5,19 @@ from .annotation import (
5
5
  PolygonAnnotation,
6
6
  Segment,
7
7
  SegmentationAnnotation,
8
+ CuboidAnnotation,
9
+ Point3D,
8
10
  )
9
11
  from .constants import (
10
12
  ANNOTATION_ID_KEY,
11
- DATASET_ITEM_ID_KEY,
13
+ BOX_TYPE,
14
+ CUBOID_TYPE,
15
+ POLYGON_TYPE,
12
16
  REFERENCE_ID_KEY,
13
17
  METADATA_KEY,
14
18
  GEOMETRY_KEY,
15
19
  LABEL_KEY,
20
+ TYPE_KEY,
16
21
  X_KEY,
17
22
  Y_KEY,
18
23
  WIDTH_KEY,
@@ -21,11 +26,24 @@ from .constants import (
21
26
  CONFIDENCE_KEY,
22
27
  VERTICES_KEY,
23
28
  ANNOTATIONS_KEY,
24
- ITEM_ID_KEY,
25
29
  MASK_URL_KEY,
30
+ POSITION_KEY,
31
+ DIMENSIONS_KEY,
32
+ YAW_KEY,
26
33
  )
27
34
 
28
35
 
36
+ def from_json(payload: dict):
37
+ if payload.get(TYPE_KEY, None) == BOX_TYPE:
38
+ return BoxPrediction.from_json(payload)
39
+ elif payload.get(TYPE_KEY, None) == POLYGON_TYPE:
40
+ return PolygonPrediction.from_json(payload)
41
+ elif payload.get(TYPE_KEY, None) == CUBOID_TYPE:
42
+ return CuboidPrediction.from_json(payload)
43
+ else:
44
+ return SegmentationPrediction.from_json(payload)
45
+
46
+
29
47
  class SegmentationPrediction(SegmentationAnnotation):
30
48
  # No need to define init or to_payload methods because
31
49
  # we default to functions defined in the parent class
@@ -37,8 +55,7 @@ class SegmentationPrediction(SegmentationAnnotation):
37
55
  Segment.from_json(ann)
38
56
  for ann in payload.get(ANNOTATIONS_KEY, [])
39
57
  ],
40
- reference_id=payload.get(REFERENCE_ID_KEY, None),
41
- item_id=payload.get(ITEM_ID_KEY, None),
58
+ reference_id=payload[REFERENCE_ID_KEY],
42
59
  annotation_id=payload.get(ANNOTATION_ID_KEY, None),
43
60
  )
44
61
 
@@ -51,8 +68,7 @@ class BoxPrediction(BoxAnnotation):
51
68
  y: int,
52
69
  width: int,
53
70
  height: int,
54
- reference_id: Optional[str] = None,
55
- item_id: Optional[str] = None,
71
+ reference_id: str,
56
72
  confidence: Optional[float] = None,
57
73
  annotation_id: Optional[str] = None,
58
74
  metadata: Optional[Dict] = None,
@@ -65,7 +81,6 @@ class BoxPrediction(BoxAnnotation):
65
81
  width=width,
66
82
  height=height,
67
83
  reference_id=reference_id,
68
- item_id=item_id,
69
84
  annotation_id=annotation_id,
70
85
  metadata=metadata,
71
86
  )
@@ -90,8 +105,7 @@ class BoxPrediction(BoxAnnotation):
90
105
  y=geometry.get(Y_KEY, 0),
91
106
  width=geometry.get(WIDTH_KEY, 0),
92
107
  height=geometry.get(HEIGHT_KEY, 0),
93
- reference_id=payload.get(REFERENCE_ID_KEY, None),
94
- item_id=payload.get(DATASET_ITEM_ID_KEY, None),
108
+ reference_id=payload[REFERENCE_ID_KEY],
95
109
  confidence=payload.get(CONFIDENCE_KEY, None),
96
110
  annotation_id=payload.get(ANNOTATION_ID_KEY, None),
97
111
  metadata=payload.get(METADATA_KEY, {}),
@@ -104,8 +118,7 @@ class PolygonPrediction(PolygonAnnotation):
104
118
  self,
105
119
  label: str,
106
120
  vertices: List[Point],
107
- reference_id: Optional[str] = None,
108
- item_id: Optional[str] = None,
121
+ reference_id: str,
109
122
  confidence: Optional[float] = None,
110
123
  annotation_id: Optional[str] = None,
111
124
  metadata: Optional[Dict] = None,
@@ -115,7 +128,6 @@ class PolygonPrediction(PolygonAnnotation):
115
128
  label=label,
116
129
  vertices=vertices,
117
130
  reference_id=reference_id,
118
- item_id=item_id,
119
131
  annotation_id=annotation_id,
120
132
  metadata=metadata,
121
133
  )
@@ -139,8 +151,57 @@ class PolygonPrediction(PolygonAnnotation):
139
151
  vertices=[
140
152
  Point.from_json(_) for _ in geometry.get(VERTICES_KEY, [])
141
153
  ],
142
- reference_id=payload.get(REFERENCE_ID_KEY, None),
143
- item_id=payload.get(DATASET_ITEM_ID_KEY, None),
154
+ reference_id=payload[REFERENCE_ID_KEY],
155
+ confidence=payload.get(CONFIDENCE_KEY, None),
156
+ annotation_id=payload.get(ANNOTATION_ID_KEY, None),
157
+ metadata=payload.get(METADATA_KEY, {}),
158
+ class_pdf=payload.get(CLASS_PDF_KEY, None),
159
+ )
160
+
161
+
162
+ class CuboidPrediction(CuboidAnnotation):
163
+ def __init__(
164
+ self,
165
+ label: str,
166
+ position: Point3D,
167
+ dimensions: Point3D,
168
+ yaw: float,
169
+ reference_id: str,
170
+ confidence: Optional[float] = None,
171
+ annotation_id: Optional[str] = None,
172
+ metadata: Optional[Dict] = None,
173
+ class_pdf: Optional[Dict] = None,
174
+ ):
175
+ super().__init__(
176
+ label=label,
177
+ position=position,
178
+ dimensions=dimensions,
179
+ yaw=yaw,
180
+ reference_id=reference_id,
181
+ annotation_id=annotation_id,
182
+ metadata=metadata,
183
+ )
184
+ self.confidence = confidence
185
+ self.class_pdf = class_pdf
186
+
187
+ def to_payload(self) -> dict:
188
+ payload = super().to_payload()
189
+ if self.confidence is not None:
190
+ payload[CONFIDENCE_KEY] = self.confidence
191
+ if self.class_pdf is not None:
192
+ payload[CLASS_PDF_KEY] = self.class_pdf
193
+
194
+ return payload
195
+
196
+ @classmethod
197
+ def from_json(cls, payload: dict):
198
+ geometry = payload.get(GEOMETRY_KEY, {})
199
+ return cls(
200
+ label=payload.get(LABEL_KEY, 0),
201
+ position=Point3D.from_json(geometry.get(POSITION_KEY, {})),
202
+ dimensions=Point3D.from_json(geometry.get(DIMENSIONS_KEY, {})),
203
+ yaw=geometry.get(YAW_KEY, 0),
204
+ reference_id=payload[REFERENCE_ID_KEY],
144
205
  confidence=payload.get(CONFIDENCE_KEY, None),
145
206
  annotation_id=payload.get(ANNOTATION_ID_KEY, None),
146
207
  metadata=payload.get(METADATA_KEY, {}),