From d2eb542e262f0b90e1974138f7b9a15757011513 Mon Sep 17 00:00:00 2001 From: vcjdeboer Date: Tue, 4 Aug 2026 14:29:24 +0200 Subject: [PATCH 1/2] fix(byonoy): keep plane 0 when the L96 streams multiple result tables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Byonoy Luminescence 96 firmware streams the plate as one or more concatenated 96-well tables (planes): seq_len chunks of 12 => seq_len/8 tables. Some units return one plane (seq_len=8, 96 values), others return 8 planes (seq_len=64, 768 values). read_luminescence asserted exactly 96 values and raised 'produced 768 values (expected 96)' on the latter. Take the first table (plane 0) and require a positive multiple of 96 — mirroring the vendor library, whose lum96_measure copies results[0] and discards the rest (the extra planes are firmware-internal gain/diagnostic data). This restores the pre-review all_rows[96*0:96*1] behavior and was verified live against a Luminescence 96 returning 8 planes. Co-Authored-By: Claude Opus 4.8 --- pylabrobot/byonoy/luminescence_96.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/pylabrobot/byonoy/luminescence_96.py b/pylabrobot/byonoy/luminescence_96.py index f245df0fa65..48b231c3652 100644 --- a/pylabrobot/byonoy/luminescence_96.py +++ b/pylabrobot/byonoy/luminescence_96.py @@ -198,15 +198,24 @@ async def read_luminescence( f"(chunk flags: {[f'0x{f:02x}' for f in chunk_flags]})" ) self._warn_chunk_flags(chunk_flags) - if len(all_rows) != 96: + # The firmware streams the plate as one or more concatenated 96-well "tables" + # (planes): seq_len chunks of 12 => seq_len / 8 tables. Some units return a + # single plane (seq_len == 8), others return several (e.g. seq_len == 64 => 8 + # planes / 768 values). Only the first table is the reportable measurement — + # this mirrors the vendor library, whose lum96_measure copies results[0] and + # discards the rest (the extra planes are firmware-internal gain/diagnostic + # data). Keep plane 0. + if len(all_rows) == 0 or len(all_rows) % 96 != 0: raise RuntimeError( - f"{self.name} luminescence read produced {len(all_rows)} values (expected 96)" + f"{self.name} luminescence read produced {len(all_rows)} values " + f"(expected a positive multiple of 96)" ) + plate = all_rows[0:96] # Firmware zero-fills wells outside the mask. Convert those to None per # the LuminescenceResult contract ("None for unmeasured wells") — 0.0 is # a legitimate measurement (baseline subtraction can yield ~0 or negative). - masked: List[Optional[float]] = [v if m else None for v, m in zip(all_rows, mask_bools)] + masked: List[Optional[float]] = [v if m else None for v, m in zip(plate, mask_bools)] return [ LuminescenceResult( From 12375f5d34a4e75d7794bf9fd7804087f3e39a85 Mon Sep 17 00:00:00 2001 From: vcjdeboer Date: Tue, 4 Aug 2026 15:31:39 +0200 Subject: [PATCH 2/2] fix(byonoy): rename local to avoid shadowing the plate parameter (mypy) 'plate = all_rows[0:96]' shadowed read_luminescence's 'plate: Plate' parameter, so mypy saw list[float] assigned to Plate. Rename to 'plane0'. Co-Authored-By: Claude Opus 4.8 --- pylabrobot/byonoy/luminescence_96.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pylabrobot/byonoy/luminescence_96.py b/pylabrobot/byonoy/luminescence_96.py index 48b231c3652..1adccfeb9ef 100644 --- a/pylabrobot/byonoy/luminescence_96.py +++ b/pylabrobot/byonoy/luminescence_96.py @@ -210,12 +210,12 @@ async def read_luminescence( f"{self.name} luminescence read produced {len(all_rows)} values " f"(expected a positive multiple of 96)" ) - plate = all_rows[0:96] + plane0 = all_rows[0:96] # Firmware zero-fills wells outside the mask. Convert those to None per # the LuminescenceResult contract ("None for unmeasured wells") — 0.0 is # a legitimate measurement (baseline subtraction can yield ~0 or negative). - masked: List[Optional[float]] = [v if m else None for v, m in zip(plate, mask_bools)] + masked: List[Optional[float]] = [v if m else None for v, m in zip(plane0, mask_bools)] return [ LuminescenceResult(