scale-nucleus 0.1.24__py3-none-any.whl → 0.6.4__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.
- cli/client.py +14 -0
- cli/datasets.py +77 -0
- cli/helpers/__init__.py +0 -0
- cli/helpers/nucleus_url.py +10 -0
- cli/helpers/web_helper.py +40 -0
- cli/install_completion.py +33 -0
- cli/jobs.py +42 -0
- cli/models.py +35 -0
- cli/nu.py +42 -0
- cli/reference.py +8 -0
- cli/slices.py +62 -0
- cli/tests.py +121 -0
- nucleus/__init__.py +446 -710
- nucleus/annotation.py +405 -85
- nucleus/autocurate.py +9 -0
- nucleus/connection.py +87 -0
- nucleus/constants.py +5 -1
- nucleus/data_transfer_object/__init__.py +0 -0
- nucleus/data_transfer_object/dataset_details.py +9 -0
- nucleus/data_transfer_object/dataset_info.py +26 -0
- nucleus/data_transfer_object/dataset_size.py +5 -0
- nucleus/data_transfer_object/scenes_list.py +18 -0
- nucleus/dataset.py +1137 -212
- nucleus/dataset_item.py +130 -26
- nucleus/dataset_item_uploader.py +297 -0
- nucleus/deprecation_warning.py +32 -0
- nucleus/errors.py +9 -0
- nucleus/job.py +71 -3
- nucleus/logger.py +9 -0
- nucleus/metadata_manager.py +45 -0
- nucleus/metrics/__init__.py +10 -0
- nucleus/metrics/base.py +117 -0
- nucleus/metrics/categorization_metrics.py +197 -0
- nucleus/metrics/errors.py +7 -0
- nucleus/metrics/filters.py +40 -0
- nucleus/metrics/geometry.py +198 -0
- nucleus/metrics/metric_utils.py +28 -0
- nucleus/metrics/polygon_metrics.py +480 -0
- nucleus/metrics/polygon_utils.py +299 -0
- nucleus/model.py +121 -15
- nucleus/model_run.py +34 -57
- nucleus/payload_constructor.py +29 -19
- nucleus/prediction.py +259 -17
- nucleus/pydantic_base.py +26 -0
- nucleus/retry_strategy.py +4 -0
- nucleus/scene.py +204 -19
- nucleus/slice.py +230 -67
- nucleus/upload_response.py +20 -9
- nucleus/url_utils.py +4 -0
- nucleus/utils.py +134 -37
- nucleus/validate/__init__.py +24 -0
- nucleus/validate/client.py +168 -0
- nucleus/validate/constants.py +20 -0
- nucleus/validate/data_transfer_objects/__init__.py +0 -0
- nucleus/validate/data_transfer_objects/eval_function.py +81 -0
- nucleus/validate/data_transfer_objects/scenario_test.py +19 -0
- nucleus/validate/data_transfer_objects/scenario_test_evaluations.py +11 -0
- nucleus/validate/data_transfer_objects/scenario_test_metric.py +12 -0
- nucleus/validate/errors.py +6 -0
- nucleus/validate/eval_functions/__init__.py +0 -0
- nucleus/validate/eval_functions/available_eval_functions.py +212 -0
- nucleus/validate/eval_functions/base_eval_function.py +60 -0
- nucleus/validate/scenario_test.py +143 -0
- nucleus/validate/scenario_test_evaluation.py +114 -0
- nucleus/validate/scenario_test_metric.py +14 -0
- nucleus/validate/utils.py +8 -0
- {scale_nucleus-0.1.24.dist-info → scale_nucleus-0.6.4.dist-info}/LICENSE +0 -0
- scale_nucleus-0.6.4.dist-info/METADATA +213 -0
- scale_nucleus-0.6.4.dist-info/RECORD +71 -0
- {scale_nucleus-0.1.24.dist-info → scale_nucleus-0.6.4.dist-info}/WHEEL +1 -1
- scale_nucleus-0.6.4.dist-info/entry_points.txt +3 -0
- scale_nucleus-0.1.24.dist-info/METADATA +0 -85
- scale_nucleus-0.1.24.dist-info/RECORD +0 -21
nucleus/scene.py
CHANGED
@@ -1,21 +1,37 @@
|
|
1
1
|
import json
|
2
2
|
from abc import ABC
|
3
3
|
from dataclasses import dataclass, field
|
4
|
-
from typing import
|
4
|
+
from typing import Any, Dict, List, Optional
|
5
|
+
|
5
6
|
from nucleus.constants import (
|
6
7
|
FRAMES_KEY,
|
8
|
+
IMAGE_LOCATION_KEY,
|
7
9
|
LENGTH_KEY,
|
8
10
|
METADATA_KEY,
|
9
11
|
NUM_SENSORS_KEY,
|
10
|
-
REFERENCE_ID_KEY,
|
11
12
|
POINTCLOUD_LOCATION_KEY,
|
12
|
-
|
13
|
+
REFERENCE_ID_KEY,
|
13
14
|
)
|
15
|
+
|
14
16
|
from .annotation import is_local_path
|
15
|
-
from .dataset_item import
|
17
|
+
from .dataset_item import DatasetItem, DatasetItemType
|
16
18
|
|
17
19
|
|
18
20
|
class Frame:
|
21
|
+
"""Collection of sensor data pertaining to a single timestep.
|
22
|
+
|
23
|
+
For 3D data, each Frame hosues a sensor-to-data mapping and must have exactly
|
24
|
+
one pointcloud with any number of camera images.
|
25
|
+
|
26
|
+
Parameters:
|
27
|
+
**kwargs (Dict[str, :class:`DatasetItem`]): Mappings from sensor name
|
28
|
+
to dataset item. Each frame of a lidar scene must contain exactly one
|
29
|
+
pointcloud and any number of images (e.g. from different angles).
|
30
|
+
|
31
|
+
Refer to our `guide to uploading 3D data
|
32
|
+
<https://docs.nucleus.scale.com/docs/uploading-3d-data>`_ for more info!
|
33
|
+
"""
|
34
|
+
|
19
35
|
def __init__(self, **kwargs):
|
20
36
|
self.items = {}
|
21
37
|
for key, value in kwargs.items():
|
@@ -31,31 +47,64 @@ class Frame:
|
|
31
47
|
def __repr__(self) -> str:
|
32
48
|
return f"Frame(items={self.items})"
|
33
49
|
|
34
|
-
def
|
50
|
+
def __eq__(self, other):
|
51
|
+
for key, value in self.items.items():
|
52
|
+
if key not in other.items:
|
53
|
+
return False
|
54
|
+
if value != other.items[key]:
|
55
|
+
return False
|
56
|
+
return True
|
57
|
+
|
58
|
+
def add_item(self, item: DatasetItem, sensor_name: str) -> None:
|
59
|
+
"""Adds DatasetItem object to frame as sensor data.
|
60
|
+
|
61
|
+
Parameters:
|
62
|
+
item (:class:`DatasetItem`): Pointcloud or camera image item to add.
|
63
|
+
sensor_name: Name of the sensor, e.g. "lidar" or "front_cam."
|
64
|
+
"""
|
35
65
|
self.items[sensor_name] = item
|
36
66
|
|
37
|
-
def get_item(self, sensor_name: str):
|
67
|
+
def get_item(self, sensor_name: str) -> DatasetItem:
|
68
|
+
"""Fetches the DatasetItem object associated with the given sensor.
|
69
|
+
|
70
|
+
Parameters:
|
71
|
+
sensor_name: Name of the sensor, e.g. "lidar" or "front_cam."
|
72
|
+
|
73
|
+
Returns:
|
74
|
+
:class:`DatasetItem`: DatasetItem object pertaining to the sensor.
|
75
|
+
"""
|
38
76
|
if sensor_name not in self.items:
|
39
77
|
raise ValueError(
|
40
78
|
f"This frame does not have a {sensor_name} sensor"
|
41
79
|
)
|
42
80
|
return self.items[sensor_name]
|
43
81
|
|
44
|
-
def get_items(self):
|
82
|
+
def get_items(self) -> List[DatasetItem]:
|
83
|
+
"""Fetches all items in the frame.
|
84
|
+
|
85
|
+
Returns:
|
86
|
+
List[:class:`DatasetItem`]: List of all DatasetItem objects in the frame.
|
87
|
+
"""
|
45
88
|
return list(self.items.values())
|
46
89
|
|
47
|
-
def get_sensors(self):
|
90
|
+
def get_sensors(self) -> List[str]:
|
91
|
+
"""Fetches all sensor names of the frame.
|
92
|
+
|
93
|
+
Returns:
|
94
|
+
List of all sensor names of the frame."""
|
48
95
|
return list(self.items.keys())
|
49
96
|
|
50
97
|
@classmethod
|
51
98
|
def from_json(cls, payload: dict):
|
99
|
+
"""Instantiates frame object from schematized JSON dict payload."""
|
52
100
|
items = {
|
53
|
-
sensor: DatasetItem.from_json(item
|
101
|
+
sensor: DatasetItem.from_json(item)
|
54
102
|
for sensor, item in payload.items()
|
55
103
|
}
|
56
104
|
return cls(**items)
|
57
105
|
|
58
106
|
def to_payload(self) -> dict:
|
107
|
+
"""Serializes frame object to schematized JSON dict."""
|
59
108
|
return {
|
60
109
|
sensor: dataset_item.to_payload(is_scene=True)
|
61
110
|
for sensor, dataset_item in self.items.items()
|
@@ -66,30 +115,53 @@ class Frame:
|
|
66
115
|
class Scene(ABC):
|
67
116
|
reference_id: str
|
68
117
|
frames: List[Frame] = field(default_factory=list)
|
69
|
-
metadata: Optional[dict] =
|
118
|
+
metadata: Optional[dict] = field(default_factory=dict)
|
70
119
|
|
71
120
|
def __post_init__(self):
|
72
121
|
self.sensors = set(
|
73
122
|
flatten([frame.get_sensors() for frame in self.frames])
|
74
123
|
)
|
75
124
|
self.frames_dict = dict(enumerate(self.frames))
|
125
|
+
if self.metadata is None:
|
126
|
+
self.metadata = {}
|
127
|
+
|
128
|
+
def __eq__(self, other):
|
129
|
+
return all(
|
130
|
+
[
|
131
|
+
self.reference_id == other.reference_id,
|
132
|
+
self.frames == other.frames,
|
133
|
+
self.metadata == other.metadata,
|
134
|
+
]
|
135
|
+
)
|
76
136
|
|
77
137
|
@property
|
78
138
|
def length(self) -> int:
|
139
|
+
"""Number of frames in the scene."""
|
79
140
|
return len(self.frames_dict)
|
80
141
|
|
81
142
|
@property
|
82
143
|
def num_sensors(self) -> int:
|
144
|
+
"""Number of sensors in the scene."""
|
83
145
|
return len(self.get_sensors())
|
84
146
|
|
85
147
|
def validate(self):
|
148
|
+
# TODO: make private
|
86
149
|
assert self.length > 0, "Must have at least 1 frame in a scene"
|
87
150
|
for frame in self.frames_dict.values():
|
88
151
|
assert isinstance(
|
89
152
|
frame, Frame
|
90
153
|
), "Each frame in a scene must be a Frame object"
|
91
154
|
|
92
|
-
def add_item(
|
155
|
+
def add_item(
|
156
|
+
self, index: int, sensor_name: str, item: DatasetItem
|
157
|
+
) -> None:
|
158
|
+
"""Adds DatasetItem to the specified frame as sensor data.
|
159
|
+
|
160
|
+
Parameters:
|
161
|
+
index: Serial index of the frame to which to add the item.
|
162
|
+
item (:class:`DatasetItem`): Pointcloud or camera image item to add.
|
163
|
+
sensor_name: Name of the sensor, e.g. "lidar" or "front_cam."
|
164
|
+
"""
|
93
165
|
self.sensors.add(sensor_name)
|
94
166
|
if index not in self.frames_dict:
|
95
167
|
new_frame = Frame(**{sensor_name: item})
|
@@ -97,7 +169,17 @@ class Scene(ABC):
|
|
97
169
|
else:
|
98
170
|
self.frames_dict[index].items[sensor_name] = item
|
99
171
|
|
100
|
-
def add_frame(
|
172
|
+
def add_frame(
|
173
|
+
self, frame: Frame, index: int, update: bool = False
|
174
|
+
) -> None:
|
175
|
+
"""Adds frame to scene at the specified index.
|
176
|
+
|
177
|
+
Parameters:
|
178
|
+
frame (:class:`Frame`): Frame object to add.
|
179
|
+
index: Serial index at which to add the frame.
|
180
|
+
update: Whether to overwrite the frame at the specified index, if it
|
181
|
+
exists. Default is False.
|
182
|
+
"""
|
101
183
|
if (
|
102
184
|
index not in self.frames_dict
|
103
185
|
or index in self.frames_dict
|
@@ -106,14 +188,26 @@ class Scene(ABC):
|
|
106
188
|
self.frames_dict[index] = frame
|
107
189
|
self.sensors.update(frame.get_sensors())
|
108
190
|
|
109
|
-
def get_frame(self, index: int):
|
191
|
+
def get_frame(self, index: int) -> Frame:
|
192
|
+
"""Fetches the Frame object at the specified index.
|
193
|
+
|
194
|
+
Parameters:
|
195
|
+
index: Serial index for which to retrieve the Frame.
|
196
|
+
|
197
|
+
Return:
|
198
|
+
:class:`Frame`: Frame object at the specified index."""
|
110
199
|
if index not in self.frames_dict:
|
111
200
|
raise ValueError(
|
112
201
|
f"This scene does not have a frame at index {index}"
|
113
202
|
)
|
114
203
|
return self.frames_dict[index]
|
115
204
|
|
116
|
-
def get_frames(self):
|
205
|
+
def get_frames(self) -> List[Frame]:
|
206
|
+
"""Fetches a sorted list of Frames of the scene.
|
207
|
+
|
208
|
+
Returns:
|
209
|
+
List[:class:`Frame`]: List of Frames, sorted by index ascending.
|
210
|
+
"""
|
117
211
|
return [
|
118
212
|
frame
|
119
213
|
for _, frame in sorted(
|
@@ -121,14 +215,36 @@ class Scene(ABC):
|
|
121
215
|
)
|
122
216
|
]
|
123
217
|
|
124
|
-
def get_sensors(self):
|
218
|
+
def get_sensors(self) -> List[str]:
|
219
|
+
"""Fetches all sensor names of the scene.
|
220
|
+
|
221
|
+
Returns:
|
222
|
+
List of all sensor names associated with frames in the scene."""
|
125
223
|
return list(self.sensors)
|
126
224
|
|
127
|
-
def get_item(self, index: int, sensor_name: str):
|
225
|
+
def get_item(self, index: int, sensor_name: str) -> DatasetItem:
|
226
|
+
"""Fetches the DatasetItem object of the given frame and sensor.
|
227
|
+
|
228
|
+
Parameters:
|
229
|
+
index: Serial index of the frame from which to fetch the item.
|
230
|
+
sensor_name: Name of the sensor, e.g. "lidar" or "front_cam."
|
231
|
+
|
232
|
+
Returns:
|
233
|
+
:class:`DatasetItem`: DatasetItem object of the frame and sensor.
|
234
|
+
"""
|
128
235
|
frame = self.get_frame(index)
|
129
236
|
return frame.get_item(sensor_name)
|
130
237
|
|
131
|
-
def get_items_from_sensor(self, sensor_name: str):
|
238
|
+
def get_items_from_sensor(self, sensor_name: str) -> List[DatasetItem]:
|
239
|
+
"""Fetches all DatasetItem objects of the given sensor.
|
240
|
+
|
241
|
+
Parameters:
|
242
|
+
sensor_name: Name of the sensor, e.g. "lidar" or "front_cam."
|
243
|
+
|
244
|
+
Returns:
|
245
|
+
List[:class:`DatasetItem`]: List of DatasetItem objects associated
|
246
|
+
with the specified sensor.
|
247
|
+
"""
|
132
248
|
if sensor_name not in self.sensors:
|
133
249
|
raise ValueError(
|
134
250
|
f"This scene does not have a {sensor_name} sensor"
|
@@ -143,10 +259,27 @@ class Scene(ABC):
|
|
143
259
|
items_from_sensor.append(None)
|
144
260
|
return items_from_sensor
|
145
261
|
|
146
|
-
def get_items(self):
|
262
|
+
def get_items(self) -> List[DatasetItem]:
|
263
|
+
"""Fetches all items in the scene.
|
264
|
+
|
265
|
+
Returns:
|
266
|
+
List[:class:`DatasetItem`]: Unordered list of all DatasetItem
|
267
|
+
objects in the scene.
|
268
|
+
"""
|
147
269
|
return flatten([frame.get_items() for frame in self.get_frames()])
|
148
270
|
|
149
271
|
def info(self):
|
272
|
+
"""Fetches information about the scene.
|
273
|
+
|
274
|
+
Returns:
|
275
|
+
Payload containing::
|
276
|
+
|
277
|
+
{
|
278
|
+
"reference_id": str,
|
279
|
+
"length": int,
|
280
|
+
"num_sensors": int
|
281
|
+
}
|
282
|
+
"""
|
150
283
|
return {
|
151
284
|
REFERENCE_ID_KEY: self.reference_id,
|
152
285
|
LENGTH_KEY: self.length,
|
@@ -154,6 +287,7 @@ class Scene(ABC):
|
|
154
287
|
}
|
155
288
|
|
156
289
|
def validate_frames_dict(self):
|
290
|
+
# TODO: make private
|
157
291
|
is_continuous = set(list(range(len(self.frames_dict)))) == set(
|
158
292
|
self.frames_dict.keys()
|
159
293
|
)
|
@@ -163,15 +297,17 @@ class Scene(ABC):
|
|
163
297
|
|
164
298
|
@classmethod
|
165
299
|
def from_json(cls, payload: dict):
|
300
|
+
"""Instantiates scene object from schematized JSON dict payload."""
|
166
301
|
frames_payload = payload.get(FRAMES_KEY, [])
|
167
302
|
frames = [Frame.from_json(frame) for frame in frames_payload]
|
168
303
|
return cls(
|
169
304
|
reference_id=payload[REFERENCE_ID_KEY],
|
170
305
|
frames=frames,
|
171
|
-
metadata=payload.get(METADATA_KEY,
|
306
|
+
metadata=payload.get(METADATA_KEY, {}),
|
172
307
|
)
|
173
308
|
|
174
309
|
def to_payload(self) -> dict:
|
310
|
+
"""Serializes scene object to schematized JSON dict."""
|
175
311
|
self.validate_frames_dict()
|
176
312
|
ordered_frames = self.get_frames()
|
177
313
|
frames_payload = [frame.to_payload() for frame in ordered_frames]
|
@@ -184,15 +320,64 @@ class Scene(ABC):
|
|
184
320
|
return payload
|
185
321
|
|
186
322
|
def to_json(self) -> str:
|
323
|
+
"""Serializes scene object to schematized JSON string."""
|
187
324
|
return json.dumps(self.to_payload(), allow_nan=False)
|
188
325
|
|
189
326
|
|
190
327
|
@dataclass
|
191
328
|
class LidarScene(Scene):
|
329
|
+
"""Sequence of lidar pointcloud and camera images over time.
|
330
|
+
|
331
|
+
Nucleus 3D datasets are comprised of LidarScenes, which are sequences of
|
332
|
+
lidar pointclouds and camera images over time. These sequences are in turn
|
333
|
+
comprised of :class:`Frames <Frame>`.
|
334
|
+
|
335
|
+
By organizing data across multiple sensors over time, LidarScenes make it
|
336
|
+
easier to interpret pointclouds, allowing you to see objects move over time
|
337
|
+
by clicking through frames and providing context in the form of corresponding
|
338
|
+
images.
|
339
|
+
|
340
|
+
You can think of scenes and frames as nested groupings of sensor data across
|
341
|
+
time:
|
342
|
+
|
343
|
+
* LidarScene for a given location
|
344
|
+
* Frame at timestep 0
|
345
|
+
* DatasetItem of pointcloud
|
346
|
+
* DatasetItem of front camera image
|
347
|
+
* DatasetItem of rear camera image
|
348
|
+
* Frame at timestep 1
|
349
|
+
* ...
|
350
|
+
* ...
|
351
|
+
* LidarScene for another location
|
352
|
+
* ...
|
353
|
+
|
354
|
+
LidarScenes are uploaded to a :class:`Dataset` with any accompanying
|
355
|
+
metadata. Frames do not accept metadata, but each of its constituent
|
356
|
+
:class:`DatasetItems <DatasetItem>` does.
|
357
|
+
|
358
|
+
Note: Uploads with a different number of frames/items will error out (only
|
359
|
+
on scenes that now differ). Existing scenes are expected to retain the
|
360
|
+
same structure, i.e. the same number of frames, and same items per frame.
|
361
|
+
If a scene definition is changed (for example, additional frames added) the
|
362
|
+
update operation will be ignored. If you would like to alter the structure
|
363
|
+
of a scene, please delete the scene and re-upload.
|
364
|
+
|
365
|
+
Parameters:
|
366
|
+
reference_id (str): User-specified identifier to reference the scene.
|
367
|
+
frames (Optional[List[:class:`Frame`]]): List of frames to be a part of
|
368
|
+
the scene. A scene can be created before frames or items have been
|
369
|
+
added to it, but must be non-empty when uploading to a :class:`Dataset`.
|
370
|
+
metadata (Optional[Dict]): Optional metadata to include with the scene.
|
371
|
+
|
372
|
+
Refer to our `guide to uploading 3D data
|
373
|
+
<https://docs.nucleus.scale.com/docs/uploading-3d-data>`_ for more info!
|
374
|
+
"""
|
375
|
+
|
192
376
|
def __repr__(self) -> str:
|
193
377
|
return f"LidarScene(reference_id='{self.reference_id}', frames={self.get_frames()}, metadata={self.metadata})"
|
194
378
|
|
195
379
|
def validate(self):
|
380
|
+
# TODO: make private
|
196
381
|
super().validate()
|
197
382
|
lidar_sensors = flatten(
|
198
383
|
[
|