Skip to content

Commit 6a9a05a

Browse files
author
Diego Ardila
committed
Merge master
2 parents b9eab12 + 06a0862 commit 6a9a05a

File tree

12 files changed

+111
-187
lines changed

12 files changed

+111
-187
lines changed

.circleci/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,11 @@ jobs:
4848
path: test_results
4949
- store_artifacts:
5050
path: test_results
51-
5251
- slack/notify:
5352
branch_pattern: master
5453
event: fail
5554
template: basic_fail_1
55+
5656
pypi_publish:
5757
docker:
5858
- image: cimg/python:3.6

README.md

Lines changed: 1 addition & 162 deletions
Original file line numberDiff line numberDiff line change
@@ -28,168 +28,7 @@ pip install --upgrade scale-nucleus
2828

2929
## Usage
3030

31-
The first step to using the Nucleus library is instantiating a client object.
32-
The client abstractions serves to authenticate the user and act as the gateway
33-
for users to interact with their datasets, models, and model runs.
34-
35-
### Create a client object
36-
37-
```python
38-
import nucleus
39-
client = nucleus.NucleusClient("YOUR_API_KEY_HERE")
40-
```
41-
42-
### Create Dataset
43-
44-
```python
45-
dataset = client.create_dataset("My Dataset")
46-
```
47-
48-
### List Datasets
49-
50-
```python
51-
datasets = client.list_datasets()
52-
```
53-
54-
### Delete a Dataset
55-
56-
By specifying target dataset id.
57-
A response code of 200 indicates successful deletion.
58-
59-
```python
60-
client.delete_dataset("YOUR_DATASET_ID")
61-
```
62-
63-
### Append Items to a Dataset
64-
65-
You can append both local images and images from the web. Simply specify the location and Nucleus will automatically infer if it's remote or a local file.
66-
67-
```python
68-
dataset_item_1 = DatasetItem(image_location="./1.jpeg", reference_id="1", metadata={"key": "value"})
69-
dataset_item_2 = DatasetItem(image_location="s3://srikanth-nucleus/9-1.jpg", reference_id="2", metadata={"key": "value"})
70-
```
71-
72-
The append function expects a list of `DatasetItem` objects to upload, like this:
73-
74-
```python
75-
response = dataset.append([dataset_item_1, dataset_item_2])
76-
```
77-
78-
### Get Dataset Info
79-
80-
Tells us the dataset name, number of dataset items, model_runs, and slice_ids.
81-
82-
```python
83-
dataset.info
84-
```
85-
86-
### Access Dataset Items
87-
88-
There are three methods to access individual Dataset Items:
89-
90-
(1) Dataset Items are accessible by reference id
91-
92-
```python
93-
item = dataset.refloc("my_img_001.png")
94-
```
95-
96-
(2) Dataset Items are accessible by index
97-
98-
```python
99-
item = dataset.iloc(0)
100-
```
101-
102-
(3) Dataset Items are accessible by the dataset_item_id assigned internally
103-
104-
```python
105-
item = dataset.loc("dataset_item_id")
106-
```
107-
108-
### Add Annotations
109-
110-
Upload groundtruth annotations for the items in your dataset.
111-
Box2DAnnotation has same format as https://dashboard.scale.com/nucleus/docs/api#add-ground-truth
112-
113-
```python
114-
annotation_1 = BoxAnnotation(reference_id="1", label="label", x=0, y=0, width=10, height=10, annotation_id="ann_1", metadata={})
115-
annotation_2 = BoxAnnotation(reference_id="2", label="label", x=0, y=0, width=10, height=10, annotation_id="ann_2", metadata={})
116-
response = dataset.annotate([annotation_1, annotation_2])
117-
```
118-
119-
For particularly large payloads, please reference the accompanying scripts in **references**
120-
121-
### Add Model
122-
123-
The model abstraction is intended to represent a unique architecture.
124-
Models are independent of any dataset.
125-
126-
```python
127-
model = client.add_model(name="My Model", reference_id="newest-cnn-its-new", metadata={"timestamp": "121012401"})
128-
```
129-
130-
### Upload Predictions to ModelRun
131-
132-
This method populates the model_run object with predictions. `ModelRun` objects need to reference a `Dataset` that has been created.
133-
Returns the associated model_id, human-readable name of the run, status, and user specified metadata.
134-
Takes a list of Box2DPredictions within the payload, where Box2DPrediction
135-
is formulated as in https://dashboard.scale.com/nucleus/docs/api#upload-model-outputs
136-
137-
```python
138-
prediction_1 = BoxPrediction(reference_id="1", label="label", x=0, y=0, width=10, height=10, annotation_id="pred_1", confidence=0.9)
139-
prediction_2 = BoxPrediction(reference_id="2", label="label", x=0, y=0, width=10, height=10, annotation_id="pred_2", confidence=0.2)
140-
141-
model_run = model.create_run(name="My Model Run", metadata={"timestamp": "121012401"}, dataset=dataset, predictions=[prediction_1, prediction_2])
142-
```
143-
144-
### Commit ModelRun
145-
146-
The commit action indicates that the user is finished uploading predictions associated
147-
with this model run. Committing a model run kicks off Nucleus internal processes
148-
to calculate performance metrics like IoU. After being committed, a ModelRun object becomes immutable.
149-
150-
```python
151-
model_run.commit()
152-
```
153-
154-
### Get ModelRun Info
155-
156-
Returns the associated model_id, human-readable name of the run, status, and user specified metadata.
157-
158-
```python
159-
model_run.info
160-
```
161-
162-
### Accessing ModelRun Predictions
163-
164-
You can access the modelRun predictions for an individual dataset_item through three methods:
165-
166-
(1) user specified reference_id
167-
168-
```python
169-
model_run.refloc("my_img_001.png")
170-
```
171-
172-
(2) Index
173-
174-
```python
175-
model_run.iloc(0)
176-
```
177-
178-
(3) Internally maintained dataset_item_id
179-
180-
```python
181-
model_run.loc("dataset_item_id")
182-
```
183-
184-
### Delete ModelRun
185-
186-
Delete a model run using the target model_run_id.
187-
188-
A response code of 200 indicates successful deletion.
189-
190-
```python
191-
client.delete_model_run("model_run_id")
192-
```
31+
For the most up to date documentation, reference: https://dashboard.scale.com/nucleus/docs/api?language=python.
19332

19433
## For Developers
19534

nucleus/__init__.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,16 @@
8686
ERROR_ITEMS,
8787
ERROR_PAYLOAD,
8888
ERRORS_KEY,
89+
JOB_ID_KEY,
90+
JOB_LAST_KNOWN_STATUS_KEY,
91+
JOB_TYPE_KEY,
92+
JOB_CREATION_TIME_KEY,
8993
IMAGE_KEY,
9094
IMAGE_URL_KEY,
9195
ITEM_METADATA_SCHEMA_KEY,
9296
ITEMS_KEY,
9397
KEEP_HISTORY_KEY,
98+
MESSAGE_KEY,
9499
MODEL_RUN_ID_KEY,
95100
NAME_KEY,
96101
NUCLEUS_ENDPOINT,
@@ -110,6 +115,7 @@
110115
NotFoundError,
111116
NucleusAPIError,
112117
)
118+
from .job import AsyncJob
113119
from .model import Model
114120
from .model_run import ModelRun
115121
from .payload_constructor import (
@@ -199,6 +205,26 @@ def list_datasets(self) -> Dict[str, Union[str, List[str]]]:
199205
"""
200206
return self.make_request({}, "dataset/", requests.get)
201207

208+
def list_jobs(
209+
self, show_completed=None, date_limit=None
210+
) -> List[AsyncJob]:
211+
"""
212+
Lists jobs for user.
213+
:return: jobs
214+
"""
215+
payload = {show_completed: show_completed, date_limit: date_limit}
216+
job_objects = self.make_request(payload, "jobs/", requests.get)
217+
return [
218+
AsyncJob(
219+
job_id=job[JOB_ID_KEY],
220+
job_last_known_status=job[JOB_LAST_KNOWN_STATUS_KEY],
221+
job_type=job[JOB_TYPE_KEY],
222+
job_creation_time=job[JOB_CREATION_TIME_KEY],
223+
client=self,
224+
)
225+
for job in job_objects
226+
]
227+
202228
def get_dataset_items(self, dataset_id) -> List[DatasetItem]:
203229
"""
204230
Gets all the dataset items inside your repo as a json blob.

nucleus/constants.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@
4242
ITEM_METADATA_SCHEMA_KEY = "item_metadata_schema"
4343
JOB_ID_KEY = "job_id"
4444
KEEP_HISTORY_KEY = "keep_history"
45+
JOB_STATUS_KEY = "job_status"
46+
JOB_LAST_KNOWN_STATUS_KEY = "job_last_known_status"
47+
JOB_TYPE_KEY = "job_type"
48+
JOB_CREATION_TIME_KEY = "job_creation_time"
4549
LABEL_KEY = "label"
4650
MASK_URL_KEY = "mask_url"
4751
MESSAGE_KEY = "message"

nucleus/dataset.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
DATASET_SLICES_KEY,
2424
DEFAULT_ANNOTATION_UPDATE_MODE,
2525
EXPORTED_ROWS,
26-
JOB_ID_KEY,
2726
NAME_KEY,
2827
REFERENCE_IDS_KEY,
2928
REQUEST_ID_KEY,
@@ -181,8 +180,7 @@ def annotate(
181180
payload={REQUEST_ID_KEY: request_id, UPDATE_KEY: update},
182181
route=f"dataset/{self.id}/annotate?async=1",
183182
)
184-
185-
return AsyncJob(response[JOB_ID_KEY], self._client)
183+
return AsyncJob.from_json(response, self._client)
186184

187185
return self._client.annotate_dataset(
188186
self.id, annotations, update=update, batch_size=batch_size
@@ -241,7 +239,7 @@ def append(
241239
payload={REQUEST_ID_KEY: request_id, UPDATE_KEY: update},
242240
route=f"dataset/{self.id}/append?async=1",
243241
)
244-
return AsyncJob(response[JOB_ID_KEY], self._client)
242+
return AsyncJob.from_json(response, self._client)
245243

246244
return self._client.populate_dataset(
247245
self.id,
@@ -368,4 +366,4 @@ def delete_annotations(
368366
response = self._client.delete_annotations(
369367
self.id, reference_ids, keep_history
370368
)
371-
return AsyncJob(response[JOB_ID_KEY], self._client)
369+
return AsyncJob.from_json(response, self._client)

nucleus/job.py

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,39 @@
11
from dataclasses import dataclass
22
import time
33
from typing import Dict, List
4-
54
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+
)
612

713
JOB_POLLING_INTERVAL = 5
814

915

1016
@dataclass
1117
class AsyncJob:
12-
id: str
18+
job_id: str
19+
job_last_known_status: str
20+
job_type: str
21+
job_creation_time: str
1322
client: "NucleusClient" # type: ignore # noqa: F821
1423

1524
def status(self) -> Dict[str, str]:
16-
return self.client.make_request(
25+
response = self.client.make_request(
1726
payload={},
18-
route=f"job/{self.id}",
27+
route=f"job/{self.job_id}",
1928
requests_command=requests.get,
2029
)
30+
self.job_last_known_status = response[STATUS_KEY]
31+
return response
2132

2233
def errors(self) -> List[str]:
2334
return self.client.make_request(
2435
payload={},
25-
route=f"job/{self.id}/errors",
36+
route=f"job/{self.job_id}/errors",
2637
requests_command=requests.get,
2738
)
2839

@@ -42,6 +53,16 @@ def sleep_until_complete(self, verbose_std_out=True):
4253
if final_status["status"] == "Errored":
4354
raise JobError(final_status, self)
4455

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+
4566

4667
class JobError(Exception):
4768
def __init__(self, job_status: Dict[str, str], job: AsyncJob):

nucleus/model_run.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
ANNOTATIONS_KEY,
99
BOX_TYPE,
1010
DEFAULT_ANNOTATION_UPDATE_MODE,
11-
JOB_ID_KEY,
1211
POLYGON_TYPE,
1312
REQUEST_ID_KEY,
1413
SEGMENTATION_TYPE,
@@ -115,8 +114,7 @@ def predict(
115114
payload={REQUEST_ID_KEY: request_id, UPDATE_KEY: update},
116115
route=f"modelRun/{self.model_run_id}/predict?async=1",
117116
)
118-
119-
return AsyncJob(response[JOB_ID_KEY], self._client)
117+
return AsyncJob.from_json(response, self._client)
120118
else:
121119
return self._client.predict(self.model_run_id, annotations, update)
122120

nucleus/slice.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
from nucleus.dataset_item import DatasetItem
77
from nucleus.job import AsyncJob
88
from nucleus.utils import convert_export_payload, format_dataset_item_response
9-
from nucleus.constants import EXPORTED_ROWS
9+
from nucleus.constants import (
10+
EXPORTED_ROWS,
11+
)
1012

1113

1214
class Slice:
@@ -122,7 +124,7 @@ def send_to_labeling(self, project_id: str):
122124
response = self._client.make_request(
123125
{}, f"slice/{self.slice_id}/{project_id}/send_to_labeling"
124126
)
125-
return AsyncJob(response["job_id"], self._client)
127+
return AsyncJob.from_json(response, self._client)
126128

127129

128130
def check_annotations_are_in_slice(

0 commit comments

Comments
 (0)