From 8ec7c52d2f2852c33679ca7c867e42154a6ac569 Mon Sep 17 00:00:00 2001 From: ovalle15 Date: Wed, 21 Feb 2024 08:36:33 -0500 Subject: [PATCH 1/7] update title --- examples/exports/composite_mask_export.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/exports/composite_mask_export.ipynb b/examples/exports/composite_mask_export.ipynb index 88a5410c3..5a352b3e5 100644 --- a/examples/exports/composite_mask_export.ipynb +++ b/examples/exports/composite_mask_export.ipynb @@ -305,7 +305,7 @@ { "metadata": {}, "source": [ - "### Create an export from a Video project with mask annotations " + "## Create an export from a Video project with mask annotations " ], "cell_type": "markdown" }, From 592c932b7144f6732ef0aeb0fffafe4e32728a04 Mon Sep 17 00:00:00 2001 From: ovalle15 Date: Wed, 21 Feb 2024 14:48:11 -0500 Subject: [PATCH 2/7] Include composite masks examples for the image notebook --- examples/annotation_import/image.ipynb | 183 +++++++++++++++---------- 1 file changed, 110 insertions(+), 73 deletions(-) diff --git a/examples/annotation_import/image.ipynb b/examples/annotation_import/image.ipynb index ae48abc7e..3229be949 100644 --- a/examples/annotation_import/image.ipynb +++ b/examples/annotation_import/image.ipynb @@ -85,10 +85,12 @@ "metadata": {}, "source": [ "import uuid\n", + "from PIL import Image\n", "import requests\n", "import base64\n", "import labelbox as lb\n", - "import labelbox.types as lb_types" + "import labelbox.types as lb_types\n", + "from io import BytesIO\n" ], "cell_type": "code", "outputs": [], @@ -500,47 +502,27 @@ { "metadata": {}, "source": [ - "### Segmentation Mask" + "### Composite mask upload using multiple tools \n", + "This example shows how to assigned different annotations (mask instances) from a composite mask using multiple tools" ], "cell_type": "markdown" }, { "metadata": {}, "source": [ - "### Raster Segmentation (Byte string array)\n", - "url = \"https://storage.googleapis.com/labelbox-datasets/image_sample_data/raster_seg.png\"\n", - "response = requests.get(url)\n", + "# First we need to extract all the unique colors from the composite mask\n", + "def extract_rgb_colors_from_url(image_url):\n", + " response = requests.get(image_url)\n", + " img = Image.open(BytesIO(response.content))\n", "\n", - "mask_data = lb.types.MaskData(im_bytes=response.content) # You can also use \"url\" instead of img_bytes to pass the PNG mask url.\n", - "mask_annotation = lb_types.ObjectAnnotation(\n", - " name=\"mask\",\n", - " value=lb_types.Mask(\n", - " mask=mask_data,\n", - " color=(255, 255, 255))\n", - ")\n", - "\n", - "# NDJSON using instanceURI, or bytes array.\n", - "mask_annotation_ndjson = {\n", - " \"name\": \"mask\",\n", - " \"classifications\": [],\n", - " \"mask\": {\n", - " \t\"instanceURI\": url,\n", - " \t\"colorRGB\": (255, 255, 255)\n", - " }\n", - "}\n", - "\n", - "#Using bytes array.\n", - "response = requests.get(url)\n", - "im_bytes = base64.b64encode(response.content).decode('utf-8')\n", + " colors = set()\n", + " for x in range(img.width):\n", + " for y in range(img.height):\n", + " pixel = img.getpixel((x, y))\n", + " if pixel[:3] != (0,0,0):\n", + " colors.add(pixel[:3]) # Get only the RGB values\n", "\n", - "mask_annotation_ndjson = {\n", - " \"name\": \"mask\",\n", - " \"classifications\": [],\n", - " \"mask\": {\n", - " \t\"imBytes\": im_bytes,\n", - " \"colorRGB\": (255, 255, 255)\n", - " }\n", - " }" + " return colors" ], "cell_type": "code", "outputs": [], @@ -549,40 +531,97 @@ { "metadata": {}, "source": [ - "### Segmentation mask with nested classification " - ], - "cell_type": "markdown" - }, - { - "metadata": {}, - "source": [ - "url_2 = \"https://storage.googleapis.com/labelbox-datasets/image_sample_data/raster_seg_with_subclass.png\"\n", - "response = requests.get(url_2)\n", - "mask_data = lb_types.MaskData(im_bytes=response.content)\n", "\n", - "# Python annotation\n", - "mask_with_text_subclass_annotation = lb_types.ObjectAnnotation(\n", - " name = \"mask_with_text_subclass\", # must match your ontology feature\"s name\n", - " value=lb_types.Mask(\n", - " mask=mask_data,\n", - " color=(255, 255, 255)),\n", - " classifications=[\n", - " lb_types.ClassificationAnnotation(\n", - " name=\"sub_free_text\",\n", - " value=lb_types.Text(answer=\"free text answer\")\n", - " )]\n", - ")\n", + "cp_mask_url = \"https://storage.googleapis.com/labelbox-datasets/image_sample_data/composite_mask.png\"\n", + "colors = extract_rgb_colors_from_url(cp_mask_url)\n", + "response = requests.get(cp_mask_url)\n", "\n", - "# NDJSON using instanceURI, bytes array is not fully supported.\n", - "mask_with_text_subclass_ndjson = {\n", - " \"name\": \"mask_with_text_subclass\",\n", - " \"mask\": {\"instanceURI\": url_2,\n", - " \"colorRGB\": (255, 255, 255)},\n", - " \"classifications\":[{\n", - " \"name\": \"sub_free_text\",\n", - " \"answer\": \"free text answer\"\n", - " }]\n", - "}" + "mask_data = lb.types.MaskData(im_bytes=response.content) # You can also use \"url\" instead of img_bytes to pass the PNG mask url.\n", + "rgb_colors_for_mask_with_text_subclass_tool = [(73, 39, 85), (111, 87, 176), (23, 169, 254)]\n", + "\n", + "cp_mask = []\n", + "for color in colors:\n", + " # We are assigning the color related to the mask_with_text_subclass tool by identifying the unique RGB colors\n", + " if color in rgb_colors_for_mask_with_text_subclass_tool:\n", + " cp_mask.append(\n", + " lb_types.ObjectAnnotation(\n", + " name = \"mask_with_text_subclass\", # must match your ontology feature\"s name\n", + " value=lb_types.Mask(\n", + " mask=mask_data,\n", + " color=color),\n", + " classifications=[\n", + " lb_types.ClassificationAnnotation(\n", + " name=\"sub_free_text\",\n", + " value=lb_types.Text(answer=\"free text answer sample\")\n", + " )]\n", + " )\n", + " )\n", + " else:\n", + " # Create ObjectAnnotation for other masks\n", + " cp_mask.append(\n", + " lb_types.ObjectAnnotation(\n", + " name=\"mask\",\n", + " value=lb_types.Mask(\n", + " mask=mask_data,\n", + " color=color)\n", + " )\n", + " )\n", + "\n", + "\n", + "# NDJSON using instanceURI, or bytes array - use one of the two options\n", + "cp_mask_ndjson = []\n", + "\n", + "for color in colors:\n", + " if color in rgb_colors_for_mask_with_text_subclass_tool:\n", + " cp_mask_ndjson.append({\n", + " \"name\": \"mask_with_text_subclass\",\n", + " \"mask\": {\"instanceURI\": cp_mask_url,\n", + " \"colorRGB\": color },\n", + " \"classifications\":[{\n", + " \"name\": \"sub_free_text\",\n", + " \"answer\": \"free text answer\"\n", + " }]\n", + " }\n", + " )\n", + " else:\n", + " cp_mask_ndjson.append({\n", + " \"name\": \"mask\",\n", + " \"classifications\": [],\n", + " \"mask\": {\n", + " \"instanceURI\": cp_mask_url,\n", + " \"colorRGB\": color\n", + " }\n", + " }\n", + " )\n", + "\n", + "\n", + "#Using bytes array.\n", + "response = requests.get(cp_mask_url)\n", + "im_bytes = base64.b64encode(response.content).decode('utf-8')\n", + "for color in colors:\n", + " if color in rgb_colors_for_mask_with_text_subclass_tool:\n", + " cp_mask_ndjson.append({\n", + " \"name\": \"mask_with_text_subclass\",\n", + " \"mask\": {\"instanceURI\": im_bytes,\n", + " \"colorRGB\": color },\n", + " \"classifications\":[{\n", + " \"name\": \"sub_free_text\",\n", + " \"answer\": \"free text answer\"\n", + " }]\n", + " }\n", + " )\n", + " else:\n", + " cp_mask_ndjson.append({\n", + " \"name\": \"mask\",\n", + " \"classifications\": [],\n", + " \"mask\": {\n", + " \"instanceURI\": im_bytes,\n", + " \"colorRGB\": color\n", + " }\n", + " }\n", + " )\n", + "\n", + "\n" ], "cell_type": "code", "outputs": [], @@ -897,14 +936,13 @@ " bbox_annotation,\n", " bbox_with_radio_subclass_annotation,\n", " polygon_annotation,\n", - " mask_annotation,\n", - " mask_with_text_subclass_annotation,\n", " point_annotation,\n", " polyline_annotation,\n", " bbox_source,\n", " bbox_target,\n", - " relationship,\n", - "]\n", + " relationship\n", + "] + cp_mask\n", + "\n", "label.append(\n", " lb_types.Label(data=lb_types.ImageData(global_key=global_key),\n", " annotations=annotations))" @@ -934,14 +972,13 @@ " bbox_annotation_ndjson,\n", " bbox_with_radio_subclass_ndjson,\n", " polygon_annotation_ndjson,\n", - " mask_annotation_ndjson,\n", - " mask_with_text_subclass_ndjson,\n", " point_annotation_ndjson,\n", " polyline_annotation_ndjson,\n", " bbox_source_ndjson,\n", " bbox_target_ndjson,\n", " relationship_ndjson, ## Only supported for MAL imports\n", - "]\n", + "] + cp_mask_ndjson\n", + "\n", "for annotation in annotations:\n", " annotation.update({\n", " \"dataRow\": {\n", From a1223c6990a8edc476c199b1abecb8b2c97d8770 Mon Sep 17 00:00:00 2001 From: ovalle15 Date: Wed, 21 Feb 2024 14:57:37 -0500 Subject: [PATCH 3/7] latest updates --- examples/annotation_import/image.ipynb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/annotation_import/image.ipynb b/examples/annotation_import/image.ipynb index 3229be949..6eba9eb81 100644 --- a/examples/annotation_import/image.ipynb +++ b/examples/annotation_import/image.ipynb @@ -563,7 +563,8 @@ " name=\"mask\",\n", " value=lb_types.Mask(\n", " mask=mask_data,\n", - " color=color)\n", + " color=color\n", + " )\n", " )\n", " )\n", "\n", From 24fc0a9bbfad7db4f27a5c4ce4099dc72ba49c62 Mon Sep 17 00:00:00 2001 From: ovalle15 Date: Thu, 22 Feb 2024 10:37:18 -0500 Subject: [PATCH 4/7] removed how to fetch single instance masks --- examples/exports/export_data.ipynb | 45 ------------------------------ 1 file changed, 45 deletions(-) diff --git a/examples/exports/export_data.ipynb b/examples/exports/export_data.ipynb index 100590cd8..d26cedacf 100644 --- a/examples/exports/export_data.ipynb +++ b/examples/exports/export_data.ipynb @@ -849,51 +849,6 @@ "cell_type": "code", "outputs": [], "execution_count": null - }, - { - "metadata": {}, - "source": [ - "## How to access a `mask` URL \n", - "\n", - "Annotations of the kind `ImageSegmentationMask` and `VideoSegmentationMask` can only be present in labels made on image or video data rows, respectively. In order to access the mask data, you must pass your Labelbox API key stored in `client.headers` in an API request.\n", - "\n", - "When you grab a URL from the mask annotation in the export, the `project_id` and `feature_id` will already be in place. Here, we provide the framework for structuring a URL with any project ID and feature ID." - ], - "cell_type": "markdown" - }, - { - "metadata": {}, - "source": [ - "# Provide a project ID and feature ID. Alternatively, replace the entire mask_url with a URL grabbed from your export.\n", - "project_id = \"\"\n", - "feature_id = \"\"\n", - "\n", - "mask_url = f\"https://api.labelbox.com/api/v1/projects/{project_id}/annotations/{feature_id}/index/1/mask\"" - ], - "cell_type": "code", - "outputs": [], - "execution_count": null - }, - { - "metadata": {}, - "source": [ - "# Make the API request\n", - "req = urllib.request.Request(mask_url, headers=client.headers)" - ], - "cell_type": "code", - "outputs": [], - "execution_count": null - }, - { - "metadata": {}, - "source": [ - "# Print the image of the mask\n", - "image = Image.open(urllib.request.urlopen(req))\n", - "image\n" - ], - "cell_type": "code", - "outputs": [], - "execution_count": null } ] } \ No newline at end of file From 3443557993fe52cfb430fd7606abfc4a2930fb5f Mon Sep 17 00:00:00 2001 From: ovalle15 Date: Mon, 26 Feb 2024 14:20:32 -0500 Subject: [PATCH 5/7] incorporated Gabe's suggestions --- examples/annotation_import/image.ipynb | 30 ++++++++++++++++++++------ 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/examples/annotation_import/image.ipynb b/examples/annotation_import/image.ipynb index 6eba9eb81..f3b97a354 100644 --- a/examples/annotation_import/image.ipynb +++ b/examples/annotation_import/image.ipynb @@ -502,8 +502,8 @@ { "metadata": {}, "source": [ - "### Composite mask upload using multiple tools \n", - "This example shows how to assigned different annotations (mask instances) from a composite mask using multiple tools" + "### Composite mask upload using different mask tools from the project's ontology\n", + "This example shows how to assigned different annotations (mask instances) from a composite mask using different mask tools" ], "cell_type": "markdown" }, @@ -749,7 +749,7 @@ "metadata": {}, "source": [ "# send a sample image as batch to the project\n", - "global_key = \"2560px-Kitano_Street_Kobe01s5s41102.jpeg\"\n", + "global_key = \"2560px-Kitano_Street_Kobe01s5s4110.jpeg\"\n", "\n", "test_img_url = {\n", " \"row_data\":\n", @@ -762,8 +762,25 @@ "task = dataset.create_data_rows([test_img_url])\n", "task.wait_till_done()\n", "\n", - "print(f\"Errors: {task.errors}\")\n", - "print(f\"Failed data rows: {task.failed_data_rows}\")" + "if task.errors:\n", + " print(\"booo\")\n", + " for error in task.errors:\n", + " if 'Duplicate global key' in error['message'] and dataset.row_count == 0:\n", + " # If the global key already exists in the workspace the dataset will be created empty, so we can delete it.\n", + " print(f\"Deleting empty dataset: {dataset}\")\n", + " dataset.delete()\n", + "\n", + "print(f\"Failed data rows: {task.failed_data_rows}\")\n", + "print(f\"Errors: {task.errors}\")" + ], + "cell_type": "code", + "outputs": [], + "execution_count": null + }, + { + "metadata": {}, + "source": [ + "print(dataset)" ], "cell_type": "code", "outputs": [], @@ -1056,8 +1073,7 @@ { "metadata": {}, "source": [ - "# project.delete()\n", - "# dataset.delete()" + "# project.delete()" ], "cell_type": "code", "outputs": [], From b8e39ce90045349f06cb7c9d5244842bf9605eae Mon Sep 17 00:00:00 2001 From: ovalle15 Date: Mon, 26 Feb 2024 14:22:36 -0500 Subject: [PATCH 6/7] incorporated Gabe's suggestions --- examples/annotation_import/image.ipynb | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/annotation_import/image.ipynb b/examples/annotation_import/image.ipynb index f3b97a354..b3cfcbfdf 100644 --- a/examples/annotation_import/image.ipynb +++ b/examples/annotation_import/image.ipynb @@ -763,7 +763,6 @@ "task.wait_till_done()\n", "\n", "if task.errors:\n", - " print(\"booo\")\n", " for error in task.errors:\n", " if 'Duplicate global key' in error['message'] and dataset.row_count == 0:\n", " # If the global key already exists in the workspace the dataset will be created empty, so we can delete it.\n", From 3eedc62860ad2d16bf5852f88395fbd12e7ceaaa Mon Sep 17 00:00:00 2001 From: ovalle15 Date: Mon, 26 Feb 2024 14:23:52 -0500 Subject: [PATCH 7/7] incorporated Gabe's suggestions --- examples/annotation_import/image.ipynb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/annotation_import/image.ipynb b/examples/annotation_import/image.ipynb index b3cfcbfdf..0e5e81461 100644 --- a/examples/annotation_import/image.ipynb +++ b/examples/annotation_import/image.ipynb @@ -762,15 +762,16 @@ "task = dataset.create_data_rows([test_img_url])\n", "task.wait_till_done()\n", "\n", + "print(f\"Failed data rows: {task.failed_data_rows}\")\n", + "print(f\"Errors: {task.errors}\")\n", + "\n", "if task.errors:\n", " for error in task.errors:\n", " if 'Duplicate global key' in error['message'] and dataset.row_count == 0:\n", " # If the global key already exists in the workspace the dataset will be created empty, so we can delete it.\n", " print(f\"Deleting empty dataset: {dataset}\")\n", " dataset.delete()\n", - "\n", - "print(f\"Failed data rows: {task.failed_data_rows}\")\n", - "print(f\"Errors: {task.errors}\")" + "\n" ], "cell_type": "code", "outputs": [],