Using trackers#
Trackers in PyLabRobot are objects that keep track of the state of the deck throughout a protocol. Two types of trackers currently exist: tip trackers (tracking the presence of tips in tip racks and on the pipetting channels) and volume trackers (tracking the volume in pipetting tips and wells).
from pylabrobot.liquid_handling import LiquidHandler
from pylabrobot.liquid_handling.backends.chatterbox import LiquidHandlerChatterboxBackend
from pylabrobot.resources import (
TIP_CAR_480_A00,
HTF,
PLT_CAR_L5AC_A00,
Cor_96_wellplate_360ul_Fb,
set_tip_tracking,
set_volume_tracking
)
from pylabrobot.resources.hamilton import STARLetDeck
lh = LiquidHandler(backend=LiquidHandlerChatterboxBackend(num_channels=8), deck=STARLetDeck())
await lh.setup()
Setting up the liquid handler.
Resource deck was assigned to the liquid handler.
Resource trash was assigned to the liquid handler.
Resource trash_core96 was assigned to the liquid handler.
tip_carrier = TIP_CAR_480_A00(name="tip carrier") # initialize a tip carrier
plt_carrier = PLT_CAR_L5AC_A00(name="plate carrier") # initialize a plate carrier
We enable tip and volume tracking globally using the set_volume_tracking
and set_tip_tracking
methods.
set_volume_tracking(enabled=True)
set_tip_tracking(enabled=True)
Tip trackers#
The tip tracker is a simple class that keeps track of the current tip, and the previous operations that have been performed on an object. This enables features like return_tips()
and automated tip type detection.
Initializing tip racks#
Whether or not tip tracking is turned on, spots on a tip rack initialize with a tip tracker that defaults to having a tip. The tip tracker only comes into play with performing operations.
tip_carrier[0] = tip_rack = HTF(name="tip rack")
tip_rack.get_item("A1").tracker.has_tip
True
To initialize a tip rack without tips, pass with_tips=False
:
tip_carrier[1] = empty_tip_rack = HTF(name="empty tip rack", with_tips=False)
empty_tip_rack.get_item("A1").tracker.has_tip
False
To “empty” a tip rack after initialization, use the empty()
method. To “fill” a tip rack after initialization, use the fill()
method.
empty_tip_rack.fill()
empty_tip_rack.get_item("A1").tracker.has_tip
True
empty_tip_rack.empty()
empty_tip_rack.get_item("A1").tracker.has_tip
False
lh.deck.assign_child_resource(tip_carrier, rails=3)
Resource tip carrier was assigned to the liquid handler.
Tip tracker errors#
The tip tracker is most useful for catching hardware errors before they happen. With tip tracking turned on, the following errors can be raised:
from pylabrobot.resources.errors import HasTipError, NoTipError
NoTipError
when picking up a tip#
This error is raised when the tip tracker is trying to access a spot that has no tip.
await lh.pick_up_tips(tip_rack[0])
await lh.drop_tips(empty_tip_rack[0])
try:
await lh.pick_up_tips(tip_rack[0])
except NoTipError as e:
print("As expected:", e)
Picking up tips:
pip# resource offset tip type max volume (µL) fitting depth (mm) tip length (mm) filter
p0: tip rack_tipspot_0_0 0,0,0 HamiltonTip 1065 8 95.1 Yes
Dropping tips:
pip# resource offset tip type max volume (µL) fitting depth (mm) tip length (mm) filter
p0: empty tip rack_tipspot_0_0 0,0,0 HamiltonTip 1065 8 95.1 Yes
As expected: Tip spot does not have a tip.
HasTipError
when dropping a tip#
This error is raised when the tip tracker is trying to access a spot that has a tip.
await lh.pick_up_tips(tip_rack[1])
try:
await lh.drop_tips(empty_tip_rack[0])
except HasTipError as e:
print("As expected:", e)
Picking up tips:
pip# resource offset tip type max volume (µL) fitting depth (mm) tip length (mm) filter
p0: tip rack_tipspot_0_1 0,0,0 HamiltonTip 1065 8 95.1 Yes
As expected: Tip spot already has a tip.
await lh.drop_tips(empty_tip_rack[1])
Dropping tips:
pip# resource offset tip type max volume (µL) fitting depth (mm) tip length (mm) filter
p0: empty tip rack_tipspot_0_1 0,0,0 HamiltonTip 1065 8 95.1 Yes
NoTipError
when dropping a tip#
This error is raised when the tip tracker is trying to use a channel that has no tip.
try:
await lh.drop_tips(empty_tip_rack[2])
except NoTipError as e:
print("As expected:", e)
As expected: Channel 0 does not have a tip.
HasTipError
when picking up a tip#
This error is raised when the tip tracker is trying to use a channel that has a tip.
await lh.pick_up_tips(tip_rack[2])
try:
await lh.pick_up_tips(tip_rack[3])
except HasTipError as e:
print("As expected:", e)
Picking up tips:
pip# resource offset tip type max volume (µL) fitting depth (mm) tip length (mm) filter
p0: tip rack_tipspot_0_2 0,0,0 HamiltonTip 1065 8 95.1 Yes
As expected: Channel has tip
Disabling the tip tracker#
The tip tracker can be disabled in three different ways, depending on the desired behavior.
Using a context manager#
The pylabrobot.resources.no_tip_tracking()
context manager can be used to disable the tip tracker for a set of operations.
Note that we use the pylabrobot.liquid_handling.liquid_handler.LiquidHandler.clear_head_state()
method to forget the tips that are currently mounted on the channels. This is needed because even though the tip tracker is disabled, the channels still keep track of the tips that are mounted on them.
lh.clear_head_state()
from pylabrobot.resources import no_tip_tracking
with no_tip_tracking():
await lh.pick_up_tips(tip_rack[4])
await lh.pick_up_tips(tip_rack[4], use_channels=[1]) # no error
Picking up tips:
pip# resource offset tip type max volume (µL) fitting depth (mm) tip length (mm) filter
p0: tip rack_tipspot_0_4 0,0,0 HamiltonTip 1065 8 95.1 Yes
Picking up tips:
pip# resource offset tip type max volume (µL) fitting depth (mm) tip length (mm) filter
p1: tip rack_tipspot_0_4 0,0,0 HamiltonTip 1065 8 95.1 Yes
For a single tip spot#
The tip tracker can be disabled for a single object by calling pylabrobot.resources.tip_tracker.TipTracker.disable()
on the tracker object.
lh.clear_head_state()
tip_rack.get_item(5).tracker.disable()
await lh.pick_up_tips(tip_rack[5])
await lh.pick_up_tips(tip_rack[5], use_channels=[1]) # no error
tip_rack.get_item(5).tracker.enable()
Picking up tips:
pip# resource offset tip type max volume (µL) fitting depth (mm) tip length (mm) filter
p0: tip rack_tipspot_0_5 0,0,0 HamiltonTip 1065 8 95.1 Yes
Picking up tips:
pip# resource offset tip type max volume (µL) fitting depth (mm) tip length (mm) filter
p1: tip rack_tipspot_0_5 0,0,0 HamiltonTip 1065 8 95.1 Yes
For a single tip rack#
Disable the tip tracker for a single tip rack by calling pylabrobot.resources.TipRack.disable_tip_trackers()
and pylabrobot.resources.TipRack.enable_tip_trackers()
on the tip rack object.
lh.clear_head_state()
tip_rack.disable_tip_trackers()
await lh.pick_up_tips(tip_rack[5])
await lh.pick_up_tips(tip_rack[5], use_channels=[1]) # no error
tip_rack.enable_tip_trackers()
Picking up tips:
pip# resource offset tip type max volume (µL) fitting depth (mm) tip length (mm) filter
p0: tip rack_tipspot_0_5 0,0,0 HamiltonTip 1065 8 95.1 Yes
Picking up tips:
pip# resource offset tip type max volume (µL) fitting depth (mm) tip length (mm) filter
p1: tip rack_tipspot_0_5 0,0,0 HamiltonTip 1065 8 95.1 Yes
Globally#
The tip tracker can be disabled globally by using pylabrobot.resources.set_tip_tracking()
.
lh.clear_head_state()
from pylabrobot.resources import set_tip_tracking
set_tip_tracking(enabled=False)
await lh.pick_up_tips(tip_rack[6])
await lh.pick_up_tips(tip_rack[6], use_channels=[1]) # no error
set_tip_tracking(enabled=True)
Picking up tips:
pip# resource offset tip type max volume (µL) fitting depth (mm) tip length (mm) filter
p0: tip rack_tipspot_0_6 0,0,0 HamiltonTip 1065 8 95.1 Yes
Picking up tips:
pip# resource offset tip type max volume (µL) fitting depth (mm) tip length (mm) filter
p1: tip rack_tipspot_0_6 0,0,0 HamiltonTip 1065 8 95.1 Yes
Volume trackers#
The volume tracker is a simple class that keeps track of the current volume, and the previous operations that have been performed on an object. This enables features like automated liquid class selection in STAR, and raises errors before they happen on the robot.
Initializing wells#
Wells automatically initialize with a volume tracker that defaults to having no volume.
plt_carrier[0] = plate = Cor_96_wellplate_360ul_Fb(name="plate")
plate.get_item("A1").tracker.get_used_volume()
0
plate.get_item("A1").tracker.get_free_volume()
360
from pylabrobot.resources.liquid import Liquid
plate.get_item("A1").tracker.set_liquids([(Liquid.WATER, 10)])
plate.get_item("A1").tracker.get_used_volume(), plate.get_item("A1").tracker.get_free_volume()
(10, 350)
lh.deck.assign_child_resource(plt_carrier, rails=9)
Resource plate carrier was assigned to the liquid handler.
Inspecting volume tracker operation history#
await lh.aspirate(plate["A1"], vols=[10])
plate.get_item("A1").tracker.get_used_volume(), plate.get_item("A1").tracker.get_free_volume()
Aspirating:
pip# vol(ul) resource offset flow rate blowout lld_z
p0: 10.0 plate_well_0_0 0,0,0 None None None
(0, 360)
await lh.dispense(plate["A1"], vols=[10])
plate.get_item("A1").tracker.get_used_volume(), plate.get_item("A1").tracker.get_free_volume()
Dispensing:
pip# vol(ul) resource offset flow rate blowout lld_z
p0: 10.0 plate_well_0_0 0,0,0 None None None
(10, 350)
Volume tracker errors#
from pylabrobot.resources.volume_tracker import TooLittleLiquidError, TooLittleVolumeError
TooLittleLiquidError
when dispensing#
This error is raised when the volume tracker is trying to dispense from a tip that has less liquid than the requested volume.
try:
await lh.dispense(plate["A1"], vols=[100]) # this is less liquid than is currently in the tip
except TooLittleLiquidError as e:
print("As expected:", e)
As expected: Tracker only has 0uL
TooLittleVolumeError
when aspirating#
This error is raised when the volume tracker is trying to aspirate from a tip that has less free volume than the requested volume.
lh.clear_head_state()
await lh.pick_up_tips(tip_rack[8])
Picking up tips:
pip# resource offset tip type max volume (µL) fitting depth (mm) tip length (mm) filter
p0: tip rack_tipspot_1_0 0,0,0 HamiltonTip 1065 8 95.1 Yes
# fill the first two columns
for i in range(16):
plate.get_item(i).tracker.set_liquids([(Liquid.WATER, 100)])
try:
# aspirate from the first two columns - this is more liquid than the tip can hold
for i in range(16):
await lh.aspirate(plate[i], vols=[100])
except TooLittleVolumeError as e:
print("As expected:", e)
Aspirating:
pip# vol(ul) resource offset flow rate blowout lld_z
p0: 100.0 plate_well_0_0 0,0,0 None None None
Aspirating:
pip# vol(ul) resource offset flow rate blowout lld_z
p0: 100.0 plate_well_0_1 0,0,0 None None None
Aspirating:
pip# vol(ul) resource offset flow rate blowout lld_z
p0: 100.0 plate_well_0_2 0,0,0 None None None
Aspirating:
pip# vol(ul) resource offset flow rate blowout lld_z
p0: 100.0 plate_well_0_3 0,0,0 None None None
Aspirating:
pip# vol(ul) resource offset flow rate blowout lld_z
p0: 100.0 plate_well_0_4 0,0,0 None None None
Aspirating:
pip# vol(ul) resource offset flow rate blowout lld_z
p0: 100.0 plate_well_0_5 0,0,0 None None None
Aspirating:
pip# vol(ul) resource offset flow rate blowout lld_z
p0: 100.0 plate_well_0_6 0,0,0 None None None
Aspirating:
pip# vol(ul) resource offset flow rate blowout lld_z
p0: 100.0 plate_well_0_7 0,0,0 None None None
Aspirating:
pip# vol(ul) resource offset flow rate blowout lld_z
p0: 100.0 plate_well_1_0 0,0,0 None None None
Aspirating:
pip# vol(ul) resource offset flow rate blowout lld_z
p0: 100.0 plate_well_1_1 0,0,0 None None None
As expected: Container has too little volume: 100uL > 65uL.
TooLittleLiquidError
when aspirating#
This error is raised when trying to dispense into a well that has less free volume than the requested volume.
try:
await lh.aspirate(plate["A1"], vols=[100]) # this is less liquid than is currently in the well
except TooLittleLiquidError as e:
print("As expected:", e)
As expected: Tracker only has 0uL
TooLittleVolumeError
when dispensing#
This error is raised when trying to aspirate from a well that has less liquid than the requested volume.
lh.clear_head_state()
await lh.pick_up_tips(tip_rack[9])
Picking up tips:
pip# resource offset tip type max volume (µL) fitting depth (mm) tip length (mm) filter
p0: tip rack_tipspot_1_1 0,0,0 HamiltonTip 1065 8 95.1 Yes
# fill the first column
for i in range(8):
plate.get_item(i).tracker.set_liquids([(Liquid.WATER, 100)])
try:
# aspirate liquid from the first column into the first well
for i in range(1, 8):
await lh.aspirate(plate[i], vols=[100])
await lh.dispense(plate["A1"], vols=[100])
except TooLittleVolumeError as e:
print("As expected:", e)
Aspirating:
pip# vol(ul) resource offset flow rate blowout lld_z
p0: 100.0 plate_well_0_1 0,0,0 None None None
Dispensing:
pip# vol(ul) resource offset flow rate blowout lld_z
p0: 100.0 plate_well_0_0 0,0,0 None None None
Aspirating:
pip# vol(ul) resource offset flow rate blowout lld_z
p0: 100.0 plate_well_0_2 0,0,0 None None None
Dispensing:
pip# vol(ul) resource offset flow rate blowout lld_z
p0: 100.0 plate_well_0_0 0,0,0 None None None
Aspirating:
pip# vol(ul) resource offset flow rate blowout lld_z
p0: 100.0 plate_well_0_3 0,0,0 None None None
As expected: Container has too little volume: 100uL > 60uL.