384-well plate dispensing with CO-RE 96 head

I can’t find anything about this already discussed. Is it currently not possible to aspirate/dispense in a 384-well plate with the CO-RE 96 head using PLR? I’ve tried a very simple test method and got this:

NotImplementedError: It is not possible to plate aspirate from an 24 by 16 plate

Is anybody working on this or done a hard-coded work-around? I figure it could be possible if I create four artificial “96-well” labware definitions where I define the wells as being in the positions of the four 384-well quadrants for the time being until it can be implemented properly…

No, the 384 head is not supported yet. Do you want to work on adding it?

Sorry Rick, I’m using the 96-well head, but trying to do a whole-plate aspirate/dispense from/to a 384-well plate.

This would only aspirate from one “quadrant” (my terminology, hopefully not confusing) of the 384-well plate. In other words, if aspirating from the A1 quadrant, it would be aspirating from A1, A3, …, C1, C3, …, P21, P23, etc (skipping wells.)

I’m happy to contribute to this, but didn’t know if anyone else had already made a workaround for what I think is not an uncommon scenario.

oops I read that wrong.

I’m afraid this is also not supported yet. Perhaps we can add a .quadrant(1,2,3,4) method to ItemizedResource/Plate that returns a list of wells to pass to aspirate_plate?

this would be nice, right now I just use a heavy list comprehension to generate the indices for all 4 quadrants on 384w plates

@ben do you mind sharing more about your workaround if it works with the 96 head? Trying to get something prototyped quickly if possible.

Happy to help get a proper implementation built though.

Here’s my code for targeting specific quadrants with the 96 channel. The first function maps from a 4-well cell (i.e. where a single well in a 96-well plate would be) and a quadrant index in that cell to a well index in a 384 well plate. The second function gives a list of 384wp positions for a given quadrant across all 96 cells.

def cells_96_to_384(well, quadrant):
    return well*2+quadrant%2+(quadrant//2)*16+16*(well//8)

def get_384w_quadrant(quadrant):
    return [cells_96_to_384(idx, quadrant) for idx in range(96)]

This was for PyHamilton but I think it’s the same in PLR

3 Likes

Yes same in PLR

Q1
As someone without a MPH/96-channel atm I am asking more out of curiosity and for the future:

How does the coordinate system of MPH-commands work, i.e. what is the commands reference point?
Is it similar to the resource coordinate system, i.e. does the front-left tip define the movement of the entire MPH?

In other words, can the MPH aspirate/dispense commands simply be given the well_location for "O1" (marked with a yellow “X”)
and it will go with its front-left tip to this well? Or is there a different reference point?

Q2
Associated to this question:
How do we want to define “Quadrants” in PLR? (the concept is incredibly useful but I found different labs define quadrants differently, either on a ‘go-right-first’ or a ‘go-down-first’ approach)

A ‘go-right-first’ approach looks like this:

Impact
If Q1 is indeed referencing the front-left position, and we adapt the ‘go-right-first’ approach from Q2 then aspirating/dispensing from/into a 384-well plate with a 96-MPH should be as simple as:

[await lh.dispense96(lh.deck.get_resource(<384_well_plate_name>)[ref_well], dispense_volume=20)
     for ref_well in ["O1", "O13", "P1", "P13"]]
1 Like

ups… I just looked into STAR.py to answer my question:

definition of dispense_core_96, STAR.py:5064:

""" dispense CoRe 96

    Dispensing of liquid using CoRe 96

    Args:
      dispensing_mode: Type of dispensing mode 0 = Partial volume in jet mode 1 = Blow out
          in jet mode 2 = Partial volume at surface 3 = Blow out at surface 4 = Empty tip at fix
          position. Must be between 0 and 4. Default 0.
      x_position: X-Position [0.1mm] of well A1. Must be between 0 and 30000. Default 0.
      x_direction: X-direction. 0 = positive 1 = negative. Must be between 0 and 1. Default 0.
      y_position: Y-Position [0.1mm] of well A1. Must be between 1080 and 5600. Default 0.
...

→ so the reference well is not the front-left one, "O1", but it is the back-left one, "A1"!
Makes sense, even if it doesn’t comply with PLR’s resource coordinate referencing system - nothing we can do here though → looks like this is intrinsic to firmware command "C0ED".

Well, this means that correcting my suggestion from above, the dispensing command for a full 384-well plate using a 96-MPH should be:

quadrant_ref_coordinates = [(loc.get_absolute_location().x, loc.get_absolute_location().y) for loc in prep_plate_1["A1", "A13", "B1", "B13"]]

[await lh.backend.dispense_core_96(x_position=ref_well[0], y_position=ref_well[1], dispense_volume=20)
     for ref_well in quadrant_ref_coordinates]

…however, there is a bit of a problem here:
image
→ x_position and y_position type hint to integers here, which is because their units are 0.1mm. But the positions given by .get_absolute_location() are given as floats in units 1.0mm.
So, you’d have to multiply them by 10 first?

1 Like

Hey all, I’m going to work on implementing some of the above suggestions today just to get a prototype method working in time for a specific purpose tomorrow, but I am looking forward to continuing this to get a “native” implementation built.

@CamilloMoschner I think I’m following you, but there is one important thing to note regarding your notes: The dimensions of 96 SBS are such that the head (MPH = multi-pipette head?) not only skips 384 wells in the vertical/y dimension (A, C, E… and B, D, F, …) but also in the horizontal/x direction (1, 3, 5, … and 2, 4, 6, …). I think that means where you have noted A13, B13, P13 etc should actually read A23, B23, P23, etc, right? I haven’t read through it close yet so maybe I’m missing something there with what you’re doing.

To further elaborate: If you took your colored diagrams and spread them so they also skipped columns horizontally, this would match my definition of 384 quadrants, and match how the 96 head maps onto a 384 well plate:

Quadrant A1 = A1, A3, …C1, C3, …O21, O23 (aka quadrant 1)
Quadrant A2 = A2, A4, …C2, C4, …O22, O24 (aka quadrant 2)
Quadrant B1 = B1, B3, …D1, D3, …P21, P23 (aka quadrant 3)
Quadrant B2 = B2, B4, …D2, D4, …P22, P24 (aka quadrant 4)

3 Likes

Ok I read this a little closer, and I think this should actually be A1, A2, B1, B2, instead of A1, A13, B1, B13. These are the four reference “A1” cells for the four quadrants.

Now onto actually trying your strategy @CamilloMoschner. I tried this:

quadrant_ref_coordinates = [(loc.get_absolute_location().x, loc.get_absolute_location().y) for loc in lh.deck.get_resource("reaction_plate")["A1", "A2", "B1", "B2"]]
quadrant_ref_coordinates

Which gives me: [(384.1, 146.6), (388.6, 146.6), (384.1, 142.1), (388.6, 142.1)]

So I tried dispensing (using 1000 ul tips, totally dry run - no plates, no liquids. See other thread for 50 uL tip problem):

await lh.aspirate_plate(lh.deck.get_resource('premix_plate'), volume = 20)
[await lh.backend.dispense_core_96(x_position=int(ref_well[0]*10), y_position=int(ref_well[1]*10), dispense_volume=5)
     for ref_well in quadrant_ref_coordinates]

Note I did the *10 and also int() - idk if the int is necessary but it makes no difference in the outcome:

---------------------------------------------------------------------------
STARFirmwareError                         Traceback (most recent call last)
Cell In[34], line 1
----> 1 [await lh.backend.dispense_core_96(x_position=int(ref_well[0]*10), y_position=int(ref_well[1]*10), dispense_volume=5)
      2      for ref_well in quadrant_ref_coordinates]

Cell In[34], line 1, in <listcomp>(.0)
----> 1 [await lh.backend.dispense_core_96(x_position=int(ref_well[0]*10), y_position=int(ref_well[1]*10), dispense_volume=5)
      2      for ref_well in quadrant_ref_coordinates]

File ~/Documents/FutureHouse/Lab/PyLabRobot/pylabrobot/pylabrobot/liquid_handling/backends/hamilton/STAR.py:5211, in STAR.dispense_core_96(self, dispensing_mode, x_position, x_direction, y_position, tube_2nd_section_height_measured_from_zm, tube_2nd_section_ratio, lld_search_height, liquid_surface_at_function_without_lld, pull_out_distance_to_take_transport_air_in_function_without_lld, maximum_immersion_depth, immersion_depth, immersion_depth_direction, liquid_surface_sink_distance_at_the_end_of_dispense, minimum_traverse_height_at_beginning_of_a_command, minimal_end_height, dispense_volume, dispense_speed, cut_off_speed, stop_back_volume, transport_air_volume, blow_out_air_volume, lld_mode, gamma_lld_sensitivity, side_touch_off_distance, swap_speed, settling_time, mixing_volume, mixing_cycles, mixing_position_from_liquid_surface, surface_following_distance_during_mixing, speed_of_mixing, channel_pattern, limit_curve_index, tadm_algorithm, recording_mode)
   5208 channel_pattern_bin_str = reversed(["1" if x else "0" for x in channel_pattern])
   5209 channel_pattern_hex = hex(int("".join(channel_pattern_bin_str), 2)).upper()[2:]
-> 5211 return await self.send_command(
   5212   module="C0",
   5213   command="ED",
   5214   da=dispensing_mode,
   5215   xs=f"{x_position:05}",
   5216   xd=x_direction,
   5217   yh=f"{y_position:04}",
   5218   zm=f"{maximum_immersion_depth:04}",
   5219   zv=f"{tube_2nd_section_height_measured_from_zm:04}",
   5220   zq=f"{tube_2nd_section_ratio:05}",
   5221   lz=f"{lld_search_height:04}",
   5222   zt=f"{liquid_surface_at_function_without_lld:04}",
   5223   pp=f"{pull_out_distance_to_take_transport_air_in_function_without_lld:04}",
   5224   iw=f"{immersion_depth:03}",
   5225   ix=immersion_depth_direction,
   5226   fh=f"{liquid_surface_sink_distance_at_the_end_of_dispense:03}",
   5227   zh=f"{minimum_traverse_height_at_beginning_of_a_command:04}",
   5228   ze=f"{minimal_end_height:04}",
   5229   df=f"{dispense_volume:05}",
   5230   dg=f"{dispense_speed:04}",
   5231   es=f"{cut_off_speed:04}",
   5232   ev=f"{stop_back_volume:03}",
   5233   vt=f"{transport_air_volume:03}",
   5234   bv=f"{blow_out_air_volume:05}",
   5235   cm=lld_mode,
   5236   cs=gamma_lld_sensitivity,
   5237   ej=f"{side_touch_off_distance:02}",
   5238   bs=f"{swap_speed:04}",
   5239   wh=f"{settling_time:02}",
   5240   hv=f"{mixing_volume:05}",
   5241   hc=f"{mixing_cycles:02}",
   5242   hp=f"{mixing_position_from_liquid_surface:03}",
   5243   mj=f"{surface_following_distance_during_mixing:03}",
   5244   hs=f"{speed_of_mixing:04}",
   5245   cw=channel_pattern_hex,
   5246   cr=f"{limit_curve_index:03}",
   5247   cj=tadm_algorithm,
   5248   cx=recording_mode,
   5249 )

File ~/Documents/FutureHouse/Lab/PyLabRobot/pylabrobot/pylabrobot/liquid_handling/backends/hamilton/base.py:193, in HamiltonLiquidHandler.send_command(self, module, command, tip_pattern, write_timeout, read_timeout, wait, fmt, **kwargs)
    171 """ Send a firmware command to the Hamilton machine.
    172 
    173 Args:
   (...)
    188   A dictionary containing the parsed response, or None if no response was read within `timeout`.
    189 """
    191 cmd, id_ = self._assemble_command(module=module, command=command, tip_pattern=tip_pattern,
    192   **kwargs)
--> 193 resp = await self._write_and_read_command(id_=id_, cmd=cmd, write_timeout=write_timeout,
    194                 read_timeout=read_timeout, wait=wait)
    195 if resp is not None and fmt is not None:
    196   return self._parse_response(resp, fmt)

File ~/Documents/FutureHouse/Lab/PyLabRobot/pylabrobot/pylabrobot/liquid_handling/backends/hamilton/base.py:220, in HamiltonLiquidHandler._write_and_read_command(self, id_, cmd, write_timeout, read_timeout, wait)
    218 fut = loop.create_future()
    219 self._start_reading(id_, loop, fut, cmd, read_timeout)
--> 220 result = await fut
    221 return cast(str, result)

File ~/Documents/FutureHouse/Lab/PyLabRobot/pylabrobot/pylabrobot/liquid_handling/backends/hamilton/base.py:293, in HamiltonLiquidHandler._continuously_read(self)
    291 if response_id == id_:
    292   try:
--> 293     self.check_fw_string_error(resp)
    294   except Exception as e: # pylint: disable=broad-exception-caught
    295     loop.call_soon_threadsafe(fut.set_exception, e)

File ~/Documents/FutureHouse/Lab/PyLabRobot/pylabrobot/pylabrobot/liquid_handling/backends/hamilton/STAR.py:1181, in STAR.check_fw_string_error(self, resp)
   1172   if error.message == "Unknown parameter":
   1173     # temp. disabled until we figure out how to handle async in parse response (the
   1174     # background thread does not have an event loop, and I'm not sure if it should.)
   (...)
   1177 
   1178     # pylint: disable=unnecessary-dict-index-lookup
   1179     he[module_name].message += " (call lh.backend.request_name_of_last_faulty_parameter)"
-> 1181 raise he

STARFirmwareError: STARFirmwareError(errors={'CoRe 96 Head': CommandSyntaxError('Parameter out of range')}, raw_response=C0EDid0012er99/00 H001/32)

I am very new to the firmware stuff so I’m not sure how best to parse this yet.

Also FYI, if I print the refs (A1, A2, B1, B2):

[print(int(ref_well[0]*10), int(ref_well[1]*10))
     for ref_well in quadrant_ref_coordinates]

I get:

3841 1466
3886 1466
3841 1421
3886 1421

Yes, you are absolutely correct. I didn’t think about the jumping of the columns in addition to the jumping of the rows. (I’ll make some updated infographics in a bit)

Your error message is interesting:
Rick cleverly built in assertion checks so that a STAR-command is first checked for having correct values for all function arguments by the Python backend.
If a value is not in the range Python should give an error, and Python should tell you what argument is out of range.

However, in your case the error does not appear to arise during any of the assertion checks but it looks to me like the error actually comes directly from your STAR, i.e. Python hands the STAR a firmware command whose “Parameter is out of range” → that is what error 32 from C0EDid0012er99/00 H001/32 means (to my knowledge).
The difficult part is that we don’t know which parameter is out of range…

Your x and y values…

…and your volume (though a bit low 0.2 ul) are in the correct range:

assert 0 <= x_position <= 30000, "x_position must be between 0 and 30000"
...
assert 1080 <= y_position <= 5600, "y_position must be between 1080 and 5600"
...
assert 0 <= dispense_volume <= 11500, "dispense_volume must be between 0 and 11500"

I’d need to look at the STAR method definition in more detail and compare it with its default values.

@Stefan thanks for this. It works to get a list of wells, but forgive me if I don’t fully understand:

This would work well for the 8-span, for example, which takes a list of wells as input for aspirating/dispensing, but I don’t know how it would work for the 96 head, as the PLR aspirate_plate command takes a plate as input, and not a list.

@CamilloMoschner you might have a look at my other thread about using the 50 uL tips, in which I get the same “parameter out of range” error with the same machine: https://forums.pylabrobot.org/t/problem-aspirating-with-50-ul-tips/3154

1 Like

I did one more thing that may help us dig into this, though as I mentioned I am very new the firmware side of things so I’ll need some help to parse this. I ran a method with VENUS that recapitulates what I’m trying to do here:

Aspirate with the 96 head from a 96 well plate
Dispense to a 384 well plate in one quadrant

In this case I used the 50 uL tips, but below is the firmware trace for this method. It executed successfully except for the following:

  1. It threw an LLD error when first trying to aspirate for “not enough liquid”. I don’t know why this happened, as I asked for 20 uL aspirate, and there was 50 uL water in the wells.
  2. When dispensing in the 384, it does go by default to the A1 quadrant, but it dispensed from way too high (I’m using the Cos_384_PCR labware, which is essentially identical to the actual 384 PCR plate I’m using). It was probably 3-5 mm above the top of the plate.

Here’s the trace:

< 06:48:04.825 8AF#8000#00: C0RQid0101
> 06:48:04.840 8AF#8000#00: C0RQid0101rq0000
< 06:48:04.840 8AF#8000#00: C0QBid0102
> 06:48:04.856 8AF#8000#00: C0QBid0102er00/00qb1
< 06:48:04.872 8AF#8000#00: C0RIid0103
> 06:48:04.887 8AF#8000#00: C0RIid0103er00/00si2018-09-27sn1487
< 06:48:04.887 8AF#8000#00: C0QMid0104
> 06:48:04.919 8AF#8000#00: C0QMid0104er00/00ka000003xt30xa30xw08000xl05xr00xm03500xx11400ys090xu3700xv3700yu0060kl360kc0yx0060ke00000000xn00xo00ym6065kr0km360
< 06:48:04.919 8AF#8000#00: C0RMid0105
> 06:48:04.981 8AF#8000#00: C0RMid0105er00/00kb01kp08 C00000 X00000 P10000 P20000 P30000 P40000 P50000 P60000 P70000 P80000 H00000
< 06:48:04.997 8AF#8000#00: C0RFid0106
> 06:48:05.012 8AF#8000#00: C0RFid0106er00/00rf7.5S 14 2017_09_26 (GRU C0 PLE)
< 06:48:05.012 8AF#8000#00: P1RFid0107
> 06:48:05.012 8AF#8000#00: P1RFid0107rf3.9S f 2016-06-02
< 06:48:05.028 8AF#8000#00: P1RJid0108
> 06:48:05.028 8AF#8000#00: P1RJid0108jd2020-06-10js1
< 06:48:05.028 8AF#8000#00: P2RFid0109
> 06:48:05.044 8AF#8000#00: P2RFid0109rf3.9S f 2016-06-02
< 06:48:05.044 8AF#8000#00: P2RJid0110
> 06:48:05.059 8AF#8000#00: P2RJid0110jd2020-06-10js1
< 06:48:05.059 8AF#8000#00: P3RFid0111
> 06:48:05.075 8AF#8000#00: P3RFid0111rf3.9S f 2016-06-02
< 06:48:05.075 8AF#8000#00: P3RJid0112
> 06:48:05.091 8AF#8000#00: P3RJid0112jd2020-06-10js1
< 06:48:05.091 8AF#8000#00: P4RFid0113
> 06:48:05.106 8AF#8000#00: P4RFid0113rf3.9S f 2016-06-02
< 06:48:05.106 8AF#8000#00: P4RJid0114
> 06:48:05.122 8AF#8000#00: P4RJid0114jd2020-06-10js1
< 06:48:05.122 8AF#8000#00: P5RFid0115
> 06:48:05.122 8AF#8000#00: P5RFid0115rf3.9S f 2016-06-02
< 06:48:05.137 8AF#8000#00: P5RJid0116
> 06:48:05.137 8AF#8000#00: P5RJid0116jd2020-06-10js1
< 06:48:05.137 8AF#8000#00: P6RFid0117
> 06:48:05.153 8AF#8000#00: P6RFid0117rf3.9S f 2016-06-02
< 06:48:05.153 8AF#8000#00: P6RJid0118
> 06:48:05.169 8AF#8000#00: P6RJid0118jd2020-06-10js1
< 06:48:05.169 8AF#8000#00: P7RFid0119
> 06:48:05.184 8AF#8000#00: P7RFid0119rf3.9S f 2016-06-02
< 06:48:05.184 8AF#8000#00: P7RJid0120
> 06:48:05.200 8AF#8000#00: P7RJid0120jd2020-06-10js1
< 06:48:05.200 8AF#8000#00: P8RFid0121
> 06:48:05.200 8AF#8000#00: P8RFid0121rf3.9S f 2016-06-02
< 06:48:05.200 8AF#8000#00: P8RJid0122
> 06:48:05.215 8AF#8000#00: P8RJid0122jd2020-06-10js1
< 06:48:05.215 8AF#8000#00: C0QBid0123
> 06:48:05.231 8AF#8000#00: C0QBid0123er00/00qb1
< 06:48:05.231 8AF#8000#00: X0RFid0124
> 06:48:05.247 8AF#8000#00: X0RFid0124rf1.4S 2012-04-25
< 06:48:05.247 8AF#8000#00: X0RJid0125
> 06:48:05.247 8AF#8000#00: X0RJid0125jd2020-06-10js1
< 06:48:05.247 8AF#8000#00: H0RFid0126
> 06:48:05.262 8AF#8000#00: H0RFid0126rf5.0S 2010-11-03 (H0 XE167)
< 06:48:05.262 8AF#8000#00: H0RJid0127
> 06:48:05.278 8AF#8000#00: H0RJid0127jd2020-06-10js1
< 06:48:05.278 8AF#8000#00: C0RMid0128
> 06:48:05.340 8AF#8000#00: C0RMid0128er00/00kb01kp08 C00000 X00000 P10000 P20000 P30000 P40000 P50000 P60000 P70000 P80000 H00000
< 06:48:05.356 8AF#8000#00: C0QMid0129
> 06:48:05.387 8AF#8000#00: C0QMid0129er00/00ka000003xt30xa30xw08000xl05xr00xm03500xx11400ys090xu3700xv3700yu0060kl360kc0yx0060ke00000000xn00xo00ym6065kr0km360
< 06:48:05.387 8AF#8000#00: H0QGid0130
> 06:48:05.403 8AF#8000#00: H0QGid0130qg2
< 06:48:05.403 8AF#8000#00: C0SRid0131
> 06:48:05.419 8AF#8000#00: C0SRid0131er00/00sr055
< 06:48:05.434 8AF#8000#00: C0RUid0132
> 06:48:05.481 8AF#8000#00: C0RUid0132er00/00ru00950 08000 30000 30000
< 06:48:12.886 8AF#8000#00: C0RVid0133vo00
> 06:48:12.948 8AF#8000#00: C0RVid0133er00/00vd2020-06-09 14:10vs1
< 06:48:12.948 8AF#8000#00: C0RVid0134vo01
> 06:48:13.011 8AF#8000#00: C0RVid0134er00/00vd2020-06-09 14:10vs1
< 06:48:13.011 8AF#8000#00: C0RSid0135
> 06:48:13.027 8AF#8000#00: C0RSid0135er00/00ts2020-06-09 14:21
< 06:48:13.027 8AF#8000#00: C0RIid0136
> 06:48:13.042 8AF#8000#00: C0RIid0136er00/00si2018-09-27sn1487
< 06:48:13.042 8AF#8000#00: C0RMid0137
> 06:48:13.105 8AF#8000#00: C0RMid0137er00/00kb01kp08 C00000 X00000 P10000 P20000 P30000 P40000 P50000 P60000 P70000 P80000 H00000
< 06:48:13.120 8AF#8000#00: P1RVid0138
> 06:48:13.136 8AF#8000#00: P1RVid0138na0000048580nb0000003118nc0000056682nd0000209828
< 06:48:13.136 8AF#8000#00: P2RVid0139
> 06:48:13.152 8AF#8000#00: P2RVid0139na0000028369nb0000003112nc0000037075nd0000084285
< 06:48:13.152 8AF#8000#00: P3RVid0140
> 06:48:13.167 8AF#8000#00: P3RVid0140na0000023841nb0000003105nc0000033656nd0000077230
< 06:48:13.167 8AF#8000#00: P4RVid0141
> 06:48:13.183 8AF#8000#00: P4RVid0141na0000027664nb0000003095nc0000036824nd0000087131
< 06:48:13.183 8AF#8000#00: P5RVid0142
> 06:48:13.199 8AF#8000#00: P5RVid0142na0000027451nb0000003095nc0000037019nd0000088311
< 06:48:13.199 8AF#8000#00: P6RVid0143
> 06:48:13.214 8AF#8000#00: P6RVid0143na0000014435nb0000003095nc0000024876nd0000059883
< 06:48:13.214 8AF#8000#00: P7RVid0144
> 06:48:13.230 8AF#8000#00: P7RVid0144na0000106107nb0000003101nc0000032191nd0000078895
< 06:48:13.230 8AF#8000#00: P8RVid0145
> 06:48:13.245 8AF#8000#00: P8RVid0145na0000117757nb0000003103nc0000041832nd0000180285
< 06:48:13.245 8AF#8000#00: P1VWid0146
> 06:48:13.261 8AF#8000#00: P1VWid0146er30
< 06:48:13.261 8AF#8000#00: P2VWid0147
> 06:48:13.277 8AF#8000#00: P2VWid0147er30
< 06:48:13.277 8AF#8000#00: P3VWid0148
> 06:48:13.292 8AF#8000#00: P3VWid0148er30
< 06:48:13.292 8AF#8000#00: P4VWid0149
> 06:48:13.308 8AF#8000#00: P4VWid0149er30
< 06:48:13.308 8AF#8000#00: P5VWid0150
> 06:48:13.308 8AF#8000#00: P5VWid0150er30
< 06:48:13.323 8AF#8000#00: P6VWid0151
> 06:48:13.323 8AF#8000#00: P6VWid0151er30
< 06:48:13.339 8AF#8000#00: P7VWid0152
> 06:48:13.339 8AF#8000#00: P7VWid0152er30
< 06:48:13.339 8AF#8000#00: P8VWid0153
> 06:48:13.355 8AF#8000#00: P8VWid0153er30
< 06:48:13.386 8AF#8000#00: C0TTid0154tt44tf1tl0570tv01750tg3tu0
> 06:48:13.417 8AF#8000#00: C0TTid0154er00/00
< 06:48:13.433 8AF#8000#00: C0TTid0155tt33tf0tl0319tv00700tg6tu0
> 06:48:13.464 8AF#8000#00: C0TTid0155er00/00
< 06:48:13.479 8AF#8000#00: C0TTid0156tt22tf0tl0424tv00650tg2tu0
> 06:48:13.495 8AF#8000#00: C0TTid0156er00/00
< 06:48:13.511 8AF#8000#00: C0TTid0157tt11tf0tl0490tv00150tg2tu1
> 06:48:13.542 8AF#8000#00: C0TTid0157er00/00
< 06:48:13.558 8AF#8000#00: C0TTid0158tt03tf1tl0219tv00100tg1tu0
> 06:48:13.573 8AF#8000#00: C0TTid0158er00/00
< 06:48:13.589 8AF#8000#00: C0TTid0159tt45tf1tl0870tv03450tg3tu0
> 06:48:13.620 8AF#8000#00: C0TTid0159er00/00
< 06:48:13.636 8AF#8000#00: C0TTid0160tt34tf0tl0235tv00010tg2tu0
> 06:48:13.667 8AF#8000#00: C0TTid0160er00/00
< 06:48:13.683 8AF#8000#00: C0TTid0161tt23tf1tl0424tv00600tg2tu0
> 06:48:13.698 8AF#8000#00: C0TTid0161er00/00
< 06:48:13.714 8AF#8000#00: C0TTid0162tt12tf0tl0490tv03500tg2tu1
> 06:48:13.745 8AF#8000#00: C0TTid0162er00/00
< 06:48:13.761 8AF#8000#00: C0TTid0163tt04tf0tl0871tv12500tg3tu0
> 06:48:13.777 8AF#8000#00: C0TTid0163er00/00
< 06:48:13.792 8AF#8000#00: C0TTid0164tt46tf0tl0290tv17200tg3tu1
> 06:48:13.823 8AF#8000#00: C0TTid0164er00/00
< 06:48:13.839 8AF#8000#00: C0TTid0165tt35tf0tl0570tv03500tg3tu0
> 06:48:13.855 8AF#8000#00: C0TTid0165er00/00
< 06:48:13.870 8AF#8000#00: C0TTid0166tt24tf0tl0820tv01800tg3tu1
> 06:48:13.902 8AF#8000#00: C0TTid0166er00/00
< 06:48:13.917 8AF#8000#00: C0TTid0167tt13tf0tl0820tv12500tg3tu1
> 06:48:13.948 8AF#8000#00: C0TTid0167er00/00
< 06:48:13.964 8AF#8000#00: C0TTid0168tt05tf1tl0871tv10650tg3tu0
> 06:48:13.980 8AF#8000#00: C0TTid0168er00/00
< 06:48:13.995 8AF#8000#00: C0TTid0169tt47tf0tl0465tv00010tg2tu0
> 06:48:14.027 8AF#8000#00: C0TTid0169er00/00
< 06:48:14.042 8AF#8000#00: C0TTid0170tt36tf0tl0870tv03850tg3tu0
> 06:48:14.073 8AF#8000#00: C0TTid0170er00/00
< 06:48:14.089 8AF#8000#00: C0TTid0171tt25tf0tl1080tv54200tg5tu0
> 06:48:14.105 8AF#8000#00: C0TTid0171er00/00
< 06:48:14.120 8AF#8000#00: C0TTid0172tt14tf0tl0220tv00010tg0tu0
> 06:48:14.151 8AF#8000#00: C0TTid0172er00/00
< 06:48:14.167 8AF#8000#00: C0TTid0173tt06tf0tl0519tv00700tg2tu1
> 06:48:14.198 8AF#8000#00: C0TTid0173er00/00
< 06:48:14.214 8AF#8000#00: C0TTid0174tt48tf0tl0829tv08000tg3tu1
> 06:48:14.230 8AF#8000#00: C0TTid0174er00/00
< 06:48:14.245 8AF#8000#00: C0TTid0175tt37tf0tl0319tv00700tg6tu0
> 06:48:14.276 8AF#8000#00: C0TTid0175er00/00
< 06:48:14.292 8AF#8000#00: C0TTid0176tt26tf0tl1080tv00010tg5tu0
> 06:48:14.323 8AF#8000#00: C0TTid0176er00/00
< 06:48:14.323 8AF#8000#00: C0TTid0177tt15tf0tl0256tv00350tg4tu0
> 06:48:14.355 8AF#8000#00: C0TTid0177er00/00
< 06:48:14.370 8AF#8000#00: C0TTid0178tt07tf0tl0519tv03500tg2tu1
> 06:48:14.401 8AF#8000#00: C0TTid0178er00/00
< 06:48:14.417 8AF#8000#00: C0TTid0179tt49tf0tl0820tv03020tg3tu1
> 06:48:14.433 8AF#8000#00: C0TTid0179er00/00
< 06:48:14.464 8AF#8000#00: C0TTid0180tt38tf0tl0490tv00650tg2tu1
> 06:48:14.480 8AF#8000#00: C0TTid0180er00/00
< 06:48:14.495 8AF#8000#00: C0TTid0181tt27tf0tl0190tv00010tg0tu0
> 06:48:14.526 8AF#8000#00: C0TTid0181er00/00
< 06:48:14.542 8AF#8000#00: C0TTid0182tt16tf0tl0251tv00350tg0tu0
> 06:48:14.573 8AF#8000#00: C0TTid0182er00/00
< 06:48:14.589 8AF#8000#00: C0TTid0183tt08tf0tl0871tv12500tg3tu1
> 06:48:14.605 8AF#8000#00: C0TTid0183er00/00
< 06:48:14.620 8AF#8000#00: C0TTid0184tt09tf0tl0731tv12500tg3tu0
> 06:48:14.651 8AF#8000#00: C0TTid0184er00/00
< 06:48:14.667 8AF#8000#00: C0TTid0185tt39tf0tl0820tv01200tg3tu1
> 06:48:14.683 8AF#8000#00: C0TTid0185er00/00
< 06:48:14.698 8AF#8000#00: C0TTid0186tt28tf0tl0626tv04000tg6tu0
> 06:48:14.729 8AF#8000#00: C0TTid0186er00/00
< 06:48:14.745 8AF#8000#00: C0TTid0187tt17tf0tl0251tv00350tg0tu0
> 06:48:14.776 8AF#8000#00: C0TTid0187er00/00
< 06:48:14.792 8AF#8000#00: C0TTid0188tt29tf1tl1080tv43670tg5tu0
> 06:48:14.808 8AF#8000#00: C0TTid0188er00/00
< 06:48:14.823 8AF#8000#00: C0TTid0189tt18tf1tl0210tv12500tg0tu1
> 06:48:14.854 8AF#8000#00: C0TTid0189er00/00
< 06:48:14.870 8AF#8000#00: C0TTid0190tt19tf0tl0534tv00670tg2tu0
> 06:48:14.901 8AF#8000#00: C0TTid0190er00/00
< 06:48:14.901 8AF#8000#00: C0TTid0191tt50tf0tl0820tv08000tg3tu1
> 06:48:14.933 8AF#8000#00: C0TTid0191er00/00
< 06:48:14.948 8AF#8000#00: C0TTid0192tt51tf0tl0111tv00010tg0tu0
> 06:48:14.980 8AF#8000#00: C0TTid0192er00/00
< 06:48:14.995 8AF#8000#00: C0TTid0193tt40tf0tl0820tv02000tg3tu1
> 06:48:15.027 8AF#8000#00: C0TTid0193er00/00
< 06:48:15.042 8AF#8000#00: C0TTid0194tt52tf0tl0430tv00010tg0tu0
> 06:48:15.058 8AF#8000#00: C0TTid0194er00/00
< 06:48:15.073 8AF#8000#00: C0TTid0195tt41tf0tl0820tv03460tg3tu1
> 06:48:15.105 8AF#8000#00: C0TTid0195er00/00
< 06:48:15.120 8AF#8000#00: C0TTid0196tt30tf0tl0519tv04000tg2tu0
> 06:48:15.152 8AF#8000#00: C0TTid0196er00/00
< 06:48:15.167 8AF#8000#00: C0TTid0197tt00tf0tl0519tv04000tg2tu0
> 06:48:15.183 8AF#8000#00: C0TTid0197er00/00
< 06:48:15.214 8AF#8000#00: C0TTid0198tt53tf0tl0485tv35000tg0tu0
> 06:48:15.230 8AF#8000#00: C0TTid0198er00/00
< 06:48:15.245 8AF#8000#00: C0TTid0199tt42tf0tl0490tv03550tg2tu1
> 06:48:15.277 8AF#8000#00: C0TTid0199er00/00
< 06:48:15.292 8AF#8000#00: C0TTid0200tt31tf0tl0422tv00650tg2tu0
> 06:48:15.323 8AF#8000#00: C0TTid0200er00/00
< 06:48:15.339 8AF#8000#00: C0TTid0201tt20tf0tl0343tv00650tg4tu0
> 06:48:15.355 8AF#8000#00: C0TTid0201er00/00
< 06:48:15.370 8AF#8000#00: C0TTid0202tt01tf1tl0519tv03600tg2tu0
> 06:48:15.402 8AF#8000#00: C0TTid0202er00/00
< 06:48:15.417 8AF#8000#00: C0TTid0203tt43tf0tl0820tv11350tg3tu1
> 06:48:15.448 8AF#8000#00: C0TTid0203er00/00
< 06:48:15.464 8AF#8000#00: C0TTid0204tt32tf0tl0219tv00150tg1tu0
> 06:48:15.480 8AF#8000#00: C0TTid0204er00/00
< 06:48:15.495 8AF#8000#00: C0TTid0205tt21tf0tl0442tv00610tg6tu1
> 06:48:15.527 8AF#8000#00: C0TTid0205er00/00
< 06:48:15.542 8AF#8000#00: C0TTid0206tt10tf0tl0519tv04000tg2tu0
> 06:48:15.573 8AF#8000#00: C0TTid0206er00/00
< 06:48:15.589 8AF#8000#00: C0TTid0207tt02tf0tl0219tv00150tg1tu0
> 06:48:15.605 8AF#8000#00: C0TTid0207er00/00
< 06:48:15.605 8AF#8000#00: C0DRid0208
> 06:48:15.620 8AF#8000#00: C0DRid0208er00/00
< 06:48:15.620 8AF#8000#00: C0CUid0209cu0
> 06:48:15.652 8AF#8000#00: C0CUid0209er00/00
< 06:48:15.652 8AF#8000#00: PXAFid0210af0
> 06:48:15.777 8AF#8000#00: PXAFid0210er00
< 06:48:15.792 8AF#8000#00: C0QSid0211
> 06:48:15.839 8AF#8000#00: C0QSid0211er00/00qs0 0 0 0 0 0 0 0
< 06:48:15.839 8AF#8000#00: C0RJid0212
> 06:48:15.886 8AF#8000#00: C0RJid0212er00/00tq1 1 1 1 1 1 1 1
< 06:48:15.886 8AF#8000#00: C0AWid0213
> 06:48:16.230 8AF#8000#00: C0AWid0213er00/00
< 06:48:16.245 8AF#8000#00: C0CBid0214bt7Fmq00
> 06:48:16.261 8AF#8000#00: C0CBid0214er00/00
< 06:48:16.323 8AF#8000#00: C0QWid0215
> 06:48:16.371 8AF#8000#00: C0QWid0215er00/00qw1
< 06:48:16.371 8AF#8000#00: C0RTid0216
> 06:48:16.417 8AF#8000#00: C0RTid0216er00/00rt0 0 0 0 0 0 0 0
< 06:48:16.417 8AF#8000#00: C0QHid0217
> 06:48:16.433 8AF#8000#00: C0QHid0217er00/00qh0
< 06:48:16.464 8AF#8000#00: C0EPid0218xs01179xd0yh2418tt23wu0za2164zh2450ze2450
> 06:48:22.229 8AF#8000#00: C0EPid0218er00/00
< 06:48:22.291 8AF#8000#00: C0EAid0219aa0xs02530xd0yh1460zh2450ze2450lz2096zt1866zm1866iw000ix0fh009af00224ag1000vt050bv00300wv00000cm0cs1bs0020wh10hv00000hc00hp000hs1000zv0115zq00000mj000cj0cx0cr000cwFFFFFFFFFFFFFFFFFFFFFFFFpp0050
> 06:48:28.462 8AF#8000#00: C0EAid0219er99/00 H006/71
< 06:48:43.727 8AF#8000#00: C0EAid0220aa0xs02530xd0yh1460zh2450ze2450lz2096zt1866zm1866iw000ix0fh000af00224ag1000vt050bv00000wv00000cm0cs1bs0020wh10hv00000hc00hp000hs1000zv0115zq00000mj000cj0cx0cr000cwFFFFFFFFFFFFFFFFFFFFFFFFpp0050
> 06:48:53.260 8AF#8000#00: C0EAid0220er00/00
< 06:48:53.306 8AF#8000#00: C0EDid0221da1xs03857xd0yh1482zh2450ze2450lz1991zt1971zm1871iw000ix0fh020df00056dg1800vt050bv00300cm0cs1bs0010wh00hv00000hc00hp000hs0010es1000ev000zv0095ej00zq00000mj000cj0cx0cr000cwFFFFFFFFFFFFFFFFFFFFFFFFpp0050
> 06:48:57.447 8AF#8000#00: C0EDid0221er00/00
< 06:48:57.462 8AF#8000#00: C0ERid0222xs01179xd0yh2418za2164zh2450ze2450
> 06:49:04.118 8AF#8000#00: C0ERid0222er00/00
< 06:49:04.196 8AF#8000#00: C0AWid0223
> 06:49:04.540 8AF#8000#00: C0AWid0223er00/00
< 06:49:04.555 8AF#8000#00: C0RTid0224
> 06:49:04.602 8AF#8000#00: C0RTid0224er00/00rt0 0 0 0 0 0 0 0
< 06:49:04.602 8AF#8000#00: C0QHid0225
> 06:49:04.618 8AF#8000#00: C0QHid0225er00/00qh0
< 06:49:04.633 8AF#8000#00: C0ZAid0226
> 06:49:04.774 8AF#8000#00: C0ZAid0226er00/00
< 06:49:04.774 8AF#8000#00: C0EVid0227
> 06:49:13.101 8AF#8000#00: C0EVid0227er00/00
< 06:49:13.101 8AF#8000#00: C0JEid0228
> 06:49:13.257 8AF#8000#00: C0JEid0228er00/00
< 06:49:13.257 8AF#8000#00: C0DRid0229
> 06:49:13.272 8AF#8000#00: C0DRid0229er00/00
< 06:49:13.272 8AF#8000#00: C0AZid0230
> 06:49:14.054 8AF#8000#00: C0AZid0230er00/00
< 06:49:14.054 8AF#8000#00: P1RVid0231
> 06:49:14.070 8AF#8000#00: P1RVid0231na0000048580nb0000003118nc0000056682nd0000209828
< 06:49:14.070 8AF#8000#00: P2RVid0232
> 06:49:14.086 8AF#8000#00: P2RVid0232na0000028369nb0000003112nc0000037075nd0000084285
< 06:49:14.086 8AF#8000#00: P3RVid0233
> 06:49:14.101 8AF#8000#00: P3RVid0233na0000023841nb0000003105nc0000033656nd0000077230
< 06:49:14.101 8AF#8000#00: P4RVid0234
> 06:49:14.117 8AF#8000#00: P4RVid0234na0000027664nb0000003095nc0000036824nd0000087131
< 06:49:14.117 8AF#8000#00: P5RVid0235
> 06:49:14.133 8AF#8000#00: P5RVid0235na0000027451nb0000003095nc0000037019nd0000088311
< 06:49:14.133 8AF#8000#00: P6RVid0236
> 06:49:14.148 8AF#8000#00: P6RVid0236na0000014435nb0000003095nc0000024876nd0000059883
< 06:49:14.148 8AF#8000#00: P7RVid0237
> 06:49:14.164 8AF#8000#00: P7RVid0237na0000106107nb0000003101nc0000032191nd0000078895
< 06:49:14.164 8AF#8000#00: P8RVid0238
> 06:49:14.180 8AF#8000#00: P8RVid0238na0000117757nb0000003103nc0000041832nd0000180285
< 06:49:14.180 8AF#8000#00: H0RVid0239
> 06:49:14.180 8AF#8000#00: H0RVid0239na0000004733nb0000004734nc0000008613nd0000009754

For plate based 96 channel references like what PLR uses you can use XY offsets. There might be other ways to do it but that is the most obvious to me.

(I saw some of what I wrote below was already written in other posts)

Center of well A1, or rather the channel that would usually go to A1 (left back as viewed from the cover).

I think for the quadrants you showed, you’d have to skip alternating columns. The wells are too close otherwise for the 96 head to reach simultaneously. Stefan’s code gives intuition for this.

This is very Hamilton specific. I think PLR should take a Plate/List[Well], and then STAR can be responsible for translating this.

This happens here

correct

This is very helpful!

I don’t think this is very clean tbh. It’s not clear what’s happening when you aspirate_plate with offset=something or even offset=(well.location.x, …). This might work quickly though, so I’d recommend people play with it.


In PLR, I think we should do the following:

  • Rename lh.aspirate_plate back to lh.aspriate96: in the 384 well plate, we are using the 96 channels but not doing a plate at a time.
  • Have lh.aspirate96 take either a Plate or List[Well] as an argument. If Plate, take all of its wells. (after that make sure len(wells)==96 to cover both cases.)
  • We can do some checking to ensure these plates are in the right order: same plate, good offset, good ordering, etc.
  • The wells-case of lh.aspirate96 can take eg plate.quadrant(1) = [Well, Well, ...] so: lh.aspirate96(plate.quadrant(1)). This would only work for 384 well plates on the 96 head, although the behavior will work for 96-well plates as well to be future proof.
  • Then, STAR will just take well[0] as the reference point. Other backends may treat it differently.

I can push this probably tonight or tomorrow, and use @jonlaurent’s firmware log as a test case, in addition to a local test on one of our plates.

hehe yeah… I have been using the quadrants I showed for 8-channel processing of 384-well plates and haven’t thought about the column-jumping of the 96-MPH.

Here are the correct quadrants for the 384-well plate using a 96-MPH:

I completely agree with Rick. Offsets are “quick & dirty” solutions and very good if you have to perform an experiment or have a showcase tomorrow but otherwise they are too hacky and lead to confusion in the long-term.
We should figure out a more permanent solution now that the opportunity has arisen.


A general question regarding the 96-MPH:
How can we use this problem to enable picking up and using non-96 position tip pickups and aspiration dispensations?

E.g. how can the 96-MPH pick up only 4 columns of tips? or 4-columns of tips using only 6 of the 8 possible rows?

I don’t think this is physically supported on the machine. What you could do is just the 8 channel head to remove the tips from the tip rack that you don’t want picked up.