{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Transferring Labels to a Twin Project\n", "\n", "This example demonstrates how to transfer checklist labels from \"Project A\" and convert them into yes/no radio labels in \"Project B.\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Requirements\n", "\n", "This notebook guides you through the Workflow template and Ontology required.\n", "\n", "For this notebook, you need:\n", "- Two Encord Projects with Ontologies and Workflows shown in this example.\n", "- Both Projects must be linked to the same Datasets. \n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "### Ontologies\n", "[📖 Here](https://docs.encord.com/platform-documentation/GettingStarted/gettingstarted-create-ontology) is the documentation for creating Ontologies.\n", "\n", "- **Ontology in Project A:** \n", " The Ontology in Project A contains checklist classifications.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "
\n", " \n", "
Figure 1: Source project ontology (Project A).\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- **Ontology in Project B:** \n", " Each completed task in Project A is converted into a \"model-friendly version\" in Project B, where radio classifications are used. Project B includes three classifications with the same names as in Project A, but each offers two radio options.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "
\n", " \n", "
Figure 2: Sink Project Ontology (Project B).\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Example Workflows\n", "\n", "The following are examples of Workflows to be used. Create and save a Workflow template for each of the following Workflows.\n", "\n", "[📖 Here](https://docs.encord.com/platform-documentation/Annotate/annotate-projects/annotate-workflows-and-templates#creating-workflows) is the documentation for creating a Workflow with Encord.\n", "\n", "- **Project A Workflow:**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", " \n", "
Figure 3: Project A Workflow
\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "- **Project B Workflow:**\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", " \n", "
Figure 4: Project B Workflow
\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "With this configuration, all annotation work happens in Project A, while Project B mirrors the transformed labels." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Installation\n", "\n", "Ensure that you have the `encord-agents` library installed:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!python -m pip install encord-agents" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Encord Authentication\n", "\n", "Encord uses ssh-keys for authentication. The following is a code cell for setting the `ENCORD_SSH_KEY` environment variable. It contains the raw content of your private ssh key file.\n", "\n", "If you have not setup an ssh key, see our [documentation](https://agents-docs.encord.com/authentication/).\n", "\n", "> 💡 In colab, you can set the key once in the secrets in the left sidebar and load it in new notebooks. IF YOU ARE NOT RUNNING THE CODE IN THE COLLAB NOTEBOOK, you must set the environment variable directly.\n", "> ```python\n", "> os.environ[\"ENCORD_SSH_KEY\"] = \"\"\"paste-private-key-here\"\"\"\n", "> ```" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import os\n", "\n", "os.environ[\"ENCORD_SSH_KEY\"] = \"private_key_file_content\"\n", "# or you can set a path to a file\n", "# os.environ[\"ENCORD_SSH_KEY_FILE\"] = \"/path/to/your/private/key\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### [Alternative] Temporary Key\n", "There's also the option of generating a temporary (fresh) ssh key pair via the code cell below.\n", "Please follow the instructions printed when executing the code." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# ⚠️ Safe to skip if you have authenticated already\n", "import os\n", "\n", "from encord_agents.utils.colab import generate_public_private_key_pair_with_instructions\n", "\n", "private_key_path, public_key_path = generate_public_private_key_pair_with_instructions()\n", "os.environ[\"ENCORD_SSH_KEY_FILE\"] = private_key_path.as_posix()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Define the Agent\n", "\n", "An agent can perform this translation using the [`dep_twin_label_row` dependency](../../reference/task_agents.md#encord_agents.tasks.dependencies.dep_twin_label_row). For every label row from Project A, the agent automatically fetches the corresponding label row (and optionally the Workflow task) from Project B.\n", "\n", "In the following code cell, we define the custom code for the translation.\n", "\n", "Ensure that you add::\n", "- ``: The Project hash for Project A\n", "- ``: The Project hash for Project B\n", "- ``: The task agent node uuid in Project A.\n", "- ``: The uuid (or name) of the pathway in Project A that leads to the complete state.\n", "- ``: The uuid (or name) of the pathway in Project B that leads to the complete state.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from encord.objects.ontology_labels_impl import LabelRowV2\n", "from encord.objects.options import Option\n", "from encord.workflow.stages.agent import AgentTask\n", "from typing_extensions import Annotated\n", "\n", "from encord_agents.tasks import Depends, Runner\n", "from encord_agents.tasks.dependencies import Twin, dep_twin_label_row\n", "\n", "# 1. Setup the runner\n", "runner = Runner(project_hash=\"\")\n", "\n", "# 2. Get the classification attribute used to query answers\n", "checklist_classification = runner.project.ontology_structure.classifications[0] # type: ignore\n", "checklist_attribute = checklist_classification.attributes[0]\n", "\n", "\n", "# 3. Define the agent\n", "@runner.stage(stage=\"\")\n", "def copy_labels(\n", " manually_annotated_lr: LabelRowV2,\n", " twin: Annotated[Twin, Depends(dep_twin_label_row(twin_project_hash=\"\"))],\n", ") -> str | None:\n", " # 4. Reading the checkboxes that have been set\n", " instance = manually_annotated_lr.get_classification_instances()[0]\n", " answers = instance.get_answer(attribute=checklist_attribute)\n", " if answers is None or isinstance(answers, (str, Option)):\n", " return None\n", "\n", " set_options = {o.title for o in answers} # Use title to match\n", "\n", " # 5. Set answer on the sink labels\n", " for radio_clf in twin.label_row.ontology_structure.classifications:\n", " ins = radio_clf.create_instance()\n", "\n", " attr = radio_clf.attributes[0]\n", " if radio_clf.title in set_options:\n", " ins.set_answer(attr.options[0])\n", " else:\n", " ins.set_answer(attr.options[1])\n", "\n", " ins.set_for_frames(frames=0)\n", " twin.label_row.add_classification_instance(ins)\n", "\n", " # 6. Save labels and proceed tasks\n", " twin.label_row.save()\n", " if twin.task and isinstance(twin.task, AgentTask):\n", " twin.task.proceed(pathway_uuid=\"\")\n", "\n", " return \"\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The code does six things:\n", "\n", "1. Instantiates a runner that executes the agent code against every task in the agent stage of the Project.\n", "2. Read the necessary information to do the label translation from the Ontology of Project A.\n", "3. Links the implementation to the correct stage in Project A + define a `twin_label_dependency` to the twin Project in order to be able to write the converted labels to the other Project.\n", "4. Reads the manual annotations.\n", "5. Converts and writes the labels to Project B.\n", "6. Proceeds the two \"sibling tasks\" from Project A and B to the complete state.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Running the Agent\n", "\n", "The `runner` object is callable which means that you can just call it to prioritize your tasks." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Run the agent\n", "runner()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Outcome\n", "\n", "When the agent runs, tasks approved in Project A’s review stage move to the \"Complete\" stage in Project B, with the labels automatically converted and displayed.\n", "\n", "> 💡 To run this as a command-line interface, save the code in an `agents.py` file and replace: \n", "> ```python\n", "> runner()\n", "> ``` \n", "> with: \n", "> ```python\n", "> if __name__ == \"__main__\":\n", "> runner.run()\n", "> ``` \n", "> This lets you set parameters like the project hash from the command line: \n", "> ```bash\n", "> python agent.py --project-hash \"...\"\n", "> ```\n", "\n", "\n" ] } ], "metadata": { "colab": { "provenance": [], "toc_visible": true }, "kernelspec": { "display_name": "encord-agents-Cw_LL1Rx-py3.11", "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" } }, "nbformat": 4, "nbformat_minor": 0 }