Source code for autoprotocol.liquid_handle.transfer

"""Transfer LiquidHandleMethod

    :copyright: 2021 by The Autoprotocol Development Team, see AUTHORS
        for more details.
    :license: BSD, see LICENSE for more details

Base LiquidHandleMethod used by Protocol.transfer to generate a series of
movements between pairs of wells.
"""
import dataclasses

from typing import Optional, Union

from ..instruction import LiquidHandle
from ..unit import Unit
from ..util import parse_unit
from .liquid_handle_method import LiquidHandleMethod


# pylint: disable=unused-argument,too-many-instance-attributes,protected-access
[docs]@dataclasses.dataclass class Transfer(LiquidHandleMethod): """LiquidHandleMethod for generating transfers between pairs of wells LiquidHandleMethod for transferring volume from one well to another. Parameters ---------- tip_type : str, optional tip_type to be used for the LiquidHandlingMethod blowout : bool or dict, optional whether to execute a blowout step or the parameters for one. this generates a pair of operations: an initial air aspiration before entering any wells and a corresponding air dispense after the last operation that involves liquid See Also LiquidHandle.builders.blowout prime : bool or Unit, optional whether to execute a prime step or the parameters for one. this generates a pair of aspirate/dispense operations around the aspiration step in the sequence: aspirate_prime -> aspirate_target_volume -> dispense_prime transit : bool or Unit, optional whether to execute a transit step or the parameters for one. this generates a pair of operations wherein air is aspirated just before leaving the source location and dispensed immediately after reaching the destination location mix_before : bool or dict, optional whether to execute a mix_before step or the parameters for one. this generates a series of aspirate and dispense steps within the source location before aspirating the target volume See Also LiquidHandle.builders.mix mix_after : bool or dict, optional whether to execute a mix_after step or the parameters for one. this generates a series of aspirate and dispense steps within the destination location after dispensing the target volume See Also LiquidHandle.builders.mix aspirate_z : dict, optional the position that the tip should move to prior to aspirating, if the position references the `liquid_surface` then aspirate movements will track the surface with the defined offset. See Also LiquidHandle.builders.position_z dispense_z : dict, optional the position that the tip should move to prior to dispensing, if the position references the `liquid_surface` then dispense will track the surface with the defined offset. See Also LiquidHandle.builders.position_z Attributes ---------- _source_liquid : LiquidClass used to determine calibration, flowrates, and sensing thresholds _destination_liquid : LiquidClass used to determine calibration, flowrates, and sensing thresholds Notes ----- The primary entry points that for this class are: - _aspirate_transports : generates transports for a source location - _dispense_transports : generates transports for a destination location See Also -------- LiquidHandleMethod : base LiquidHandleMethod with reused functionality Protocol.transfer : the standard interface for interacting with Transfer """ tip_type: Optional[str] = None blowout: Optional[Union[bool, dict]] = True prime: Optional[Union[bool, Unit]] = True transit: Optional[Union[bool, Unit]] = True mix_before: Optional[Union[bool, dict]] = False mix_after: Optional[Union[bool, dict]] = True aspirate_z: Optional[dict] = None dispense_z: Optional[dict] = None def __post_init__(self): # LiquidHandle parameters that are generated and modified at runtime self._source_liquid = None self._destination_liquid = None def _has_calibration(self): liquids = [self._source_liquid, self._destination_liquid] return any(_ and _._has_calibration() for _ in liquids) def _calculate_overage_volume(self, volume): calibration_overage = ( self._estimate_calibrated_volume(volume, self._source_liquid, self.tip_type) - volume ) # handle whichever is larger, prime or transit volume if self.prime is True: prime = self.default_prime(volume) elif self.prime is False: prime = Unit(0, "uL") else: prime = self.prime if self.transit is True: transit = self.default_transit(volume) elif self.transit is False: transit = Unit(0, "uL") else: transit = self.transit prime_or_transit = max([prime, transit]) return calibration_overage + prime_or_transit
[docs] def default_blowout(self, volume): if self._is_single_channel(): if volume < Unit("10:ul"): blowout_vol = Unit("5:ul") elif volume < Unit("25:ul"): blowout_vol = Unit("10:ul") elif volume < Unit("75:ul"): blowout_vol = Unit("15:ul") elif volume < Unit("100:ul"): blowout_vol = Unit("20:ul") else: blowout_vol = Unit("25:ul") else: blowout_vol = Unit("5:ul") return LiquidHandle.builders.blowout( volume=blowout_vol, initial_z=self.default_well_top_position_z(), flowrate=None, )
def _aspirate_transports(self, volume, density): """Generates source well transports Generates and returns all of the transports that should happen within the source well of a transfer operation. Calls a series of _transport_`y` helper methods that each query the `y` parameter and default_`y` method to decide on a set of behavior and use that to define transports that are appended to the _transports list. Parameters ---------- volume : Unit Return ------ list source well transports corresponding to the aspirate operation Notes ----- This method defines what lower level transport-generating methods are called and in what order. It can be overwritten when adding an entirely new set of transport-generating behavior. See Also -------- _dispense_transports : corresponding destination well method """ self._transports = [] volume = parse_unit(Unit(volume), "ul") # No transports if no volume specified if volume == Unit("0:ul"): return [] self._transport_pre_buffer(volume) self._transport_mix_before(volume) self._transport_aspirate_target_volume(volume, density) self._transport_aspirate_transit(volume) return self._transports def _dispense_transports(self, volume, density): """Generates destination well transports Generates and returns all of the transports that should happen within the destination well of a transfer operation. Calls a series of _transport_`y` helper methods that each query the `y` parameter and default_`y` method to decide on a set of behavior and use that to define transports that are appended to the _transports list. Parameters ---------- volume : Unit Return ------ list destination well transports corresponding to the dispense operation Notes ----- This method defines what lower level transport-generating methods are called and in what order. It can be overwritten when adding an entirely new set of transport-generating behavior. See Also -------- _aspirate_transports : corresponding source well method """ self._transports = [] volume = parse_unit(volume, "ul") # No transports if no volume specified if volume == Unit("0:ul"): return [] self._transport_dispense_transit(volume) self._transport_dispense_target_volume(volume, density) self._transport_mix_after(volume) self._transport_blowout(volume) return self._transports def _transport_mix_before(self, volume): """Mixes volume in the source well before aspirating Parameters ---------- volume : Unit See Also -------- mix_before : holds any user defined mix_before parameters default_mix_before : specifies default mix_before parameters _mix : lower level helper that generates the mix_before transports """ if self.mix_before is True: mix_before = self.default_mix_before(volume) elif self.mix_before is False: mix_before = False else: mix_before = self.mix_before if mix_before is not False: mix_before = LiquidHandle.builders.mix(**mix_before) self._mix( delay_time=self._source_liquid.delay_time, liquid_class=self._source_liquid.name, **mix_before ) # pragma pylint: disable=missing-param-doc
[docs] def default_mix_before(self, volume: Unit): """Default mix_before parameters Parameters ---------- volume : Unit Returns ------- dict mix_before params See Also -------- mix_before : holds any user defined mix_before parameters _transport_mix : generates the actual mix_before transports """ if self._is_single_channel(): mix_z = self.default_lld_position_z(liquid=self._source_liquid) else: mix_z = self.default_well_bottom_position_z() return LiquidHandle.builders.mix( volume=volume, repetitions=10, initial_z=mix_z, asp_flowrate=self._source_liquid._get_aspirate_flowrate( volume, self.tip_type ), dsp_flowrate=self._source_liquid._get_dispense_flowrate( volume, self.tip_type ), )
def _transport_aspirate_target_volume(self, volume, density): """Aspirates the target volume from the source location Parameters ---------- volume : Unit density : Unit See Also -------- aspirate_z : holds any user defined aspirate_z parameters default_aspirate_z : specifies default aspirate_z parameters prime : holds any user defined prime volume default_prime : specifies default prime volume _aspirate_simple : lower level helper that generates aspirate transports _aspirate_with_prime : lower level helper for aspirating with priming """ aspirate_z = self.aspirate_z or self.default_aspirate_z(volume) if self.prime is True: prime = self.default_prime(volume) elif self.prime is False: prime = False else: prime = self.prime aspirate_z = LiquidHandle.builders.position_z(**aspirate_z) if prime is not False: prime = parse_unit(prime, "uL") self._aspirate_with_prime( volume=volume, prime_vol=prime, calibrated_vol=self._source_liquid._get_calibrated_volume( volume, self.tip_type ), initial_z=aspirate_z, asp_flowrate=self._source_liquid._get_aspirate_flowrate( volume, self.tip_type ), dsp_flowrate=self._source_liquid._get_dispense_flowrate( volume, self.tip_type ), delay_time=self._source_liquid.delay_time, liquid_class=self._source_liquid.name, density=density, ) else: self._aspirate_simple( volume=volume, calibrated_vol=self._source_liquid._get_calibrated_volume( volume, self.tip_type ), initial_z=aspirate_z, flowrate=self._source_liquid._get_aspirate_flowrate( volume, self.tip_type ), delay_time=self._source_liquid.delay_time, liquid_class=self._source_liquid.name, density=density, ) # pragma pylint: disable=missing-param-doc
[docs] def default_aspirate_z(self, volume: Unit): """Default aspirate_z parameters Parameters ---------- volume : Unit Returns ------- dict aspirate position_z See Also -------- aspirate_z : holds any user defined aspirate_z parameters _transport_aspirate_target_volume : generates actual aspirate transports """ if self._is_single_channel(): aspirate_z = self.default_lld_position_z(liquid=self._source_liquid) else: aspirate_z = self.default_well_bottom_position_z() return aspirate_z
# pylint: disable=no-self-use, missing-param-doc
[docs] def default_prime(self, volume: Unit): """Default prime volume Parameters ---------- volume : Unit Returns ------- Unit priming volume See Also -------- prime : holds any user defined prime volume _transport_aspirate_target_volume : generates actual aspirate transports """ return Unit(5, "ul")
def _transport_aspirate_transit(self, volume): """Aspirates air above the source before moving to the destination Parameters ---------- volume : Unit See Also -------- transit : holds any user defined transit volume default_transit : specifies default transit volume _transport_dispense_transit : the corresponding air dispense step """ if self.transit is True: transit = self.default_transit(volume) elif self.transit is False: transit = False else: transit = self.transit if transit is not False: transit_vol = parse_unit(transit, "uL") self._aspirate_simple( volume=transit_vol, initial_z=self.default_well_top_position_z(), liquid_class="air", ) def _transport_dispense_transit(self, volume): """Dispenses air above the destination after moving from the source Parameters ---------- volume : Unit See Also -------- transit : holds any user defined transit volume default_transit : specifies default transit volume _transport_aspirate_transit : the corresponding air aspirate step """ if self.transit is True: transit = self.default_transit(volume) elif self.transit is False: transit = False else: transit = self.transit if transit is not False: transit_vol = parse_unit(transit, "uL") self._dispense_simple( volume=transit_vol, initial_z=self.default_well_top_position_z(), liquid_class="air", ) # pragma pylint: disable=missing-param-doc
[docs] def default_transit(self, volume: Unit): """Default transit volume Parameters ---------- volume : Unit Returns ------- Unit transit volume See Also -------- transit : holds any user defined transit volume _transport_aspirate_transit : generates the actual transit transports _transport_dispense_transit : generates the actual transit transports """ if self._is_single_channel(): transit_vol = Unit("2:ul") else: transit_vol = Unit("1:ul") return transit_vol
def _transport_dispense_target_volume(self, volume, density): """Dispenses the target volume into the destination location Parameters ---------- volume : Unit density : Unit See Also -------- dispense_z : holds any user defined dispense_z parameters default_dispense_z : specifies default dispense_z parameters _dispense_simple : lower level helper that generates dispense transports """ dispense_z = self.dispense_z or self.default_dispense_z(volume) dispense_z = LiquidHandle.builders.position_z(**dispense_z) self._dispense_simple( volume=volume, calibrated_vol=self._source_liquid._get_calibrated_volume( volume, self.tip_type ), initial_z=dispense_z, flowrate=self._source_liquid._get_dispense_flowrate(volume, self.tip_type), delay_time=self._source_liquid.delay_time, liquid_class=self._source_liquid.name, density=density, ) # pragma pylint: disable=missing-param-doc
[docs] def default_dispense_z(self, volume: Unit): """Default aspirate_z parameters Parameters ---------- volume : Unit Returns ------- dict dispense position_z See Also -------- dispense_z : holds any user defined dispense_z parameters _transport_dispense_target_volume : generates actual dispense transports """ if self._is_single_channel(): dispense_z = self.default_lld_position_z(liquid=self._destination_liquid) else: dispense_z = self.default_tracked_position_z() return dispense_z
def _transport_mix_after(self, volume): """Mixes volume in the destination well after dispensing Parameters ---------- volume : Unit See Also -------- mix_after : holds any user defined mix_after parameters default_mix_after : specifies default mix_after parameters _mix : lower level helper that generates the mix_after transports """ if self.mix_after is True: mix_after = self.default_mix_after(volume) elif self.mix_after is False: mix_after = False else: mix_after = self.mix_after if mix_after is not False: mix_after = LiquidHandle.builders.mix(**mix_after) self._mix( delay_time=self._source_liquid.delay_time, liquid_class=self._source_liquid.name, **mix_after ) # pragma pylint: disable=missing-param-doc
[docs] def default_mix_after(self, volume: Unit): """Default mix_after parameters Parameters ---------- volume : Unit Returns ------- dict mix_after params See Also -------- mix_after : holds any user defined mix_after parameters _transport_mix : generates the actual mix_after transports """ if self._is_single_channel(): mix_z = self.default_lld_position_z(liquid=self._destination_liquid) else: mix_z = self.default_well_bottom_position_z() return LiquidHandle.builders.mix( volume=volume, repetitions=10, initial_z=mix_z, asp_flowrate=self._source_liquid._get_aspirate_flowrate( volume, self.tip_type ), dsp_flowrate=self._source_liquid._get_dispense_flowrate( volume, self.tip_type ), )
[docs]class DryWellTransfer(Transfer): """Dispenses while tracking liquid without mix_after""" def __init__( self, tip_type=None, blowout=True, prime=True, transit=True, mix_before=False, mix_after=False, aspirate_z=None, dispense_z=None, ): super(DryWellTransfer, self).__init__( tip_type=tip_type, blowout=blowout, prime=prime, transit=transit, mix_before=mix_before, mix_after=mix_after, aspirate_z=aspirate_z, dispense_z=dispense_z, )
[docs] def default_dispense_z(self, volume): return self.default_tracked_position_z()
[docs]class PreMixBlowoutTransfer(Transfer): """Adds an additional blowout before the mix_after step""" def __init__( self, tip_type=None, blowout=True, prime=True, transit=True, mix_before=False, mix_after=True, aspirate_z=None, dispense_z=None, pre_mix_blowout=True, ): super(PreMixBlowoutTransfer, self).__init__( tip_type=tip_type, blowout=blowout, prime=prime, transit=transit, mix_before=mix_before, mix_after=mix_after, aspirate_z=aspirate_z, dispense_z=dispense_z, ) self.pre_mix_blowout = pre_mix_blowout def _dispense_transports(self, volume=None, density=None): self._transports = [] volume = parse_unit(volume, "ul") # No transports if no volume specified if volume == Unit("0:ul"): return [] self._transport_dispense_transit(volume) self._transport_dispense_target_volume(volume, density) self._transport_pre_mix_blowout(volume) self._transport_mix_after(volume) self._transport_blowout(volume) return self._transports def _calculate_pre_buffer(self, volume): if self.blowout is True: blowout = self.default_blowout(volume) elif self.blowout is False: blowout = {} else: blowout = self.blowout if self.pre_mix_blowout is True: secondary_blowout = self.default_pre_mix_blowout(volume) elif self.pre_mix_blowout is False: secondary_blowout = {} else: secondary_blowout = self.pre_mix_blowout blowout_vol = parse_unit(blowout.get("volume", Unit("0:uL")), "uL") secondary_blowout_vol = parse_unit( secondary_blowout.get("volume", Unit("0:uL")), "uL" ) return blowout_vol + secondary_blowout_vol def _transport_pre_mix_blowout(self, volume): """Dispenses a secondary air volume befiore the mix_after step Notes ----- For some liquid classes this has resulted in more complete dispensing of the target volume than just a single blowout. Parameters ---------- volume : Unit See Also -------- pre_mix_blowout : holds any user defined pre_mix_blowout parameters default_pre_mix_blowout : specifies default pre_mix_blowout parameters """ if self.pre_mix_blowout is True: pre_mix_blowout = self.default_pre_mix_blowout(volume) elif self.pre_mix_blowout is False: pre_mix_blowout = False else: pre_mix_blowout = self.pre_mix_blowout if pre_mix_blowout is not False: pre_mix_blowout = LiquidHandle.builders.blowout(**pre_mix_blowout) self._dispense_simple(liquid_class="air", **pre_mix_blowout) # pragma pylint: disable=missing-param-doc
[docs] def default_pre_mix_blowout(self, volume: Unit): """Default pre_mix_blowout parameters Parameters ---------- volume : Unit Returns ------- dict pre_mix_blowout params See Also -------- pre_mix_blowout : holds any user defined pre_mix_blowout parameters _transport_pre_mix_blowout : generates the actual blowout transports """ return LiquidHandle.builders.blowout( volume=Unit(5, "ul"), initial_z=self.default_well_top_position_z(), flowrate=None, )