OpentronsOT2Backend: head8 motion-safety guards - reachability and collision#1134
Draft
BioCam wants to merge 20 commits into
Draft
OpentronsOT2Backend: head8 motion-safety guards - reachability and collision#1134BioCam wants to merge 20 commits into
OpentronsOT2Backend: head8 motion-safety guards - reachability and collision#1134BioCam wants to merge 20 commits into
Conversation
The OT-2's deck/gantry geometry is factory-identical across machines, so it lives as a constant rather than something probed from the device (unlike STAR hardware). This dataclass holds the gantry extents, partial-tip padding offsets, and left/right mount offsets, sourced from the Opentrons shared-data definitions. `single_channel_reach` / `can_reach_position` / `channel_y_offsets` mirror `STARBackend.can_reach_position`; the head8 channel offsets are validated against the Opentrons eight_channel nozzle map (A1 back-most at +31.5, 9 mm pitch). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Model the 12 slots as ResourceHolder children (one per slot) so the deck geometry has a single source of truth that serializes and renders like the rest of PLR, instead of a bare coordinate list paralleled by hardcoded grids elsewhere. Re-base the slots onto the deck plate corner (the Opentrons cornerOffsetFromOrigin of 115.65, 68.03), so they sit correctly inside the plate rather than flush in the front-left. Set size_z to 0 to match the flat reference surface, and size slot 12's holder to the fixed-trash footprint (172.86 x 165.86 x 82) since the trash overhangs a standard slot. Default deck name is now ot2_deck. `OpentronsOT2Backend`: convert deck-frame coordinates to the robot frame before issuing moves, which cancels the inset so real hardware moves are unchanged; and walk holder->deck for the slot lookup now that labware sits in a holder. Deserializing a deck saved before slots were holders now errors loudly rather than misplacing labware. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
assign_child_resource replaced the existing slot holder via list.index(existing), but `existing` is typed Resource (not ResourceHolder), which mypy rejected. Find it by identity with an enumerate loop instead; behaviour is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…so mypy passes The serialize/deserialize test indexed loaded.slots[6]/[3] (typed Resource | None) directly for .name and get_slot; bind them and assert non-None first. Behaviour unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Characterization tests ahead of the transport-seam refactor (Phase 0). The existing suite already asserts the pickup/drop/aspirate/dispense wire calls; this adds the four remaining ot_api call sites so the seam refactor cannot change them silently: - home() issues one ot_api.health.home() - list_connected_modules() returns ot_api.modules.list_connected_modules() verbatim - stop() cancels the active run via the requestor and clears mounted pipettes - a discard to the deck trash at api_version >= 7.1.0 routes through the addressable area (move_to_addressable_area_for_drop_tip + drop_tip_in_place), not drop_tip No production code changes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Introduces a single transport seam (Phase 1): every ot_api call - including the run-cancel requestor calls in stop() - now goes through self._ot, set to the ot_api module in __init__. Behaviour is unchanged (self._ot is ot_api), but a subclass can now dry-run the backend by swapping the handle for a recording stand-in, the way STARChatterboxBackend overrides STAR's transport. The dedicated `import ot_api.requestor as _req` is dropped; ot_api.requestor is reachable through the handle (self._ot.requestor). Guarded by the Phase 0 characterization tests, which patch ot_api.* and stay green because self._ot is the same module object. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nsport seam Phase 2. A chatterbox sibling to OpentronsOT2Backend that swaps the self._ot transport handle for a recorder, so the real backend logic - pipette selection, tip and volume tracking, the per-operation wire calls - runs unchanged with no hardware and no ot_api library. Mirrors STARChatterboxBackend (transport-only override) rather than OpentronsOT2Simulator (which reimplements the high-level methods single-channel). Issued calls are printed and collected in .commands. The recorder returns canned data for the reads setup() and the operations make back (mounted pipettes, api version, labware define, save_position, modules). Exported from backends/__init__. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…mulator Phase 3. Direct tests run the chatterbox with no ot_api installed: setup resolves both mounts, a pickup -> aspirate -> dispense -> trash-discard records exactly one wire call per operation, an unknown pipette name raises, and serialize() captures the mounts. A differential audit runs the same single-channel protocol through the chatterbox and the reference OpentronsOT2Simulator and asserts identical tracked outcomes (tip mounted, source/destination volumes). The audit is scoped to the single-channel overlap, since the simulator cannot represent a multi-channel head. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The OT-2 talks HTTP through ot_api rather than a pylabrobot.io transport, so its wire traffic never reached the IO log other backends produce. Wrap the real ot_api handle in a transparent _IOLogger proxy that logs every call (submodules recursed, plain attributes passed through) at LOG_LEVEL_IO. The chatterbox recorder logs the same way, so a dry run captures the same wire trace as a real run. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Now that OTDeck's slots are ResourceHolder children, draw them from the serialized resource tree like any other holder instead of a hardcoded slot grid. Drop the deck `location` override that double-counted the plate-corner offset (labware was rendering and measuring one slot-offset too far). Slot-number labels are inset to match the holders and drawn on top of empty slots only, so labware in an occupied slot is shown instead of being covered. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Trash renderer and the drag-snapping helpers looked up `resources["deck"]`, assuming the deck is named "deck". Renaming the OT-2 deck to ot2_deck left that lookup undefined, so `Trash.drawMainShape` read `undefined.constructor` and crashed the whole render (blank canvas and empty tree). Add a `getDeck()` helper that finds the deck by `instanceof Deck` and route all three call sites through it, so the visualizer is robust to any deck name. The Trash stays hidden whenever a deck is present, as before. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
First slice of building the 8-channel head into the backend itself. Adds the channel model: _pipette_channel_count (8 for a multi, 1 for a single), _channel_map (per-mount channel blocks, left then right), num_channels derived from it, and _pipette_id_for_channel routed through it. A multi mount now reports 8 channels (0 = back / row A) instead of 1; single-channel setups are unchanged, so the characterization net and the chatterbox tests stay green. Per-column command issuing and the pickup geometry guard build on this next. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ep 2) Routes pick_up_tips / aspirate / dispense / drop_tips through a new _resolve_pipette_and_primary(ops, use_channels): it maps the channels to the single mount they address (rejecting a mix of mounts) and picks the primary op (lowest nozzle). A multi column op now issues exactly one ot_api command at the primary well while PLR tracks all 8 channels. can_pick_up_tip is likewise routed through the channel map so the multi's channels 1-7 are accepted (previously hardcoded channel 0 = left, 1 = right). The single-channel _get_*_pipette helpers stay - OpentronsOT2Simulator still uses them. Verified in simulation through the chatterbox: one pick_up_tip / one aspirate_in_place / one dispense_in_place for a full 8-channel column, all eight channels tracked, and a cross-mount selection rejected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Remove test_multi_mount_reports_eight_channels: num_channels==9 is already pinned by test_channel_map_multi_mount_is_eight_channels and exercised by the 8-channel column test. - Remove the chatterbox-vs-simulator differential: both backends share the PLR frontend that does the tracking, so they can only diverge by raising, which the full-protocol recording test already guards. - Strip tautological assertEqual(offset_x, offset_x) (x3 in each of test_tip_pick_up and test_tip_drop) and a duplicate well_name assert in test_tip_drop. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…p 3) Adds _check_head8_pickup: a multi pickup must use a channel-0-anchored contiguous block [0..k] (the API anchors at the back nozzle and only declares front-anchored layouts), and the head also grabs occupied tipspots below the selection within its 8-nozzle reach. pick_up_tips rejects such a pickup unless allow_undeclared_tip_pickup is set (per call, keyword-only, or on the backend), in which case _absorb_undeclared_tips accounts for the grabbed tips so PLR tracking stays consistent, with a warning. The keyword-only override keeps OpentronsOT2Simulator's **kwargs signature compatible. Verified through the chatterbox: full column OK, back-anchored [1..7] rejected, grab-extra A1:F1 rejected, and allow_undeclared absorbs all eight with a warning. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…pirate/dispense Wires the merged OT2RobotGeometry reach predicate into the operations (the STAR pattern). Adds can_reach_position(channel_idx, position): maps the channel to its mount and per-nozzle y offset - a single pipette resolves to its centre, a multi to each nozzle's offset - converts the target to the robot frame via _deck_to_robot_frame, and tests the gantry envelope. ensure_can_reach_position raises if any declared channel can't reach its target, called at the start of pick_up_tips, aspirate, and dispense, and in drop_tips for rack-drops only. drop_tips guards the check with `if not use_fixed_trash`: the fixed-trash drop routes through the addressable area, not a reach-bounded move, so the trash (which sits outside the gantry envelope) must be exempt - caught while prototyping the integration. Composes with the existing _check_head8_pickup tip-grab guard: reach-envelope (can the nozzles get there) plus tip-grab geometry (which spots a multi grabs). Works for single and multi alike. The chatterbox carries a geometry instance too so dry runs check reach. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…artial-pickup tests Adds a second head8 guard: a bounding box around the eight-nozzle column (padded by _HEAD8_XY_TOLERANCE in x/y) is tested against every other deck resource; one whose footprint overlaps the box and whose top sits within _HEAD8_Z_TOLERANCE of the head's lowest travel point is a collision, and pick_up_tips rejects it. Complements _check_head8_pickup (same-rack tip-grab, which ignores z): this is z-aware, so a short resource under the overhang is cleared while a tall one (a tiprack) is caught. The reference height is the nozzle-engagement z (tipspot z + tip length - fitting depth), where the bare nozzles sit during the descent - not the tipspot anchor, which is the tip bottom and for 300/1000 uL tips sits near the deck (driving the z threshold negative and flagging everything). Tests: partial pickup picks exactly k for k=1..7, bottom-rows overhang allowed at slot 1 and slot 10, grab-extra boundary, and the surrounding-resource guard (tall neighbour rejected, short neighbour cleared). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…on z-reference Addresses review notes on the head8 guards: - The 9 mm nozzle pitch now lives in one constant (_HEAD8_NOZZLE_PITCH); both _check_head8_pickup and the surrounding-resource check alias it instead of two hardcoded copies. - Adds the z-reference regression test: a 300 uL partial pickup over a short 96-well plate must be allowed. With the buggy tipspot-anchor reference a 300 uL tip's threshold goes negative and would reject it, so this fails if the nozzle-engagement reference is reverted - the previous 20 uL tests could not catch that. - Adds a synthetic-coordinate unit test of _check_head8_surrounding_resources (stub deck/resources) that exercises the x-y box overlap and the z threshold directly, decoupled from deck geometry. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Visualizer: render the OT-2 deck slots from resource holdersOpentronsOT2Backend: head8 motion-safety guards - reachability and collision
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds the two physical-safety checks for the 8-channel head, both center-based so they work for single and multi pipettes alike.
Reachability: before a pickup, drop, aspirate, or dispense, each declared channel's target is mapped to its mount and per-nozzle y offset, converted into the robot frame, and tested against the OT-2 gantry envelope. Near the front/back deck limits only a subset of a multi's nozzles can reach, so an out-of-envelope move is rejected with the offending channels named rather than driven into a hard stop.
Surrounding-resource collision: a bounding box is taken around the 8-nozzle column (xy tolerance 5 mm); any other deck resource whose footprint overlaps it and whose top sits within 10 mm of the tip-pickup height is flagged, so the head is not lowered into adjacent labware.
Stacked on #1136 (head8 + pickup guard), and additionally builds on #1130 (geometry, for the reach envelope) and #1131 (deck holders, for enumerating surrounding resources). Its own delta over those is the reachability and collision checks.
🤖 Generated with Claude Code