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:8The 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:
- Resolve a
SubDeviceIdhandle for each hardware component by matching on functional type and name keywords. - Define each channel as a dictionary mapping sub-device handles to their required setpoints.
- Wrap each channel dictionary in a
Statusobject usingaddParam(). - Register each
Statusas a named sequence in the bridge usingloadSequence(). - Alternate between channels using
runSequence(), with stabilization pauses between transitions. - 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)