OpentronsOT2Backend: support the 8-channel pipette - #1135

Draft
BioCam wants to merge 3 commits into
PyLabRobot:mainfrom
BioCam:ot2-head8-channels
Draft

OpentronsOT2Backend: support the 8-channel pipette#1135
BioCam wants to merge 3 commits into
PyLabRobot:mainfrom
BioCam:ot2-head8-channels

Conversation

@BioCam

Copy link
Copy Markdown
Collaborator

The backend modeled every mount as a single channel, so the OT-2 8-channel pipette could not be driven correctly. This models a multi mount as 8 channels - anchored at channel 0 (back, row A) and filling toward row H at the 9 mm nozzle pitch - and issues one wire command per column for multi-channel pickup, aspirate, and dispense, matching the hardware, which acts on a whole column at once rather than channel by channel. Single-channel pipettes are unchanged: they stay one channel and one command per operation.

Tests cover the channel model (mount -> channel count, channel-0 anchoring) and the per-column wire grouping, run as a dry run against the chatterbox so no hardware or ot_api is needed.

Stacked on #1132, whose OpentronsOT2ChatterboxBackend is the dry-run harness these tests use. The self-contained change here is the three commits above that base.

🤖 Generated with Claude Code

BioCamand others added 3 commits July 8, 2026 08:56
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>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@BioCam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

OpentronsOT2Backend: support the 8-channel pipette - #1135

Draft
BioCam wants to merge 3 commits into
PyLabRobot:mainfrom
BioCam:ot2-head8-channels
Draft

OpentronsOT2Backend: support the 8-channel pipette#1135
BioCam wants to merge 3 commits into
PyLabRobot:mainfrom
BioCam:ot2-head8-channels

Conversation

@BioCam

Copy link
Copy Markdown
Collaborator

The backend modeled every mount as a single channel, so the OT-2 8-channel pipette could not be driven correctly. This models a multi mount as 8 channels - anchored at channel 0 (back, row A) and filling toward row H at the 9 mm nozzle pitch - and issues one wire command per column for multi-channel pickup, aspirate, and dispense, matching the hardware, which acts on a whole column at once rather than channel by channel. Single-channel pipettes are unchanged: they stay one channel and one command per operation.

Tests cover the channel model (mount -> channel count, channel-0 anchoring) and the per-column wire grouping, run as a dry run against the chatterbox so no hardware or ot_api is needed.

Stacked on #1132, whose OpentronsOT2ChatterboxBackend is the dry-run harness these tests use. The self-contained change here is the three commits above that base.

🤖 Generated with Claude Code

BioCamand others added 3 commits July 8, 2026 08:56
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>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@BioCam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

OpentronsOT2Backend: support the 8-channel pipette - #1135

Draft
BioCam wants to merge 3 commits into
PyLabRobot:mainfrom
BioCam:ot2-head8-channels
Draft

OpentronsOT2Backend: support the 8-channel pipette#1135
BioCam wants to merge 3 commits into
PyLabRobot:mainfrom
BioCam:ot2-head8-channels

Conversation

@BioCam

Copy link
Copy Markdown
Collaborator

The backend modeled every mount as a single channel, so the OT-2 8-channel pipette could not be driven correctly. This models a multi mount as 8 channels - anchored at channel 0 (back, row A) and filling toward row H at the 9 mm nozzle pitch - and issues one wire command per column for multi-channel pickup, aspirate, and dispense, matching the hardware, which acts on a whole column at once rather than channel by channel. Single-channel pipettes are unchanged: they stay one channel and one command per operation.

Tests cover the channel model (mount -> channel count, channel-0 anchoring) and the per-column wire grouping, run as a dry run against the chatterbox so no hardware or ot_api is needed.

Stacked on #1132, whose OpentronsOT2ChatterboxBackend is the dry-run harness these tests use. The self-contained change here is the three commits above that base.

🤖 Generated with Claude Code

BioCamand others added 3 commits July 8, 2026 08:56
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>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@BioCam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

OpentronsOT2Backend: support the 8-channel pipette - #1135

Draft
BioCam wants to merge 3 commits into
PyLabRobot:mainfrom
BioCam:ot2-head8-channels
Draft

OpentronsOT2Backend: support the 8-channel pipette#1135
BioCam wants to merge 3 commits into
PyLabRobot:mainfrom
BioCam:ot2-head8-channels

Conversation

@BioCam

Copy link
Copy Markdown
Collaborator

The backend modeled every mount as a single channel, so the OT-2 8-channel pipette could not be driven correctly. This models a multi mount as 8 channels - anchored at channel 0 (back, row A) and filling toward row H at the 9 mm nozzle pitch - and issues one wire command per column for multi-channel pickup, aspirate, and dispense, matching the hardware, which acts on a whole column at once rather than channel by channel. Single-channel pipettes are unchanged: they stay one channel and one command per operation.

Tests cover the channel model (mount -> channel count, channel-0 anchoring) and the per-column wire grouping, run as a dry run against the chatterbox so no hardware or ot_api is needed.

Stacked on #1132, whose OpentronsOT2ChatterboxBackend is the dry-run harness these tests use. The self-contained change here is the three commits above that base.

🤖 Generated with Claude Code

BioCamand others added 3 commits July 8, 2026 08:56
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>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@BioCam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

OpentronsOT2Backend: support the 8-channel pipette - #1135

Draft
BioCam wants to merge 3 commits into
PyLabRobot:mainfrom
BioCam:ot2-head8-channels
Draft

OpentronsOT2Backend: support the 8-channel pipette#1135
BioCam wants to merge 3 commits into
PyLabRobot:mainfrom
BioCam:ot2-head8-channels

Conversation

@BioCam

Copy link
Copy Markdown
Collaborator

The backend modeled every mount as a single channel, so the OT-2 8-channel pipette could not be driven correctly. This models a multi mount as 8 channels - anchored at channel 0 (back, row A) and filling toward row H at the 9 mm nozzle pitch - and issues one wire command per column for multi-channel pickup, aspirate, and dispense, matching the hardware, which acts on a whole column at once rather than channel by channel. Single-channel pipettes are unchanged: they stay one channel and one command per operation.

Tests cover the channel model (mount -> channel count, channel-0 anchoring) and the per-column wire grouping, run as a dry run against the chatterbox so no hardware or ot_api is needed.

Stacked on #1132, whose OpentronsOT2ChatterboxBackend is the dry-run harness these tests use. The self-contained change here is the three commits above that base.

🤖 Generated with Claude Code

BioCamand others added 3 commits July 8, 2026 08:56
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>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@BioCam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

OpentronsOT2Backend: support the 8-channel pipette - #1135

Draft
BioCam wants to merge 3 commits into
PyLabRobot:mainfrom
BioCam:ot2-head8-channels
Draft

OpentronsOT2Backend: support the 8-channel pipette#1135
BioCam wants to merge 3 commits into
PyLabRobot:mainfrom
BioCam:ot2-head8-channels

Conversation

@BioCam

Copy link
Copy Markdown
Collaborator

The backend modeled every mount as a single channel, so the OT-2 8-channel pipette could not be driven correctly. This models a multi mount as 8 channels - anchored at channel 0 (back, row A) and filling toward row H at the 9 mm nozzle pitch - and issues one wire command per column for multi-channel pickup, aspirate, and dispense, matching the hardware, which acts on a whole column at once rather than channel by channel. Single-channel pipettes are unchanged: they stay one channel and one command per operation.

Tests cover the channel model (mount -> channel count, channel-0 anchoring) and the per-column wire grouping, run as a dry run against the chatterbox so no hardware or ot_api is needed.

Stacked on #1132, whose OpentronsOT2ChatterboxBackend is the dry-run harness these tests use. The self-contained change here is the three commits above that base.

🤖 Generated with Claude Code

BioCamand others added 3 commits July 8, 2026 08:56
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>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@BioCam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

OpentronsOT2Backend: support the 8-channel pipette - #1135

Draft
BioCam wants to merge 3 commits into
PyLabRobot:mainfrom
BioCam:ot2-head8-channels
Draft

OpentronsOT2Backend: support the 8-channel pipette#1135
BioCam wants to merge 3 commits into
PyLabRobot:mainfrom
BioCam:ot2-head8-channels

Conversation

@BioCam

Copy link
Copy Markdown
Collaborator

The backend modeled every mount as a single channel, so the OT-2 8-channel pipette could not be driven correctly. This models a multi mount as 8 channels - anchored at channel 0 (back, row A) and filling toward row H at the 9 mm nozzle pitch - and issues one wire command per column for multi-channel pickup, aspirate, and dispense, matching the hardware, which acts on a whole column at once rather than channel by channel. Single-channel pipettes are unchanged: they stay one channel and one command per operation.

Tests cover the channel model (mount -> channel count, channel-0 anchoring) and the per-column wire grouping, run as a dry run against the chatterbox so no hardware or ot_api is needed.

Stacked on #1132, whose OpentronsOT2ChatterboxBackend is the dry-run harness these tests use. The self-contained change here is the three commits above that base.

🤖 Generated with Claude Code

BioCamand others added 3 commits July 8, 2026 08:56
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>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@BioCam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

OpentronsOT2Backend: support the 8-channel pipette - #1135

Draft
BioCam wants to merge 3 commits into
PyLabRobot:mainfrom
BioCam:ot2-head8-channels
Draft

OpentronsOT2Backend: support the 8-channel pipette#1135
BioCam wants to merge 3 commits into
PyLabRobot:mainfrom
BioCam:ot2-head8-channels

Conversation

@BioCam

Copy link
Copy Markdown
Collaborator

The backend modeled every mount as a single channel, so the OT-2 8-channel pipette could not be driven correctly. This models a multi mount as 8 channels - anchored at channel 0 (back, row A) and filling toward row H at the 9 mm nozzle pitch - and issues one wire command per column for multi-channel pickup, aspirate, and dispense, matching the hardware, which acts on a whole column at once rather than channel by channel. Single-channel pipettes are unchanged: they stay one channel and one command per operation.

Tests cover the channel model (mount -> channel count, channel-0 anchoring) and the per-column wire grouping, run as a dry run against the chatterbox so no hardware or ot_api is needed.

Stacked on #1132, whose OpentronsOT2ChatterboxBackend is the dry-run harness these tests use. The self-contained change here is the three commits above that base.

🤖 Generated with Claude Code

BioCamand others added 3 commits July 8, 2026 08:56
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>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@BioCam