LI-COR Odyssey Classic#

LI-COR
Odyssey Classic
WIPv1
imager
fluorescence
Models
ModelSupport
9120WIP
APIpylabrobot.li_cor.OdysseyClassic
Manager@vcjdeboer
NotesHTTP scan control and TIFF retrieval; port pending hardware verification.

Property

Value

Model

9120

Connection

Ethernet, HTTP Basic Auth

Acquisition

700 and 800 nm fluorescence

Length units

millimeters, including resolution

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.

Connection and sample preparation#

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.

The optional pylabrobot[odyssey] extra installs Pillow for inspecting TIFF images. Instrument communication uses the standard-library HTTP transport.

Setup#

Open the transport and read the authenticated status page.

from pylabrobot.li_cor import OdysseyClassic

odyssey = OdysseyClassic()  # Host and credentials come from the environment.
await odyssey.setup()

Read status#

Status includes scanner state, progress in percent, remaining-time text, and the lid state when reported by the firmware.

await odyssey.request_status()

Configure the scan#

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.

await odyssey.configure_scan(
    "membrane_001",
    group="odyssey",
    width=100,
    height=100,
    resolution=0.169,
    focus=0,
    channel_700=True,
    channel_800=True,
)

Acquire#

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.

final_status = await odyssey.scan(timeout=3600)
final_status

Download the channel TIFFs#

Each channel remains a separate image. download_channel(group, name, 700) retrieves only one channel; download retrieves both.

from pathlib import Path

images = await odyssey.download("odyssey", "membrane_001")
for channel, tiff in images.items():
    Path(f"membrane_001-{channel}.tif").write_bytes(tiff)

Optional instrument identity tags#

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.

from pylabrobot.li_cor.odyssey import tag_tiff_with_identity

identity = {"name": "My Odyssey"}  # Add your unit's persistent identifier as "pid", if available.
tagged = tag_tiff_with_identity(images[700], identity, scan_name="membrane_001", channel=700)
Path("membrane_001-700-tagged.tif").write_bytes(tagged)

Pause or interrupt an active acquisition#

For interactive control, configure a scan and use await odyssey.start_scan() to start without waiting. During acquisition:

  • await odyssey.pause_scan() pauses; await odyssey.start_scan() resumes.

  • await odyssey.stop_and_save() gracefully stops and reports available channel TIFFs.

  • await odyssey.cancel_scan() aborts and discards partial output.

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.

Disconnect#

Closing the transport does not stop acquisition. Use stop_and_save() or cancel_scan() first when an active scan needs to be interrupted.

await odyssey.stop()

Chatterbox#

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.

from pylabrobot.li_cor import OdysseyChatterbox

async with OdysseyClassic(io=OdysseyChatterbox()) as simulated:
    await simulated.configure_scan("demo")
    await simulated.scan(poll_interval=0.01)
    demo_tiff = await simulated.download_channel("odyssey", "demo", 700)