Help with getting started

Hi all. I’m just getting started with PyLabRobot, trying to get it going with a Hamilton STAR. I have limited Hamilton experience, and I haven’t touched Python for several years, so this might be an ELI5 situation as I try to reboot my knowledge.

I’ve gone through the installation in my virtual env, seemingly without any issues that I noticed. However, when attempting to run even the most basic of commands following along with the docs:

from pylabrobot.liquid_handling import LiquidHandler

I get the following large error tree that I can’t seem to parse. Any ideas what the problem might be?

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[2], line 1
----> 1 from pylabrobot.liquid_handling import LiquidHandler

File ~/Documents/FutureHouse/Lab/PyLabRobot/pylabrobot/pylabrobot/liquid_handling/__init__.py:3
      1 """ Liquid handling module for PyLabRobot """
----> 3 from .backends import STAR, SerializingSavingBackend
      4 from .liquid_handler import LiquidHandler
      5 from .standard import (
      6   Pickup,
      7   Drop,
   (...)
     14   Move
     15 )

File ~/Documents/FutureHouse/Lab/PyLabRobot/pylabrobot/pylabrobot/liquid_handling/backends/__init__.py:3
      1 """ Various backends that can be used to control a liquid handling robots. """
----> 3 from .backend import LiquidHandlerBackend
      4 from .serializing_backend import SerializingBackend, SerializingSavingBackend # many rely on this
      5 from .websocket import WebSocketBackend # simulation relies on websocket backend

File ~/Documents/FutureHouse/Lab/PyLabRobot/pylabrobot/pylabrobot/liquid_handling/backends/backend.py:8
      6 from pylabrobot.machine import MachineBackend
      7 from pylabrobot.resources import Resource
----> 8 from pylabrobot.liquid_handling.standard import (
      9   Pickup,
     10   PickupTipRack,
     11   Drop,
     12   DropTipRack,
     13   Aspiration,
     14   AspirationPlate,
     15   Dispense,
     16   DispensePlate,
     17   Move,
     18 )
     21 class LiquidHandlerBackend(MachineBackend, metaclass=ABCMeta):
     22   """
     23   Abstract base class for liquid handling robot backends.
     24 
   (...)
     29     setup_finished: Whether the backend has been set up.
     30   """

File ~/Documents/FutureHouse/Lab/PyLabRobot/pylabrobot/pylabrobot/liquid_handling/standard.py:113
    109   LEFT = enum.auto()
    110   RIGHT = enum.auto()
--> 113 @dataclass
    114 class Move:
    115   """ A move operation.
    116 
    117   Attributes:
   (...)
    124     put_direction: The direction from which to put the resource.
    125   """
    127   resource: Resource

File /opt/homebrew/Cellar/python@3.11/3.11.7/Frameworks/Python.framework/Versions/3.11/lib/python3.11/dataclasses.py:1230, in dataclass(cls, init, repr, eq, order, unsafe_hash, frozen, match_args, kw_only, slots, weakref_slot)
   1227     return wrap
   1229 # We're called as @dataclass without parens.
-> 1230 return wrap(cls)

File /opt/homebrew/Cellar/python@3.11/3.11.7/Frameworks/Python.framework/Versions/3.11/lib/python3.11/dataclasses.py:1220, in dataclass.<locals>.wrap(cls)
   1219 def wrap(cls):
-> 1220     return _process_class(cls, init, repr, eq, order, unsafe_hash,
   1221                           frozen, match_args, kw_only, slots,
   1222                           weakref_slot)

File /opt/homebrew/Cellar/python@3.11/3.11.7/Frameworks/Python.framework/Versions/3.11/lib/python3.11/dataclasses.py:958, in _process_class(cls, init, repr, eq, order, unsafe_hash, frozen, match_args, kw_only, slots, weakref_slot)
    955         kw_only = True
    956     else:
    957         # Otherwise it's a field of some type.
--> 958         cls_fields.append(_get_field(cls, name, type, kw_only))
    960 for f in cls_fields:
    961     fields[f.name] = f

File /opt/homebrew/Cellar/python@3.11/3.11.7/Frameworks/Python.framework/Versions/3.11/lib/python3.11/dataclasses.py:815, in _get_field(cls, a_name, a_type, default_kw_only)
    811 # For real fields, disallow mutable defaults.  Use unhashable as a proxy
    812 # indicator for mutability.  Read the __hash__ attribute from the class,
    813 # not the instance.
    814 if f._field_type is _FIELD and f.default.__class__.__hash__ is None:
--> 815     raise ValueError(f'mutable default {type(f.default)} for field '
    816                      f'{f.name} is not allowed: use default_factory')
    818 return f

ValueError: mutable default <class 'pylabrobot.resources.coordinate.Coordinate'> for field resource_offset is not allowed: use default_factory

Welcome to the forum!

It seems like you’re using Python 3.11. PLR only supports 3.8-3.10.

1 Like

Thanks Rick, didn’t notice that! I will get to 3.10 and try again.

I’ve gotten this working after a little banging my head against the wall. I feel like I may boost the activity in the forum here by myself over the coming days and weeks!

I have now started going through the basic liquid handling demo in the docs, and have run into a new issue, which started when the robot failed to pickup the tip in channel 1 (second channel). It threw an error about not getting a tip, but I just reran the commands and it successfully reattempted loading the tip.

However, it then immediately threw another error where it thinks channel 0 does not have a tip, though clearly there are three tips on the three channels 0, 1, 2. It also will not unload the tips, giving the same error.

This brings up two questions for me:

  1. Is there an easy way to recover from this current state via PLR, without just manually removing the tips and restarting?

  2. Is there a generic method for overriding errors like this? I know in the Hamilton run control sw, it provides ignoring or forcing commands to run through error handling. Is this a supported feature in PLR?

The robot physically failing to pick up a tip is interesting. Are you sure you are using the correct labware? Did you change anything in the setup between runs?

It sounds like you reran setup on lh in between runs. State is not preserved by default, but:

You can use the lh.save_state and lh.load_state methods to save and load the state.

I would recommend using a jupyter notebook for interactively developing protocols so you don’t have to run setup and stop each time.

PLR uses trackers to track state. See resources/tip_tracker.py and volume_tracker.py. There’s some more information on (disabling) trackers here: https://docs.pylabrobot.org/using-trackers.html.

To tell PLR there is a tip in a location where it doesn’t know, something not really covered in that guide (yet), you can access trackers on the resources you want to adjust using .tracker. You can use the tracker.add_tip and tracker.remove_tip. To get the Tip object from a tip spot, use tip_rack.get_tip("A1") (or equivalently tip_rack.get_item("A1").get_tip().) Channel trackers are available through lh.update_head_state.

1 Like

Thanks again Rick. I’ll dig into the tracker stuff, that sounds useful, just hadn’t gotten that far yet.

I typically will disable trackers while doing notebook-based liquid handling, as sometimes the tracker state diverges from physical robot state. We’re still trying to nail down when this happens - a big source of this type of errors is from halting a notebook kernel while running liquid handling commands.

Currently the best way for me to quickly restore state is to refresh the jupyter kernel and run lh.setup() again. This type of error is rare when everything is going as planned, but there are still some edgecases that pop up. Thanks for documenting this one!

1 Like