Skip to content

Define Channels

Defining acquisition channels is the first step toward a structured, repeatable imaging workflow. A channel describes a complete hardware state — the active objective, the selected filter cube, the light source intensity, and the shutter positions — that the system must reach before an image is captured. This example demonstrates how to declare multiple channels programmatically, register them with the bridge, and switch between them during an acquisition loop.

The two channels defined in this example represent the two fundamental illumination modes of a widefield fluorescence microscope:

graph TB
    subgraph CH1 ["Channel 1 — Transmitted Light"]
        direction TB
        TL_Lamp["TL Lamp"]:::app
        TL_Shutter["TL Shutter — OPEN"]:::app
        Condenser["Condenser"]:::box
        TL_Lamp --> TL_Shutter --> Condenser
        Epi_Lamp_Ch1["Epi Source"]:::box
        Epi_Shutter_Ch1["Epi Shutter — CLOSED"]:::box
        Epi_Lamp_Ch1 -.->|"blocked"| Epi_Shutter_Ch1
    end

    subgraph CH2 ["Channel 2 — Epi-fluorescence"]
        direction TB
        LED["Blue LED ~360 nm"]:::app
        Epi_Shutter["Epi Shutter — OPEN"]:::app
        ExFilter["Excitation Filter 350/50 BP"]:::app
        Dichroic["Dichroic Mirror"]:::app
        LED --> Epi_Shutter --> ExFilter --> Dichroic
        TL_Lamp_Ch2["TL Lamp"]:::box
        TL_Shutter_Ch2["TL Shutter — CLOSED"]:::box
        TL_Lamp_Ch2 -.->|"blocked"| TL_Shutter_Ch2
    end

    Sample(["Sample"]):::core

    Obj_exc["Objective — excitation"]:::box
    Obj_col["Objective — collection"]:::box

    subgraph Det ["Detection Path"]
        direction TB
        EmptySlot["Empty Cube Slot"]:::box
        EmFilter["Emission Filter 460/50 BP"]:::app
        TubeLens["Tube Lens"]:::box
        Camera[/"Camera"/]:::box
    end

    Condenser  -->|"white light"| Sample
    Dichroic   -->|"excitation"| Obj_exc
    Obj_exc    -->|"focuses on sample"| Sample
    Sample     -->|"signal"| Obj_col
    Obj_col    -->|"Ch1"| EmptySlot
    Obj_col    -->|"Ch2"| EmFilter
    EmptySlot  --> TubeLens
    EmFilter   --> TubeLens
    TubeLens   --> Camera

    classDef app fill:#d0f0fa,stroke:#00a8cc,stroke-width:2px,color:#000,font-size:16px
    classDef box fill:#fff,stroke:#00a8cc,stroke-width:2px,color:#000,font-size:16px
    classDef core fill:#00a8cc,stroke:#00a8cc,stroke-width:2px,color:#fff,font-size:16px,font-weight:bold

    linkStyle default stroke:#00a8cc,stroke-width:2px

    style CH1 fill:none,stroke:#00a8cc,stroke-width:1px,rx:8,ry:8
    style CH2 fill:none,stroke:#00a8cc,stroke-width:1px,rx:8,ry:8
    style Det fill:none,stroke:#00a8cc,stroke-width:1px,rx:8,ry:8

The three channels used in this example are defined as follows:

Sub-device Ch 1 — Transmitted Light Ch 2 — Epi-fluorescence (DAPI) Ch 3 — Off (safe park)
Objective position 0 — 10× brightfield objective 1 — 20× fluorescence objective 0
Filter cube slot 0 — empty slot (no filter) 2 — DAPI filter cube 0
TL lamp intensity 30 % 0 % 0 %
TL shutter 1 — open 0 — closed 0 — closed
Epi shutter 0 — closed 1 — open 0 — closed
Blue LED intensity 0 % 100 % 0 %
Blue LED shutter 0 — closed 1 — open 0 — closed

Channel 3 serves as a safe park position: all shutters are closed and all light sources are disabled, protecting the sample from unnecessary illumination between acquisitions and at the end of a run.

Prerequisites

This example builds on the device exploration pattern introduced in Explore Devices. The device map constructed in that example is reused here to locate all relevant sub-devices by their functional type.

Overview

The channel definition sequence relies on six sub-device types:

Sub-device type Role
OBJECTIVE Selects the active objective on the nosepiece turret
CUBE Selects the active filter cube on the optical turret
Transmission lamp intensity Sets the brightness of the transmitted light source (as a percentage)
SHUTTER (TL) Opens and closes the transmitted-light illumination path
Epi-fluorescence lamp intensity Sets the output power of the fluorescence light source (as a percentage)
SHUTTER (Epi) Opens and closes the fluorescence illumination path

The example executes the following steps in order:

  1. Resolve a SubDeviceId handle for each hardware component by matching on functional type and name keywords.
  2. Define each channel as a dictionary mapping sub-device handles to their required setpoints.
  3. Wrap each channel dictionary in a Status object using addParam().
  4. Register each Status as a named sequence in the bridge using loadSequence().
  5. Alternate between channels using runSequence(), with stabilization pauses between transitions.
  6. Park all shutters and light sources in their inactive state at the end of the run.

Note

This example uses a configuration named camera_and_microscope_and_fluo_source. Replace this value with the name defined in your own hardware configuration file.

Define channels

# ── Step 1: Resolve the sub-device IDs for every optical component of interest ──────────────
# We need a typed handle (SubDeviceId) for each hardware component that will be controlled
# during channel switching. These handles uniquely identify a sub-device by combining the
# parent device name with the sub-device name, matching exactly what the Inscoper runtime expects.
# All variables start as None and are filled in during the scan below.
objective_turret_sub_device_id = None   # Turret that selects the objective
cube_turret_sub_device_id = None        # Filter-cube turret
tl_power_sub_device_id = None           # Power controller for the transmitted light
tl_shutter_sub_device_id = None         # Shutter inside the microscope for the transmitted light
epi_shutter_sub_device_id = None        # Shutter inside the microscope for the epi-fluorescence
led_blue_power_sub_device_id = None     # Power controller for the blue (440nm) LED
led_blue_shutter_sub_device_id = None   # Shutter inside the light source for the blue (440nm) LED

# Walk every configured device and its sub-devices.
# For each sub-device we match on two criteria:
#   - its functional type  (e.g., "SHUTTER", "OBJECTIVE", "CUBE")
#   - a keyword in its name that distinguishes the sub-device (to adapt to your system)
for device_id, device in device_map.items():
    for sub_device_tag, sub_device in device.sub_device_map.items():
        if sub_device.inscoper_type == "SHUTTER" and "TL" in sub_device.name:
            # Transmitted-light shutter: type is SHUTTER and the name contains "TL"
            tl_shutter_sub_device_id = inscoper_api.SubDeviceId(device.name, sub_device.name)
        elif sub_device.inscoper_type == "SHUTTER" and "IL" in sub_device.name and "Down" in sub_device.name:
            # Epi-fluorescence (incident-light) shutter: type is SHUTTER, path is "IL Down"
            epi_shutter_sub_device_id = inscoper_api.SubDeviceId(device.name, sub_device.name)
        elif sub_device.inscoper_type == "OBJECTIVE":
            # Objective turret: uniquely identified by the "OBJECTIVE" type
            objective_turret_sub_device_id = inscoper_api.SubDeviceId(device.name, sub_device.name)
        elif sub_device.inscoper_type == "CUBE" and "Down" in sub_device.name:
            # Filter-cube turret in the epi (downward) light path
            cube_turret_sub_device_id = inscoper_api.SubDeviceId(device.name, sub_device.name)
        elif ("Intensity" in sub_device.name or "Power" in sub_device.name) and "TL" in sub_device.name:
            # Transmitted-light intensity: name contains "Intensity" or "Power" AND "TL"
            tl_power_sub_device_id = inscoper_api.SubDeviceId(device.name, sub_device.name)
        elif ("Intensity" in sub_device.name or "Power" in sub_device.name) and "440" in sub_device.name:
            # 440 nm LED power: name contains "Intensity" or "Power" AND the wavelength "440"
            led_blue_power_sub_device_id = inscoper_api.SubDeviceId(device.name, sub_device.name)
        elif sub_device.inscoper_type == "SHUTTER" and "440" in sub_device.name:
            # 440 nm LED shutter: type is SHUTTER and the wavelength "440" appears in the name
            led_blue_shutter_sub_device_id = inscoper_api.SubDeviceId(device.name, sub_device.name)

# Diagnostic printout: verify that all expected sub-device handles were resolved.
# Any None value here indicates that the config does not expose that component,
# which would cause the channel definitions below to include a None key.
print(f"Objective turret sub device ID: {objective_turret_sub_device_id}")
print(f"Cube turret sub device ID: {cube_turret_sub_device_id}")
print(f"TL light power sub device ID: {tl_power_sub_device_id}")
print(f"TL shutter sub device ID: {tl_shutter_sub_device_id}")
print(f"Epi shutter sub device ID: {epi_shutter_sub_device_id}")
print(f"LED blue power sub device ID: {led_blue_power_sub_device_id}")
print(f"LED blue shutter sub device ID: {led_blue_shutter_sub_device_id}")

# ── Step 2: Declare the hardware state for each imaging channel ───────────────────────────────
# A channel describes the optical path, it is associated with a state of the system. It is described
# as a dictionary mapping each sub-device handle to its required value.

# Transmitted-light (brightfield) channel:
#   - Objective position 0  (e.g., 10× brightfield objective)
#   - Cube slot 0           (empty / BF cube, no fluorescence filter)
#   - TL intensity at 30%   (moderate illumination for brightfield)
#   - TL shutter OPEN  (1)  / Epi shutter CLOSED (0)
#   - Blue LED OFF          (power 0, shutter 0)
TL_channel_description = {objective_turret_sub_device_id: 0,
             cube_turret_sub_device_id: 0,
             tl_power_sub_device_id: 30,
             tl_shutter_sub_device_id: 1,
             epi_shutter_sub_device_id: 0,
             led_blue_power_sub_device_id: 0,
             led_blue_shutter_sub_device_id: 0
             }

# Blue fluorescence channel (440 nm excitation):
#   - Objective position 1  (e.g., 20× fluorescence objective)
#   - Cube slot 2           (fluorescence cube matching the 440 nm excitation / emission)
#   - TL shutter CLOSED (0) / TL power 0  → transmitted light is off
#   - Epi shutter OPEN  (1) → epi-fluorescence path is active
#   - Blue LED at full power (100) with its shutter OPEN (1)
blue_channel_description = {objective_turret_sub_device_id: 1,
             cube_turret_sub_device_id: 2,
             tl_power_sub_device_id: 0,
             tl_shutter_sub_device_id: 0,
             epi_shutter_sub_device_id: 1,
             led_blue_power_sub_device_id: 100,
             led_blue_shutter_sub_device_id: 1}

# "Off" (safe) channel: every light source and shutter is driven to its inactive state.
# This is used as a safe park position between experiments or at the end of a run
# to avoid unnecessary sample exposure or hardware wear.
off_channel = {objective_turret_sub_device_id: 0,
             cube_turret_sub_device_id: 0,
             tl_power_sub_device_id: 0,
             tl_shutter_sub_device_id: 0,
             epi_shutter_sub_device_id: 0,
             led_blue_power_sub_device_id: 0,
             led_blue_shutter_sub_device_id: 0}

# ── Step 3: Build Status objects from the channel descriptions ───────────────────────────────
# inscoper_api.Status is the representation of a complete hardware state snapshot.
# Each (sub_device_id, value) pair is registered via addParam() to the Status object.

TL_status = inscoper_api.Status()
for sub_device_id, value in TL_channel_description.items():
    TL_status.addParam(sub_device_id, value)

blue_status = inscoper_api.Status()
for sub_device_id, value in blue_channel_description.items():
    blue_status.addParam(sub_device_id, value)

off_status = inscoper_api.Status()
for sub_device_id, value in off_channel.items():
    off_status.addParam(sub_device_id, value)

# ── Step 4: Register the channels as three sequences ─────────────────────
# After loading, the bridge holds three addressable sequences:
#   index 0 → brightfield (TL) channel
#   index 1 → blue fluorescence channel
#   index 2 → all-off (safe park)
my_bridge.loadSequence(0, "DeviceUpdate", [TL_status])
my_bridge.loadSequence(1, "DeviceUpdate", [blue_status])
my_bridge.loadSequence(2, "DeviceUpdate", [off_status])

# ── Step 5: Run a two-cycle acquisition alternating between channels ─────────────────────────
# dispatching the hardware command, without waiting for motion/shutter settling.
# A 5-second sleep is inserted between channel switches to give the hardware time to stabilize
# before the next acquisition would be triggered by an external imaging call.
for i in range(0,2):
    my_bridge.runSequence(0, False)   # Switch to brightfield and acquire (external trigger)
    time.sleep(5)                     # Wait for the microscope to settle in TL mode
    my_bridge.runSequence(1, False)   # Switch to blue fluorescence and acquire
    time.sleep(5)                     # Wait for the microscope to settle in fluorescence mode

# ── Step 6: Park the microscope in the safe "off" state ──────────────────────────────────────
# After the acquisition loop, drive all channels to their inactive setpoints.
# This closes every shutter and disables every light source, protecting the sample
# from prolonged illumination and putting the hardware in a known idle state.
my_bridge.runSequence(2, False)