Skip to content

Commit 5856d38

Browse files
committed
nucleus side working
1 parent 4be1981 commit 5856d38

File tree

5 files changed

+81
-42
lines changed

5 files changed

+81
-42
lines changed

nucleus/__init__.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
from typing import List, Union, Dict, Callable, Any, Optional
5959

6060
import tqdm
61-
import tqdm.notebook
61+
import tqdm.notebook as tqdm_notebook
6262

6363
import grequests
6464
import requests
@@ -69,14 +69,14 @@
6969

7070
from .dataset import Dataset
7171
from .dataset_item import DatasetItem
72-
from .annotation import BoxAnnotation, PolygonAnnotation
72+
from .annotation import AnnotationItem, BoxAnnotation, PolygonAnnotation
7373
from .prediction import BoxPrediction, PolygonPrediction
7474
from .model_run import ModelRun
7575
from .slice import Slice
7676
from .upload_response import UploadResponse
7777
from .payload_constructor import (
7878
construct_append_payload,
79-
construct_box_annotation_payload,
79+
construct_annotation_payload,
8080
construct_model_creation_payload,
8181
construct_box_predictions_payload,
8282
)
@@ -129,7 +129,7 @@ def __init__(self, api_key: str, use_notebook: bool = False):
129129
self.api_key = api_key
130130
self.tqdm_bar = tqdm.tqdm
131131
if use_notebook:
132-
self.tqdm_bar = tqdm.notebook.tqdm
132+
self.tqdm_bar = tqdm_notebook.tqdm
133133

134134
def list_models(self) -> List[str]:
135135
"""
@@ -462,13 +462,14 @@ def exception_handler(request, exception):
462462
def annotate_dataset(
463463
self,
464464
dataset_id: str,
465-
annotations: List[DatasetItem],
465+
annotations: List[AnnotationItem],
466+
edit_mode: str,
466467
batch_size: int = 100,
467468
):
468469
"""
469470
Uploads ground truth annotations for a given dataset.
470471
:param dataset_id: id of the dataset
471-
:param annotations: List[DatasetItem]
472+
:param annotations: List[AnnotationItem]
472473
:return: {"dataset_id: str, "annotations_processed": int}
473474
"""
474475

@@ -485,7 +486,7 @@ def annotate_dataset(
485486
tqdm_batches = self.tqdm_bar(batches)
486487

487488
for batch in tqdm_batches:
488-
payload = construct_box_annotation_payload(batch)
489+
payload = construct_annotation_payload(batch, edit_mode)
489490
response = self._make_request(
490491
payload, f"dataset/{dataset_id}/annotate"
491492
)

nucleus/annotation.py

Lines changed: 46 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from enum import Enum
22
from typing import Dict, Optional, Any, Union, List
33
from .constants import (
4+
ANNOTATIONS_KEY,
5+
ANNOTATION_ID_KEY,
46
DATASET_ITEM_ID_KEY,
57
REFERENCE_ID_KEY,
68
METADATA_KEY,
@@ -32,22 +34,14 @@ def __init__(
3234
y: Union[float, int],
3335
width: Union[float, int],
3436
height: Union[float, int],
35-
reference_id: str = None,
36-
item_id: str = None,
37-
annotation_id: str = None,
37+
annotation_id: Optional[str] = None,
3838
metadata: Optional[Dict] = None,
3939
):
40-
if bool(reference_id) == bool(item_id):
41-
raise Exception(
42-
"You must specify either a reference_id or an item_id for an annotation."
43-
)
4440
self.label = label
4541
self.x = x
4642
self.y = y
4743
self.width = width
4844
self.height = height
49-
self.reference_id = reference_id
50-
self.item_id = item_id
5145
self.annotation_id = annotation_id
5246
self.metadata = metadata if metadata else {}
5347

@@ -60,9 +54,7 @@ def from_json(cls, payload: dict):
6054
y=geometry.get(Y_KEY, 0),
6155
width=geometry.get(WIDTH_KEY, 0),
6256
height=geometry.get(HEIGHT_KEY, 0),
63-
reference_id=payload.get(REFERENCE_ID_KEY, None),
64-
item_id=payload.get(DATASET_ITEM_ID_KEY, None),
65-
annotation_id=payload.get(ANNOTATION_ID, None),
57+
annotation_id=payload.get(ANNOTATION_ID_KEY, None),
6658
metadata=payload.get(METADATA_KEY, {}),
6759
)
6860

@@ -76,8 +68,7 @@ def to_payload(self) -> dict:
7668
WIDTH_KEY: self.width,
7769
HEIGHT_KEY: self.height,
7870
},
79-
REFERENCE_ID_KEY: self.reference_id,
80-
ANNOTATION_ID: self.annotation_id,
71+
ANNOTATION_ID_KEY: self.annotation_id,
8172
METADATA_KEY: self.metadata,
8273
}
8374

@@ -91,19 +82,11 @@ def __init__(
9182
self,
9283
label: str,
9384
vertices: List[Any],
94-
reference_id: str = None,
95-
item_id: str = None,
96-
annotation_id: str = None,
85+
annotation_id: Optional[str] = None,
9786
metadata: Optional[Dict] = None,
9887
):
99-
if bool(reference_id) == bool(item_id):
100-
raise Exception(
101-
"You must specify either a reference_id or an item_id for an annotation."
102-
)
10388
self.label = label
10489
self.vertices = vertices
105-
self.reference_id = reference_id
106-
self.item_id = item_id
10790
self.annotation_id = annotation_id
10891
self.metadata = metadata if metadata else {}
10992

@@ -113,9 +96,7 @@ def from_json(cls, payload: dict):
11396
return cls(
11497
label=payload.get(LABEL_KEY, 0),
11598
vertices=geometry.get(VERTICES_KEY, []),
116-
reference_id=payload.get(REFERENCE_ID_KEY, None),
117-
item_id=payload.get(DATASET_ITEM_ID_KEY, None),
118-
annotation_id=payload.get(ANNOTATION_ID, None),
99+
annotation_id=payload.get(ANNOTATION_ID_KEY, None),
119100
metadata=payload.get(METADATA_KEY, {}),
120101
)
121102

@@ -124,10 +105,47 @@ def to_payload(self) -> dict:
124105
LABEL_KEY: self.label,
125106
TYPE_KEY: POLYGON_TYPE,
126107
GEOMETRY_KEY: {VERTICES_KEY: self.vertices},
127-
REFERENCE_ID_KEY: self.reference_id,
128-
ANNOTATION_ID: self.annotation_id,
108+
ANNOTATION_ID_KEY: self.annotation_id,
129109
METADATA_KEY: self.metadata,
130110
}
131111

132112
def __str__(self):
133113
return str(self.to_payload())
114+
115+
116+
class AnnotationItem:
117+
def __init__(
118+
self,
119+
annotations: List[Union[PolygonAnnotation, BoxAnnotation]],
120+
reference_id: Optional[str] = None,
121+
item_id: Optional[str] = None,
122+
):
123+
if bool(reference_id) == bool(item_id):
124+
raise Exception(
125+
"You must specify either a reference_id or an item_id for an annotation."
126+
)
127+
self.annotations = annotations
128+
self.reference_id = reference_id
129+
self.item_id = item_id
130+
131+
@classmethod
132+
def from_json(cls, payload: dict):
133+
return cls(
134+
annotations=[
135+
BoxAnnotation.from_json(ann)
136+
if ann[TYPE_KEY] == "box"
137+
else PolygonAnnotation.from_json(ann)
138+
for ann in payload.get(ANNOTATIONS_KEY)
139+
],
140+
reference_id=payload.get(REFERENCE_ID_KEY, None),
141+
item_id=payload.get(DATASET_ITEM_ID_KEY, None),
142+
)
143+
144+
def to_payload(self) -> dict:
145+
return {
146+
ANNOTATIONS_KEY: [annotation.to_payload() for annotation in self.annotations],
147+
REFERENCE_ID_KEY: self.reference_id,
148+
}
149+
150+
def __str__(self):
151+
return str(self.to_payload())

nucleus/constants.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,13 @@
1414
ERROR_PAYLOAD = "error_payload"
1515
ERROR_CODES = "error_codes"
1616
ANNOTATIONS_KEY = "annotations"
17+
ANNOTATION_ITEMS_KEY = "annotation_items"
18+
ANNOTATION_ID_KEY = "annotation_id"
1719
ANNOTATIONS_PROCESSED_KEY = "annotations_processed"
1820
PREDICTIONS_PROCESSED_KEY = "predictions_processed"
21+
EDIT_MODE_KEY = "edit_mode"
22+
ANNOTATION_EDIT_MODES = ["update", "insert", "rewrite"]
23+
DEFAULT_ANNOTATION_EDIT_MODE = "insert"
1924
STATUS_CODE_KEY = "status_code"
2025
SUCCESS_STATUS_CODES = [200, 201]
2126
ERRORS_KEY = "errors"

nucleus/dataset.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
from typing import List, Dict, Any, Optional, Union
1+
from typing import List, Dict, Any, Optional
22
from .dataset_item import DatasetItem
3-
from .annotation import BoxAnnotation, PolygonAnnotation
3+
from .annotation import AnnotationItem, BoxAnnotation, PolygonAnnotation
44
from .constants import (
55
DATASET_NAME_KEY,
66
DATASET_MODEL_RUNS_KEY,
@@ -10,11 +10,15 @@
1010
REFERENCE_IDS_KEY,
1111
NAME_KEY,
1212
ITEM_KEY,
13+
ANNOTATION_EDIT_MODES,
1314
ANNOTATIONS_KEY,
15+
DEFAULT_ANNOTATION_EDIT_MODE,
1416
)
1517
from .payload_constructor import construct_model_run_creation_payload
1618

1719

20+
21+
1822
class Dataset:
1923
"""
2024
Nucleus Dataset. You can append images with metadata to your dataset,
@@ -87,7 +91,8 @@ def create_model_run(
8791

8892
def annotate(
8993
self,
90-
annotations: List[Union[BoxAnnotation, PolygonAnnotation]],
94+
annotations: List[AnnotationItem],
95+
edit_mode: Optional[str] = None,
9196
batch_size: int = 20,
9297
) -> dict:
9398
"""
@@ -102,8 +107,15 @@ def annotate(
102107
"ignored_items": int,
103108
}
104109
"""
110+
if edit_mode is None:
111+
edit_mode = DEFAULT_ANNOTATION_EDIT_MODE
112+
if edit_mode not in ANNOTATION_EDIT_MODES:
113+
raise Exception(
114+
f'Edit mode must be empty or one of {ANNOTATION_EDIT_MODES}!'
115+
)
116+
105117
return self._client.annotate_dataset(
106-
self.id, annotations, batch_size=batch_size
118+
self.id, annotations, edit_mode=edit_mode, batch_size=batch_size
107119
)
108120

109121
def ingest_tasks(self, task_ids: dict):

nucleus/payload_constructor.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77
METADATA_KEY,
88
REFERENCE_ID_KEY,
99
ANNOTATIONS_KEY,
10+
ANNOTATION_ITEMS_KEY,
1011
ITEMS_KEY,
1112
FORCE_KEY,
1213
MODEL_ID_KEY,
14+
EDIT_MODE_KEY,
1315
)
1416

1517

@@ -27,14 +29,15 @@ def construct_append_payload(
2729
)
2830

2931

30-
def construct_box_annotation_payload(
31-
box_annotations: List[BoxAnnotation],
32+
def construct_annotation_payload(
33+
box_annotations: List[Union[BoxAnnotation, PolygonAnnotation]],
34+
edit_mode: str,
3235
) -> dict:
3336
annotations = []
3437
for annotation in box_annotations:
3538
annotations.append(annotation.to_payload())
3639

37-
return {ANNOTATIONS_KEY: annotations}
40+
return {ANNOTATION_ITEMS_KEY: annotations, EDIT_MODE_KEY: edit_mode}
3841

3942

4043
def construct_box_predictions_payload(

0 commit comments

Comments
 (0)