Light panel: illumination mode is the root of the control - #164
Conversation
The panel had LED and laser as two independent rows. They are not two settings that happen to be adjacent — they are the two ways this instrument illuminates a sample, and the workflow alternates between them: the LED to find embryos, the laser to calibrate and acquire. So mode is the first question the panel asks, and each mode's own controls are disclosed beneath it. THE CONFIG WAS AN ECHO Mode cannot be derived from a value that is not read, and the laser config was not read. `readAll` polled beam and led from hardware; config was whatever the select's own `onchange` had last written. The entire laser branch rested on a request that returned 200 — the assumption gently-project#106 is made of. `DiSPIMLightSource.read()` has had the answer all along and no route exposed it; `GET /api/laser/configs` now returns `current` alongside the preset list. The string "unknown", which `read()` yields when the group cannot be queried, stays an em dash rather than becoming a preset name. BOTH OPEN IS A STATE, NOT AN IMPOSSIBILITY LED and Laser are two independent Micro-Manager config groups (`devices/optical.py`), so their exclusivity is a policy this panel asserts, not an interlock the hardware enforces. Both paths are wanted separately — `spim_head_focus` opens the LED on the SPIM camera by design — and gently-project#106 IS the overlap: `calibrateSelected` never closed the LED, so every vision frame was brightfield with a 50 ms laser gate on top and the detector hunted nuclei in a DIC-like image. So `mode()` returns `both`, and the panel states it. Collapsing the pair into an either/or would have made its own founding bug unrepresentable. Entering LED or Off mode gates the lines to ALL OFF, which is the documented brightfield-safe state (spec §2.7); entering laser mode only closes the LED. Routing a wavelength stays an explicit act, because it is an emission decision. BeamEnabled is left alone throughout — setting it No here would recreate the state gently-project#106 is about, that nothing sets back. Engaged and disclosed are different things and look different: filled when the device says a source is open, underlined when a branch is merely on screen so it can be configured on a dark rig. FOUND IN THE BROWSER, NOT IN A TEST Two things the DOM showed that the assertions had not: - armed beam, nothing routed reads as mode `off`, so the laser branch is closed — and `idleBeamNote` was inside it, invisible in the one state it describes. It is at the panel root now, with a test that says so. This is the resting state of the rig. - the mode word ("LED + laser") had nowhere to go in a 246 px panel and was clipped. Dropped: the buttons are the readout, and the two states a word would have added each have their own line already. RIG CHECK `getCurrentConfig` on the Laser group is one more property read per 10 s poll, and this scope already logs "Property read slow: 2.4s". Watch the device-layer log; the poll is easy to slow down if it bites. Verified in the browser across seven states (unknown, LED, off, armed and unrouted, routed with the beam off, laser emitting, both open) and by intercepting the writes each mode button issues. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…t#106) The other half of gently-project#106, and the one that ruined data rather than just misreporting it. `calibrate_embryo` never closed the LED. A session finds embryos in brightfield — that is what the LED is for. Nothing closed it afterwards, so every frame the edge detector saw was LED brightfield with a 50 ms laser gate on top, and Claude was asked to find nuclei in a DIC-like image. The calibration pane's own method text already said the LED alone shows no nuclei; the code did not act on it. One call, in the one place both callers pass through: the Operate pane's `POST /api/devices/embryos/{id}/calibrate` and the agent's own `calibrate_embryo` tool. Closing the LED in the route would have left the agent path broken, and the reverse. Unconditional, because this path does no brightfield work of its own: every frame comes from `capture_lightsheet_image` and there is no head-focus phase — `spim_head_focus` is the plan that legitimately wants the LED open, and it is not on this path. A failed shutter close is logged, not fatal. The cost of an open LED is a poor fit; refusing to calibrate because a status call timed out is worse mid-session, and the Light panel now shows the LED state throughout. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Pushed
One call, at the point both callers route through ( Still rig-gated: whether the beam actually fires. This fixes what the beam was firing into. |
LED and laser are the two ways this instrument illuminates a sample, and the workflow alternates between them — LED to find embryos, laser to calibrate and acquire. They were two independent rows in the panel. Mode is now the first question, with each mode's controls disclosed beneath it.
The config was an echo
Mode cannot be derived from a value that is not read, and the laser config was not read.
readAllpolledbeamandledfrom hardware;configwas whatever the select's ownonchangehad last written — so the whole laser branch rested on a request that returned 200, which is the assumption #106 is made of.DiSPIMLightSource.read()has had the answer all along and no route exposed it.GET /api/laser/configsnow returnscurrentbeside the preset list. The string"unknown", whichread()yields when the group cannot be queried, stays an em dash rather than becoming a preset name.Both open is a state, not an impossibility
LEDandLaserare two independent Micro-Manager config groups (devices/optical.py:26,131), so exclusivity is a policy this panel asserts, not an interlock the hardware enforces. Both paths are wanted separately —spim_head_focusopens the LED on the SPIM camera by design (plans/acquisition.py:1462) — and #106 is the overlap:calibrateSelectednever closed the LED, so every vision frame was brightfield with a 50 ms laser gate on top and the detector hunted nuclei in a DIC-like image.So
mode()returnsbothand the panel says so. An either/or would have made the panel's own founding bug unrepresentable.ledlaseroffboth→ amber linenullWhat a mode click does
ALL OFF(the documented brightfield-safe state, spec §2.7), then set the LED.Nohere would recreate the state Two-point calibration cannot run: the beam is left disabled, the LED is left open, and the UI reports ON either way #106 is about, the one nothing sets back.Engaged and disclosed look different: filled when the device says a source is open, underlined when a branch is merely on screen so it can be configured on a dark rig.
Found in the browser, not in a test
off, so the laser branch is closed — andidleBeamNotewas inside it, invisible in the one state it describes. That is the resting state of the rig. Hoisted to the panel root, with a test that says so.both, unread) each have their own line..lp-btngetswhite-space: nowrap— "Set No" was wrapping to two lines.Verification
Seven states driven through the live DOM (unknown, LED, off, armed-and-unrouted, routed-with-beam-off, laser emitting, both open), plus interception of the writes each mode button issues:
node --test tests/js/light-panel.test.mjs18 pass ·tests/test_light_panel_disclosure.py10 pass · ruff, ruff format, mypy clean.Rig check
getCurrentConfigon the Laser group is one more property read per 10 s poll, and this scope already logsProperty read slow: 2.4s. Worth a glance at the device-layer log; the poll interval is one constant if it bites.Relates to #106.
🤖 Generated with Claude Code