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.