Using the Visualizer#

The Visualizer is a tool that allows you to visualize the a Resource (like LiquidHandler) including its state to easily see what is going on, for example when executing a protocol on a robot or when developing a new protocol.

When using a backend that does not require access to a physical robot, such as the ChatterBoxBackend, the Visualizer can be used to simulate a robot’s behavior. Of course, you may also use the Visualizer when working with a real robot to see what is happening in the PLR resource and state trackers.

Setting up a connection with the robot#

As described in the basic liquid handling tutorial, we will use the LiquidHandler class to control the robot. This time, however, instead of using the Hamilton STAR backend, we are using the software-only ChatterBoxBackend backend. This means that liquid handling will work exactly the same, but commands are simply printed out to the console instead of being sent to a physical robot. We are still using the same deck.

from pylabrobot.liquid_handling import LiquidHandler
from pylabrobot.liquid_handling.backends import ChatterBoxBackend
from pylabrobot.visualizer.visualizer import Visualizer
from pylabrobot.resources.hamilton import STARLetDeck
lh = LiquidHandler(backend=ChatterBoxBackend(), deck=STARLetDeck())

Calling setup() will print out “Setting up the robot” and also that two resources were assigned: the deck and the trash. Other than that, the chatter box backend has no setup to do.

await lh.setup()
Setting up the robot.
Resource deck was assigned to the robot.
Resource trash was assigned to the robot.

Next, we will create a Visualizer object. The Visualizer expects a Resource, and we will pass the LiquidHandler object to it. This will allow us to visualize the robot’s state and actions.

vis = Visualizer(resource=lh)
await vis.setup()
Websocket server started at http://127.0.0.1:2121
/Users/rickwierenga/pyhamilton/Dropbox (MIT)/Hamilton_Methods/Pyhamilton_Methods/220319_rick_folder/pylabrobot/pylabrobot/visualizer/.
File server started at http://127.0.0.1:1337 . Open this URL in your browser.

The empty simulator

Build the deck layout: Assigning plates and tips#

When resources are assigned to the root resource of the Visualizer, in this case lh, they will automatically appear in the visualization.

from pylabrobot.resources import (
    TIP_CAR_480_A00,
    PLT_CAR_L5AC_A00,
    Cos_96_DW_1mL,
    HTF_L
)
tip_car = TIP_CAR_480_A00(name='tip carrier')
tip_car[0] = tip_rack1 = HTF_L(name='tips_01', with_tips=False)
tip_car[1] = tip_rack2 = HTF_L(name='tips_02', with_tips=False)
tip_car[2] = tip_rack3 = HTF_L(name='tips_03', with_tips=False)
tip_car[3] = tip_rack4 = HTF_L(name='tips_04', with_tips=False)
tip_car[4] = tip_rack5 = HTF_L(name='tips_05', with_tips=False)
lh.deck.assign_child_resource(tip_car, rails=15)
Resource tip carrier was assigned to the robot.
plt_car = PLT_CAR_L5AC_A00(name='plate carrier')
plt_car[0] = plate_1 = Cos_96_DW_1mL(name='plate_01')
plt_car[1] = plate_2 = Cos_96_DW_1mL(name='plate_02')
plt_car[2] = plate_3 = Cos_96_DW_1mL(name='plate_03')
lh.deck.assign_child_resource(plt_car, rails=8)
Resource plate carrier was assigned to the robot.

The simulator after the resources have been assigned

Configuring the state of the deck#

As with every PyLabRobot script, you have the option of updating the state of the deck before you actually start your method. This will allow PyLabRobot to keep track of what is going on, enabling features like return_tips() and catching errors (like missed tips) before a command would be executed on the robot. With the visualizer, this state has the additional effect of updating the visualization.

Tips#

Let’s use fill() to place tips at all spots in the tip rack in location 0.

tip_rack1.fill()

You can precisely control the presence of tips using set_tip_state(). This function allows you to set whether there is a tip in each TipSpot.

tip_rack4 = lh.deck.get_resource("tips_04")
tip_rack4.set_tip_state([[True]*6 + [False]*6]*8)
tip_rack3.set_tip_state([[True, False]*6]*8)
tip_rack2.set_tip_state([[True, True, False, False]*3]*8)

Liquids#

Adding liquid to wells works similarly. You can use set_well_liquids() to set the liquid in each well of a plate. Each liquid is represented by a tuple where the first element corresponds to the type of liquid and the second to the volume in uL. Here, None is used to designate an unknown liquid.

plate_1_liquids = [[(None, 500)]]*96
plate_1.set_well_liquids(plate_1_liquids)
plate_2_liquids = [[(None, 100)], [(None, 500)]]*(96//2)
plate_2.set_well_liquids(plate_2_liquids)

In the visualizer, you can see that the opacity of the well is proportional to how full the well is relative to its maximum volume.

Simulator after the tips have been placed and the volumes adjusted

Liquid handling#

Once the layout is complete, you can run the same commands as described in the basic liquid handling tutorial.

It is important that both tip tracking and volume tracking are enabled globally, so that the visualizer can keep track of the state of the tips and the volumes of the liquids.

from pylabrobot.resources import set_tip_tracking, set_volume_tracking
set_tip_tracking(True), set_volume_tracking(True)
(None, None)

Picking up tips#

Note that since we are using the ChatterBoxBackend, we just print out “Picking up tips” instead of actually performing an operation. The visualizer will show the tips being picked up.

await lh.pick_up_tips(tip_rack1["A1", "B2", "C3", "D4"])
Picking up tips [Pickup(resource=TipSpot(name=tips_01_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=None, tip=HamiltonTip(HIGH_VOLUME, has_filter=True, maximal_volume=1065, fitting_depth=8, total_tip_length=95.1, pickup_method=OUT_OF_RACK)), Pickup(resource=TipSpot(name=tips_01_tipspot_1_1, location=(016.200, 059.300, -83.500), size_x=9.0, size_y=9.0, size_z=0, category=tip_spot), offset=None, tip=HamiltonTip(HIGH_VOLUME, has_filter=True, maximal_volume=1065, fitting_depth=8, total_tip_length=95.1, pickup_method=OUT_OF_RACK)), Pickup(resource=TipSpot(name=tips_01_tipspot_2_2, location=(025.200, 050.300, -83.500), size_x=9.0, size_y=9.0, size_z=0, category=tip_spot), offset=None, tip=HamiltonTip(HIGH_VOLUME, has_filter=True, maximal_volume=1065, fitting_depth=8, total_tip_length=95.1, pickup_method=OUT_OF_RACK)), Pickup(resource=TipSpot(name=tips_01_tipspot_3_3, location=(034.200, 041.300, -83.500), size_x=9.0, size_y=9.0, size_z=0, category=tip_spot), offset=None, tip=HamiltonTip(HIGH_VOLUME, has_filter=True, maximal_volume=1065, fitting_depth=8, total_tip_length=95.1, pickup_method=OUT_OF_RACK))].
await lh.drop_tips(tip_rack1["A1", "B2", "C3", "D4"])
Dropping tips [Drop(resource=TipSpot(name=tips_01_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=None, tip=HamiltonTip(HIGH_VOLUME, has_filter=True, maximal_volume=1065, fitting_depth=8, total_tip_length=95.1, pickup_method=OUT_OF_RACK)), Drop(resource=TipSpot(name=tips_01_tipspot_1_1, location=(016.200, 059.300, -83.500), size_x=9.0, size_y=9.0, size_z=0, category=tip_spot), offset=None, tip=HamiltonTip(HIGH_VOLUME, has_filter=True, maximal_volume=1065, fitting_depth=8, total_tip_length=95.1, pickup_method=OUT_OF_RACK)), Drop(resource=TipSpot(name=tips_01_tipspot_2_2, location=(025.200, 050.300, -83.500), size_x=9.0, size_y=9.0, size_z=0, category=tip_spot), offset=None, tip=HamiltonTip(HIGH_VOLUME, has_filter=True, maximal_volume=1065, fitting_depth=8, total_tip_length=95.1, pickup_method=OUT_OF_RACK)), Drop(resource=TipSpot(name=tips_01_tipspot_3_3, location=(034.200, 041.300, -83.500), size_x=9.0, size_y=9.0, size_z=0, category=tip_spot), offset=None, tip=HamiltonTip(HIGH_VOLUME, has_filter=True, maximal_volume=1065, fitting_depth=8, total_tip_length=95.1, pickup_method=OUT_OF_RACK))].

Aspirating and dispensing#

await lh.pick_up_tips(tip_rack1["A1"])
Picking up tips [Pickup(resource=TipSpot(name=tips_01_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=None, tip=HamiltonTip(HIGH_VOLUME, has_filter=True, maximal_volume=1065, fitting_depth=8, total_tip_length=95.1, pickup_method=OUT_OF_RACK))].
await lh.aspirate(plate_1["A2"], vols=[300])
Aspirating [Aspiration(resource=Well(name=plate_01_well_1_0, location=(018.500, 070.000, 001.000), size_x=9.0, size_y=9.0, size_z=40.0, category=well), offset=None, tip=HamiltonTip(HIGH_VOLUME, has_filter=True, maximal_volume=1065, fitting_depth=8, total_tip_length=95.1, pickup_method=OUT_OF_RACK), volume=300, flow_rate=None, liquid_height=None, blow_out_air_volume=0, liquids=[(None, 300)])].
await lh.dispense(plate_2["A1"], vols=[300])
Dispensing [Dispense(resource=Well(name=plate_02_well_0_0, location=(009.500, 070.000, 001.000), size_x=9.0, size_y=9.0, size_z=40.0, category=well), offset=None, tip=HamiltonTip(HIGH_VOLUME, has_filter=True, maximal_volume=1065, fitting_depth=8, total_tip_length=95.1, pickup_method=OUT_OF_RACK), volume=300, flow_rate=None, liquid_height=None, blow_out_air_volume=0, liquids=[(None, 300)])].
await lh.return_tips()
Dropping tips [Drop(resource=TipSpot(name=tips_01_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=None, tip=HamiltonTip(HIGH_VOLUME, has_filter=True, maximal_volume=1065, fitting_depth=8, total_tip_length=95.1, pickup_method=OUT_OF_RACK))].

Aspirating using CoRe 96#

The CoRe 96 head supports liquid handling operations for 96 channels at once. Here’s how to use:

await lh.pick_up_tips96(tip_rack1)
Picking up tips from tips_01.
await lh.aspirate96(plate_1, volume=100)
Aspirating 100 from Plate(name=plate_01, size_x=127.0, size_y=86.0, size_z=42.0, location=(000.000, 000.000, 000.000)).
await lh.dispense96(plate_3, volume=100)
Dispensing 100 to Plate(name=plate_03, size_x=127.0, size_y=86.0, size_z=42.0, location=(000.000, 000.000, 000.000)).
await lh.drop_tips96(tip_rack1)
Dropping tips to tips_01.

The simulator after the liquid handling operations completed

Shutting down#

When you’re done, you can stop the visualizer by calling stop(). This will stop the visualization.

await vis.stop()