Skip to content

Commit

Permalink
only load liquid-class once at beginning of test-run
Browse files Browse the repository at this point in the history
  • Loading branch information
andySigler committed Feb 4, 2025
1 parent 888182b commit 8e9f2b9
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 23 deletions.
4 changes: 4 additions & 0 deletions hardware-testing/hardware_testing/gravimetric/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ class RunArgs:
liquid: str
dilution: float
reverse_tips: bool
tune_volume_correction: bool

@classmethod
def _get_protocol_context(cls, args: argparse.Namespace) -> ProtocolContext:
Expand Down Expand Up @@ -414,6 +415,7 @@ def build_run_args(cls, args: argparse.Namespace) -> "RunArgs": # noqa: C901
liquid=args.liquid,
dilution=args.dilution,
reverse_tips=args.reverse_tips,
tune_volume_correction=args.tune_volume_correction,
)


Expand Down Expand Up @@ -474,6 +476,7 @@ def build_gravimetric_cfg(
dilution=run_args.dilution,
starting_tip=starting_tip,
use_old_method=use_old_method,
tune_volume_correction=run_args.tune_volume_correction,
)


Expand Down Expand Up @@ -680,6 +683,7 @@ def _main(
default="A1",
)
parser.add_argument("--serial-log", action="store_true")
parser.add_argument("--tune-volume-correction", action="store_true")
args = parser.parse_args()
run_args = RunArgs.build_run_args(args)
config.NUM_BLANK_TRIALS = args.blank_trials
Expand Down
1 change: 1 addition & 0 deletions hardware-testing/hardware_testing/gravimetric/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class GravimetricConfig(VolumetricConfig):
scale_delay: int
isolate_channels: List[int]
isolate_volumes: List[float]
tune_volume_correction: bool


@dataclass
Expand Down
105 changes: 82 additions & 23 deletions hardware-testing/hardware_testing/gravimetric/execute.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@
from time import sleep
from typing import Optional, Tuple, List, Dict

from opentrons.protocol_api import ProtocolContext, Well, Labware, InstrumentContext
from opentrons.protocol_api import (
ProtocolContext,
Well,
Labware,
InstrumentContext,
LiquidClass,
)
from opentrons.protocol_api._liquid import TransferProperties

from opentrons.protocol_api.core.engine.transfer_components_executor import (
TransferType,
LiquidAndAirGapPair,
Expand Down Expand Up @@ -42,6 +50,7 @@
_get_approach_submerge_retract_heights,
)
from .liquid_class.defaults import get_liquid_class, set_liquid_class
from .liquid_class.definition import LiquidClassSettings
from .liquid_class.interactive import interactively_build_liquid_class
from .liquid_height.height import LiquidTracker
from .measurement import (
Expand Down Expand Up @@ -271,11 +280,16 @@ def _take_photos(trial: GravimetricTrial, stage_str: str) -> None:


def _run_trial(
trial: GravimetricTrial, use_old_method: bool = False
trial: GravimetricTrial,
new_liquid_class: Optional[LiquidClass] = None,
tune_volume_correction: bool = False,
) -> Tuple[float, MeasurementData, float, MeasurementData]:
global _PREV_TRIAL_GRAMS
assert trial.pipette.has_tip
if use_old_method:
transfer_properties: Optional[TransferProperties] = None
old_liquid_class: Optional[LiquidClassSettings] = None
pipetting_callbacks = None
if not new_liquid_class:
pipetting_callbacks = _generate_callbacks_for_trial(
trial.ctx,
trial.pipette,
Expand All @@ -286,7 +300,7 @@ def _run_trial(
trial.trial,
trial.blank,
)
liquid_class = get_liquid_class(
old_liquid_class = get_liquid_class(
trial.cfg.liquid,
trial.cfg.dilution,
int(trial.pipette.max_volume),
Expand All @@ -295,10 +309,10 @@ def _run_trial(
trial.volume,
)
if trial.cfg.interactive:
liquid_class = interactively_build_liquid_class(liquid_class)
old_liquid_class = interactively_build_liquid_class(old_liquid_class)
# store it, so that next loop we don't have to think so much
set_liquid_class(
liquid_class,
old_liquid_class,
trial.cfg.liquid,
trial.cfg.dilution,
int(trial.pipette.max_volume),
Expand All @@ -307,20 +321,13 @@ def _run_trial(
trial.volume,
)
else:
pipetting_callbacks = None
lc_name = SupportedLiquid.from_string(trial.cfg.liquid).name_with_dilution(
trial.cfg.dilution
)
liquid_class_root = trial.ctx.define_liquid_class(
lc_name.lower().replace("-", "_")
)
pip_load_name = (
f"flex_{trial.pipette.channels}channel_{int(trial.pipette.max_volume)}"
)
tiprack_load_name = (
f"opentrons/opentrons_flex_96_tiprack_{trial.cfg.tip_volume}ul/1"
)
liquid_class = liquid_class_root.get_for(pip_load_name, tiprack_load_name)
transfer_properties = new_liquid_class.get_for(pip_load_name, tiprack_load_name)

def _tag(m_type: MeasurementType) -> str:
tag = create_measurement_tag(
Expand Down Expand Up @@ -405,7 +412,7 @@ def _record_measurement_and_store(m_type: MeasurementType) -> MeasurementData:
# tell the API's liquid-class to move the correct submerge depth (relative to well-bottom)
def _calculate_meniscus_relative_offsets(is_aspirate: bool) -> None:
_attr_name = "aspirate" if is_aspirate else "dispense"
_asp_or_disp = getattr(liquid_class, _attr_name)
_asp_or_disp = getattr(transfer_properties, _attr_name)
_retract_mm = max(
0.0, _asp_or_disp.submerge.offset.z, _asp_or_disp.retract.offset.z
)
Expand All @@ -430,10 +437,10 @@ def _calculate_meniscus_relative_offsets(is_aspirate: bool) -> None:
_asp_or_disp.retract.offset.z = retract

# RUN ASPIRATE
if use_old_method:
if not transfer_properties:
aspirate_with_liquid_class(
trial.ctx,
liquid_class,
old_liquid_class,
trial.pipette,
trial.volume,
trial.well,
Expand Down Expand Up @@ -464,10 +471,42 @@ def _calculate_meniscus_relative_offsets(is_aspirate: bool) -> None:
# FIXME: This assumes whatever is in the pipette from last trial is air (not liquid),
# and so this would break any sort of multi-dispense testing
assumed_air_gap = trial.pipette.current_volume
if tune_volume_correction:
_prev_vol_corr_val_asp = (
transfer_properties.aspirate.correction_by_volume.get_for_volume(
trial.volume
)
)
_prev_vol_corr_val_disp = (
transfer_properties.dispense.correction_by_volume.get_for_volume(
trial.volume
)
)
assert abs(_prev_vol_corr_val_asp - _prev_vol_corr_val_disp) < 0.001
while True:
_inp = input(
f"ENTER volumeCorrection (uL) for Aspirate={trial.volume} "
f"(currently set to {_prev_vol_corr_val_asp}): "
)
if not _inp:
print(f"using old volumeCorrection {_prev_vol_corr_val_asp}")
break
try:
_new_vol_corr_val = float(_inp.strip())
# NOTE: aspirate/dispense need identical values to avoid errors
transfer_properties.aspirate.correction_by_volume.set_for_volume(
trial.volume, _new_vol_corr_val
)
transfer_properties.dispense.correction_by_volume.set_for_volume(
trial.volume, _new_vol_corr_val
)
break
except ValueError as e:
print(e)
tip_contents = trial.pipette._core.aspirate_liquid_class(
volume=trial.volume,
source=(Location(Point(), trial.well), trial.well._core),
transfer_properties=liquid_class,
transfer_properties=transfer_properties,
transfer_type=TransferType.ONE_TO_ONE,
tip_contents=[LiquidAndAirGapPair(liquid=0, air_gap=assumed_air_gap)],
)
Expand All @@ -488,10 +527,10 @@ def _calculate_meniscus_relative_offsets(is_aspirate: bool) -> None:
ui.get_user_ready("dispensing")

# RUN DISPENSE
if use_old_method:
if not transfer_properties:
dispense_with_liquid_class(
ctx=trial.ctx,
liquid_class=liquid_class,
liquid_class=old_liquid_class,
pipette=trial.pipette,
dispense_volume=trial.volume,
well=trial.well,
Expand All @@ -510,7 +549,7 @@ def _calculate_meniscus_relative_offsets(is_aspirate: bool) -> None:
volume=trial.volume,
dest=(Location(Point(), trial.well), trial.well._core),
source=(Location(Point(), trial.well), trial.well._core),
transfer_properties=liquid_class,
transfer_properties=transfer_properties,
transfer_type=TransferType.ONE_TO_ONE,
tip_contents=tip_contents,
add_final_air_gap=True,
Expand Down Expand Up @@ -663,8 +702,16 @@ def _calculate_evaporation(
actual_disp_list_evap: List[float] = []
for b_trial in blank_trials[resources.test_volumes[-1]][0]:
ui.print_header(f"BLANK {b_trial.trial + 1}/{config.NUM_BLANK_TRIALS}")
new_liquid_class = None
if not use_old_method:
lc_name = SupportedLiquid.from_string(
b_trial.cfg.liquid
).name_with_dilution(b_trial.cfg.dilution)
new_liquid_class = b_trial.ctx.define_liquid_class(
lc_name.lower().replace("-", "_")
)
evap_aspirate, _, evap_dispense, _ = _run_trial(
b_trial, use_old_method=use_old_method
b_trial, new_liquid_class=new_liquid_class
)
ui.print_info(
f"blank {b_trial.trial + 1}/{config.NUM_BLANK_TRIALS}:\n"
Expand Down Expand Up @@ -716,6 +763,14 @@ def _get_liquid_height(

def run(cfg: config.GravimetricConfig, resources: TestResources) -> None: # noqa: C901
"""Run."""
new_liquid_class = None
if not cfg.use_old_method:
lc_name = SupportedLiquid.from_string(
cfg.liquid
).name_with_dilution(cfg.dilution)
new_liquid_class = resources.ctx.define_liquid_class(
lc_name.lower().replace("-", "_")
)
global _PREV_TRIAL_GRAMS
global _MEASUREMENTS
ui.print_header("LOAD LABWARE")
Expand Down Expand Up @@ -897,7 +952,11 @@ def run(cfg: config.GravimetricConfig, resources: TestResources) -> None: # noq
aspirate_data,
actual_dispense,
dispense_data,
) = _run_trial(run_trial, use_old_method=cfg.use_old_method)
) = _run_trial(
run_trial,
new_liquid_class=new_liquid_class,
tune_volume_correction=cfg.tune_volume_correction,
)
ui.print_info(
"measured volumes:\n"
f"\taspirate: {round(actual_aspirate, 2)} uL\n"
Expand Down

0 comments on commit 8e9f2b9

Please sign in to comment.