Well compute_height_from_volume method integration (LLF part 2)
Hi everyone,
This PR is part 2 in a series to expose liquid level following (LLF) in PLR. See #136 for part 1.
In this PR I
- added a new class argument to
Wellinresources/well.pycalledcompute_height_from_volume, - added a new class attribute to
Wellcalled_compute_height_from_volumewhich is assigned to the value fromcompute_height_from_volume, - added a new class method to
Wellcalledcompute_volume_from_heightwhich executes the function stored in_compute_height_from_volumeif it is notNone.
This implementation is a direct counter to _compute_volume_from_height which already exists.
Example definition
I showcase how to define this method in resources/thermo_fisher/plates.py:
# # # # # # # # # # ThermoScientific_96_1200ul_Rd # # # # # # # # # #
def _compute_volume_from_height_ThermoScientific_96_1200ul_Rd(h: float):
if h > 20.5:
raise ValueError(f"Height {h} is too large for ThermoScientific_96_1200ul_Rd")
return calculate_liquid_volume_container_2segments_square_ubottom(
x=8.15,
h_cuboid=16.45,
liquid_height=h)
def _compute_height_from_volume_ThermoScientific_96_1200ul_Rd(liquid_volume: float):
if liquid_volume > 1260: # 5% tolerance
raise ValueError(f"Volume {liquid_volume} is too large for ThermoScientific_96_1200ul_Rd")
return round(calculate_liquid_height_in_container_2segments_square_ubottom(
x=8.15,
h_cuboid=16.45,
liquid_volume=liquid_volume),3)
def ThermoScientific_96_1200ul_Rd(name: str, with_lid: bool = False) -> Plate:
""" Fisher Scientific/Thermo Fisher cat. no.: 10243223/AB1127.
- Material: Polypropylene (AB-1068, polystyrene)
- Suitable for Autoclaving (15 minutes at 121°C) or Gamma Irradiation
- Resistant to DMSO (100%); Ethanol (100%); Isopropanol (100%)
- Round well shape designed for optimal sample recovery or square shape to
maximize sample volume within ANSI footprint design
- Each well has an independent sealing rim to prevent cross-contamination
- U-bottomed wells ideally suited for sample resuspension
- Sealing options: Adhesive Seals, Heat Seals, Storage Plate Caps and Cap
Strips, and Storage Plate Sealing Mats
- Cleanroom manufactured
- ANSI-format for compatibility with automated systems
"""
return Plate(
name=name,
size_x=127.0,
size_y=86.0,
size_z=24.0,
with_lid=with_lid,
model="ThermoScientific_96_1200ul_Rd",
lid_height=5,
items=create_equally_spaced(Well,
num_items_x=12,
num_items_y=8,
dx=9.6,
dy=7.3,
dz=0.2,
item_dx=9,
item_dy=9,
size_x=8.3,
size_y=8.3,
size_z=20.5,
bottom_type=WellBottomType.U,
cross_section_type=CrossSectionType.RECTANGLE,
compute_volume_from_height=_compute_volume_from_height_ThermoScientific_96_1200ul_Rd,
compute_height_from_volume=_compute_height_from_volume_ThermoScientific_96_1200ul_Rd
),
)
Example use
This now enables on-the-fly calculations of the height of a liquid inside a container/well.
# Define labware & position
plt_carrier_1[0] = ThermoScientific_96_1200ul_Rd = ThermoScientific_96_1200ul_Rd(name="ThermoScientific_96_1200ul_Rd")
# Test calculations
for x in np.arange(0,26.8,0.4):
vol_cal = ThermoScientific_96_1200ul_Rd['A1'][0].compute_volume_from_height(x)
height_calc = ThermoScientific_96_1200ul_Rd['A1'][0].compute_height_from_volume(vol_cal)
print(f"start_height_for_test={round(x,2)}")
print(f"vol_cal={round(vol_cal,2)}")
print(f"height_calc={round(height_calc,2)}\n")
Together with the volume tracking this enables a good estimate of at what height (relative to the bottom of the well) the meniscus of each liquid is, and can be used to calculate where the meniscus will be after a given aspiration/dispensation.
-> With this information everyone can then execute their own LLF using the aspirate argument surface_following_distance (thank you, @rickwierenga, for pointing me to this).
(At the moment, PLR does not offer complex commands directly from the library but everyone is expected to generate these themselves. This is i.a. to avoid unpredicted machine behaviour.)
Next steps
At the moment, compute_height_from_volume() will raise a NotImplementedError for any plate besides the showcase ThermoScientific_96_1200ul_Rd.
To enable this feature for other plates we have to define and add the corresponding function to each plate definition, probably in quite a couple of separate PRs :)