{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "%matplotlib inline" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n# DEPRECATED - Saving project labels\n\nThis tutorial introduces a deprecated script to save labels to your Encord project. You are encouraged to\nuse the tools introduced in the Working with the LabelRowV2 section instead.\n\nThe code uses a couple of utility functions for constructing dictionaries following the\nstructure of Encord label rows and finding ontology dictionaries from the Encord\nontology. You can safely skip those details.\n\n.. raw:: html\n\n
\n Utility code\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from datetime import datetime\nfrom pathlib import Path\nfrom typing import Dict, Iterable, List, Optional, Tuple, Union\n\nimport pytz\nfrom encord.objects.utils import _lower_snake_case, short_uuid_str\n\nGMT_TIMEZONE = pytz.timezone(\"GMT\")\nDATETIME_STRING_FORMAT = \"%a, %d %b %Y %H:%M:%S %Z\"\nPoint = Union[Tuple[float, float], List[float]]\nBBOX_KEYS = {\"x\", \"y\", \"h\", \"w\"}\n\n\n# === UTILITIES === #\ndef __get_timestamp():\n now = datetime.now()\n new_timezone_timestamp = now.astimezone(GMT_TIMEZONE)\n return new_timezone_timestamp.strftime(DATETIME_STRING_FORMAT)\n\n\ndef make_object_dict(\n ontology_object: dict,\n object_data: Union[Point, Iterable[Point], Dict[str, float]] = None,\n object_hash: Optional[str] = None,\n) -> dict:\n \"\"\"\n :type ontology_object: The ontology object to associate with the ``object_data``.\n :type object_data: The data to put in the object dictionary. This has to conform\n with the ``shape`` parameter defined in\n ``ontology_object[\"shape\"]``.\n - ``shape == \"point\"``: For key-points, the object data should be\n a tuple with x, y coordinates as floats.\n - ``shape == \"bounding_box\"``: For bounding boxes, the\n ``object_data`` needs to be a dict with info:\n {\"x\": float, \"y\": float, \"h\": float, \"w\": float} specifying the\n top right corner of the box (x, y) and the height and width of\n the bounding box.\n - ``shape in (\"polygon\", \"polyline\")``: For polygons and\n polylines, the format is an iterable of points:\n [(x, y), ...] specifying (ordered) points in the\n polygon/polyline.\n If ``object_hash`` is none, a new hash will be generated.\n :type object_hash: If you want the object to have the same id across frames (for\n videos only), you can specify the object hash, which need to be\n an eight-character hex string (e.g., use\n ``short_uuid_str()`` or the ``objectHash`` from an\n associated object.\n :returns: An object dictionary conforming with the Encord label row data format.\n \"\"\"\n if object_hash is None:\n object_hash = short_uuid_str()\n\n timestamp: str = __get_timestamp()\n shape: str = ontology_object.get(\"shape\")\n\n object_dict = {\n \"name\": ontology_object[\"name\"],\n \"color\": ontology_object[\"color\"],\n \"value\": _lower_snake_case(ontology_object[\"name\"]),\n \"createdAt\": timestamp,\n \"createdBy\": \"robot@cord.tech\",\n \"confidence\": 1,\n \"objectHash\": object_hash,\n \"featureHash\": ontology_object[\"featureNodeHash\"],\n \"lastEditedAt\": timestamp,\n \"lastEditedBy\": \"robot@encord.com\",\n \"shape\": shape,\n \"manualAnnotation\": False,\n }\n\n if shape in [\"polygon\", \"polyline\"]:\n # Check type\n try:\n data_iter = iter(object_data)\n except TypeError:\n raise ValueError(\n f\"The `object_data` for {shape} should be an iterable of points.\"\n )\n\n object_dict[shape] = {\n str(i): {\"x\": round(x, 4), \"y\": round(y, 4)}\n for i, (x, y) in enumerate(data_iter)\n }\n\n elif shape == \"point\":\n # Check type\n if not isinstance(object_data, (list, tuple)):\n raise ValueError(\n f\"The `object_data` for {shape} should be a list or tuple.\"\n )\n\n if len(object_data) != 2:\n raise ValueError(\n f\"The `object_data` for {shape} should have two coordinates.\"\n )\n\n if not isinstance(object_data[0], float):\n raise ValueError(\n f\"The `object_data` for {shape} should contain floats.\"\n )\n\n # Make dict\n object_dict[shape] = {\n \"0\": {\"x\": round(object_data[0], 4), \"y\": round(object_data[1], 4)}\n }\n\n elif shape == \"bounding_box\":\n # Check type\n if not isinstance(object_data, dict):\n raise ValueError(\n f\"The `object_data` for {shape} should be a dictionary.\"\n )\n\n if len(BBOX_KEYS.union(set(object_data.keys()))) != 4:\n raise ValueError(\n f\"The `object_data` for {shape} should have keys {BBOX_KEYS}.\"\n )\n\n if not isinstance(object_data[\"x\"], float):\n raise ValueError(\n f\"The `object_data` for {shape} should float values.\"\n )\n\n # Make dict\n object_dict[\"boundingBox\"] = {\n k: round(v, 4) for k, v in object_data.items()\n }\n\n return object_dict\n\n\ndef make_classification_dict_and_answer_dict(\n ontology_class: dict,\n answers: Union[List[dict], dict, str],\n classification_hash: Optional[str] = None,\n):\n \"\"\"\n\n :type ontology_class: The ontology classification dictionary obtained from the\n project ontology.\n :type answers: The classification option (potentially list) or text answer to apply.\n If this is a dictionary, it is interpreted as an option of either a\n radio button answer or a checklist answer.\n If it is a string, it is interpreted as the actual text answer.\n :type classification_hash: If a classification should persist with the same id over\n multiple frames (for videos), you can reuse the\n ``classificationHash`` of a classifications from a\n previous frame.\n\n :returns: A classification and an answer dictionary conforming with the Encord label\n row data format.\n \"\"\"\n if classification_hash is None:\n classification_hash = short_uuid_str()\n\n if isinstance(answers, dict):\n answers = [answers]\n\n if isinstance(answers, list): # Radio og checklist\n answers_list: List[dict] = []\n for answer in answers:\n try:\n attribute = next(\n (attr for attr in ontology_class[\"attributes\"])\n )\n except StopIteration:\n raise ValueError(\n f\"Couldn't find answer `{answer['label']}` in the ontology class\"\n )\n answers_list.append(\n {\n \"featureHash\": answer[\"featureNodeHash\"],\n \"name\": answer[\"label\"],\n \"value\": answer[\"value\"],\n }\n )\n\n else: # Text attribute\n try:\n attribute = ontology_class\n answers_list = answers\n except StopIteration:\n raise ValueError(\n f\"Couldn't find ontology with type text for the string answer {answers}\"\n )\n\n classification_dict = {\n \"classificationHash\": classification_hash,\n \"confidence\": 1,\n \"createdAt\": __get_timestamp(),\n \"createdBy\": \"robot@encord.com\",\n \"featureHash\": ontology_class[\"featureNodeHash\"],\n \"manualAnnotation\": False,\n \"name\": attribute[\"name\"],\n \"value\": __lower_snake_case(attribute[\"name\"]),\n }\n\n classification_answer = {\n \"classificationHash\": classification_hash,\n \"classifications\": [\n {\n \"answers\": answers_list,\n \"featureHash\": attribute[\"featureNodeHash\"],\n \"manualAnnotation\": False,\n \"name\": attribute[\"name\"],\n \"value\": __lower_snake_case(attribute[\"name\"]),\n }\n ],\n }\n\n return classification_dict, classification_answer\n\n\ndef find_ontology_object(ontology: dict, encord_name: str):\n try:\n obj = next(\n (\n o\n for o in ontology[\"objects\"]\n if o[\"name\"].lower() == encord_name.lower()\n )\n )\n except StopIteration:\n raise ValueError(\n f\"Couldn't match Encord ontology name `{encord_name}` to objects in the \"\n f\"Encord ontology.\"\n )\n return obj\n\n\ndef __find_option(\n top_level_classification: dict, encord_option_name: Optional[str]\n):\n if top_level_classification[\"type\"] == \"text\":\n # Text classifications do not have options\n return None\n try:\n option = next(\n (\n o\n for o in top_level_classification[\"options\"]\n if o[\"label\"].lower() == encord_option_name\n )\n )\n except StopIteration:\n raise ValueError(\n f\"Couldn't match option name {encord_option_name} to any ontology object.\"\n )\n return option\n\n\ndef find_ontology_classification(\n ontology: dict, local_to_encord_classifications: dict\n):\n encord_name = local_to_encord_classifications[\"name\"]\n top_level_attribute = None\n for classification in ontology[\"classifications\"]:\n for attribute in classification[\"attributes\"]:\n if attribute[\"name\"].lower() == encord_name.lower():\n top_level_attribute = classification\n break\n if top_level_attribute:\n break\n\n if top_level_attribute is None:\n raise ValueError(\n f\"Couldn't match {encord_name} to Encord classification.\"\n )\n\n options = {\n o[0]: __find_option(top_level_attribute[\"attributes\"][0], o[1])\n for o in local_to_encord_classifications.get(\"options\", [])\n }\n return {\"classification\": top_level_attribute, \"options\": options}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ ".. raw:: html\n\n
\n\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Imports and authentication\nFirst, import dependencies and authenticate a project manager.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from encord import EncordUserClient, Project\nfrom encord.orm.project import Project as OrmProject\nfrom encord.utilities.label_utilities import construct_answer_dictionaries" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "

Note

To interact with Encord, you need to authenticate a client. You can find more details\n `here `.

\n\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Authentication: adapt the following line to your private key path\nprivate_key_path = Path.home() / \".ssh\" / \"id_ed25519\"\n\nwith private_key_path.open() as f:\n private_key = f.read()\n\nuser_client = EncordUserClient.create_with_ssh_private_key(private_key)\n\n# Find project to work with based on title.\nproject_orm: OrmProject = next(\n (\n p[\"project\"]\n for p in user_client.get_projects(title_eq=\"Your project name\")\n )\n)\nproject: Project = user_client.get_project(project_orm.project_hash)\n\nontology = project.ontology" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Saving objects\n\nTo save labels to Encord, you take two steps.\n\n1. Define a map between your local object type identifiers and Encord ontology\n objects.\n2. Add objects to Encord label rows\n\n### 1. Defining object mapping\n\nYou need a way to map between your local object identifiers and the objects from the\nEncord ontology. The mapping in this example is based on the ontology names that\nwere defined when `tutorials/projects:Adding components to a project ontology`.\nYou find the Encord ontology object names with the following lines of code::\n\n ontology = project.get_project()[\"editor_ontology\"]\n for obj in ontology[\"objects\"]:\n print(f\"Type: {obj['shape']:15s} Name: {obj['name']}\")\n\nThe code will print something similar to this:\n\n.. code:: text\n\n Type: polygon Name: Dog (polygon)\n Type: polyline Name: Snake (polyline)\n Type: bounding_box Name: Tiger (bounding_box)\n Type: point Name: Ant (key-point)\n\n\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Below, is an example of how to define your own mapping between your local object\nidentifiers and Encord ontology objects. Note that the keys in the dictionary could\nbe any type of keys. So if your local object types are defined by integers, for\nexample, you can use integers as keys.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "LOCAL_TO_ENCORD_NAMES = {\n # local object identifier: Encord object name\n \"Dog\": \"Dog (polygon)\",\n \"Snake\": \"Snake (polyline)\",\n \"Tiger\": \"Tiger (bounding_box)\",\n \"Ant\": \"Ant (key-point)\",\n}\n\nlocal_to_encord_ont_objects = {\n k: find_ontology_object(ontology, v)\n for k, v in LOCAL_TO_ENCORD_NAMES.items()\n}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 2. Saving objects to Encord\n\nAs the structure of label rows depends on the type of data in the label row, there\nare separate workflows for videos and image groups.\n\n**Saving objects to label rows with videos**\n\nSuppose you have the following local data that you want to save to Encord.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# A list of objects to save to Encord.\nlocal_objects = [\n {\n \"frame\": 0,\n \"objects\": [\n { # Polygon\n \"type\": \"Dog\", # The local object type identifier\n # The data of the object\n \"data\": [[0.1, 0.1], [0.2, 0.1], [0.2, 0.2], [0.1, 0.2]],\n # If the object is present in multiple images, specify a unique id\n # across frames here\n \"track_id\": 0,\n },\n { # Polyline\n \"type\": \"Snake\",\n \"data\": [[0.3, 0.3], [0.4, 0.3], [0.4, 0.4], [0.3, 0.4]],\n \"track_id\": 1,\n },\n ],\n },\n {\n \"frame\": 3,\n \"objects\": [\n { # Polyline\n \"type\": \"Snake\",\n \"data\": [[0.4, 0.4], [0.5, 0.4], [0.5, 0.5], [0.4, 0.5]],\n \"track_id\": 1,\n },\n { # Bounding box\n \"type\": \"Tiger\",\n \"data\": {\"x\": 0.7, \"y\": 0.7, \"w\": 0.2, \"h\": 0.2},\n \"track_id\": 2,\n },\n { # Key-point\n \"type\": \"Ant\",\n \"data\": [0.3, 0.3],\n \"track_id\": 1,\n },\n ],\n },\n # ...\n]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The data is saved by the following code example.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Title of video to which the local objects are associated\nvideo_name = \"example_video.mp4\"\n\n# Find the label row corresponding to the video associated with the local objects.\nlabel_row: dict = next(\n (lr for lr in project.label_rows if lr[\"data_title\"] == video_name)\n)\n\n# Create or fetch details of the label row from Encord.\nif label_row[\"label_hash\"] is None:\n label_row: dict = project.create_label_row(label_row[\"data_hash\"])\nelse:\n label_row: dict = project.get_label_row(label_row[\"label_hash\"])\n\n# Videos only have one data unit, so fetch the labels of that data unit.\nencord_labels: dict = next((du for du in label_row[\"data_units\"].values()))[\n \"labels\"\n]\n\n# Collection of Encord object_hashes to allow track_ids to persist across frames.\nobject_hash_idx: Dict[int, str] = {}\n\nfor local_frame_level_objects in local_objects:\n frame: int = local_frame_level_objects[\"frame\"]\n\n # Note that we will append to list of existing objects in the label row.\n encord_frame_labels: dict = encord_labels.setdefault(\n str(frame), {\"objects\": [], \"classifications\": []}\n )\n # Uncomment this line if you want to overwrite the objects on the platform\n # encord_frame_labels[\"objects\"] = []\n\n for local_class in local_frame_level_objects[\"objects\"]:\n local_obj_type: str = local_class[\"type\"]\n encord_obj_type: dict = local_to_encord_ont_objects[local_obj_type]\n\n track_id = local_class.get(\"track_id\")\n object_hash: Optional[str] = object_hash_idx.get(track_id)\n\n # Construct Encord object dictionary\n encord_object: dict = make_object_dict(\n encord_obj_type, local_class[\"data\"], object_hash=object_hash\n )\n # Add to existing objects in this frame.\n encord_frame_labels[\"objects\"].append(encord_object)\n\n # Remember object hash for next time.\n object_hash_idx.setdefault(track_id, encord_object[\"objectHash\"])\n\n# NB: This call is important to maintain a valid label_row structure!\nlabel_row = construct_answer_dictionaries(label_row)\nproject.save_label_row(label_row[\"label_hash\"], label_row)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Saving objects to label rows with image groups**\n\nSuppose you have the following local data that you want to save to Encord.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# A list of local objects to save to Encord.\nlocal_objects = {\n # Local image name\n \"000001.jpg\": [\n { # Polygon\n \"type\": \"Dog\", # The local object type identifier\n # The data of the object\n \"data\": [[0.1, 0.1], [0.2, 0.1], [0.2, 0.2], [0.1, 0.2]],\n # If the object is present in multiple images, specify a unique id\n # across frames here\n \"track_id\": 0,\n },\n { # Polyline\n \"type\": \"Snake\",\n \"data\": [[0.3, 0.3], [0.4, 0.3], [0.4, 0.4], [0.3, 0.4]],\n \"track_id\": 1,\n },\n ],\n \"000002.jpg\": [\n { # Polyline\n \"type\": \"Snake\",\n \"data\": [[0.4, 0.4], [0.5, 0.4], [0.5, 0.5], [0.4, 0.5]],\n \"track_id\": 1,\n },\n { # Bounding box\n \"type\": \"Tiger\",\n \"data\": {\"x\": 0.7, \"y\": 0.7, \"w\": 0.2, \"h\": 0.2},\n \"track_id\": 2,\n },\n { # Key-point\n \"name\": \"Ant\",\n \"data\": [0.3, 0.3],\n },\n ],\n # ...\n}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The data is saved by the following code example.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Take any label row, which contains images with names from `local_objects`.\nlabel_row = project.label_rows[0]\n\n# Create or fetch details of the label row.\nif label_row[\"label_hash\"] is None:\n label_row = project.create_label_row(label_row[\"data_hash\"])\nelse:\n label_row = project.get_label_row(label_row[\"label_hash\"])\n\n# Collection of Encord object_hashes to allow track_ids to persist across frames.\nobject_hash_idx: Dict[int, str] = {}\n\n# Image groups a variable number of data units so iterate over those.\nfor encord_data_unit in label_row[\"data_units\"].values():\n if encord_data_unit[\"data_title\"] not in local_objects:\n continue # No match for this data unit.\n\n # Note: The following line will append objects to the list of existing objects on\n # the Encord platform. To overwrite, existing objects, uncomment this:\n # encord_data_unit[\"labels\"] = {\"objects\": [], \"classifications\": []}\n encord_labels: dict = encord_data_unit[\"labels\"]\n\n for local_class in local_objects[encord_data_unit[\"data_title\"]]:\n local_obj_type: str = local_class[\"type\"]\n encord_obj_type: dict = local_to_encord_ont_objects[local_obj_type]\n track_id = local_class.get(\"track_id\")\n object_hash: Optional[str] = object_hash_idx.get(track_id)\n\n # Construct Encord object dictionary\n encord_object: dict = make_object_dict(\n encord_obj_type, local_class[\"data\"], object_hash=object_hash\n )\n # Add to existing objects in this frame.\n encord_labels[\"objects\"].append(encord_object)\n\n # Remember object hash for other data units in the image group.\n object_hash_idx.setdefault(track_id, encord_object[\"objectHash\"])\n\n# NB: This call is important to maintain a valid label_row structure!\nlabel_row = construct_answer_dictionaries(label_row)\nproject.save_label_row(label_row[\"label_hash\"], label_row)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Saving classifications\nThe workflow is very similar for classifications. Much of the code will be\nidentical to that above, but with slight modifications, highlighted with ``# NEW``.\nThe steps are:\n\n1. Define a classification identifier map.\n\n 1. For top-level classifications\n 2. *additional step*: map classification options.\n\n2. Add classifications to frames or data units for videos and image groups,\n respectively.\n\n 1. Add classification to ``labels`` dictionaries.\n 2. *additional step*: Add option to the label row's ``classification_answers``.\n\n### 1. Defining classification mapping\n\nTo define the mapping, you need to know the names of the ontology classifications\nand their associated options.\nUse the following code snippet to list the names of the Encord classifications and\ntheir options::\n\n ontology = projet_manager.get_project()[\"editor_ontology\"]\n for classification in ontology[\"classifications\"]:\n for att in classification[\"attributes\"]:\n options = (\n \"No options for text\"\n if att[\"type\"] == \"text\"\n else [o[\"label\"] for o in att[\"options\"]]\n )\n print(f\"Type: {att['type']:9s} Name: {att['name']:20s} options: {options}\")\n\nThis will produce an output similar to the following:\n\n.. code:: text\n\n Type: radio Name: Has Animal (radio) options: ['yes', 'no']\n Type: checklist Name: Other objects (checklist) options: ['person', 'car', 'leash'],\n Type: text Name: Description (text) options: No options for text\n\nBelow, is an example of how to define your own mapping between your \"local\"\nclassification identifiers and Encord classifications.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "LOCAL_TO_ENCORD_NAMES: dict = { # NEW\n # Local classification identifier\n \"has_animal\": {\n # Encord classification name\n \"name\": \"Has Animal (radio)\",\n # Tuples of (\"local option identifier\", \"encord option name\")\n \"options\": [(1, \"yes\"), (0, \"no\")],\n },\n \"other_objects\": {\n \"name\": \"Other objects (checklist)\",\n \"options\": [(0, \"person\"), (1, \"car\"), (2, \"leash\")],\n },\n \"description\": {\n \"name\": \"Description (text)\",\n # No options for text\n },\n}\n\nlocal_to_encord_ont_classifications = { # NEW\n k: find_ontology_classification(ontology, v)\n for k, v in LOCAL_TO_ENCORD_NAMES.items()\n}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 2. Saving classifications to Encord\nAs the structure of label rows depends on the type of data in the label row, there are\nseparate workflows for videos and image groups.\n\n**Saving classifications to label rows with videos**\n\nSuppose you have the following local data that you want to save to Encord.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "local_classifications = [ # NEW\n {\n \"frame\": 0,\n \"classifications\": [\n { # Radio type classicifation\n \"type\": \"has_animal\", # The local object type identifier\n # The data of the object\n \"data\": 0,\n # If the _same_ classification is present across multiple images,\n # specify a unique id across frames here\n \"track_id\": 0,\n },\n { # Checklist type classification\n \"type\": \"other_objects\",\n \"data\": [\n 1,\n 2,\n ], # Choose both car (local id 1) and leash (local id 2)\n },\n { # Text type classification\n \"type\": \"description\",\n \"data\": \"Your description of the frame\",\n },\n ],\n },\n {\n \"frame\": 1,\n \"classifications\": [\n {\n \"type\": \"has_animal\",\n \"data\": 0,\n \"track_id\": 0,\n },\n ],\n },\n # ...\n]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The data is saved by the following code example.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Title of video for which the objects are associated\nvideo_name = \"example_video.mp4\"\n\n# Find the label row corresponding to the video that the labels are associated to.\nlabel_row: dict = next(\n (lr for lr in project.label_rows if lr[\"data_title\"] == video_name)\n)\n\n# Create or fetch details of the label row.\nif label_row[\"label_hash\"] is None:\n label_row: dict = project.create_label_row(label_row[\"data_hash\"])\nelse:\n label_row: dict = project.get_label_row(label_row[\"label_hash\"])\n\n# Videos only have one data unit, so fetch the labels of that data unit.\nencord_labels: dict = next((du for du in label_row[\"data_units\"].values()))[\n \"labels\"\n]\nclassification_answers = label_row[\"classification_answers\"] # New\n\n# Collection of Encord object_hashes to allow track_ids to persist across frames.\nobject_hash_idx: Dict[int, str] = {}\n\nfor local_frame_level_classifications in local_classifications:\n frame: int = local_frame_level_classifications[\"frame\"]\n\n # Note that we will append to list of existing objects in the label row.\n encord_frame_labels: dict = encord_labels.setdefault(\n str(frame), {\"objects\": [], \"classifications\": []}\n )\n # Uncomment this line if you want to overwrite the classifications on the platform\n # encord_frame_labels[\"classifications\"] = []\n\n for local_class in local_frame_level_classifications[\"classification\"]:\n local_class_type: str = local_class[\"type\"]\n\n # NEW start\n encord_class_info: dict = local_to_encord_ont_classifications[\n local_class_type\n ]\n encord_classification: dict = encord_class_info[\"classification\"]\n option_map: dict = encord_class_info[\"options\"]\n\n if not option_map: # Text classification\n answers = local_class[\"data\"]\n elif isinstance(\n local_class[\"data\"], (list, tuple)\n ): # Multi-option checklist\n answers = [option_map[o] for o in local_class[\"data\"]]\n else: # Single option\n answers = option_map[local_class[\"data\"]]\n # NEW end\n\n track_id = local_class.get(\"track_id\")\n classification_hash: Optional[str] = object_hash_idx.get(track_id)\n\n # NEW start\n # Construct Encord object dictionary\n (\n encord_class_dict,\n encord_answers,\n ) = make_classification_dict_and_answer_dict(\n encord_classification,\n answers,\n classification_hash=classification_hash,\n )\n\n # Check if the same annotation already exist, if it exists, replace it with the local annotation\n frame_classifications = encord_labels[str(frame)][\"classifications\"]\n label_already_exist = False\n for i in range(len(frame_classifications)):\n if (\n frame_classifications[i][\"name\"]\n == encord_classification[\"name\"]\n ):\n classification_answers.pop(\n frame_classifications[i][\"classificationHash\"]\n )\n frame_classifications[i] = encord_class_dict\n label_already_exist = True\n break\n if not label_already_exist:\n encord_labels[str(frame)][\"classifications\"].append(\n encord_class_dict\n )\n\n if classification_hash is None: # Save answers once for each track id.\n classification_answers[\n encord_class_dict[\"classificationHash\"]\n ] = encord_answers\n\n # Remember object hash for next time.\n object_hash_idx.setdefault(\n track_id, encord_class_dict[\"classificationHash\"]\n )\n # NEW end\n\n# NB: This call is important to maintain a valid label_row structure!\nlabel_row = construct_answer_dictionaries(label_row)\nproject.save_label_row(label_row[\"label_hash\"], label_row)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Saving classification to label rows with image groups**\n\nSuppose you have the following local data that you want to save to Encord.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# A list of local objects to save to Encord.\nlocal_classifications = {\n # Local image name\n \"000001.jpg\": [\n { # Radio type classicifation\n \"type\": \"has_animal\", # The local object type identifier\n \"data\": 0, # The data of the object\n # If the _same_ classification is present across multiple images,\n # specify a unique id across frames here\n \"track_id\": 0,\n },\n { # Checklist classification\n \"type\": \"other_objects\",\n \"data\": [\n 1,\n 2,\n ], # Choose both car (local id 1) and leash (local id 2)\n },\n { # Text classification\n \"type\": \"description\",\n \"data\": \"Your description of the frame\",\n },\n ],\n # ...\n}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The data is saved by the following code example.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Take any label row, which contains images from your local dictionary.\nlabel_row = project.label_rows[0]\n\n# Create or fetch details of the label row.\nif label_row[\"label_hash\"] is None:\n label_row = project.create_label_row(label_row[\"data_hash\"])\nelse:\n label_row = project.get_label_row(label_row[\"label_hash\"])\n\nclassification_answers = label_row[\"classification_answers\"]\n\n# Collection of Encord object_hashes to allow track_ids to persist across frames.\nobject_hash_idx: Dict[int, str] = {}\n\n# Image groups a variable number of data units so iterate over those.\nfor encord_data_unit in label_row[\"data_units\"].values():\n if encord_data_unit[\"data_title\"] not in local_objects:\n continue # No match for this data unit.\n\n # Note: The following line will append objects to the list of existing objects on\n # the Encord platform. To overwrite, existing objects, uncomment this:\n # encord_data_unit[\"labels\"][\"classifications\"] = []\n encord_labels: dict = encord_data_unit[\"labels\"]\n\n for local_class in local_classifications[encord_data_unit[\"data_title\"]]:\n local_class_type: str = local_class[\"type\"]\n\n # NEW start\n encord_class_info: dict = local_to_encord_ont_classifications[\n local_class_type\n ]\n encord_classification: dict = encord_class_info[\"classification\"]\n option_map: dict = encord_class_info[\"options\"]\n\n if not option_map: # Text classification\n answers = local_class[\"data\"]\n elif isinstance(\n local_class[\"data\"], (list, tuple)\n ): # Multi-option checklist\n answers = [option_map[o] for o in local_class[\"data\"]]\n else: # Single option\n answers = option_map[local_class[\"data\"]]\n # NEW end\n\n track_id = local_class.get(\"track_id\")\n classification_hash: Optional[str] = object_hash_idx.get(track_id)\n\n # NEW start\n # Construct Encord object dictionary\n (\n encord_class_dict,\n encord_answers,\n ) = make_classification_dict_and_answer_dict(\n encord_classification,\n answers,\n classification_hash=classification_hash,\n )\n # Add to existing classifications in this frame.\n encord_labels[\"classifications\"].append(encord_class_dict)\n\n if classification_hash is None: # Save answers once for each track id.\n classification_answers[\n encord_class_dict[\"classificationHash\"]\n ] = encord_answers\n\n # Remember object hash for next time.\n object_hash_idx.setdefault(\n track_id, encord_class_dict[\"classificationHash\"]\n )\n\n# NB: This call is important to maintain a valid label_row structure!\nlabel_row = construct_answer_dictionaries(label_row)\nproject.save_label_row(label_row[\"label_hash\"], label_row)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.17" } }, "nbformat": 4, "nbformat_minor": 0 }