{
"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": [
"## Prerequisites\n",
"\n",
"In order to follow this example, you will need to have two project with ontologies and workflows following a specific template.\n",
"\n",
"> **Note:** Both Project A and Project B 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, as shown below:\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",
" Every completed task in Project A is translated into a \"model-friendly version\" with radio classifications in Project B:\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"\n",
" \n",
" Figure 2: Sink project ontology (Project B).\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"> Notice that Project B has three classifications with identical names to those in Project A, but with two radio options each.\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Workflows\n",
"\n",
"The following are examples of Workflows to be used. Create and save a Workflow template for each workflow.\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",
"Please 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": [
"### Authentication\n",
"\n",
"The library authenticates via ssh-keys. Below, is a code cell for setting the `ENCORD_SSH_KEY` environment variable. It should contain the raw content of your private ssh key file.\n",
"\n",
"If you have not yet setup an ssh key, please follow the [documentation](https://agents-docs.encord.com/authentication/).\n",
"\n",
"> 💡 **Colab users**: In colab, you can set the key once in the secrets in the left sidebar and load it in new notebooks with\n",
"> ```python\n",
"> from google.colab import userdata\n",
"> key_content = userdata.get(\"ENCORD_SSH_KEY\")\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": [
"## Agent definition\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",
"Below is a template for doing the translation.\n",
"\n",
"\n",
"You will have to fill in the following pieces of information to wire things together:\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 which will execute 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",
"Now that we've defined the projects, workflows, and the agent, it's time to try it out.\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": [
"Once the agent is running, 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",
"> 💡*Hint:* If you execute this as a Python script, you can run it as a command line interface by putting the above code in an `agents.py` file and replacing\n",
"> ```python\n",
"> runner()\n",
"> ```\n",
"> with\n",
"> ```python\n",
"> if __name__ == \"__main__\":\n",
"> runner.run()\n",
"> ```\n",
"> Which allows you to set, for example the Project hash using the command line:\n",
"> ```bash\n",
"> python agent.py --project-hash \"...\"\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
}