Skip to content

Commit 37b4dcb

Browse files
authored
fix(api): fix float conversion issue when truncating absorbance values. (#17680)
1 parent 98c7c12 commit 37b4dcb

File tree

2 files changed

+66
-9
lines changed
  • api
    • src/opentrons/protocol_engine/state
    • tests/opentrons/protocol_engine/commands/absorbance_reader

2 files changed

+66
-9
lines changed

api/src/opentrons/protocol_engine/state/modules.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from __future__ import annotations
44

5+
import math
56
from dataclasses import dataclass
67
from typing import (
78
Dict,
@@ -1237,22 +1238,20 @@ def is_flex_deck_with_thermocycler(self) -> bool:
12371238
else:
12381239
return False
12391240

1240-
def convert_absorbance_reader_data_points(
1241-
self, data: List[float]
1242-
) -> Dict[str, float]:
1241+
@staticmethod
1242+
def convert_absorbance_reader_data_points(data: List[float]) -> Dict[str, float]:
12431243
"""Return the data from the Absorbance Reader module in a map of wells for each read value."""
12441244
if len(data) == 96:
12451245
# We have to reverse the reader values because the Opentrons Absorbance Reader is rotated 180 degrees on the deck
1246-
data.reverse()
1246+
raw_data = data.copy()
1247+
raw_data.reverse()
12471248
well_map: Dict[str, float] = {}
1248-
for i, value in enumerate(data):
1249+
for i, value in enumerate(raw_data):
12491250
row = chr(ord("A") + i // 12) # Convert index to row (A-H)
12501251
col = (i % 12) + 1 # Convert index to column (1-12)
12511252
well_key = f"{row}{col}"
1252-
truncated_value = float(
1253-
"{:.5}".format(str(value))
1254-
) # Truncate the returned value to the third decimal place
1255-
well_map[well_key] = truncated_value
1253+
# Truncate the value to the third decimal place
1254+
well_map[well_key] = math.floor(value * 1000) / 1000
12561255
return well_map
12571256
else:
12581257
raise ValueError(

api/tests/opentrons/protocol_engine/commands/absorbance_reader/test_read.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
"""Test absorbance reader initilize command."""
2+
import math
3+
from typing import Dict, List, Optional
24
import pytest
35
from decoy import Decoy
46

@@ -12,6 +14,7 @@
1214
from opentrons.protocol_engine.execution import EquipmentHandler
1315
from opentrons.protocol_engine.resources import FileProvider
1416
from opentrons.protocol_engine.state import update_types
17+
from opentrons.protocol_engine.state.modules import ModuleView
1518
from opentrons.protocol_engine.state.state import StateView
1619
from opentrons.protocol_engine.state.module_substates import (
1720
AbsorbanceReaderSubState,
@@ -27,6 +30,19 @@
2730
)
2831

2932

33+
def _get_absorbance_map(data: Optional[List[float]] = None) -> Dict[str, float]:
34+
raw_values = (data or [0] * 96).copy()
35+
raw_values.reverse()
36+
well_map: Dict[str, float] = {}
37+
for i, value in enumerate(raw_values):
38+
row = chr(ord("A") + i // 12) # Convert index to row (A-H)
39+
col = (i % 12) + 1 # Convert index to column (1-12)
40+
well_key = f"{row}{col}"
41+
# Truncate the value to the third decimal place
42+
well_map[well_key] = math.floor(value * 1000) / 1000
43+
return well_map
44+
45+
3046
async def test_absorbance_reader_implementation(
3147
decoy: Decoy,
3248
state_view: StateView,
@@ -88,6 +104,48 @@ async def test_absorbance_reader_implementation(
88104
)
89105

90106

107+
async def test_convert_absorbance_reader_data_points() -> None:
108+
"""It should validate and convert the absorbance reader values."""
109+
# Test valid values
110+
raw_data = (
111+
[0.04877041280269623, 0.046341221779584885]
112+
+ [0.43] * 92 # fill rest of the values with 0.43
113+
+ [0.03789025545120239, 2.8744750022888184]
114+
)
115+
expected = _get_absorbance_map(raw_data)
116+
converted = ModuleView.convert_absorbance_reader_data_points(raw_data)
117+
assert len(converted) == 96
118+
assert converted == expected
119+
assert converted["A1"] == 2.874
120+
assert converted["A2"] == 0.037
121+
assert converted["E1"] == 0.43
122+
assert converted["H12"] == 0.048 # the data is flipped, so arr[0] == H12
123+
124+
# Test near-zero values in scientic notation
125+
raw_data = (
126+
[0.24877041280269623, -9.5000e-9]
127+
+ [0.11] * 92 # fill rest of the values with 0.11
128+
+ [1.3489025545120239, 8.2987e-9]
129+
)
130+
expected = _get_absorbance_map(raw_data)
131+
converted = ModuleView.convert_absorbance_reader_data_points(raw_data)
132+
assert len(converted) == 96
133+
assert converted == expected
134+
assert converted["A1"] == 0.0
135+
assert converted["A2"] == 1.348
136+
assert converted["E1"] == 0.11
137+
assert converted["H11"] == -0.001
138+
assert converted["H12"] == 0.248 # the data is flipped, so arr[0] == H12
139+
140+
# Test invalid data len 1
141+
with pytest.raises(ValueError):
142+
ModuleView.convert_absorbance_reader_data_points([0])
143+
144+
# Test invalid data len 107
145+
with pytest.raises(ValueError):
146+
ModuleView.convert_absorbance_reader_data_points([1] * 107)
147+
148+
91149
async def test_read_raises_cannot_preform_action(
92150
decoy: Decoy,
93151
state_view: StateView,

0 commit comments

Comments
 (0)