Skip to content

Integrate material_z_thickness (Container) & skirt_base_to_well_base (Plate) (fix PLR Plate location 2) #183

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jul 15, 2024
Merged
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- The `offset` parameter is no longer optional in `Pickup`, `Drop`, `Aspirate`, and `Dispense` dataclasses. With LiquidHandler it defaults to `Coordinate(0, 0, 0)`.
- When providing offsets to LH, individual items in the `offsets` list are no longer optional. They must be provided as `Coordinate` objects. The `offsets` list itself is still optional and defaults to `[Coordinate(0, 0, 0)]*len(use_channels)`.
- To aspirate from a single resource with multiple channels, you must now provide that single resource in a list when calling `LiquidHandler.aspirate` and `LiquidHandler.dispense`.
- Fixed well z position: now it actually refers to the distance between the bottom of the well (including the material) and the bottom of the plate. Before, it sometimes mistakenly referred to what is now `material_z_thickness` (https://github.com/PyLabRobot/pylabrobot/pull/183).
- A resource's origin (lfb) is not changed on rotation, it is always fixed locally (https://github.com/PyLabRobot/pylabrobot/pull/195). Before, we updated the location after 90, 180, and 270 degree rotations.
- `Resource.rotate` and `Resource.rotated` now support all planes and all angles (before it was limited to 90 degree rotations around the z axis) (https://github.com/PyLabRobot/pylabrobot/pull/195)
- Resource children will not be relocated when the parent resource is rotated (https://github.com/PyLabRobot/pylabrobot/pull/195)
Expand All @@ -20,6 +21,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
### Added

- Cor_96_wellplate_360ul_Fb plate (catalog number [3603](https://ecatalog.corning.com/life-sciences/b2b/NL/en/Microplates/Assay-Microplates/96-Well-Microplates/Corning®-96-well-Black-Clear-and-White-Clear-Bottom-Polystyrene-Microplates/p/3603))
- Add attribute `material_z_thickness: Optional[float]` to `Container`s (https://github.com/PyLabRobot/pylabrobot/pull/183).
- `Coordinate.vector()` to return a 3-item list of floats.
- `Rotation` class to represent a rotation in 3D space (https://github.com/PyLabRobot/pylabrobot/pull/195)
- `Resource.get_absolute_rotation()` to get the absolute rotation of a resource (https://github.com/PyLabRobot/pylabrobot/pull/195)
Expand All @@ -37,3 +39,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- Don't apply an offset to rectangle drawing in the Visualizer.
- Fix Opentrons resource loading (well locations are now lfb instead of ccc)
- Fix Opentrons backend resource definitions: Opentrons takes well locations as ccc instead of lfb
- Fix ThermoScientific_96_DWP_1200ul_Rd to ThermoScientific_96_wellplate_1200ul_Rd (https://github.com/PyLabRobot/pylabrobot/pull/183).
15 changes: 14 additions & 1 deletion pylabrobot/resources/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def __init__(
size_x: float,
size_y: float,
size_z: float,
material_z_thickness: Optional[float] = None,
max_volume: Optional[float] = None,
category: Optional[str] = None,
model: Optional[str] = None,
Expand All @@ -22,20 +23,32 @@ def __init__(
""" Create a new container.

Args:
material_z_thickness: Container cavity base to the (outer) base of the container object. If
`None`, certain operations may not be supported.
max_volume: Maximum volume of the container. If `None`, will be inferred from resource size.
"""

super().__init__(name=name, size_x=size_x, size_y=size_y, size_z=size_z, category=category,
model=model)
self._material_z_thickness = material_z_thickness
self.max_volume = max_volume or (size_x * size_y * size_z)
self.tracker = VolumeTracker(max_volume=self.max_volume)
self._compute_volume_from_height = compute_volume_from_height
self._compute_height_from_volume = compute_height_from_volume

@property
def material_z_thickness(self) -> float:
if self._material_z_thickness is None:
raise NotImplementedError(
f"The current operation is not supported for resource named '{self.name}' of type "
f"'{self.__class__.__name__}' because material_z_thickness is not defined.")
return self._material_z_thickness

def serialize(self) -> dict:
return {
**super().serialize(),
"max_volume": self.max_volume
"max_volume": self.max_volume,
"material_z_thickness": self._material_z_thickness,
}

def serialize_state(self) -> Dict[str, Any]:
Expand Down
1 change: 1 addition & 0 deletions pylabrobot/resources/corning_costar/plates.py
Original file line number Diff line number Diff line change
Expand Up @@ -1256,6 +1256,7 @@ def Cor_96_wellplate_360ul_Fb(name: str, with_lid: bool = False) -> Plate:
size_x=6.86, # top
size_y=6.86, # top
size_z=10.67,
material_z_thickness=0.5,
bottom_type=WellBottomType.FLAT,
cross_section_type=CrossSectionType.CIRCLE,
max_volume=360,
Expand Down
1 change: 1 addition & 0 deletions pylabrobot/resources/opentrons/load.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ def volume_from_name(name: str) -> float:
size_x=well_size_x,
size_y=well_size_y,
size_z=well_size_z,
material_z_thickness=None, # not known for OT labware
max_volume=well_data["totalLiquidVolume"],
cross_section_type=cross_section_type
)
Expand Down
2 changes: 2 additions & 0 deletions pylabrobot/resources/petri_dish.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def __init__(
name: str,
diameter: float,
height: float,
material_z_thickness: Optional[float] = None,
category: str = "petri_dish",
model: Optional[str] = None,
max_volume: Optional[float] = None
Expand All @@ -22,6 +23,7 @@ def __init__(
size_x=diameter,
size_y=diameter,
size_z=height,
material_z_thickness=material_z_thickness,
category=category,
model=model,
max_volume=max_volume
Expand Down
3 changes: 2 additions & 1 deletion pylabrobot/resources/petri_dish_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ class TestPetriDish(unittest.TestCase):
""" Test the PetriDish and PetriDishHolder classes """

def test_petri_dish_serialization(self):
petri_dish = PetriDish("petri_dish", 90.0, 15.0)
petri_dish = PetriDish("petri_dish", diameter=90.0, height=15.0)
serialized = petri_dish.serialize()
self.assertEqual(serialized, {
"name": "petri_dish",
"category": "petri_dish",
"diameter": 90.0,
"height": 15.0,
"material_z_thickness": None,
"parent_name": None,
"type": "PetriDish",
"children": [],
Expand Down
2 changes: 1 addition & 1 deletion pylabrobot/resources/plate.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def __init__(
category: str = "plate",
lid: Optional[Lid] = None,
model: Optional[str] = None,
plate_type: Literal["skirted", "semi-skirted", "non-skirted"] = "skirted"
plate_type: Literal["skirted", "semi-skirted", "non-skirted"] = "skirted",
):
""" Initialize a Plate resource.

Expand Down
45 changes: 28 additions & 17 deletions pylabrobot/resources/thermo_fisher/plates.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,29 @@
from pylabrobot.resources.height_functions import calculate_liquid_height_in_container_2segments_square_ubottom


# # # # # # # # # # ThermoScientific_96_DWP_1200ul_Rd # # # # # # # # # #
# # # # # # # # # # ThermoScientific_96_wellplate_1200ul_Rd # # # # # # # # # #

def _compute_volume_from_height_ThermoScientific_96_DWP_1200ul_Rd(h: float):
def _compute_volume_from_height_ThermoScientific_96_wellplate_1200ul_Rd(h: float):
if h > 20.5:
raise ValueError(f"Height {h} is too large for ThermoScientific_96_DWP_1200ul_Rd")
raise ValueError(f"Height {h} is too large for" + \
"ThermoScientific_96_wellplate_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_DWP_1200ul_Rd(liquid_volume: float):
def _compute_height_from_volume_ThermoScientific_96_wellplate_1200ul_Rd(liquid_volume: float):
if liquid_volume > 1260: # 5% tolerance
raise ValueError(f"Volume {liquid_volume} is too large for ThermoScientific_96_DWP_1200ul_Rd")
raise ValueError(f"Volume {liquid_volume} is too large for" + \
"ThermoScientific_96_wellplate_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_DWP_1200ul_Rd_Lid(name: str) -> Lid:
def ThermoScientific_96_wellplate_1200ul_Rd_Lid(name: str) -> Lid:
raise NotImplementedError("This lid is not currently defined.")
# See https://github.com/PyLabRobot/pylabrobot/pull/161.
# return Lid(
Expand All @@ -42,8 +44,12 @@ def ThermoScientific_96_DWP_1200ul_Rd_Lid(name: str) -> Lid:
# model="ThermoScientific_96_DWP_1200ul_Rd_Lid",
# )


def ThermoScientific_96_DWP_1200ul_Rd(name: str, with_lid: bool = False) -> Plate:
raise NotImplementedError("This function is deprecated and will be removed in a future version."
" Use 'ThermoScientific_96_wellplate_1200ul_Rd' instead.")


def ThermoScientific_96_wellplate_1200ul_Rd(name: str, with_lid: bool = False) -> Plate:
""" Fisher Scientific/Thermo Fisher cat. no.: 10243223/AB1127.
- Material: Polypropylene (AB-1068, polystyrene)
- Sterilization compatibility: Autoclaving (15 minutes at 121°C) or
Expand All @@ -55,36 +61,41 @@ def ThermoScientific_96_DWP_1200ul_Rd(name: str, with_lid: bool = False) -> Plat
- 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
- Cleanliness: 10243223/AB1127: Cleanroom manufactured
- Cleanliness: 10243223/AB1127: Cleanroom manufacture
- ANSI/SLAS-format for compatibility with automated systems
"""
return Plate(
name=name,
size_x=127.76,
size_y=85.48,
size_z=24.0,
lid=ThermoScientific_96_DWP_1200ul_Rd_Lid(name + "_lid") if with_lid else None,
model="ThermoScientific_96_DWP_1200ul_Rd",
lid=ThermoScientific_96_wellplate_1200ul_Rd_Lid(name + "_lid") if with_lid else None,
model="ThermoScientific_96_wellplate_1200ul_Rd",
items=create_equally_spaced_2d(Well,
num_items_x=12,
num_items_y=8,
dx=10.0,
dy=7.3,
dz=1.0,
dz=1.0, # 2.5. https://github.com/PyLabRobot/pylabrobot/pull/183
item_dx=9,
item_dy=9,
size_x=8.3,
size_y=8.3,
size_z=20.5,
bottom_type=WellBottomType.U,
material_z_thickness=1.0,
cross_section_type=CrossSectionType.RECTANGLE,
compute_volume_from_height=_compute_volume_from_height_ThermoScientific_96_DWP_1200ul_Rd,
compute_height_from_volume=_compute_height_from_volume_ThermoScientific_96_DWP_1200ul_Rd
compute_volume_from_height=(
_compute_volume_from_height_ThermoScientific_96_wellplate_1200ul_Rd
),
compute_height_from_volume=(
_compute_height_from_volume_ThermoScientific_96_wellplate_1200ul_Rd
)
),
)

def ThermoScientific_96_DWP_1200ul_Rd_L(name: str, with_lid: bool = False) -> Plate:
return ThermoScientific_96_DWP_1200ul_Rd(name=name, with_lid=with_lid)
def ThermoScientific_96_wellplate_1200ul_Rd_L(name: str, with_lid: bool = False) -> Plate:
return ThermoScientific_96_wellplate_1200ul_Rd(name=name, with_lid=with_lid)

def ThermoScientific_96_DWP_1200ul_Rd_P(name: str, with_lid: bool = False) -> Plate:
return ThermoScientific_96_DWP_1200ul_Rd(name=name, with_lid=with_lid).rotated(z=90)
def ThermoScientific_96_wellplate_1200ul_Rd_P(name: str, with_lid: bool = False) -> Plate:
return ThermoScientific_96_wellplate_1200ul_Rd(name=name, with_lid=with_lid).rotated(90)
4 changes: 2 additions & 2 deletions pylabrobot/resources/trough.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def __init__(
size_y: float,
size_z: float,
max_volume: float,
material_z_thickness: float = 0,
material_z_thickness: Optional[float] = None,
through_base_to_container_base: float = 0,
category: Optional[str] = "trough",
model: Optional[str] = None,
Expand All @@ -38,12 +38,12 @@ def __init__(
size_x=size_x,
size_y=size_y,
size_z=size_z,
material_z_thickness=material_z_thickness,
max_volume=max_volume,
category=category,
model=model,
compute_volume_from_height=compute_volume_from_height,
compute_height_from_volume=compute_height_from_volume
)
self.material_z_thickness = material_z_thickness
self.through_base_to_container_base = through_base_to_container_base
self.bottom_type = bottom_type
7 changes: 5 additions & 2 deletions pylabrobot/resources/tube.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,17 @@ class Tube(Container):
:class:`pylabrobot.resources.TubeRack` class.
"""

def __init__(self, name: str, size_x: float, size_y: float, size_z: float,
max_volume: float, category: str = "tube", model: Optional[str] = None):
def __init__(self, name: str, size_x: float, size_y: float, size_z: float, max_volume: float,
material_z_thickness: Optional[float] = None, category: str = "tube",
model: Optional[str] = None):
""" Create a new tube.

Args:
name: Name of the tube.
size_x: Size of the tube in the x direction.
size_y: Size of the tube in the y direction.
size_z: Size of the tube in the z direction.
material_z_thickness: Tube base to cavity base.
max_volume: Maximum volume of the tube.
category: Category of the tube.
"""
Expand All @@ -28,6 +30,7 @@ def __init__(self, name: str, size_x: float, size_y: float, size_z: float,
size_x=size_x,
size_y=size_y,
size_z=size_z,
material_z_thickness=material_z_thickness,
category=category,
max_volume=max_volume,
model=model
Expand Down
16 changes: 11 additions & 5 deletions pylabrobot/resources/well.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,14 @@ class Well(Container):
:class:`pylabrobot.resources.Plate` class.
"""

def __init__(self, name: str, size_x: float, size_y: float, size_z: float,
bottom_type: Union[WellBottomType, str] = WellBottomType.UNKNOWN, category: str = "well",
max_volume: Optional[float] = None, model: Optional[str] = None,
def __init__(
self,
name: str,
size_x: float, size_y: float, size_z: float,
material_z_thickness: Optional[float] = None,
bottom_type: Union[WellBottomType, str] = WellBottomType.UNKNOWN,
category: str = "well", model: Optional[str] = None,
max_volume: Optional[float] = None,
compute_volume_from_height: Optional[Callable[[float], float]] = None,
compute_height_from_volume: Optional[Callable[[float], float]] = None,
cross_section_type: Union[CrossSectionType, str] = CrossSectionType.CIRCLE):
Expand Down Expand Up @@ -75,8 +80,9 @@ def __init__(self, name: str, size_x: float, size_y: float, size_z: float,
max_volume = compute_volume_from_height(size_z)

super().__init__(name, size_x=size_x, size_y=size_y, size_z=size_z, category=category,
max_volume=max_volume, model=model, compute_volume_from_height = compute_volume_from_height,
compute_height_from_volume = compute_height_from_volume)
max_volume=max_volume, model=model, compute_volume_from_height=compute_volume_from_height,
compute_height_from_volume=compute_height_from_volume,
material_z_thickness=material_z_thickness)
self.bottom_type = bottom_type
self.cross_section_type = cross_section_type

Expand Down
1 change: 1 addition & 0 deletions pylabrobot/resources/well_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def test_serialize(self):
"size_x": 1,
"size_y": 2,
"size_z": 3,
"material_z_thickness": None,
"bottom_type": "flat",
"cross_section_type": "circle",
"max_volume": 10,
Expand Down
Loading