Skip to content

Commit c8a987d

Browse files
author
Val Brodsky
committed
Merge branch 'develop' into VB/test-new-test-key
2 parents 32d8041 + 4e78990 commit c8a987d

File tree

8 files changed

+165
-67
lines changed

8 files changed

+165
-67
lines changed

libs/labelbox/src/labelbox/client.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -874,26 +874,26 @@ def create_offline_model_evaluation_project(self, **kwargs) -> Project:
874874
kwargs.pop("data_row_count", None)
875875

876876
return self._create_project(**kwargs)
877-
877+
878878

879879
def create_prompt_response_generation_project(self,
880880
dataset_id: Optional[str] = None,
881881
dataset_name: Optional[str] = None,
882882
data_row_count: int = 100,
883883
**kwargs) -> Project:
884884
"""
885-
Use this method exclusively to create a prompt and response generation project.
886-
885+
Use this method exclusively to create a prompt and response generation project.
886+
887887
Args:
888888
dataset_name: When creating a new dataset, pass the name
889889
dataset_id: When using an existing dataset, pass the id
890890
data_row_count: The number of data row assets to use for the project
891891
**kwargs: Additional parameters to pass see the create_project method
892892
Returns:
893893
Project: The created project
894-
895-
NOTE: Only a dataset_name or dataset_id should be included
896-
894+
895+
NOTE: Only a dataset_name or dataset_id should be included
896+
897897
Examples:
898898
>>> client.create_prompt_response_generation_project(name=project_name, dataset_name="new data set", media_type=MediaType.LLMPromptResponseCreation)
899899
>>> This creates a new dataset with a default number of rows (100), creates new prompt and response creation project and assigns a batch of the newly created data rows to the project.
@@ -912,12 +912,12 @@ def create_prompt_response_generation_project(self,
912912
raise ValueError(
913913
"dataset_name or dataset_id must be present and not be an empty string."
914914
)
915-
915+
916916
if dataset_id and dataset_name:
917917
raise ValueError(
918918
"Only provide a dataset_name or dataset_id, not both."
919-
)
920-
919+
)
920+
921921
if data_row_count <= 0:
922922
raise ValueError("data_row_count must be a positive integer.")
923923

@@ -927,7 +927,7 @@ def create_prompt_response_generation_project(self,
927927
else:
928928
append_to_existing_dataset = False
929929
dataset_name_or_id = dataset_name
930-
930+
931931
if "media_type" in kwargs and kwargs.get("media_type") not in [MediaType.LLMPromptCreation, MediaType.LLMPromptResponseCreation]:
932932
raise ValueError(
933933
"media_type must be either LLMPromptCreation or LLMPromptResponseCreation"
@@ -936,11 +936,11 @@ def create_prompt_response_generation_project(self,
936936
kwargs["dataset_name_or_id"] = dataset_name_or_id
937937
kwargs["append_to_existing_dataset"] = append_to_existing_dataset
938938
kwargs["data_row_count"] = data_row_count
939-
939+
940940
kwargs.pop("editor_task_type", None)
941-
941+
942942
return self._create_project(**kwargs)
943-
943+
944944
def create_response_creation_project(self, **kwargs) -> Project:
945945
"""
946946
Creates a project for response creation.
@@ -1280,7 +1280,7 @@ def create_ontology_from_feature_schemas(
12801280
leave as None otherwise.
12811281
Returns:
12821282
The created Ontology
1283-
1283+
12841284
NOTE for chat evaluation, we currently force media_type to Conversational and for response creation, we force media_type to Text.
12851285
"""
12861286
tools, classifications = [], []

libs/labelbox/src/labelbox/data/annotation_types/label.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,10 @@ class Label(pydantic_compat.BaseModel):
5050
data: DataType
5151
annotations: List[Union[ClassificationAnnotation, ObjectAnnotation,
5252
VideoMaskAnnotation, ScalarMetric,
53-
ConfusionMatrixMetric,
54-
RelationshipAnnotation,
53+
ConfusionMatrixMetric, RelationshipAnnotation,
5554
PromptClassificationAnnotation]] = []
5655
extra: Dict[str, Any] = {}
56+
is_benchmark_reference: Optional[bool] = False
5757

5858
@pydantic_compat.root_validator(pre=True)
5959
def validate_data(cls, label):
@@ -219,9 +219,8 @@ def validate_union(cls, value):
219219
)
220220
# Validates only one prompt annotation is included
221221
if isinstance(v, PromptClassificationAnnotation):
222-
prompt_count+=1
223-
if prompt_count > 1:
224-
raise TypeError(
225-
f"Only one prompt annotation is allowed per label"
226-
)
222+
prompt_count += 1
223+
if prompt_count > 1:
224+
raise TypeError(
225+
f"Only one prompt annotation is allowed per label")
227226
return value

libs/labelbox/src/labelbox/data/serialization/ndjson/converter.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,14 +106,16 @@ def serialize(
106106
if not isinstance(annotation, RelationshipAnnotation):
107107
uuid_safe_annotations.append(annotation)
108108
label.annotations = uuid_safe_annotations
109-
for example in NDLabel.from_common([label]):
110-
annotation_uuid = getattr(example, "uuid", None)
109+
for annotation in NDLabel.from_common([label]):
110+
annotation_uuid = getattr(annotation, "uuid", None)
111111

112-
res = example.dict(
112+
res = annotation.dict(
113113
by_alias=True,
114114
exclude={"uuid"} if annotation_uuid == "None" else None,
115115
)
116116
for k, v in list(res.items()):
117117
if k in IGNORE_IF_NONE and v is None:
118118
del res[k]
119+
if getattr(label, 'is_benchmark_reference'):
120+
res['isBenchmarkReferenceLabel'] = True
119121
yield res

libs/labelbox/src/labelbox/schema/ontology.py

Lines changed: 63 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@ class Option:
5353
label: Optional[Union[str, int]] = None
5454
schema_id: Optional[str] = None
5555
feature_schema_id: Optional[FeatureSchemaId] = None
56-
options: Union[List["Classification"], List["PromptResponseClassification"]] = field(default_factory=list)
56+
options: Union[List["Classification"],
57+
List["PromptResponseClassification"]] = field(
58+
default_factory=list)
5759

5860
def __post_init__(self):
5961
if self.label is None:
@@ -82,7 +84,9 @@ def asdict(self) -> Dict[str, Any]:
8284
"options": [o.asdict(is_subclass=True) for o in self.options]
8385
}
8486

85-
def add_option(self, option: Union["Classification", "PromptResponseClassification"]) -> None:
87+
def add_option(
88+
self, option: Union["Classification",
89+
"PromptResponseClassification"]) -> None:
8690
if option.name in (o.name for o in self.options):
8791
raise InconsistentOntologyException(
8892
f"Duplicate nested classification '{option.name}' "
@@ -140,7 +144,7 @@ class Type(Enum):
140144
class Scope(Enum):
141145
GLOBAL = "global"
142146
INDEX = "index"
143-
147+
144148
class UIMode(Enum):
145149
HOTKEY = "hotkey"
146150
SEARCHABLE = "searchable"
@@ -155,7 +159,8 @@ class UIMode(Enum):
155159
schema_id: Optional[str] = None
156160
feature_schema_id: Optional[str] = None
157161
scope: Scope = None
158-
ui_mode: Optional[UIMode] = None # How this classification should be answered (e.g. hotkeys / autocomplete, etc)
162+
ui_mode: Optional[
163+
UIMode] = None # How this classification should be answered (e.g. hotkeys / autocomplete, etc)
159164

160165
def __post_init__(self):
161166
if self.class_type == Classification.Type.DROPDOWN:
@@ -187,7 +192,8 @@ def from_dict(cls, dictionary: Dict[str, Any]) -> Dict[str, Any]:
187192
instructions=dictionary["instructions"],
188193
required=dictionary.get("required", False),
189194
options=[Option.from_dict(o) for o in dictionary["options"]],
190-
ui_mode=cls.UIMode(dictionary["uiMode"]) if "uiMode" in dictionary else None,
195+
ui_mode=cls.UIMode(dictionary["uiMode"])
196+
if "uiMode" in dictionary else None,
191197
schema_id=dictionary.get("schemaNodeId", None),
192198
feature_schema_id=dictionary.get("featureSchemaId", None),
193199
scope=cls.Scope(dictionary.get("scope", cls.Scope.GLOBAL)))
@@ -206,7 +212,8 @@ def asdict(self, is_subclass: bool = False) -> Dict[str, Any]:
206212
"schemaNodeId": self.schema_id,
207213
"featureSchemaId": self.feature_schema_id
208214
}
209-
if (self.class_type == self.Type.RADIO or self.class_type == self.Type.CHECKLIST) and self.ui_mode:
215+
if (self.class_type == self.Type.RADIO or
216+
self.class_type == self.Type.CHECKLIST) and self.ui_mode:
210217
# added because this key does nothing for text so no point of including
211218
classification["uiMode"] = self.ui_mode.value
212219
if is_subclass:
@@ -221,7 +228,8 @@ def add_option(self, option: Option) -> None:
221228
f"Duplicate option '{option.value}' "
222229
f"for classification '{self.name}'.")
223230
self.options.append(option)
224-
231+
232+
225233
@dataclass
226234
class ResponseOption(Option):
227235
"""
@@ -239,7 +247,7 @@ class ResponseOption(Option):
239247
feature_schema_id: (str)
240248
options: (list)
241249
"""
242-
250+
243251
@classmethod
244252
def from_dict(
245253
cls,
@@ -294,7 +302,7 @@ class PromptResponseClassification:
294302
schema_id: (str)
295303
feature_schema_id: (str)
296304
"""
297-
305+
298306
def __post_init__(self):
299307
if self.name is None:
300308
msg = (
@@ -314,7 +322,7 @@ def __post_init__(self):
314322

315323
class Type(Enum):
316324
PROMPT = "prompt"
317-
RESPONSE_TEXT= "response-text"
325+
RESPONSE_TEXT = "response-text"
318326
RESPONSE_CHECKLIST = "response-checklist"
319327
RESPONSE_RADIO = "response-radio"
320328

@@ -332,15 +340,18 @@ class Type(Enum):
332340

333341
@classmethod
334342
def from_dict(cls, dictionary: Dict[str, Any]) -> Dict[str, Any]:
335-
return cls(class_type=cls.Type(dictionary["type"]),
336-
name=dictionary["name"],
337-
instructions=dictionary["instructions"],
338-
required=True, # always required
339-
options=[ResponseOption.from_dict(o) for o in dictionary["options"]],
340-
character_min=dictionary.get("minCharacters", None),
341-
character_max=dictionary.get("maxCharacters", None),
342-
schema_id=dictionary.get("schemaNodeId", None),
343-
feature_schema_id=dictionary.get("featureSchemaId", None))
343+
return cls(
344+
class_type=cls.Type(dictionary["type"]),
345+
name=dictionary["name"],
346+
instructions=dictionary["instructions"],
347+
required=True, # always required
348+
options=[
349+
ResponseOption.from_dict(o) for o in dictionary["options"]
350+
],
351+
character_min=dictionary.get("minCharacters", None),
352+
character_max=dictionary.get("maxCharacters", None),
353+
schema_id=dictionary.get("schemaNodeId", None),
354+
feature_schema_id=dictionary.get("featureSchemaId", None))
344355

345356
def asdict(self, is_subclass: bool = False) -> Dict[str, Any]:
346357
if self.class_type in self._REQUIRES_OPTIONS \
@@ -351,12 +362,13 @@ def asdict(self, is_subclass: bool = False) -> Dict[str, Any]:
351362
"type": self.class_type.value,
352363
"instructions": self.instructions,
353364
"name": self.name,
354-
"required": True, # always required
365+
"required": True, # always required
355366
"options": [o.asdict() for o in self.options],
356367
"schemaNodeId": self.schema_id,
357368
"featureSchemaId": self.feature_schema_id
358369
}
359-
if (self.class_type == self.Type.PROMPT or self.class_type == self.Type.RESPONSE_TEXT):
370+
if (self.class_type == self.Type.PROMPT or
371+
self.class_type == self.Type.RESPONSE_TEXT):
360372
if self.character_min:
361373
classification["minCharacters"] = self.character_min
362374
if self.character_max:
@@ -488,7 +500,8 @@ class Ontology(DbObject):
488500
def __init__(self, *args, **kwargs) -> None:
489501
super().__init__(*args, **kwargs)
490502
self._tools: Optional[List[Tool]] = None
491-
self._classifications: Optional[Union[List[Classification],List[PromptResponseClassification]]] = None
503+
self._classifications: Optional[Union[
504+
List[Classification], List[PromptResponseClassification]]] = None
492505

493506
def tools(self) -> List[Tool]:
494507
"""Get list of tools (AKA objects) in an Ontology."""
@@ -498,15 +511,20 @@ def tools(self) -> List[Tool]:
498511
]
499512
return self._tools
500513

501-
def classifications(self) -> List[Union[Classification, PromptResponseClassification]]:
514+
def classifications(
515+
self) -> List[Union[Classification, PromptResponseClassification]]:
502516
"""Get list of classifications in an Ontology."""
503517
if self._classifications is None:
504518
self._classifications = []
505519
for classification in self.normalized["classifications"]:
506-
if "type" in classification and classification["type"] in PromptResponseClassification.Type._value2member_map_.keys():
507-
self._classifications.append(PromptResponseClassification.from_dict(classification))
520+
if "type" in classification and classification[
521+
"type"] in PromptResponseClassification.Type._value2member_map_.keys(
522+
):
523+
self._classifications.append(
524+
PromptResponseClassification.from_dict(classification))
508525
else:
509-
self._classifications.append(Classification.from_dict(classification))
526+
self._classifications.append(
527+
Classification.from_dict(classification))
510528
return self._classifications
511529

512530

@@ -536,14 +554,22 @@ class OntologyBuilder:
536554
537555
"""
538556
tools: List[Tool] = field(default_factory=list)
539-
classifications: List[Union[Classification, PromptResponseClassification]] = field(default_factory=list)
557+
classifications: List[Union[Classification,
558+
PromptResponseClassification]] = field(
559+
default_factory=list)
560+
561+
def foo(self):
562+
pass
540563

541564
@classmethod
542565
def from_dict(cls, dictionary: Dict[str, Any]) -> Dict[str, Any]:
543566
classifications = []
544567
for c in dictionary["classifications"]:
545-
if "type" in c and c["type"] in PromptResponseClassification.Type._value2member_map_.keys():
546-
classifications.append(PromptResponseClassification.from_dict(c))
568+
if "type" in c and c[
569+
"type"] in PromptResponseClassification.Type._value2member_map_.keys(
570+
):
571+
classifications.append(
572+
PromptResponseClassification.from_dict(c))
547573
else:
548574
classifications.append(Classification.from_dict(c))
549575
return cls(tools=[Tool.from_dict(t) for t in dictionary["tools"]],
@@ -554,11 +580,13 @@ def asdict(self) -> Dict[str, Any]:
554580
classifications = []
555581
prompts = 0
556582
for c in self.classifications:
557-
if hasattr(c, "class_type") and c.class_type in PromptResponseClassification.Type:
583+
if hasattr(c, "class_type"
584+
) and c.class_type in PromptResponseClassification.Type:
558585
if c.class_type == PromptResponseClassification.Type.PROMPT:
559586
prompts += 1
560587
if prompts > 1:
561-
raise ValueError("Only one prompt is allowed per ontology")
588+
raise ValueError(
589+
"Only one prompt is allowed per ontology")
562590
classifications.append(PromptResponseClassification.asdict(c))
563591
else:
564592
classifications.append(Classification.asdict(c))
@@ -592,7 +620,10 @@ def add_tool(self, tool: Tool) -> None:
592620
f"Duplicate tool name '{tool.name}'. ")
593621
self.tools.append(tool)
594622

595-
def add_classification(self, classification: Union[Classification, PromptResponseClassification]) -> None:
623+
def add_classification(
624+
self, classification: Union[Classification,
625+
PromptResponseClassification]
626+
) -> None:
596627
if classification.name in (c.name for c in self.classifications):
597628
raise InconsistentOntologyException(
598629
f"Duplicate classification name '{classification.name}'. ")

libs/labelbox/tests/data/annotation_import/test_data_types.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
1-
import datetime
2-
from labelbox.schema.label import Label
31
import pytest
4-
import uuid
52

63
from labelbox.data.annotation_types.data import (
74
AudioData,
85
ConversationData,
9-
DicomData,
106
DocumentData,
117
HTMLData,
128
ImageData,
@@ -15,11 +11,8 @@
1511
from labelbox.data.serialization import NDJsonConverter
1612
from labelbox.data.annotation_types.data.video import VideoData
1713

18-
import labelbox as lb
1914
import labelbox.types as lb_types
2015
from labelbox.schema.media_type import MediaType
21-
from labelbox.schema.annotation_import import AnnotationImportState
22-
from labelbox import Project, Client
2316

2417
# Unit test for label based on data type.
2518
# TODO: Dicom removed it is unstable when you deserialize and serialize on label import. If we intend to keep this library this needs add generic data types tests work with this data type.
@@ -83,4 +76,4 @@ def test_data_row_type_by_global_key(
8376
annotations=label.annotations)
8477

8578
assert data_label.data.global_key == label.data.global_key
86-
assert label.annotations == data_label.annotations
79+
assert label.annotations == data_label.annotations

0 commit comments

Comments
 (0)