Creating new labware definitions

Heya!

I’m busy playing with PyHamilton to build simple protocols that go above and beyond what I’ve done so far. A quick problem I’ve run into is the definition/importing of different labware from pyhamilton. Thus far we have done tips and plates via Tip96 and Plate96, respectively. How do I import something like 32/24/16 position sample carriers and reservoirs?

Another question I have is regarding documentation for PyHamilton. I seem to recall being able to access full documentation regarding all the function calls etc. Is this still available, or am I just bad at looking? :smile:

Many thanks!

2 Likes

Hi, great question about new resource imports. You can follow the pattern below for defining new labware in PyHamilton, pulled from pyhamilton\pyhamilton\deckresource.py. The most important part is making sure the position_id method returns the right well identifier based on the well integer index.

class Plate12(DeckResource):

    def __init__(self, layout_name):
        self._layout_name = layout_name
        self._num_items = 12
        self.resource_type = DeckResource.types.VESSEL
        self._items = [Vessel(self, i) for i in range(self._num_items)]

    def well_coords(self, idx):
        self._assert_idx_in_range(idx)
        return int(idx)//3, int(idx)%3

    def _alignment_delta(self, start, end):
        [self._assert_idx_in_range(p) for p in (start, end)]
        xs, ys = self.well_coords(start)
        xe, ye = self.well_coords(end)
        return (xe - xs, ye - ys, [DeckResource.align.VERTICAL] if xs == xe and ys != ye else [])

    def position_id(self, idx):
        x, y = self.well_coords(idx)
        return 'ABC'[y] + str(x + 1)

You can then import this class into your script and use it the same way you’d use any other deck resource. You could even add it to deckresource.py and make a pull request to the PyHamilton repo!

2 Likes

Thanks for the reply Stefan. I’ve gone into the deckresource.py source file and added two classes that inherit from DeckResource. The one is a 60 mL reagent reservoir, and the other a 32-position sample carrier. At the moment I’m just trying to use the 60 mL reservoir, but as you stated above I’m having issues with the position_id. I’m clearly just not supplying the correct combination of strings, but I can’t figure out which combination to use! Both the reservoir and the EppiCarrier are linear (one column with 8/32 rows, respectively), so I’m not sure if the indexing A1-H1 etc is appropriate. Do you have any suggestions?


image

1 Like

So a quick troubleshooting update on this. I have managed to get the 60mL reservoir functional in the simulation. Having looked at the sequence positions for the reservoir in the labware file, they are just numeric values of 1-8. So first, in position_id of the class definition, I attempted setting the string indexed by y to ‘12345678’ and commenting out the string indexed by x (see image below). This allowed the software to recognise the reservoir and interact with it, but only in sequence positions 1 & 2.
image

Next I tried returning only the string indexed by x, as in the image below. This helped, as the software can now interact with the reservoir in the first four positions of the sequence.
image

I think I need some combination of the two!

1 Like

You were exactly right about using the sequence indexing, I should have mentioned that.

For a linear index, don’t split your coords into a 2-tuple. You dont even need to use well_coords at all for these resources (it’s only useful for a 2D array like a microplate). Just make sure that position_id returns the same value as is given in the sequence list on Venus. That’s ultimately what PyHamilton is going to use to make your commands target the right wells.

2 Likes

Absolutely fantastic Stefan! Thanks a million. The below addition to the reservoir class works perfectly :slight_smile:
image

1 Like

Always glad to hear it, good luck with your scripts

Hello again!

A quick question regarding the Plate96 class which inherits from Standard96. In particular I am wondering about the use of different plates that would all technically fit into the Plate96 definition (e.g. Greiner U-Well, Greiner Half Area, DeepWellPlates, etc). These are all technically Plate96 but are very clearly distinct from one another in terms of well geometries, liquid-level search height, clearance heights etc.

I am wondering if I need to create independent classes for each of these different types of plates, or if I can just using the Standard96 class for all of them, provided that I handle the resource allocation correctly with resource_list_with_prefix.

Am I making sense?

Edit: I realize that from a simulation standpoint what I have said above is valid, i.e. no need to create extra classes. I just wonder if the simulation would transfer to the hardware! I guess I’m mostly just trying to determine if the labware definition comes from the class itself, or if the class pulls in labware data “on the fly” when PyHamilton calls resource_list_with_prefix.

Hi! Thanks for the question. I see exactly what you’re asking.

The information that the resource class needs to convey in PyHamilton is primarily about translating integer indices of wells to alphanumeric well coordinates i.e. {0, 1, 2, …} → {‘A1’, ‘B1’, ‘C1’, …}. These build command strings which you can view in the Trace file output when you run a simulation. For example, this is from the log of the template project:

2022-10-03 11:48:35> HSLJSONLib : _Method::HSLJsonLib::GetStringProperty - complete; plate_0, A2;plate_0, B2;plate_0, C2;plate_0, D2;plate_0, E2;plate_0, F2;plate_0, G2;plate_0, H2

This was pulled from the JSON packet that defined an aspiration command. From here, Venus builds a sequence based on these pairs of wells and plates, referencing the plate geometry of the resource in the Layfile.

In short, the layfile and labware definitions contain all of the necessary geometric parameters for robot actions in PyHamilton, and Python is responsible for building command strings like the example above.

1 Like