{ "cells": [ { "cell_type": "markdown", "id": "07ed52b8", "metadata": {}, "source": [ "# LI-COR Odyssey Classic\n", "\n", "```{device-card} li-cor-odyssey-classic\n", "```\n", "\n", "| Property | Value |\n", "|---|---|\n", "| Model | 9120 |\n", "| Connection | Ethernet, HTTP Basic Auth |\n", "| Acquisition | 700 and 800 nm fluorescence |\n", "| Length units | millimeters, including resolution |\n", "\n", "**This port has not been verified on hardware.** Review the protocol and scan settings before using an instrument. These cells are not executed by the documentation build." ] }, { "cell_type": "markdown", "id": "42811c1a", "metadata": {}, "source": [ "## Connection and sample preparation\n", "\n", "Connect the scanner and computer to the same network and use the instrument's configured IP address. The driver uses the embedded web interface; no vendor desktop application is required. Set `ODYSSEY_HOST`, `ODYSSEY_USER`, and `ODYSSEY_PASS` in your environment. Load the sample on the scan bed and close the lid before starting acquisition.\n", "\n", "The optional `pylabrobot[odyssey]` extra installs Pillow for inspecting TIFF images. Instrument communication uses the standard-library HTTP transport." ] }, { "cell_type": "markdown", "id": "cddf8a7b", "metadata": {}, "source": [ "## Setup\n", "\n", "Open the transport and read the authenticated status page." ] }, { "cell_type": "code", "execution_count": null, "id": "4ddbaf39", "metadata": {}, "outputs": [], "source": [ "from pylabrobot.li_cor import OdysseyClassic\n", "\n", "odyssey = OdysseyClassic() # Host and credentials come from the environment.\n", "await odyssey.setup()" ] }, { "cell_type": "markdown", "id": "097e5896", "metadata": {}, "source": [ "## Read status\n", "\n", "Status includes scanner state, progress in percent, remaining-time text, and the lid state when reported by the firmware." ] }, { "cell_type": "code", "execution_count": null, "id": "a6a643f3", "metadata": {}, "outputs": [], "source": [ "await odyssey.request_status()" ] }, { "cell_type": "markdown", "id": "d035ce02", "metadata": {}, "source": [ "## Configure the scan\n", "\n", "The rectangle and resolution are in millimeters. This example scans a 100 × 100 mm region at 0.169 mm resolution with both channels enabled. Configuration includes the firmware's seven-step initialization sequence." ] }, { "cell_type": "code", "execution_count": null, "id": "3ed2ca59", "metadata": {}, "outputs": [], "source": [ "await odyssey.configure_scan(\n", " \"membrane_001\",\n", " group=\"odyssey\",\n", " width=100,\n", " height=100,\n", " resolution=0.169,\n", " focus=0,\n", " channel_700=True,\n", " channel_800=True,\n", ")" ] }, { "cell_type": "markdown", "id": "93049201", "metadata": {}, "source": [ "## Acquire\n", "\n", "Start the configured scan and wait up to one hour for completion. The instrument returns to `Idle` on completion. Unknown states and firmware errors raise exceptions." ] }, { "cell_type": "code", "execution_count": null, "id": "b50d1e04", "metadata": {}, "outputs": [], "source": [ "final_status = await odyssey.scan(timeout=3600)\n", "final_status" ] }, { "cell_type": "markdown", "id": "4bc334ca", "metadata": {}, "source": [ "## Download the channel TIFFs\n", "\n", "Each channel remains a separate image. `download_channel(group, name, 700)` retrieves only one channel; `download` retrieves both." ] }, { "cell_type": "code", "execution_count": null, "id": "b2a28342", "metadata": {}, "outputs": [], "source": [ "from pathlib import Path\n", "\n", "images = await odyssey.download(\"odyssey\", \"membrane_001\")\n", "for channel, tiff in images.items():\n", " Path(f\"membrane_001-{channel}.tif\").write_bytes(tiff)" ] }, { "cell_type": "markdown", "id": "49f4b412", "metadata": {}, "source": [ "## Optional instrument identity tags\n", "\n", "Supply the identifier for your instrument as a plain dictionary. No runtime DeviceCard object is required. The helper preserves the image data and metadata on every page of a classic TIFF. Unsupported BigTIFF files and malformed directories are returned unchanged." ] }, { "cell_type": "code", "execution_count": null, "id": "d6f18af7", "metadata": {}, "outputs": [], "source": [ "from pylabrobot.li_cor.odyssey import tag_tiff_with_identity\n", "\n", "identity = {\"name\": \"My Odyssey\"} # Add your unit's persistent identifier as \"pid\", if available.\n", "tagged = tag_tiff_with_identity(images[700], identity, scan_name=\"membrane_001\", channel=700)\n", "Path(\"membrane_001-700-tagged.tif\").write_bytes(tagged)" ] }, { "cell_type": "markdown", "id": "aef77986", "metadata": {}, "source": [ "## Pause or interrupt an active acquisition\n", "\n", "For interactive control, configure a scan and use `await odyssey.start_scan()` to start without waiting. During acquisition:\n", "\n", "- `await odyssey.pause_scan()` pauses; `await odyssey.start_scan()` resumes.\n", "- `await odyssey.stop_and_save()` gracefully stops and reports available channel TIFFs.\n", "- `await odyssey.cancel_scan()` aborts and discards partial output.\n", "\n", "`await odyssey.wait_until_done()` polls for completion. It requires a fresh transition out of an initial terminal state by default; use `require_fresh=False` when you know which scan you are waiting for." ] }, { "cell_type": "markdown", "id": "f1c89b35", "metadata": {}, "source": [ "## Disconnect\n", "\n", "Closing the transport does not stop acquisition. Use `stop_and_save()` or `cancel_scan()` first when an active scan needs to be interrupted." ] }, { "cell_type": "code", "execution_count": null, "id": "e70a0d68", "metadata": {}, "outputs": [], "source": [ "await odyssey.stop()" ] }, { "cell_type": "markdown", "id": "7ba6783b", "metadata": {}, "source": [ "## Chatterbox\n", "\n", "The simulated HTTP transport runs the same scan-control code without opening a socket. Status reads advance progress and produce synthetic one-pixel TIFFs. It does not render JPEG previews." ] }, { "cell_type": "code", "execution_count": null, "id": "f0fad940", "metadata": {}, "outputs": [], "source": [ "from pylabrobot.li_cor import OdysseyChatterbox\n", "\n", "async with OdysseyClassic(io=OdysseyChatterbox()) as simulated:\n", " await simulated.configure_scan(\"demo\")\n", " await simulated.scan(poll_interval=0.01)\n", " demo_tiff = await simulated.download_channel(\"odyssey\", \"demo\", 700)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 }