Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions pylabrobot/plate_reading/tecan/infinite_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,10 @@ async def _run_scan(
_, y_stage = self._map_well_to_stage(row_wells[0])

await self._send_command(f"ABSOLUTE MTP,Y={y_stage}")
# Match the OEM one-row scan flow by explicitly pre-positioning the transport to the
# row start before issuing SCANX. Hardware testing showed the standalone XY move alone
# can reintroduce the first-row edge-read problem.
await self._send_command(f"ABSOLUTE MTP,X={start_x},Y={y_stage}")
await self._send_command(f"SCAN DIRECTION={scan_direction}")
await self._send_command(
f"SCANX {start_x},{end_x},{count}", wait_for_terminal=False, read_response=False
Expand Down
41 changes: 41 additions & 0 deletions pylabrobot/plate_reading/tecan/infinite_backend_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -733,9 +733,11 @@ async def mock_await(decoder, row_count, mode):
call(self._frame("PREPARE REF")),
# row scans (2 rows in test plate)
call(self._frame("ABSOLUTE MTP,Y=8000")),
call(self._frame("ABSOLUTE MTP,X=3000,Y=8000")),
call(self._frame("SCAN DIRECTION=ALTUP")),
call(self._frame("SCANX 3000,23000,3")),
call(self._frame("ABSOLUTE MTP,Y=16000")),
call(self._frame("ABSOLUTE MTP,X=23000,Y=16000")),
call(self._frame("SCAN DIRECTION=ALTUP")),
call(self._frame("SCANX 23000,3000,3")),
# _end_run
Expand Down Expand Up @@ -769,6 +771,41 @@ async def mock_terminal(_saw_terminal):

self.assertAlmostEqual(result[0]["data"][0][0], 0.3010299956639812)

async def test_read_absorbance_subset_prepositions_to_masked_row_start(self):
self.backend._ready = True
wells = self.plate.get_wells(["A2", "A3", "B1", "B2"])

async def mock_await(decoder, row_count, mode):
cal_len, cal_blob = _abs_calibration_blob(6000, 0, 1000, 0, 1000)
if decoder.calibration is None:
decoder.feed_bin(cal_len, cal_blob)
for _ in range(row_count):
data_len, data_blob = _abs_data_blob(6000, 500, 1000)
decoder.feed_bin(data_len, data_blob)

with patch.object(self.backend, "_await_measurements", side_effect=mock_await):
with patch.object(self.backend, "_await_scan_terminal", new_callable=AsyncMock):
result = await self.backend.read_absorbance(self.plate, wells, wavelength=600)

self.mock_usb.write.assert_has_calls(
[
call(self._frame("ABSOLUTE MTP,Y=8000")),
call(self._frame("ABSOLUTE MTP,X=13000,Y=8000")),
call(self._frame("SCAN DIRECTION=ALTUP")),
call(self._frame("SCANX 13000,23000,2")),
call(self._frame("ABSOLUTE MTP,Y=16000")),
call(self._frame("ABSOLUTE MTP,X=13000,Y=16000")),
call(self._frame("SCAN DIRECTION=ALTUP")),
call(self._frame("SCANX 13000,3000,2")),
]
)
self.assertIsNone(result[0]["data"][0][0])
self.assertAlmostEqual(result[0]["data"][0][1], 0.3010299956639812)
self.assertAlmostEqual(result[0]["data"][0][2], 0.3010299956639812)
self.assertAlmostEqual(result[0]["data"][1][0], 0.3010299956639812)
self.assertAlmostEqual(result[0]["data"][1][1], 0.3010299956639812)
self.assertIsNone(result[0]["data"][1][2])

async def test_read_fluorescence_commands(self):
"""Test that read_fluorescence sends the correct configuration commands."""
self.backend._ready = True
Expand Down Expand Up @@ -827,9 +864,11 @@ async def mock_await(decoder, row_count, mode):
call(self._frame("PREPARE REF")),
# row scans (2 rows in test plate)
call(self._frame("ABSOLUTE MTP,Y=8000")),
call(self._frame("ABSOLUTE MTP,X=3000,Y=8000")),
call(self._frame("SCAN DIRECTION=UP")),
call(self._frame("SCANX 3000,23000,3")),
call(self._frame("ABSOLUTE MTP,Y=16000")),
call(self._frame("ABSOLUTE MTP,X=23000,Y=16000")),
call(self._frame("SCAN DIRECTION=UP")),
call(self._frame("SCANX 23000,3000,3")),
# _end_run
Expand Down Expand Up @@ -886,9 +925,11 @@ async def mock_await(decoder, row_count, mode):
call(self._frame("PREPARE REF")),
# row scans (2 rows, non-serpentine so both scan left-to-right)
call(self._frame("ABSOLUTE MTP,Y=8000")),
call(self._frame("ABSOLUTE MTP,X=3000,Y=8000")),
call(self._frame("SCAN DIRECTION=UP")),
call(self._frame("SCANX 3000,23000,3")),
call(self._frame("ABSOLUTE MTP,Y=16000")),
call(self._frame("ABSOLUTE MTP,X=3000,Y=16000")),
call(self._frame("SCAN DIRECTION=UP")),
call(self._frame("SCANX 3000,23000,3")),
# _end_run
Expand Down
Loading