~big PLR update: visualizer + LH is a Resource

Just pushed some semi-big changes to PLR:

Removed the Simulator in favor of the new Visualizer

I was working on supporting all Machines in the simulator, but felt that it was too cumbersome to duplicate the state tracking in both Python (tip trackers and the like) and in JS. Simulating stuff in this way is kinda neat from a technical perspective, but does not provide any real value.

  • Instead, you can now spin up a Visualizer which will simply visualize what is happening on the deck, including state (as used to be the case in the simulator).
  • Methods like pick_up_tips no longer exist in JavaScript. The only functions in JS now are for state updates.
  • You can also start Visualizer while running a method on a physical robot such as STAR.
  • To do “classical” simulations, use a software backend like the ChatterBox, and visualize the deck.

Example:

from pylabrobot.visualizer import Visualizer
vis = Visualizer(resource=lh)
await vis.setup()

Full tutorial: https://docs.pylabrobot.org/using-the-visualizer.html (adopted from the simulator tutorial)

All Machines, including LiquidHandler, are now a Resource

I wanted the Visualizer to visualize any Resource/Machine, and so LiquidHandler had to become a Resource. PlateReader already was, and it makes a lot of sense for devices to be conceptualized as Resource-subclasses. Machine is now the base class for ‘functional’ resources / resources that take a backend.

  • In the case of LH, the LH-resource only has one child: the deck. This preserves hardware agnosticity in the interface.

  • It is now possible to assign multiple liquid handlers to a shared, top level Lab resource if you’re using multiple robots.

  • LiquidHandler, as a Resource, currently gets its name, size_{x,y,z} from the deck parameter.

  • I will provide methods for conveniently initializing devices (like already exists for plates). Perhaps like def HamiltonSTAR() -> LiquidHandler. This will probably return a LiquidHandler configured with deck and backend, but subject to change.

Resource state updates

To accommodate state mirroring, I added/changed some methods on Resource:

  • serialize_state: serialize the state for a single resource. Subclasses like Well and TipSpot override this method. Previously, this method was only available on Deck and serialized state for all children too.
  • serialize_all_state: serialize state for this resource and children. Calls serialize_state. Subclasses don’t override.
  • load_state: load state as given by serialize_state. Previously, this method was only available on Deck and loaded all state.
  • load_all_state: load all state from a resource_name: resource_state dictionary.
  • save_state_to_file: save the state of a resource and all children to a file
  • load_state_from_file: load state for this resource and all children from a file
  • register_state_update_callback: register a callback method to be called when the state updates. The registered method will receive the new state as the only parameter. ResourceDidUpdateState = Callable[[Dict[str, Any]], None].
  • deregister_state_update_callback: deregister a callback
  • _state_updated: private method to call all registered callbacks. Subclasses call this when state has been updated.

State is now less dependent on a liquid handler deck, and can easily be monitored by Visualizer but also custom methods (e.g. to get notified of a well running low in volume).


Actionable changes TLDR:

  • use Visualizer instead of the simulator
  • if you were loading serialized deck state with load_state from a dictionary, use load_all_state.

(all of this is up for discussion)

6 Likes

I really like the idea of functional resources - nice work!

1 Like