From 4c7a409fe2baecd05040c0e42747887d387046e7 Mon Sep 17 00:00:00 2001 From: Jeremy Leon Date: Mon, 2 Dec 2024 16:48:03 -0500 Subject: [PATCH] feat(api, shared-data): add correctionByVolume to liquid class schema and definitions (#16972) Adds a correctionByVolume field to liquid class definitions in the schema, pydantic models and PAPI classes for liquid class properties. --- .../protocol_api/_liquid_properties.py | 14 +++ api/tests/opentrons/conftest.py | 2 + .../test_liquid_class_properties.py | 12 ++ shared-data/command/schemas/11.json | 105 ++++++++++++++++++ .../liquid-class/definitions/1/water.json | 33 ++++++ .../fixtures/1/fixture_glycerol50.json | 12 ++ shared-data/liquid-class/schemas/1.json | 23 ++++ .../liquid_classes/liquid_class_definition.py | 18 +++ 8 files changed, 219 insertions(+) diff --git a/api/src/opentrons/protocol_api/_liquid_properties.py b/api/src/opentrons/protocol_api/_liquid_properties.py index 5aaed51edbe..d27d2bd1ba0 100644 --- a/api/src/opentrons/protocol_api/_liquid_properties.py +++ b/api/src/opentrons/protocol_api/_liquid_properties.py @@ -314,6 +314,7 @@ class BaseLiquidHandlingProperties: _position_reference: PositionReference _offset: Coordinate _flow_rate_by_volume: LiquidHandlingPropertyByVolume + _correction_by_volume: LiquidHandlingPropertyByVolume _delay: DelayProperties @property @@ -341,6 +342,10 @@ def offset(self, new_offset: Sequence[float]) -> None: def flow_rate_by_volume(self) -> LiquidHandlingPropertyByVolume: return self._flow_rate_by_volume + @property + def correction_by_volume(self) -> LiquidHandlingPropertyByVolume: + return self._correction_by_volume + @property def delay(self) -> DelayProperties: return self._delay @@ -543,6 +548,9 @@ def build_aspirate_properties( _flow_rate_by_volume=LiquidHandlingPropertyByVolume( aspirate_properties.flowRateByVolume ), + _correction_by_volume=LiquidHandlingPropertyByVolume( + aspirate_properties.correctionByVolume + ), _pre_wet=aspirate_properties.preWet, _mix=_build_mix_properties(aspirate_properties.mix), _delay=_build_delay_properties(aspirate_properties.delay), @@ -560,6 +568,9 @@ def build_single_dispense_properties( _flow_rate_by_volume=LiquidHandlingPropertyByVolume( single_dispense_properties.flowRateByVolume ), + _correction_by_volume=LiquidHandlingPropertyByVolume( + single_dispense_properties.correctionByVolume + ), _mix=_build_mix_properties(single_dispense_properties.mix), _push_out_by_volume=LiquidHandlingPropertyByVolume( single_dispense_properties.pushOutByVolume @@ -581,6 +592,9 @@ def build_multi_dispense_properties( _flow_rate_by_volume=LiquidHandlingPropertyByVolume( multi_dispense_properties.flowRateByVolume ), + _correction_by_volume=LiquidHandlingPropertyByVolume( + multi_dispense_properties.correctionByVolume + ), _conditioning_by_volume=LiquidHandlingPropertyByVolume( multi_dispense_properties.conditioningByVolume ), diff --git a/api/tests/opentrons/conftest.py b/api/tests/opentrons/conftest.py index e8ca2b059ff..7be480cfe0b 100755 --- a/api/tests/opentrons/conftest.py +++ b/api/tests/opentrons/conftest.py @@ -828,6 +828,7 @@ def minimal_liquid_class_def2() -> LiquidClassSchemaV1: positionReference=PositionReference.WELL_BOTTOM, offset=Coordinate(x=0, y=0, z=-5), flowRateByVolume=[(10.0, 40.0), (20.0, 30.0)], + correctionByVolume=[(15.0, 1.5), (30.0, -5.0)], preWet=True, mix=MixProperties(enable=False), delay=DelayProperties( @@ -853,6 +854,7 @@ def minimal_liquid_class_def2() -> LiquidClassSchemaV1: positionReference=PositionReference.WELL_BOTTOM, offset=Coordinate(x=0, y=0, z=-5), flowRateByVolume=[(10.0, 40.0), (20.0, 30.0)], + correctionByVolume=[(15.0, -1.5), (30.0, 5.0)], mix=MixProperties(enable=False), pushOutByVolume=[(10.0, 7.0), (20.0, 10.0)], delay=DelayProperties(enable=False), diff --git a/api/tests/opentrons/protocol_api/test_liquid_class_properties.py b/api/tests/opentrons/protocol_api/test_liquid_class_properties.py index f7033afb5be..356add50ae8 100644 --- a/api/tests/opentrons/protocol_api/test_liquid_class_properties.py +++ b/api/tests/opentrons/protocol_api/test_liquid_class_properties.py @@ -45,6 +45,10 @@ def test_build_aspirate_settings() -> None: assert aspirate_properties.position_reference.value == "well-bottom" assert aspirate_properties.offset == Coordinate(x=0, y=0, z=-5) assert aspirate_properties.flow_rate_by_volume.as_dict() == {10: 50.0} + assert aspirate_properties.correction_by_volume.as_dict() == { + 1.0: -2.5, + 10.0: 3, + } assert aspirate_properties.pre_wet is True assert aspirate_properties.mix.enabled is True assert aspirate_properties.mix.repetitions == 3 @@ -94,6 +98,10 @@ def test_build_single_dispense_settings() -> None: 10.0: 40.0, 20.0: 30.0, } + assert single_dispense_properties.correction_by_volume.as_dict() == { + 2.0: -1.5, + 20.0: 2, + } assert single_dispense_properties.mix.enabled is True assert single_dispense_properties.mix.repetitions == 3 assert single_dispense_properties.mix.volume == 15 @@ -146,6 +154,10 @@ def test_build_multi_dispense_settings() -> None: 10.0: 40.0, 20.0: 30.0, } + assert multi_dispense_properties.correction_by_volume.as_dict() == { + 3.0: -0.5, + 30.0: 1, + } assert multi_dispense_properties.conditioning_by_volume.as_dict() == { 5.0: 5.0, } diff --git a/shared-data/command/schemas/11.json b/shared-data/command/schemas/11.json index 38a39ea7902..2df16125574 100644 --- a/shared-data/command/schemas/11.json +++ b/shared-data/command/schemas/11.json @@ -2133,6 +2133,40 @@ ] } }, + "correctionByVolume": { + "title": "Correctionbyvolume", + "description": "Settings for volume correction keyed by by target aspiration volume, representing additional volume the plunger should move to accurately hit target volume.", + "type": "array", + "items": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": [ + { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "number", + "minimum": 0.0 + } + ] + }, + { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "number" + } + ] + } + ] + } + }, "preWet": { "title": "Prewet", "description": "Whether to perform a pre-wet action.", @@ -2163,6 +2197,7 @@ "positionReference", "offset", "flowRateByVolume", + "correctionByVolume", "preWet", "mix", "delay" @@ -2411,6 +2446,40 @@ ] } }, + "correctionByVolume": { + "title": "Correctionbyvolume", + "description": "Settings for volume correction keyed by by target dispense volume, representing additional volume the plunger should move to accurately hit target volume.", + "type": "array", + "items": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": [ + { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "number", + "minimum": 0.0 + } + ] + }, + { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "number" + } + ] + } + ] + } + }, "mix": { "title": "Mix", "description": "Mixing settings for after a dispense", @@ -2472,6 +2541,7 @@ "positionReference", "offset", "flowRateByVolume", + "correctionByVolume", "mix", "pushOutByVolume", "delay" @@ -2553,6 +2623,40 @@ ] } }, + "correctionByVolume": { + "title": "Correctionbyvolume", + "description": "Settings for volume correction keyed by by target dispense volume, representing additional volume the plunger should move to accurately hit target volume.", + "type": "array", + "items": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": [ + { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "number", + "minimum": 0.0 + } + ] + }, + { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "number" + } + ] + } + ] + } + }, "conditioningByVolume": { "title": "Conditioningbyvolume", "description": "Settings for conditioning volume keyed by target dispense volume.", @@ -2641,6 +2745,7 @@ "positionReference", "offset", "flowRateByVolume", + "correctionByVolume", "conditioningByVolume", "disposalByVolume", "delay" diff --git a/shared-data/liquid-class/definitions/1/water.json b/shared-data/liquid-class/definitions/1/water.json index b84e1676d5b..b9447aa9c52 100644 --- a/shared-data/liquid-class/definitions/1/water.json +++ b/shared-data/liquid-class/definitions/1/water.json @@ -64,6 +64,7 @@ [10.0, 24.0], [50.0, 35.0] ], + "correctionByVolume": [[0.0, 0.0]], "preWet": false, "mix": { "enable": false, @@ -133,6 +134,7 @@ "z": 2 }, "flowRateByVolume": [[1.0, 50.0]], + "correctionByVolume": [[0.0, 0.0]], "mix": { "enable": false, "params": { @@ -208,6 +210,7 @@ "z": 2 }, "flowRateByVolume": [[50.0, 50.0]], + "correctionByVolume": [[0.0, 0.0]], "conditioningByVolume": [ [1.0, 5.0], [45.0, 5.0], @@ -288,6 +291,7 @@ [10.0, 24.0], [50.0, 35.0] ], + "correctionByVolume": [[0.0, 0.0]], "preWet": false, "mix": { "enable": false, @@ -357,6 +361,7 @@ "z": 2 }, "flowRateByVolume": [[1.0, 50.0]], + "correctionByVolume": [[0.0, 0.0]], "mix": { "enable": false, "params": { @@ -432,6 +437,7 @@ "z": 2 }, "flowRateByVolume": [[1.0, 50.0]], + "correctionByVolume": [[0.0, 0.0]], "conditioningByVolume": [ [1.0, 5.0], [45.0, 5.0], @@ -512,6 +518,7 @@ [10.0, 478.0], [50.0, 478.0] ], + "correctionByVolume": [[0.0, 0.0]], "preWet": false, "mix": { "enable": false, @@ -585,6 +592,7 @@ [10.0, 478.0], [50.0, 478.0] ], + "correctionByVolume": [[0.0, 0.0]], "mix": { "enable": false, "params": { @@ -658,6 +666,7 @@ [10.0, 478.0], [50.0, 478.0] ], + "correctionByVolume": [[0.0, 0.0]], "conditioningByVolume": [ [1.0, 5.0], [45.0, 5.0], @@ -729,6 +738,7 @@ "z": 2 }, "flowRateByVolume": [[1.0, 716.0]], + "correctionByVolume": [[0.0, 0.0]], "preWet": false, "mix": { "enable": false, @@ -798,6 +808,7 @@ "z": 2 }, "flowRateByVolume": [[1.0, 716.0]], + "correctionByVolume": [[0.0, 0.0]], "mix": { "enable": false, "params": { @@ -867,6 +878,7 @@ "z": 2 }, "flowRateByVolume": [[1.0, 716.0]], + "correctionByVolume": [[0.0, 0.0]], "conditioningByVolume": [ [1.0, 5.0], [195.0, 5.0], @@ -938,6 +950,7 @@ "z": 2 }, "flowRateByVolume": [[1.0, 716.0]], + "correctionByVolume": [[0.0, 0.0]], "preWet": false, "mix": { "enable": false, @@ -1007,6 +1020,7 @@ "z": 2 }, "flowRateByVolume": [[1.0, 716.0]], + "correctionByVolume": [[0.0, 0.0]], "mix": { "enable": false, "params": { @@ -1076,6 +1090,7 @@ "z": 2 }, "flowRateByVolume": [[1.0, 716.0]], + "correctionByVolume": [[0.0, 0.0]], "conditioningByVolume": [ [1.0, 5.0], [995.0, 5.0], @@ -1156,6 +1171,7 @@ [10.0, 478.0], [50.0, 478.0] ], + "correctionByVolume": [[0.0, 0.0]], "preWet": false, "mix": { "enable": false, @@ -1229,6 +1245,7 @@ [10.0, 478.0], [50.0, 478.0] ], + "correctionByVolume": [[0.0, 0.0]], "mix": { "enable": false, "params": { @@ -1302,6 +1319,7 @@ [10.0, 478.0], [50.0, 478.0] ], + "correctionByVolume": [[0.0, 0.0]], "conditioningByVolume": [ [1.0, 5.0], [45.0, 5.0], @@ -1373,6 +1391,7 @@ "z": 2 }, "flowRateByVolume": [[1.0, 716.0]], + "correctionByVolume": [[0.0, 0.0]], "preWet": false, "mix": { "enable": false, @@ -1442,6 +1461,7 @@ "z": 2 }, "flowRateByVolume": [[1.0, 716.0]], + "correctionByVolume": [[0.0, 0.0]], "mix": { "enable": false, "params": { @@ -1511,6 +1531,7 @@ "z": 2 }, "flowRateByVolume": [[1.0, 716.0]], + "correctionByVolume": [[0.0, 0.0]], "conditioningByVolume": [ [1.0, 5.0], [195.0, 5.0], @@ -1582,6 +1603,7 @@ "z": 2 }, "flowRateByVolume": [[1.0, 716.0]], + "correctionByVolume": [[0.0, 0.0]], "preWet": false, "mix": { "enable": false, @@ -1651,6 +1673,7 @@ "z": 2 }, "flowRateByVolume": [[1.0, 716.0]], + "correctionByVolume": [[0.0, 0.0]], "mix": { "enable": false, "params": { @@ -1720,6 +1743,7 @@ "z": 2 }, "flowRateByVolume": [[1.0, 716.0]], + "correctionByVolume": [[0.0, 0.0]], "conditioningByVolume": [ [1.0, 5.0], [995.0, 5.0], @@ -1796,6 +1820,7 @@ "z": 2 }, "flowRateByVolume": [[1.0, 200.0]], + "correctionByVolume": [[0.0, 0.0]], "preWet": false, "mix": { "enable": false, @@ -1865,6 +1890,7 @@ "z": 2 }, "flowRateByVolume": [[1.0, 200.0]], + "correctionByVolume": [[0.0, 0.0]], "mix": { "enable": false, "params": { @@ -1934,6 +1960,7 @@ "z": 2 }, "flowRateByVolume": [[1.0, 200.0]], + "correctionByVolume": [[0.0, 0.0]], "conditioningByVolume": [ [1.0, 5.0], [45.0, 5.0], @@ -2005,6 +2032,7 @@ "z": 2 }, "flowRateByVolume": [[1.0, 200.0]], + "correctionByVolume": [[0.0, 0.0]], "preWet": false, "mix": { "enable": false, @@ -2074,6 +2102,7 @@ "z": 2 }, "flowRateByVolume": [[1.0, 200.0]], + "correctionByVolume": [[0.0, 0.0]], "mix": { "enable": false, "params": { @@ -2143,6 +2172,7 @@ "z": 2 }, "flowRateByVolume": [[1.0, 200.0]], + "correctionByVolume": [[0.0, 0.0]], "conditioningByVolume": [ [1.0, 5.0], [195.0, 5.0], @@ -2214,6 +2244,7 @@ "z": 2 }, "flowRateByVolume": [[1.0, 200.0]], + "correctionByVolume": [[0.0, 0.0]], "preWet": false, "mix": { "enable": false, @@ -2283,6 +2314,7 @@ "z": 2 }, "flowRateByVolume": [[1.0, 200.0]], + "correctionByVolume": [[0.0, 0.0]], "mix": { "enable": false, "params": { @@ -2352,6 +2384,7 @@ "z": 2 }, "flowRateByVolume": [[1.0, 200.0]], + "correctionByVolume": [[0.0, 0.0]], "conditioningByVolume": [ [1.0, 5.0], [995.0, 5.0], diff --git a/shared-data/liquid-class/fixtures/1/fixture_glycerol50.json b/shared-data/liquid-class/fixtures/1/fixture_glycerol50.json index 20fe7b44a3c..9c24a40452d 100644 --- a/shared-data/liquid-class/fixtures/1/fixture_glycerol50.json +++ b/shared-data/liquid-class/fixtures/1/fixture_glycerol50.json @@ -59,6 +59,10 @@ "z": -5 }, "flowRateByVolume": [[10.0, 50.0]], + "correctionByVolume": [ + [1.0, -2.5], + [10.0, 3.0] + ], "preWet": true, "mix": { "enable": true, @@ -134,6 +138,10 @@ [10.0, 40.0], [20.0, 30.0] ], + "correctionByVolume": [ + [2.0, -1.5], + [20.0, 2.0] + ], "mix": { "enable": true, "params": { @@ -208,6 +216,10 @@ [10.0, 40.0], [20.0, 30.0] ], + "correctionByVolume": [ + [3.0, -0.5], + [30.0, 1.0] + ], "conditioningByVolume": [[5.0, 5.0]], "disposalByVolume": [[5.0, 3.0]], "delay": { diff --git a/shared-data/liquid-class/schemas/1.json b/shared-data/liquid-class/schemas/1.json index f3aa85a6168..633be549d4c 100644 --- a/shared-data/liquid-class/schemas/1.json +++ b/shared-data/liquid-class/schemas/1.json @@ -144,6 +144,17 @@ }, "minItems": 1 }, + "correctionByVolume": { + "type": "array", + "description": "Settings for volume correction keyed by target aspiration/dispense volume, representing additional volume the plunger should move to accurately hit target volume.", + "items": { + "type": "array", + "items": { "type": "number" }, + "minItems": 2, + "maxItems": 2 + }, + "minItems": 1 + }, "mix": { "type": "object", "description": "Mixing properties.", @@ -310,6 +321,9 @@ "flowRateByVolume": { "$ref": "#/definitions/flowRateByVolume" }, + "correctionByVolume": { + "$ref": "#/definitions/correctionByVolume" + }, "preWet": { "type": "boolean", "description": "Whether to perform a pre-wet action." @@ -327,6 +341,7 @@ "positionReference", "offset", "flowRateByVolume", + "correctionByVolume", "preWet", "mix", "delay" @@ -352,6 +367,9 @@ "flowRateByVolume": { "$ref": "#/definitions/flowRateByVolume" }, + "correctionByVolume": { + "$ref": "#/definitions/correctionByVolume" + }, "mix": { "$ref": "#/definitions/mix" }, @@ -368,6 +386,7 @@ "positionReference", "offset", "flowRateByVolume", + "correctionByVolume", "mix", "pushOutByVolume", "delay" @@ -393,6 +412,9 @@ "flowRateByVolume": { "$ref": "#/definitions/flowRateByVolume" }, + "correctionByVolume": { + "$ref": "#/definitions/correctionByVolume" + }, "conditioningByVolume": { "$ref": "#/definitions/conditioningByVolume" }, @@ -409,6 +431,7 @@ "positionReference", "offset", "flowRateByVolume", + "correctionByVolume", "conditioningByVolume", "disposalByVolume", "delay" diff --git a/shared-data/python/opentrons_shared_data/liquid_classes/liquid_class_definition.py b/shared-data/python/opentrons_shared_data/liquid_classes/liquid_class_definition.py index 62add6a32b0..2c2de84e07e 100644 --- a/shared-data/python/opentrons_shared_data/liquid_classes/liquid_class_definition.py +++ b/shared-data/python/opentrons_shared_data/liquid_classes/liquid_class_definition.py @@ -31,6 +31,9 @@ LiquidHandlingPropertyByVolume = Sequence[Tuple[_NonNegativeNumber, _NonNegativeNumber]] """Settings for liquid class settings that are interpolated by volume.""" +CorrectionByVolume = Sequence[Tuple[_NonNegativeNumber, _Number]] +"""Settings for correctionByVolume, which unlike other `byVolume` properties allows negative values with volume.""" + class PositionReference(Enum): """Positional reference for liquid handling operations.""" @@ -253,6 +256,11 @@ class AspirateProperties(BaseModel): ..., description="Settings for flow rate keyed by target aspiration volume.", ) + correctionByVolume: CorrectionByVolume = Field( + ..., + description="Settings for volume correction keyed by by target aspiration volume," + " representing additional volume the plunger should move to accurately hit target volume.", + ) preWet: bool = Field(..., description="Whether to perform a pre-wet action.") mix: MixProperties = Field( ..., description="Mixing settings for before an aspirate" @@ -277,6 +285,11 @@ class SingleDispenseProperties(BaseModel): ..., description="Settings for flow rate keyed by target dispense volume.", ) + correctionByVolume: CorrectionByVolume = Field( + ..., + description="Settings for volume correction keyed by by target dispense volume," + " representing additional volume the plunger should move to accurately hit target volume.", + ) mix: MixProperties = Field(..., description="Mixing settings for after a dispense") pushOutByVolume: LiquidHandlingPropertyByVolume = Field( ..., description="Settings for pushout keyed by target dispense volume." @@ -301,6 +314,11 @@ class MultiDispenseProperties(BaseModel): ..., description="Settings for flow rate keyed by target dispense volume.", ) + correctionByVolume: CorrectionByVolume = Field( + ..., + description="Settings for volume correction keyed by by target dispense volume," + " representing additional volume the plunger should move to accurately hit target volume.", + ) conditioningByVolume: LiquidHandlingPropertyByVolume = Field( ..., description="Settings for conditioning volume keyed by target dispense volume.",