{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "6b6369b6",
   "metadata": {},
   "source": [
    "# Standard types\n",
    "\n",
    "`retriever.types.*` is the shared payload vocabulary of the runtime: one\n",
    "canonical class per standard type, spread across `spatial`, `perception`,\n",
    "`language`, and more. Every one is already an `@io` type, so it can drop\n",
    "straight onto a Flow port. This notebook imports a few, shows why type\n",
    "*identity* — not just field shape — is the contract, constructs a couple, and\n",
    "wires one into a Flow. Everything runs in-process with only `retriever-core`."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "569dd68a",
   "metadata": {},
   "source": [
    "> **Running in Colab?** The next cell installs `retriever-core`. From a source\n",
    "> checkout (or once it's already installed) the install is skipped."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b8e2c0d3",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Colab setup: install retriever-core only if it isn't importable yet.\n",
    "try:\n",
    "    import retriever  # noqa: F401\n",
    "except ImportError:  # pragma: no cover\n",
    "    import subprocess\n",
    "    import sys\n",
    "\n",
    "    subprocess.run(\n",
    "        [sys.executable, \"-m\", \"pip\", \"install\", \"retriever-core\"], check=True\n",
    "    )"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2ae28f47",
   "metadata": {},
   "source": [
    "## One import path per domain\n",
    "\n",
    "Standard payloads live under `retriever.types`, split by domain. You import\n",
    "the class you need directly — there is no separate registration step to do in\n",
    "your own code. Here we pull a few from three domains at once."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "daf68a3a",
   "metadata": {},
   "outputs": [],
   "source": [
    "from retriever.types.spatial import Header, Vector3, Quaternion, SE3Pose, PoseStamped\n",
    "from retriever.types.perception import BBox2D, Detection2D, DetectionBatch\n",
    "from retriever.types.language import Caption\n",
    "\n",
    "for cls in (Header, Vector3, SE3Pose, PoseStamped, DetectionBatch, Caption):\n",
    "    print(f\"{cls.__name__:15s} <- {cls.__module__}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f41436cf",
   "metadata": {},
   "source": [
    "## Type identity is the contract\n",
    "\n",
    "The rule that makes these types useful is that they are the *same class\n",
    "object* everywhere — not two look-alikes with matching fields. The stable\n",
    "surface (`retriever.types.spatial`) and the versioned module\n",
    "(`retriever.types.spatial.v1`) re-export one object, so `is` holds. Every\n",
    "payload is also `@io`-decorated, which is what lets it sit on a Flow port."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a0b2c0b2",
   "metadata": {},
   "outputs": [],
   "source": [
    "from retriever.flow.io import is_flow_io\n",
    "from retriever.types.spatial.v1 import PoseStamped as PoseStamped_v1\n",
    "\n",
    "print(\"same class object:\", PoseStamped is PoseStamped_v1)\n",
    "print(\"PoseStamped   @io?\", is_flow_io(PoseStamped))\n",
    "print(\"DetectionBatch @io?\", is_flow_io(DetectionBatch))\n",
    "print(\"Caption       @io?\", is_flow_io(Caption))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dd2bb82b",
   "metadata": {},
   "source": [
    "Because it is one class, a component that emits `PoseStamped` and a component\n",
    "that consumes `PoseStamped` are talking about the identical type — no adapter,\n",
    "no \"which PoseStamped?\" ambiguity. Never redefine a standard type locally,\n",
    "even with identical fields: identity, not shape, is what the runtime checks."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6c4d077f",
   "metadata": {},
   "source": [
    "## Construct a couple\n",
    "\n",
    "Composite spatial types (`SE3Pose`, `PoseStamped`) nest `Vector3`,\n",
    "`Quaternion`, and `Header`, so nested access reads the way you'd expect.\n",
    "Perception and language types compose the same way."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8a5d7120",
   "metadata": {},
   "outputs": [],
   "source": [
    "pose = PoseStamped(\n",
    "    header=Header(stamp_ns=1_000_000, frame_id=\"base_link\"),\n",
    "    pose=SE3Pose(\n",
    "        position=Vector3(x=0.4, y=0.0, z=0.2),\n",
    "        orientation=Quaternion(x=0.0, y=0.0, z=0.0, w=1.0),\n",
    "    ),\n",
    ")\n",
    "batch = DetectionBatch(\n",
    "    detections=(\n",
    "        Detection2D(\n",
    "            label=\"cup\",\n",
    "            bbox=BBox2D(x=12, y=30, width=40, height=55),\n",
    "            confidence=0.91,\n",
    "        ),\n",
    "    ),\n",
    "    frame_index=7,\n",
    ")\n",
    "\n",
    "print(\"pose frame:\", pose.header.frame_id, \"x=\", pose.pose.position.x)\n",
    "print(\"unit quaternion?\", pose.pose.orientation.is_unit())\n",
    "print(\"detections:\", [(d.label, d.confidence) for d in batch.detections])\n",
    "print(\"bbox area:\", batch.detections[0].bbox.area())\n",
    "print(\"caption:\", Caption(text=\"a red cup on the table\").text)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8958b632",
   "metadata": {},
   "source": [
    "## `_signals` says which fields arrived\n",
    "\n",
    "`@io` makes every field optional, so a standard type can carry a *partial*\n",
    "payload. `_signals` reports the fields whose value is set (non-`None`) — the\n",
    "same mechanism a Flow uses to branch on what a `step()` actually received."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9d5a457f",
   "metadata": {},
   "outputs": [],
   "source": [
    "partial = PoseStamped(header=Header(stamp_ns=1_000_000, frame_id=\"base_link\"))\n",
    "print(\"empty   _signals:\", PoseStamped()._signals)\n",
    "print(\"partial _signals:\", partial._signals)\n",
    "print(\"full    _signals:\", pose._signals)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5e5f9979",
   "metadata": {},
   "source": [
    "## Use a standard type as a Flow port\n",
    "\n",
    "Since each payload is already `@io`, it becomes a Flow input or output with no\n",
    "wrapping. `Flow[DetectionBatch, Caption]` is the type boundary Retriever\n",
    "checks, and the clock (`@ Trigger(...)`) fires on a field of the input type."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "75a18127",
   "metadata": {},
   "outputs": [],
   "source": [
    "from retriever.flow import Flow, Trigger\n",
    "\n",
    "\n",
    "class Detector(Flow[DetectionBatch, Caption]):  # standard types as ports\n",
    "    def step(self, batch: DetectionBatch) -> Caption:\n",
    "        labels = \", \".join(d.label for d in batch.detections)\n",
    "        return Caption(text=f\"saw: {labels}\")\n",
    "\n",
    "\n",
    "det = Detector()\n",
    "print(\"input port type: \", det.input_type.__name__)\n",
    "print(\"output port type:\", det.output_type.__name__)\n",
    "print(\"step output:     \", det.step(batch))\n",
    "\n",
    "# clock triggers on a field of the standard input type\n",
    "node = Detector() @ Trigger(\"detections\")\n",
    "print(\"wired node fires on field 'detections'\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
