Skip to content

Commit 54f6921

Browse files
authored
Merge branch 'develop' into paultancre-SN-131
2 parents 0b1f2d7 + ef598e1 commit 54f6921

File tree

4 files changed

+155
-43
lines changed

4 files changed

+155
-43
lines changed

examples/basics/projects.ipynb

Lines changed: 121 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
"source": [
6060
"import labelbox as lb\n",
6161
"import labelbox.types as lb_types\n",
62+
"from labelbox.schema.conflict_resolution_strategy import ConflictResolutionStrategy\n",
6263
"import uuid"
6364
],
6465
"cell_type": "code",
@@ -138,7 +139,7 @@
138139
{
139140
"metadata": {},
140141
"source": [
141-
"### Add a data rows to a project \n"
142+
"### Add data rows to a project \n"
142143
],
143144
"cell_type": "markdown"
144145
},
@@ -226,19 +227,14 @@
226227
"\n",
227228
"In this section, we are creating an ontology to attach to a project and creating labels to import as ground truths. We need this setup to demonstrate other methods later in the demo. For more information, please reference our [Ontology](https://docs.labelbox.com/reference/ontology) and [Import Image Annotation](https://docs.labelbox.com/reference/import-image-annotations) development guides."
228229
],
229-
"cell_type": "raw"
230-
},
231-
{
232-
"metadata": {},
233-
"source": [],
234230
"cell_type": "markdown"
235231
},
236232
{
237233
"metadata": {},
238234
"source": [
239-
"Create your ontology"
235+
"#### Create your ontology"
240236
],
241-
"cell_type": "raw"
237+
"cell_type": "markdown"
242238
},
243239
{
244240
"metadata": {},
@@ -263,14 +259,13 @@
263259
{
264260
"metadata": {},
265261
"source": [
266-
"Attach ontology to project"
262+
"#### Attach ontology to project"
267263
],
268264
"cell_type": "markdown"
269265
},
270266
{
271267
"metadata": {},
272268
"source": [
273-
"\n",
274269
"project.setup_editor(ontology)"
275270
],
276271
"cell_type": "code",
@@ -280,7 +275,7 @@
280275
{
281276
"metadata": {},
282277
"source": [
283-
"Create labels and upload them to project as ground truths"
278+
"### Create labels and upload them to project as ground truths"
284279
],
285280
"cell_type": "markdown"
286281
},
@@ -290,7 +285,7 @@
290285
"# Create labels\n",
291286
"labels = []\n",
292287
"for global_key in global_keys:\n",
293-
" labels.append(lb_types.Label(data=lb_types.ImageData(global_key=global_key),\n",
288+
" labels.append(lb_types.Label(data={\"global_key\":global_key},\n",
294289
" annotations=[\n",
295290
" # Create radio classification annotation for labels\n",
296291
" lb_types.ClassificationAnnotation(\n",
@@ -384,6 +379,118 @@
384379
"outputs": [],
385380
"execution_count": null
386381
},
382+
{
383+
"metadata": {},
384+
"source": [
385+
"### Copy labels and data rows from one project to a different project\n",
386+
"In the below steps we will be copying data rows with their corresponding labels from one project to a different project with a similar ontology. First, we must set up a new project with a ontology that matches the tooling of our source project ontology."
387+
],
388+
"cell_type": "markdown"
389+
},
390+
{
391+
"metadata": {},
392+
"source": [
393+
"# Create an empty destination project\n",
394+
"destination_project = client.create_project(name=\"destination-test-project\",\n",
395+
" description=\"a description\",\n",
396+
" media_type=lb.MediaType.Image)\n",
397+
"\n",
398+
"# Create ontology and attach to destination project\n",
399+
"destination_ontology_builder = lb.OntologyBuilder(classifications=[ # List of Classification objects\n",
400+
" lb.Classification(class_type=lb.Classification.Type.RADIO,\n",
401+
" name=\"destination_radio_question\",\n",
402+
" options=[\n",
403+
" lb.Option(value=\"destination_first_radio_answer\"),\n",
404+
" lb.Option(value=\"destination_second_radio_answer\")\n",
405+
" ]),\n",
406+
"])\n",
407+
"\n",
408+
"destination_ontology = client.create_ontology(\"dest-test-ontology\",\n",
409+
" ontology_builder.asdict())\n",
410+
"\n",
411+
"destination_project.setup_editor(destination_ontology)"
412+
],
413+
"cell_type": "code",
414+
"outputs": [],
415+
"execution_count": null
416+
},
417+
{
418+
"metadata": {},
419+
"source": [
420+
"#### Copy data rows and labels\n",
421+
"To copy our data rows and labels to our project from a source project we will be using the `send_to_annotate_from_catalog` method with our Labelbox client.\n",
422+
"\n",
423+
"##### Parameters\n",
424+
"\n",
425+
"When you send data rows with labels to our destination project, you may choose to include or exclude certain parameters, at a minimum a `source_project_id` will need to be provided:\n",
426+
"\n",
427+
"* `source_project_id`\n",
428+
" - The id of the project were our data rows with labels will originate.\n",
429+
"* `annotation_ontology_mapping`\n",
430+
" - A dictionary containing the mapping of the source project's ontology feature schema ids to the destination project's ontology feature schema ids. If left empty only the data rows will be sent to our destination project with no labels.\n",
431+
"* `exclude_data_rows_in_project`\n",
432+
" - Excludes data rows that are already in the project. \n",
433+
"* `override_existing_annotations_rule` \n",
434+
" - The strategy defining how to handle conflicts in classifications between the data rows that already exist in the project and incoming labels from the source project. \n",
435+
" * Defaults to ConflictResolutionStrategy.KeepExisting\n",
436+
" * Options include:\n",
437+
" * ConflictResolutionStrategy.KeepExisting\n",
438+
" * ConflictResolutionStrategy.OverrideWithPredictions\n",
439+
" * ConflictResolutionStrategy.OverrideWithAnnotations\n",
440+
"* `param batch_priority`\n",
441+
" - The priority of the batch."
442+
],
443+
"cell_type": "markdown"
444+
},
445+
{
446+
"metadata": {},
447+
"source": [
448+
"# Get ontology dictionary to obtain featureSchemaIds\n",
449+
"source_ontology_normalized = ontology.normalized\n",
450+
"destination_ontology_normalized = destination_ontology.normalized\n",
451+
"\n",
452+
"ANNOTATION_ONTOLOGY_MAPPING = {\n",
453+
" source_ontology_normalized[\"classifications\"][0][\"featureSchemaId\"]:destination_ontology_normalized[\"classifications\"][0][\"featureSchemaId\"], # Classification featureSchemaID\n",
454+
" source_ontology_normalized[\"classifications\"][0][\"options\"][0][\"featureSchemaId\"]:destination_ontology_normalized[\"classifications\"][0][\"options\"][0][\"featureSchemaId\"], # Different Classification Answer featureSchemaIDs\n",
455+
" source_ontology_normalized[\"classifications\"][0][\"options\"][1][\"featureSchemaId\"]:destination_ontology_normalized[\"classifications\"][0][\"options\"][1][\"featureSchemaId\"]\n",
456+
"}"
457+
],
458+
"cell_type": "code",
459+
"outputs": [],
460+
"execution_count": null
461+
},
462+
{
463+
"metadata": {},
464+
"source": [
465+
"send_to_annotate_params = {\n",
466+
" \"source_project_id\": project.uid,\n",
467+
" \"annotations_ontology_mapping\": ANNOTATION_ONTOLOGY_MAPPING,\n",
468+
" \"exclude_data_rows_in_project\": False,\n",
469+
" \"override_existing_annotations_rule\": ConflictResolutionStrategy.OverrideWithPredictions,\n",
470+
" \"batch_priority\": 5,\n",
471+
"}\n",
472+
"\n",
473+
"# Get task id to workflow you want to send data rows. If sent to initial labeling queue, labels will be pre-labels. \n",
474+
"queue_id = [queue.uid for queue in destination_project.task_queues() if queue.queue_type == \"MANUAL_REVIEW_QUEUE\" ][0]\n",
475+
"\n",
476+
"task = client.send_to_annotate_from_catalog(\n",
477+
" destination_project_id=destination_project.uid,\n",
478+
" task_queue_id=queue_id, # ID of workflow task, set ID to None if you want to send data rows with labels to the Done queue.\n",
479+
" batch_name=\"Prediction Import Demo Batch\",\n",
480+
" data_rows=lb.GlobalKeys(\n",
481+
" global_keys # Provide a list of global keys from source project\n",
482+
" ),\n",
483+
" params=send_to_annotate_params\n",
484+
" )\n",
485+
"\n",
486+
"task.wait_till_done()\n",
487+
"\n",
488+
"print(f\"Errors: {task.errors}\")"
489+
],
490+
"cell_type": "code",
491+
"outputs": [],
492+
"execution_count": null
493+
},
387494
{
388495
"metadata": {},
389496
"source": [
@@ -395,7 +502,9 @@
395502
"metadata": {},
396503
"source": [
397504
"# project.delete()\n",
505+
"# destination_project.delete()\n",
398506
"# dataset.delete()\n",
507+
"# client.delete_unused_ontology(destination_ontology.uid)\n",
399508
"# client.delete_unused_ontology(ontology.uid)"
400509
],
401510
"cell_type": "code",

examples/model_experiments/model_predictions_to_project.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
{
3131
"metadata": {},
3232
"source": [
33-
"# Import Model Run Predictions To a Project\n",
33+
"# Import Model Run Predictions to a Project\n",
3434
"Throughout the process of training your machine learning (ML) model, you may want to export your model-run predictions and import them to your new project. In this notebook, we will demonstrate the process on how to get those predictions moved over."
3535
],
3636
"cell_type": "markdown"

libs/labelbox/src/labelbox/client.py

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2002,7 +2002,7 @@ def send_to_annotate_from_catalog(self, destination_project_id: str,
20022002
task_queue_id: Optional[str],
20032003
batch_name: str,
20042004
data_rows: Union[DataRowIds, GlobalKeys],
2005-
params: SendToAnnotateFromCatalogParams):
2005+
params: Dict[str, Any]):
20062006
"""
20072007
Sends data rows from catalog to a specified project for annotation.
20082008
@@ -2033,6 +2033,8 @@ def send_to_annotate_from_catalog(self, destination_project_id: str,
20332033
20342034
"""
20352035

2036+
validated_params = SendToAnnotateFromCatalogParams(**params)
2037+
20362038
mutation_str = """mutation SendToAnnotateFromCatalogPyApi($input: SendToAnnotateFromCatalogInput!) {
20372039
sendToAnnotateFromCatalog(input: $input) {
20382040
taskId
@@ -2044,26 +2046,14 @@ def send_to_annotate_from_catalog(self, destination_project_id: str,
20442046
task_queue_id)
20452047
data_rows_query = self.build_catalog_query(data_rows)
20462048

2047-
source_model_run_id = params.get("source_model_run_id", None)
2048-
predictions_ontology_mapping = params.get(
2049-
"predictions_ontology_mapping", None)
20502049
predictions_input = build_predictions_input(
2051-
predictions_ontology_mapping,
2052-
source_model_run_id) if source_model_run_id else None
2050+
validated_params.predictions_ontology_mapping,
2051+
validated_params.source_model_run_id
2052+
) if validated_params.source_model_run_id else None
20532053

2054-
source_project_id = params.get("source_project_id", None)
2055-
annotations_ontology_mapping = params.get(
2056-
"annotations_ontology_mapping", None)
20572054
annotations_input = build_annotations_input(
2058-
annotations_ontology_mapping,
2059-
source_project_id) if source_project_id else None
2060-
2061-
batch_priority = params.get("batch_priority", 5)
2062-
exclude_data_rows_in_project = params.get(
2063-
"exclude_data_rows_in_project", False)
2064-
override_existing_annotations_rule = params.get(
2065-
"override_existing_annotations_rule",
2066-
ConflictResolutionStrategy.KeepExisting)
2055+
validated_params.annotations_ontology_mapping, validated_params.
2056+
source_project_id) if validated_params.source_project_id else None
20672057

20682058
res = self.execute(
20692059
mutation_str, {
@@ -2072,18 +2062,18 @@ def send_to_annotate_from_catalog(self, destination_project_id: str,
20722062
destination_project_id,
20732063
"batchInput": {
20742064
"batchName": batch_name,
2075-
"batchPriority": batch_priority
2065+
"batchPriority": validated_params.batch_priority
20762066
},
20772067
"destinationTaskQueue":
20782068
destination_task_queue,
20792069
"excludeDataRowsInProject":
2080-
exclude_data_rows_in_project,
2070+
validated_params.exclude_data_rows_in_project,
20812071
"annotationsInput":
20822072
annotations_input,
20832073
"predictionsInput":
20842074
predictions_input,
20852075
"conflictLabelsResolutionStrategy":
2086-
override_existing_annotations_rule,
2076+
validated_params.override_existing_annotations_rule,
20872077
"searchQuery": {
20882078
"scope": None,
20892079
"query": [data_rows_query]

libs/labelbox/src/labelbox/schema/send_to_annotate_params.py

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@
33
from typing import Optional, Dict
44

55
from labelbox.schema.conflict_resolution_strategy import ConflictResolutionStrategy
6+
from labelbox import pydantic_compat
67

78
if sys.version_info >= (3, 8):
89
from typing import TypedDict
910
else:
1011
from typing_extensions import TypedDict
1112

1213

13-
class SendToAnnotateFromCatalogParams(TypedDict):
14+
class SendToAnnotateFromCatalogParams(pydantic_compat.BaseModel):
1415
"""
1516
Extra parameters for sending data rows to a project through catalog. At least one of source_model_run_id or
1617
source_project_id must be provided.
@@ -30,14 +31,26 @@ class SendToAnnotateFromCatalogParams(TypedDict):
3031
:param batch_priority: Optional[int] - The priority of the batch. Defaults to 5.
3132
"""
3233

33-
source_model_run_id: Optional[str]
34-
predictions_ontology_mapping: Optional[Dict[str, str]]
35-
source_project_id: Optional[str]
36-
annotations_ontology_mapping: Optional[Dict[str, str]]
37-
exclude_data_rows_in_project: Optional[bool]
38-
override_existing_annotations_rule: Optional[ConflictResolutionStrategy]
39-
batch_priority: Optional[int]
40-
34+
source_model_run_id: Optional[str] = None
35+
source_project_id: Optional[str] = None
36+
predictions_ontology_mapping: Optional[Dict[str, str]] = {}
37+
annotations_ontology_mapping: Optional[Dict[str, str]] = {}
38+
exclude_data_rows_in_project: Optional[bool] = False
39+
override_existing_annotations_rule: Optional[
40+
ConflictResolutionStrategy] = ConflictResolutionStrategy.KeepExisting
41+
batch_priority: Optional[int] = 5
42+
43+
@pydantic_compat.root_validator
44+
def check_project_id_or_model_run_id(cls, values):
45+
if not values.get("source_model_run_id") and not values.get("source_project_id"):
46+
raise ValueError(
47+
'Either source_project_id or source_model_id are required'
48+
)
49+
if values.get("source_model_run_id") and values.get("source_project_id"):
50+
raise ValueError(
51+
'Provide only a source_project_id or source_model_id not both'
52+
)
53+
return values
4154

4255
class SendToAnnotateFromModelParams(TypedDict):
4356
"""

0 commit comments

Comments
 (0)