diff --git a/libs/labelbox/src/labelbox/schema/ontology.py b/libs/labelbox/src/labelbox/schema/ontology.py index 1726b514c..48fc19f32 100644 --- a/libs/labelbox/src/labelbox/schema/ontology.py +++ b/libs/labelbox/src/labelbox/schema/ontology.py @@ -562,14 +562,18 @@ class OntologyBuilder: There are no required instantiation arguments. To create an ontology, use the asdict() method after fully building your - ontology within this class, and inserting it into project.setup() as the - "labeling_frontend_options" parameter. + ontology within this class, and inserting it into client.create_ontology() as the + "normalized" parameter. Example: - builder = OntologyBuilder() - ... - frontend = list(client.get_labeling_frontends())[0] - project.setup(frontend, builder.asdict()) + >>> builder = OntologyBuilder() + >>> ... + >>> ontology = client.create_ontology( + >>> "Ontology from new features", + >>> ontology_builder.asdict(), + >>> media_type=lb.MediaType.Image, + >>> ) + >>> project.connect_ontology(ontology) attributes: tools: (list) diff --git a/libs/labelbox/src/labelbox/schema/project.py b/libs/labelbox/src/labelbox/schema/project.py index 0fd6cf24d..bdbf9e665 100644 --- a/libs/labelbox/src/labelbox/schema/project.py +++ b/libs/labelbox/src/labelbox/schema/project.py @@ -684,34 +684,6 @@ def connect_ontology(self, ontology) -> None: timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") self.update(setup_complete=timestamp) - def setup(self, labeling_frontend, labeling_frontend_options) -> None: - """This method will associate default labeling frontend with the project and create an ontology based on labeling_frontend_options. - - Args: - labeling_frontend (LabelingFrontend): Do not use, this parameter is deprecated. We now associate the default labeling frontend with the project. - labeling_frontend_options (dict or str): Labeling frontend options, - a.k.a. project ontology. If given a `dict` it will be converted - to `str` using `json.dumps`. - """ - - warnings.warn("This method is deprecated use connect_ontology instead.") - if labeling_frontend is not None: - warnings.warn( - "labeling_frontend parameter will not be used to create a new labeling frontend." - ) - - if self.is_chat_evaluation() or self.is_prompt_response(): - warnings.warn(""" - This project is a live chat evaluation project or prompt and response generation project. - Editor was setup automatically. - """) - return - - self._connect_default_labeling_front_end(labeling_frontend_options) - - timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") - self.update(setup_complete=timestamp) - def _connect_default_labeling_front_end(self, ontology_as_dict: dict): labeling_frontend = self.labeling_frontend() if ( diff --git a/libs/labelbox/tests/conftest.py b/libs/labelbox/tests/conftest.py index a57e6b842..fd68488ec 100644 --- a/libs/labelbox/tests/conftest.py +++ b/libs/labelbox/tests/conftest.py @@ -656,7 +656,7 @@ def configured_project_with_label( [data_row.uid], # sample of data row objects 5, # priority between 1(Highest) - 5(lowest) ) - ontology = _setup_ontology(project) + ontology = _setup_ontology(project, client) label = _create_label( project, data_row, ontology, wait_for_label_processing ) @@ -699,20 +699,19 @@ def create_label(): return label -def _setup_ontology(project): - editor = list( - project.client.get_labeling_frontends( - where=LabelingFrontend.name == "editor" - ) - )[0] +def _setup_ontology(project: Project, client: Client): ontology_builder = OntologyBuilder( tools=[ Tool(tool=Tool.Type.BBOX, name="test-bbox-class"), ] ) - project.setup(editor, ontology_builder.asdict()) - # TODO: ontology may not be synchronous after setup. remove sleep when api is more consistent - time.sleep(2) + ontology = client.create_ontology( + name="ontology with features", + media_type=MediaType.Image, + normalized=ontology_builder.asdict(), + ) + project.connect_ontology(ontology) + return OntologyBuilder.from_project(project) @@ -754,7 +753,7 @@ def configured_batch_project_with_label( project.create_batch("test-batch", data_rows) project.data_row_ids = data_rows - ontology = _setup_ontology(project) + ontology = _setup_ontology(project, client) label = _create_label( project, data_row, ontology, wait_for_label_processing ) @@ -786,7 +785,7 @@ def configured_batch_project_with_multiple_datarows( batch_name = f"batch {uuid.uuid4()}" project.create_batch(batch_name, global_keys=global_keys) - ontology = _setup_ontology(project) + ontology = _setup_ontology(project, client) for datarow in data_rows: _create_label(project, datarow, ontology, wait_for_label_processing) @@ -1023,11 +1022,11 @@ def _upload_invalid_data_rows_for_dataset(dataset: Dataset): @pytest.fixture def configured_project( - project_with_empty_ontology, initial_dataset, rand_gen, image_url + project_with_one_feature_ontology, initial_dataset, rand_gen, image_url ): dataset = initial_dataset data_row_id = dataset.create_data_row(row_data=image_url).uid - project = project_with_empty_ontology + project = project_with_one_feature_ontology batch = project.create_batch( rand_gen(str), @@ -1042,20 +1041,21 @@ def configured_project( @pytest.fixture -def project_with_empty_ontology(project): - editor = list( - project.client.get_labeling_frontends( - where=LabelingFrontend.name == "editor" - ) - )[0] - empty_ontology = {"tools": [], "classifications": []} - project.setup(editor, empty_ontology) +def project_with_one_feature_ontology(project, client: Client): + tools = [ + Tool(tool=Tool.Type.BBOX, name="test-bbox-class").asdict(), + ] + empty_ontology = {"tools": tools, "classifications": []} + ontology = client.create_ontology( + "empty ontology", empty_ontology, MediaType.Image + ) + project.connect_ontology(ontology) yield project @pytest.fixture def configured_project_with_complex_ontology( - client, initial_dataset, rand_gen, image_url, teardown_helpers + client: Client, initial_dataset, rand_gen, image_url, teardown_helpers ): project = client.create_project( name=rand_gen(str), @@ -1072,19 +1072,12 @@ def configured_project_with_complex_ontology( ) project.data_row_ids = data_row_ids - editor = list( - project.client.get_labeling_frontends( - where=LabelingFrontend.name == "editor" - ) - )[0] - ontology = OntologyBuilder() tools = [ Tool(tool=Tool.Type.BBOX, name="test-bbox-class"), Tool(tool=Tool.Type.LINE, name="test-line-class"), Tool(tool=Tool.Type.POINT, name="test-point-class"), Tool(tool=Tool.Type.POLYGON, name="test-polygon-class"), - Tool(tool=Tool.Type.NER, name="test-ner-class"), ] options = [ @@ -1116,7 +1109,11 @@ def configured_project_with_complex_ontology( for c in classifications: ontology.add_classification(c) - project.setup(editor, ontology.asdict()) + ontology = client.create_ontology( + "complex image ontology", ontology.asdict(), MediaType.Image + ) + + project.connect_ontology(ontology) yield [project, data_row] teardown_helpers.teardown_project_labels_ontology_feature_schemas(project) diff --git a/libs/labelbox/tests/data/export/conftest.py b/libs/labelbox/tests/data/export/conftest.py index 2bd00775f..b1b81230e 100644 --- a/libs/labelbox/tests/data/export/conftest.py +++ b/libs/labelbox/tests/data/export/conftest.py @@ -1,6 +1,6 @@ import time +from labelbox import MediaType, Client import uuid - import pytest from labelbox.schema.annotation_import import AnnotationImportState, LabelImport @@ -9,7 +9,7 @@ @pytest.fixture -def ontology(): +def ontology(client: Client): bbox_tool_with_nested_text = { "required": False, "name": "bbox_tool_with_nested_text", @@ -118,18 +118,174 @@ def ontology(): "color": "#008941", "classifications": [], } - entity_tool = { + raster_segmentation_tool = { "required": False, - "name": "entity--", - "tool": "named-entity", - "color": "#006FA6", + "name": "segmentation_mask", + "tool": "raster-segmentation", + "color": "#ff0000", "classifications": [], } - segmentation_tool = { + checklist = { "required": False, - "name": "segmentation--", - "tool": "superpixel", - "color": "#A30059", + "instructions": "checklist", + "name": "checklist", + "type": "checklist", + "options": [ + {"label": "option1", "value": "option1"}, + {"label": "option2", "value": "option2"}, + {"label": "optionN", "value": "optionn"}, + ], + } + + free_form_text = { + "required": False, + "instructions": "text", + "name": "text", + "type": "text", + "options": [], + } + + radio = { + "required": False, + "instructions": "radio", + "name": "radio", + "type": "radio", + "options": [ + { + "label": "first_radio_answer", + "value": "first_radio_answer", + "options": [], + }, + { + "label": "second_radio_answer", + "value": "second_radio_answer", + "options": [], + }, + ], + } + + tools = [ + bbox_tool, + bbox_tool_with_nested_text, + polygon_tool, + polyline_tool, + point_tool, + raster_segmentation_tool, + ] + classifications = [ + checklist, + free_form_text, + radio, + ] + ontology = client.create_ontology( + "image ontology", + {"tools": tools, "classifications": classifications}, + MediaType.Image, + ) + return ontology + + +@pytest.fixture +def video_ontology(client: Client): + bbox_tool_with_nested_text = { + "required": False, + "name": "bbox_tool_with_nested_text", + "tool": "rectangle", + "color": "#a23030", + "classifications": [ + { + "required": False, + "instructions": "nested", + "name": "nested", + "type": "radio", + "options": [ + { + "label": "radio_option_1", + "value": "radio_value_1", + "options": [ + { + "required": False, + "instructions": "nested_checkbox", + "name": "nested_checkbox", + "type": "checklist", + "options": [ + { + "label": "nested_checkbox_option_1", + "value": "nested_checkbox_value_1", + "options": [], + }, + { + "label": "nested_checkbox_option_2", + "value": "nested_checkbox_value_2", + }, + ], + }, + { + "required": False, + "instructions": "nested_text", + "name": "nested_text", + "type": "text", + "options": [], + }, + ], + }, + ], + } + ], + } + + bbox_tool = { + "required": False, + "name": "bbox", + "tool": "rectangle", + "color": "#a23030", + "classifications": [ + { + "required": False, + "instructions": "nested", + "name": "nested", + "type": "radio", + "options": [ + { + "label": "radio_option_1", + "value": "radio_value_1", + "options": [ + { + "required": False, + "instructions": "nested_checkbox", + "name": "nested_checkbox", + "type": "checklist", + "options": [ + { + "label": "nested_checkbox_option_1", + "value": "nested_checkbox_value_1", + "options": [], + }, + { + "label": "nested_checkbox_option_2", + "value": "nested_checkbox_value_2", + }, + ], + } + ], + }, + ], + } + ], + } + + polyline_tool = { + "required": False, + "name": "polyline", + "tool": "line", + "color": "#FF4A46", + "classifications": [], + } + point_tool = { + "required": False, + "name": "point--", + "tool": "point", + "color": "#008941", "classifications": [], } raster_segmentation_tool = { @@ -162,6 +318,7 @@ def ontology(): {"label": "optionN_index", "value": "optionn_index"}, ], } + free_form_text = { "required": False, "instructions": "text", @@ -169,14 +326,7 @@ def ontology(): "type": "text", "options": [], } - free_form_text_index = { - "required": False, - "instructions": "text_index", - "name": "text_index", - "type": "text", - "scope": "index", - "options": [], - } + radio = { "required": False, "instructions": "radio", @@ -195,33 +345,26 @@ def ontology(): }, ], } - named_entity = { - "tool": "named-entity", - "name": "named-entity", - "required": False, - "color": "#A30059", - "classifications": [], - } tools = [ bbox_tool, bbox_tool_with_nested_text, - polygon_tool, polyline_tool, point_tool, - entity_tool, - segmentation_tool, raster_segmentation_tool, - named_entity, ] classifications = [ - checklist, checklist_index, + checklist, free_form_text, - free_form_text_index, radio, ] - return {"tools": tools, "classifications": classifications} + ontology = client.create_ontology( + "image ontology", + {"tools": tools, "classifications": classifications}, + MediaType.Video, + ) + return ontology @pytest.fixture @@ -250,13 +393,15 @@ def configured_project_with_ontology( name=rand_gen(str), media_type=MediaType.Image, ) - editor = list( - client.get_labeling_frontends(where=LabelingFrontend.name == "editor") - )[0] - project.setup(editor, ontology) + project.connect_ontology(ontology) data_row_ids = [] - for _ in range(len(ontology["tools"]) + len(ontology["classifications"])): + normalized_ontology = ontology.normalized + + for _ in range( + len(normalized_ontology["tools"]) + + len(normalized_ontology["classifications"]) + ): data_row_ids.append(dataset.create_data_row(row_data=image_url).uid) project.create_batch( rand_gen(str), @@ -277,27 +422,23 @@ def configured_project_without_data_rows( description=rand_gen(str), media_type=MediaType.Image, ) - editor = list( - client.get_labeling_frontends(where=LabelingFrontend.name == "editor") - )[0] - project.setup(editor, ontology) + + project.connect_ontology(ontology) yield project teardown_helpers.teardown_project_labels_ontology_feature_schemas(project) @pytest.fixture def configured_video_project_without_data_rows( - client, ontology, rand_gen, teardown_helpers + client, video_ontology, rand_gen, teardown_helpers ): project = client.create_project( name=rand_gen(str), description=rand_gen(str), media_type=MediaType.Video, ) - editor = list( - client.get_labeling_frontends(where=LabelingFrontend.name == "editor") - )[0] - project.setup(editor, ontology) + + project.connect_ontology(video_ontology) yield project teardown_helpers.teardown_project_labels_ontology_feature_schemas(project) diff --git a/libs/labelbox/tests/integration/conftest.py b/libs/labelbox/tests/integration/conftest.py index f16689950..88670811e 100644 --- a/libs/labelbox/tests/integration/conftest.py +++ b/libs/labelbox/tests/integration/conftest.py @@ -77,24 +77,27 @@ def project_pack(client): @pytest.fixture -def project_with_empty_ontology(project): - editor = list( - project.client.get_labeling_frontends( - where=LabelingFrontend.name == "editor" - ) - )[0] - empty_ontology = {"tools": [], "classifications": []} - project.setup(editor, empty_ontology) +def project_with_one_feature_ontology(project, client: Client): + tools = [ + Tool(tool=Tool.Type.BBOX, name="test-bbox-class").asdict(), + ] + empty_ontology = {"tools": tools, "classifications": []} + ontology = client.create_ontology( + "empty ontology", + empty_ontology, + MediaType.Image, + ) + project.connect_ontology(ontology) yield project @pytest.fixture def configured_project( - project_with_empty_ontology, initial_dataset, rand_gen, image_url + project_with_one_feature_ontology, initial_dataset, rand_gen, image_url ): dataset = initial_dataset data_row_id = dataset.create_data_row(row_data=image_url).uid - project = project_with_empty_ontology + project = project_with_one_feature_ontology batch = project.create_batch( rand_gen(str), @@ -110,7 +113,7 @@ def configured_project( @pytest.fixture def configured_project_with_complex_ontology( - client, initial_dataset, rand_gen, image_url, teardown_helpers + client: Client, initial_dataset, rand_gen, image_url, teardown_helpers ): project = client.create_project( name=rand_gen(str), @@ -127,19 +130,12 @@ def configured_project_with_complex_ontology( ) project.data_row_ids = data_row_ids - editor = list( - project.client.get_labeling_frontends( - where=LabelingFrontend.name == "editor" - ) - )[0] - ontology = OntologyBuilder() tools = [ Tool(tool=Tool.Type.BBOX, name="test-bbox-class"), Tool(tool=Tool.Type.LINE, name="test-line-class"), Tool(tool=Tool.Type.POINT, name="test-point-class"), Tool(tool=Tool.Type.POLYGON, name="test-polygon-class"), - Tool(tool=Tool.Type.NER, name="test-ner-class"), ] options = [ @@ -171,7 +167,12 @@ def configured_project_with_complex_ontology( for c in classifications: ontology.add_classification(c) - project.setup(editor, ontology.asdict()) + ontology = client.create_ontology( + "image ontology", + ontology.asdict(), + MediaType.Image, + ) + project.connect_ontology(ontology) yield [project, data_row] teardown_helpers.teardown_project_labels_ontology_feature_schemas(project) diff --git a/libs/labelbox/tests/integration/test_project.py b/libs/labelbox/tests/integration/test_project.py index 12046eadd..0f9d66036 100644 --- a/libs/labelbox/tests/integration/test_project.py +++ b/libs/labelbox/tests/integration/test_project.py @@ -2,6 +2,7 @@ import time import uuid +from labelbox.schema.ontology import OntologyBuilder, Tool import pytest import requests from lbox.exceptions import InvalidQueryError @@ -146,11 +147,17 @@ def test_attach_instructions(client, project): str(execinfo.value) == "Cannot attach instructions to a project that has not been set up." ) - editor = list( - client.get_labeling_frontends(where=LabelingFrontend.name == "editor") - )[0] - empty_ontology = {"tools": [], "classifications": []} - project.setup(editor, empty_ontology) + ontology_builder = OntologyBuilder( + tools=[ + Tool(tool=Tool.Type.BBOX, name="test-bbox-class"), + ] + ) + ontology = client.create_ontology( + name="ontology with features", + media_type=MediaType.Image, + normalized=ontology_builder.asdict(), + ) + project.connect_ontology(ontology) project.upsert_instructions("tests/integration/media/sample_pdf.pdf") time.sleep(3) @@ -167,24 +174,20 @@ def test_attach_instructions(client, project): condition=os.environ["LABELBOX_TEST_ENVIRON"] == "onprem", reason="new mutation does not work for onprem", ) -def test_html_instructions(project_with_empty_ontology): +def test_html_instructions(project_with_one_feature_ontology): html_file_path = "/tmp/instructions.html" sample_html_str = "" with open(html_file_path, "w") as file: file.write(sample_html_str) - project_with_empty_ontology.upsert_instructions(html_file_path) - updated_ontology = project_with_empty_ontology.ontology().normalized + project_with_one_feature_ontology.upsert_instructions(html_file_path) + updated_ontology = project_with_one_feature_ontology.ontology().normalized instructions = updated_ontology.pop("projectInstructions") assert requests.get(instructions).text == sample_html_str -@pytest.mark.skipif( - condition=os.environ["LABELBOX_TEST_ENVIRON"] == "onprem", - reason="new mutation does not work for onprem", -) def test_same_ontology_after_instructions( configured_project_with_complex_ontology, ): diff --git a/libs/labelbox/tests/integration/test_project_setup.py b/libs/labelbox/tests/integration/test_project_setup.py index a09d0469d..a6fba03e0 100644 --- a/libs/labelbox/tests/integration/test_project_setup.py +++ b/libs/labelbox/tests/integration/test_project_setup.py @@ -24,35 +24,6 @@ def simple_ontology(): return {"tools": [], "classifications": classifications} -def test_project_setup(project) -> None: - client = project.client - labeling_frontends = list( - client.get_labeling_frontends(where=LabelingFrontend.name == "Editor") - ) - assert len(labeling_frontends) - labeling_frontend = labeling_frontends[0] - - time.sleep(3) - now = datetime.now().astimezone(timezone.utc) - - project.setup(labeling_frontend, simple_ontology()) - assert now - project.setup_complete <= timedelta(seconds=3) - assert now - project.last_activity_time <= timedelta(seconds=3) - - assert project.labeling_frontend() == labeling_frontend - options = list(project.labeling_frontend_options()) - assert len(options) == 1 - options = options[0] - # TODO ensure that LabelingFrontendOptions can be obtaind by ID - with pytest.raises(InvalidQueryError): - assert options.labeling_frontend() == labeling_frontend - assert options.project() == project - assert options.organization() == client.get_organization() - assert options.customization_options == json.dumps(simple_ontology()) - assert project.organization() == client.get_organization() - assert project.created_by() == client.get_user() - - def test_project_editor_setup(client, project, rand_gen): ontology_name = f"test_project_editor_setup_ontology_name-{rand_gen(str)}" ontology = client.create_ontology(ontology_name, simple_ontology())