Correct way to aspirate/dispense to/from a "1-well" reservoir using the 96 head

I’m trying to aspirate from the AGenBio_1_WP_Fl using the 96 head. This plate has only 1 well defined:

image

I get the following error

> await workcell.lh.aspirate96(workcell.cell_plate, 50)

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[12], line 1
----> 1 await workcell.lh.aspirate96(workcell.reagent_reservoir, 50)

File ~/Documents/wetlab_workcell/src/pylabrobot/pylabrobot/liquid_handling/liquid_handler.py:1384, in LiquidHandler.aspirate96(self, resource, volume, offset, flow_rate, blow_out_air_volume, **backend_kwargs)
   1381       raise ValueError("All wells must be in the same plate")
   1383 if not len(wells) == 96:
-> 1384   raise ValueError(f"aspirate96 expects 96 wells, got {len(wells)}")
   1386 for well, channel in zip(wells, self.head96.values()):
   1387   # superfluous to have append in two places but the type checker is very angry and does not
   1388   # understand that Optional[Liquid] (remove_liquid) is the same as None from the first case
   1389   if well.tracker.is_disabled or not does_volume_tracking():

ValueError: aspirate96 expects 96 wells, got 1

*edit: Trying to hack around this by passing the same well 96 times works like so:

> await workcell.lh.aspirate96([workcell.reagent_reservoir.children[0]] * 96, 50)

However, in this case, I need to pass an offset that resolves the distance between the center of the single well and the center of a “virtual” A0 well in the top left corner:

offset = Coordinate(-workcell.cell_plate.children[0].get_size_x() / 2 + 4.5, workcell.cell_plate.children[0].get_size_y() / 2 - 4.5)
await workcell.lh.aspirate96([workcell.cell_plate.children[0]] * 96, 10, offset = offset)

This works.

the api does not support this specific use case in a neat way yet.

I would recommend starting with await lh.aspirate96(reagent_reservoir.get_item("A1")) (aspirating using the 96 head from a single resource). Currently, this will move the first tip to the lbb (left back bottom), but i think it would be good to center the 96 in the requested single container by default. I can make a PR for this.

the api for aspirating from multiple wells is to support full plates with one channel going into each well. (it is checked that the number of wells passed is 96 in that case.)

Wanted to revive this thread and ask about this use case.

I ran into the same problem trying to use agilent_1_reservoir_290ml as a media reservoir and fill many 96 well plates. My best workaround right now is to call it a different 96w plate type and use offsets so it does not crash into the reservoir.

@rickwierenga, when I tried this, I got an UnboundLocalError:

---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
Cell In[13], line 3
      1 await lh.pick_up_tips96(tip_rack1)
      2 for plate in dest_plates:
----> 3     await lh.aspirate96(pbs_res.get_item("A1"), 200)
      4     #await lh.aspirate96(pbs_res, 200)
      5     await lh.dispense96(plate, 200)

File ~/pylabrobot/pylabrobot/liquid_handling/liquid_handler.py:1614, in LiquidHandler.aspirate96(self, resource, volume, offset, flow_rate, blow_out_air_volume, **backend_kwargs)
   1606   self._trigger_callback(
   1607     "aspirate96",
   1608     liquid_handler=self,
   (...)
   1611     **backend_kwargs,
   1612   )
   1613 else:
-> 1614   for channel, well in zip(self.head96.values(), wells):
   1615     if does_volume_tracking() and not well.tracker.is_disabled:
   1616       well.tracker.commit()

UnboundLocalError: cannot access local variable 'wells' where it is not associated with a value
1 Like

single container aspirate/dispense by rickwierenga · Pull Request #377 · PyLabRobot/pylabrobot · GitHub should fix, verified in silico

it is debatable whether reservoir should actually be a Plate with a single well or just the Container itself. .get_well("A1") is awkward. should just be aspirate96(reservoir). I will change in a future update

can you lmk if 377 works?

1 Like

Thanks! Happy to provide more thought or input into this.

Tested just now. Running in this branch worked in silico. Will test it in lab later today

1 Like

go ahead :slight_smile:

Tested in lab! Good to merge with main

1 Like

I’m still formulating thoughts on this but the simplest way would be to classify reservoirs as 1-well plates. So instead of reagent_reservoir.get_item("A1") you could pass reagent_reservoir["A1] to indicate going into the reservoir. This works well when using the 8-channel.

A hack that I’ve done before and would be interested in getting to work on PLR is overriding the well assignment when using the 96 head. When using the 96 head, you would provide just the plate resource, not the plate+well. Again, this makes sense for 1-well reservoirs (reagent_reservoir instead of reagent_reservoir["A1]). But, I’ve used 2-well reservoirs and the 96 head to aspirate out of it to fill plates with 2 different media at once. Similarly, I’ve used the 96 head to aspirate from a 24-well plate into a 96-well plate (ex. doing quadruplicate measurements of a 24-well growth). In both of those cases, I would want to pass in plate resource and PLR could tell me if it is too risky if the offset is insufficient (like if the tips will crash given the dimensions of the plate).

1 Like