{ "cells": [ { "cell_type": "markdown", "id": "intro", "metadata": {}, "source": [ "# Discovery and Launch\n", "\n", "This notebook demonstrates how to find running MITK Workbench instances and\n", "start new ones programmatically.\n", "\n", "You will learn how to:\n", "\n", "- Discover running Workbench instances with `mw.discover()`\n", "- Connect to a discovered instance\n", "- Launch a new Workbench with `mw.launch()`\n", "- Work with multiple Workbench instances\n", "- Shut down a launched Workbench\n", "\n", "**Prerequisites:**\n", "- For discovery: at least one running MITK Workbench with the REST API enabled\n", "- For launch: the `MITK_WORKBENCH` environment variable pointing to the\n", " Workbench executable, or pass the path explicitly" ] }, { "cell_type": "markdown", "id": "sec-discover", "metadata": {}, "source": [ "## 1. Discover running instances\n", "\n", "`mw.discover()` probes localhost ports 8080-8099 (by default) for running MITK\n", "Workbench REST servers. Probes run concurrently, so scanning 20 ports takes\n", "about as long as a single probe.\n", "\n", "Each found instance is returned as a ready-to-use `Workbench` handle." ] }, { "cell_type": "code", "execution_count": 1, "id": "discover", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Found 1 running Workbench instance(s):\n", "\n", " http://localhost:8080 — MITK Workbench REST API (MITK 2026.06.00)\n" ] } ], "source": [ "import mitk_workbench_remote as mw\n", "\n", "instances = mw.discover()\n", "print(f\"Found {len(instances)} running Workbench instance(s):\\n\")\n", "\n", "for wb in instances:\n", " info = wb.info\n", " print(f\" {wb.url} — {info.name} (MITK {info.mitk_version})\")" ] }, { "cell_type": "markdown", "id": "sec-discover-connect", "metadata": {}, "source": [ "## 2. Use a discovered instance\n", "\n", "The returned `Workbench` handles work exactly like those from `mw.connect()`.\n", "Pick one and use it." ] }, { "cell_type": "code", "execution_count": 2, "id": "discover-connect", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Showed data in http://localhost:8080\n" ] } ], "source": [ "import numpy as np\n", "\n", "if instances:\n", " wb = instances[0]\n", " node = wb.show(\n", " np.random.default_rng(42).random((32, 32, 32), dtype=np.float32),\n", " name=\"Discovery Demo\",\n", " )\n", " print(f\"Showed data in {wb.url}\")\n", " node.remove()\n", "else:\n", " print(\"No instances found — start a Workbench and re-run this cell.\")" ] }, { "cell_type": "markdown", "id": "a3ba0736", "source": [ "## 3. Connecting to an auth-enabled instance\n", "\n", "A Workbench can be configured to require authentication: in the REST API\n", "preferences, set `requireAuth=true` and a non-empty `apiToken`. Every request\n", "then needs an `Authorization: Bearer ` header, which `connect()` adds\n", "when you pass `token`:\n", "\n", "```python\n", "secured = mw.connect(\"http://localhost:8080\", token=\"your-api-token\")\n", "print(secured.info.name)\n", "```\n", "\n", "`launch()` (section 5) does this automatically: it generates a random token and\n", "wires it into the returned handle. You only need to pass a token yourself when\n", "connecting to an already-running instance that was secured manually.\n", "\n", "**Caveat for `discover()`:** discovery probes `/health`, which is exempt from\n", "authentication, so auth-enabled instances are still *found*. But the returned\n", "handles carry no token, and discovery cannot tell whether an instance requires\n", "auth (the server does not advertise it). The first non-exempt call against a\n", "secured instance therefore raises `AuthenticationError`. If you know an instance\n", "is secured, reconnect with the token:\n", "\n", "```python\n", "secured = mw.connect(wb.url, token=\"your-api-token\")\n", "```" ], "metadata": {} }, { "cell_type": "markdown", "id": "sec-custom-range", "metadata": {}, "source": [ "## 4. Custom port range\n", "\n", "Pass a custom port range if your Workbench is on a non-default port." ] }, { "cell_type": "code", "execution_count": 3, "id": "custom-range", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Found 1 instance(s) in port range 8080-8199\n" ] } ], "source": [ "# Scan a wider range\n", "found = mw.discover(ports=range(8080, 8200), timeout=1.0)\n", "print(f\"Found {len(found)} instance(s) in port range 8080-8199\")" ] }, { "cell_type": "markdown", "id": "sec-launch", "metadata": {}, "source": [ "## 5. Launch a new Workbench\n", "\n", "`mw.launch()` starts a new MITK Workbench process with the REST API enabled.\n", "It waits for the server to become healthy, then returns a connected `Workbench`\n", "handle.\n", "\n", "The executable is resolved from:\n", "1. The `executable` argument (if given)\n", "2. The `MITK_WORKBENCH` environment variable\n", "\n", "A secure random API token is generated automatically and passed to the process\n", "through the `MITK_REST_API_TOKEN` environment variable, so it is never written\n", "to disk while `requireAuth` still enforces it. The port is auto-selected from\n", "the 8080-8099 range unless specified.\n", "\n", "**MITK Workbench is single-instance.** If a Workbench is already running,\n", "starting the executable again hands off to that instance and exits without\n", "bringing up a REST server on the new port. `launch()` detects this and raises a\n", "clear error telling you to connect to the running instance instead (sections\n", "1-2). Pass `new_instance=True` to force a separate, isolated instance anyway\n", "(next cell)." ] }, { "cell_type": "code", "execution_count": 4, "id": "launch", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Launched at http://localhost:8080 (MITK 2026.06.00)\n", " Managed process: True\n" ] } ], "source": [ "import os\n", "\n", "import mitk_workbench_remote as mw\n", "\n", "# Point MITK_WORKBENCH at a Workbench build whose REST API is available.\n", "# Adjust this path to your own installation.\n", "os.environ[\"MITK_WORKBENCH\"] = (\n", " r\"YOUR_MITK_INSTALL_PATHD/MitkWorkbench.bat\"\n", ")\n", "\n", "try:\n", " wb_new = mw.launch(timeout=30)\n", " print(f\"Launched at {wb_new.url} (MITK {wb_new.info.mitk_version})\")\n", " print(f\" Managed process: {wb_new.is_launched_remotely}\")\n", "except mw.errors.MitkError as e:\n", " # Most common cause: a Workbench is already running (single-instance), or\n", " # MITK_WORKBENCH is unset / points at a build without the REST API.\n", " wb_new = None\n", " print(f\"Could not launch: {e}\")\n", " print(\"Tip: connect to a running instance with mw.discover() / mw.connect(),\")\n", " print(\"or pass new_instance=True to force a separate instance.\")\n", "except FileNotFoundError as e:\n", " wb_new = None\n", " print(f\"Executable not found: {e}\")" ] }, { "cell_type": "markdown", "id": "fe6a73f2", "source": [ "### Forcing a second, isolated instance\n", "\n", "When a Workbench is already open and you want a *separate* one under REST\n", "control, pass `new_instance=True`:\n", "\n", "```python\n", "wb_extra = mw.launch(new_instance=True)\n", "```\n", "\n", "The forced instance runs from a throwaway storage directory (MITK does not\n", "remove it afterwards) and, like every launched instance, receives its API token\n", "through the environment rather than on disk. When nothing is running,\n", "`new_instance` has no effect — an ordinary `launch()` already gives you a fresh\n", "instance." ], "metadata": {} }, { "cell_type": "markdown", "id": "sec-shutdown", "metadata": {}, "source": [ "## 6. Shutdown\n", "\n", "`wb.shutdown()` terminates a Workbench that was started with `mw.launch()`.\n", "It also closes the underlying transport session.\n", "\n", "Only instances started by `launch()` can be shut down — calling `shutdown()`\n", "on a `connect()`-ed handle raises `MitkError`. Use `wb.is_launched_remotely`\n", "to check." ] }, { "cell_type": "code", "execution_count": 5, "id": "shutdown", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Shutting down http://localhost:8080 ...\n", "Done.\n" ] } ], "source": [ "if wb_new is not None:\n", " print(f\"Shutting down {wb_new.url} ...\")\n", " wb_new.shutdown()\n", " print(\"Done.\")\n", "else:\n", " print(\"No launched instance to shut down.\")" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "name": "python", "version": "3.10.0" } }, "nbformat": 4, "nbformat_minor": 5 }