{
"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 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
To interact with Encord, you need to authenticate a client. You can find more details\n `here