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, SerializingSavingBackend
from pylabrobot.resources import (
  TIP_CAR_480_A00,
  HTF_L,
  PLT_CAR_L5AC_A00,
  Cos_96_EZWash,
)
from pylabrobot.resources.hamilton import STARLetDeck

lh = LiquidHandler(backend=SerializingSavingBackend(num_channels=8), deck=STARLetDeck())
lh.setup()
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

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_L(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_L(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)

Inspecting tip tracker operation history#

lh.pick_up_tips(tip_rack[0])
tip_rack.get_item("A1").tracker.has_tip, tip_rack.get_item("A1").tracker.history
(False,
 [Pickup(tip=TipSpot(name=tip rack_tipspot_0_0, location=(007.200, 068.300, -83.500), size_x=9.0, size_y=9.0, size_z=0, category=tip_spot), offset=Default)])
lh.drop_tips(tip_rack[0])
tip_rack.get_item("A1").tracker.has_tip, tip_rack.get_item("A1").tracker.history
(True,
 [Pickup(tip=TipSpot(name=tip rack_tipspot_0_0, location=(007.200, 068.300, -83.500), size_x=9.0, size_y=9.0, size_z=0, category=tip_spot), offset=Default),
  Drop(tip=TipSpot(name=tip rack_tipspot_0_0, location=(007.200, 068.300, -83.500), size_x=9.0, size_y=9.0, size_z=0, category=tip_spot), offset=Default)])

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.abstract.tip_tracker import TipSpotHasTipError, TipSpotHasNoTipError
from pylabrobot.liquid_handling.channel_tip_tracker import ChannelHasTipError, ChannelHasNoTipError

TipSpotHasNoTipError#

This error is raised when the tip tracker is trying to access a spot that has no tip.

lh.pick_up_tips(tip_rack[0])
lh.drop_tips(empty_tip_rack[0])

try:
  lh.pick_up_tips(tip_rack[0])
except TipSpotHasNoTipError as e:
  print("As expected:", e)
As expected: Tip spot has no tip.

TipSpotHasTipError#

This error is raised when the tip tracker is trying to access a spot that has a tip.

lh.pick_up_tips(tip_rack[1])

try:
  lh.drop_tips(empty_tip_rack[0])
except TipSpotHasTipError as e:
  print("As expected:", e)
As expected: Tip spot already has a tip.
lh.drop_tips(empty_tip_rack[1])

ChannelHasNoTipError#

This error is raised when the tip tracker is trying to use a channel that has no tip.

try:
  lh.drop_tips(empty_tip_rack[2])
except ChannelHasNoTipError as e:
  print("As expected:", e)
As expected: Channel has no tip.

ChannelHasTipError#

This error is raised when the tip tracker is trying to use a channel that has a tip.

lh.pick_up_tips(tip_rack[2])

try:
  lh.pick_up_tips(tip_rack[3])
except ChannelHasTipError as e:
  print("As expected:", e)
As expected: Channel already 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 liquid_handling package has a pylabrobot.liquid_handling.no_tip_tracking() context manager that can be used to disable the tip tracker for a set of operations.

Note that we use the pylabrobot.liquid_handling.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():
  lh.pick_up_tips(tip_rack[4])
  lh.pick_up_tips(tip_rack[4], use_channels=[1]) # no error

For a single object#

The tip tracker can be disabled for a single object by calling pylabrobot.liquid_handling.tip_tracker.TipTracker.disable() on the tracker object.

lh.clear_head_state()
tip_rack.get_item(5).tracker.disable()

lh.pick_up_tips(tip_rack[5])
lh.pick_up_tips(tip_rack[5], use_channels=[1]) # no error

tip_rack.get_item(5).tracker.enable()

Globally#

The tip tracker can be disabled globally by using pylabrobot.liquid_handling.set_tip_tracking().

lh.clear_head_state()
from pylabrobot.resources import set_tip_tracking

set_tip_tracking(enabled=False)

lh.pick_up_tips(tip_rack[6])
lh.pick_up_tips(tip_rack[6], use_channels=[1]) # no error

set_tip_tracking(enabled=True)

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 = Cos_96_EZWash(name="plate")
plate.get_item("A1").tracker.get_used_volume()
0
plate.get_item("A1").tracker.get_free_volume()
572.5552611167398
plate.get_item("A1").tracker.set_used_volume(10)
plate.get_item("A1").tracker.get_used_volume(), plate.get_item("A1").tracker.get_free_volume()
(10, 562.5552611167398)
lh.deck.assign_child_resource(plt_carrier, rails=9)

Inspecting volume tracker operation history#

lh.aspirate(plate["A1"], vols=10)
plate.get_item("A1").tracker.get_used_volume(), plate.get_item("A1").tracker.get_free_volume()
(0, 572.5552611167398)
lh.dispense(plate["A1"], vols=10)
plate.get_item("A1").tracker.get_used_volume(), plate.get_item("A1").tracker.get_free_volume()
(10, 562.5552611167398)

Volume tracker errors#

from pylabrobot.resources.abstract.volume_tracker import (
  TipTooLittleLiquidError,
  TipTooLittleVolumeError,
  WellTooLittleLiquidError,
  WellTooLittleVolumeError,
)

TipTooLittleLiquidError#

This error is raised when the volume tracker is trying to dispense from a tip that has less liquid than the requested volume.

try:
  lh.dispense(plate["A1"], vols=100) # this is less liquid than is currently in the tip
except TipTooLittleLiquidError as e:
  print("As expected:", e)
As expected: Tip has too little liquid to dispense 100 uL (0.0 uL out of 1065 uL used).

TipTooLittleVolumeError#

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()
lh.pick_up_tips(tip_rack[8])
# fill the first two columns
for i in range(16):
  plate.get_item(i).tracker.set_used_volume(100)

try:
  # aspirate from the first two columns - this is more liquid than the tip can hold
  for i in range(16):
    lh.aspirate(plate[i], vols=100)
except TipTooLittleVolumeError as e:
  print("As expected:", e)
As expected: Tip has too little volume to aspirate 100 uL (1000.0 uL out of 1065 uL used).

WellTooLittleLiquidError#

This error is raised when trying to dispense into a well that has less free volume than the requested volume.

try:
  lh.aspirate(plate["A1"], vols=100) # this is less liquid than is currently in the well
except WellTooLittleLiquidError as e:
  print("As expected:", e)
As expected: Well plate_well_0_0 has too little liquid to aspirate 100 uL (0 uL out of 572.5552611167398 uL used).

WellTooLittleVolumeError#

This error is raised when trying to aspirate from a well that has less liquid than the requested volume.

lh.clear_head_state()
lh.pick_up_tips(tip_rack[9])
# fill the first column
for i in range(8):
  plate.get_item(i).tracker.set_used_volume(100)

try:
  # aspirate liquid from the first column into the first well
  for i in range(1, 8):
    lh.aspirate(plate[i], vols=100)
    lh.dispense(plate["A1"], vols=100)
except WellTooLittleVolumeError as e:
  print("As expected:", e)
As expected: Well plate_well_0_0 has too little volume to dispense 100 uL (500 uL out of 572.5552611167398 uL used).