Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions pylabrobot/resources/hamilton/tip_creators.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ def __init__(
maximal_volume: float,
tip_size: Union[TipSize, str], # union for deserialization, will probably refactor
pickup_method: Union[TipPickupMethod, str], # union for deserialization, will probably refactor
nominal_volume: Optional[float] = None,
name: Optional[str] = None,
):
if isinstance(tip_size, str):
Expand All @@ -68,6 +69,7 @@ def __init__(
super().__init__(
total_tip_length=total_tip_length,
has_filter=has_filter,
nominal_volume=nominal_volume,
maximal_volume=maximal_volume,
fitting_depth=fitting_depth,
name=name,
Expand All @@ -82,6 +84,7 @@ def __repr__(self) -> str:
f"HamiltonTip(name={name_field}, "
f"tip_size={self.tip_size.name}, "
f"has_filter={self.has_filter}, "
f"nominal_volume={self.nominal_volume}, "
f"maximal_volume={self.maximal_volume}, "
f"fitting_depth={self.fitting_depth}, "
f"total_tip_length={self.total_tip_length}, "
Expand All @@ -103,6 +106,7 @@ def deserialize(cls, data):
name=data["name"],
has_filter=data["has_filter"],
total_tip_length=data["total_tip_length"],
nominal_volume=data.get("nominal_volume"),
maximal_volume=data["maximal_volume"],
tip_size=TipSize[data["tip_size"]],
pickup_method=TipPickupMethod[data["pickup_method"]],
Expand Down Expand Up @@ -273,6 +277,7 @@ def hamilton_tip_10uL(name: Optional[str] = None) -> HamiltonTip:
name=name,
has_filter=False,
total_tip_length=29.9,
nominal_volume=10,
maximal_volume=15,
tip_size=TipSize.LOW_VOLUME,
pickup_method=TipPickupMethod.OUT_OF_RACK,
Expand All @@ -288,6 +293,7 @@ def hamilton_tip_10uL_filter(name: Optional[str] = None) -> HamiltonTip:
name=name,
has_filter=True,
total_tip_length=29.9,
nominal_volume=10,
maximal_volume=10,
tip_size=TipSize.LOW_VOLUME,
pickup_method=TipPickupMethod.OUT_OF_RACK,
Expand All @@ -303,6 +309,7 @@ def hamilton_tip_50uL(name: Optional[str] = None) -> HamiltonTip:
name=name,
has_filter=False,
total_tip_length=50.4,
nominal_volume=50,
maximal_volume=65,
tip_size=TipSize.STANDARD_VOLUME,
pickup_method=TipPickupMethod.OUT_OF_RACK,
Expand All @@ -318,6 +325,7 @@ def hamilton_tip_50uL_filter(name: Optional[str] = None) -> HamiltonTip:
name=name,
has_filter=True,
total_tip_length=50.4,
nominal_volume=50,
maximal_volume=60,
tip_size=TipSize.STANDARD_VOLUME,
pickup_method=TipPickupMethod.OUT_OF_RACK,
Expand All @@ -330,6 +338,7 @@ def hamilton_tip_300uL(name: Optional[str] = None) -> HamiltonTip:
name=name,
has_filter=False,
total_tip_length=59.9,
nominal_volume=300,
maximal_volume=400,
tip_size=TipSize.STANDARD_VOLUME,
pickup_method=TipPickupMethod.OUT_OF_RACK,
Expand All @@ -342,6 +351,7 @@ def hamilton_tip_300uL_filter(name: Optional[str] = None) -> HamiltonTip:
name=name,
has_filter=True,
total_tip_length=59.9,
nominal_volume=300,
maximal_volume=360,
tip_size=TipSize.STANDARD_VOLUME,
pickup_method=TipPickupMethod.OUT_OF_RACK,
Expand All @@ -354,6 +364,7 @@ def hamilton_tip_300uL_filter_slim(name: Optional[str] = None) -> HamiltonTip:
name=name,
has_filter=True,
total_tip_length=94.8,
nominal_volume=300,
maximal_volume=360,
tip_size=TipSize.HIGH_VOLUME,
pickup_method=TipPickupMethod.OUT_OF_RACK,
Expand All @@ -366,6 +377,7 @@ def hamilton_tip_300uL_filter_ultrawide(name: Optional[str] = None) -> HamiltonT
name=name,
has_filter=True,
total_tip_length=51.9,
nominal_volume=300,
maximal_volume=360,
tip_size=TipSize.STANDARD_VOLUME,
pickup_method=TipPickupMethod.OUT_OF_RACK,
Expand All @@ -378,6 +390,7 @@ def hamilton_tip_1000uL(name: Optional[str] = None) -> HamiltonTip:
name=name,
has_filter=False,
total_tip_length=95.1,
nominal_volume=1000,
maximal_volume=1250,
tip_size=TipSize.HIGH_VOLUME,
pickup_method=TipPickupMethod.OUT_OF_RACK,
Expand All @@ -390,6 +403,7 @@ def hamilton_tip_1000uL_filter(name: Optional[str] = None) -> HamiltonTip:
name=name,
has_filter=True,
total_tip_length=95.1,
nominal_volume=1000,
maximal_volume=1065,
tip_size=TipSize.HIGH_VOLUME,
pickup_method=TipPickupMethod.OUT_OF_RACK,
Expand All @@ -405,6 +419,7 @@ def hamilton_tip_1000uL_filter_wide(name: Optional[str] = None) -> HamiltonTip:
name=name,
has_filter=True,
total_tip_length=91.95,
nominal_volume=1000,
maximal_volume=1065,
tip_size=TipSize.HIGH_VOLUME,
pickup_method=TipPickupMethod.OUT_OF_RACK,
Expand All @@ -420,6 +435,7 @@ def hamilton_tip_1000uL_filter_ultrawide(name: Optional[str] = None) -> Hamilton
name=name,
has_filter=True,
total_tip_length=80.0,
nominal_volume=1000,
maximal_volume=1065,
tip_size=TipSize.HIGH_VOLUME,
pickup_method=TipPickupMethod.OUT_OF_RACK,
Expand All @@ -432,6 +448,7 @@ def hamilton_tip_4000uL_filter(name: Optional[str] = None) -> HamiltonTip:
name=name,
has_filter=True,
total_tip_length=116,
nominal_volume=4000,
maximal_volume=4367,
tip_size=TipSize.XL,
pickup_method=TipPickupMethod.OUT_OF_RACK,
Expand All @@ -444,6 +461,7 @@ def hamilton_tip_5000uL(name: Optional[str] = None) -> HamiltonTip:
name=name,
has_filter=False,
total_tip_length=116,
nominal_volume=5000,
maximal_volume=5420,
tip_size=TipSize.XL,
pickup_method=TipPickupMethod.OUT_OF_RACK,
Expand All @@ -456,6 +474,7 @@ def hamilton_tip_5000uL_filter(name: Optional[str] = None) -> HamiltonTip:
name=name,
has_filter=True,
total_tip_length=116,
nominal_volume=5000,
maximal_volume=5420,
tip_size=TipSize.XL,
pickup_method=TipPickupMethod.OUT_OF_RACK,
Expand Down
13 changes: 11 additions & 2 deletions pylabrobot/resources/tip.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ class Tip(SerializableMixin):

Attributes:
has_filter: whether the tip type has a filter
total_tip_length: total length of the tip, in in mm
maximal_volume: maximal volume of the tip, in ul
total_tip_length: total length of the tip, in mm
nominal_volume: rated working volume of the tip (what it is sold and named as), in uL.
Defaults to maximal_volume when not given.
maximal_volume: physical brim-full capacity of the tip, in uL
fitting_depth: the overlap between the tip and the pipette, in mm
name: optional identifier for this tip
"""
Expand All @@ -24,6 +26,7 @@ class Tip(SerializableMixin):
total_tip_length: float
maximal_volume: float
fitting_depth: float
nominal_volume: Optional[float] = None
name: Optional[str] = None

def __post_init__(self):
Expand All @@ -35,6 +38,9 @@ def __post_init__(self):
stacklevel=2,
)

if self.nominal_volume is None:
self.nominal_volume = self.maximal_volume

thing = self.name or "tip_tracker"
self.tracker = VolumeTracker(thing=thing, max_volume=self.maximal_volume)

Expand All @@ -44,6 +50,7 @@ def serialize(self) -> dict:
"name": self.name,
"total_tip_length": self.total_tip_length,
"has_filter": self.has_filter,
"nominal_volume": self.nominal_volume,
"maximal_volume": self.maximal_volume,
"fitting_depth": self.fitting_depth,
}
Expand All @@ -53,6 +60,7 @@ def __hash__(self):
(
self.has_filter,
self.total_tip_length,
self.nominal_volume,
self.maximal_volume,
self.fitting_depth,
)
Expand All @@ -65,6 +73,7 @@ def __eq__(self, other: object) -> bool:
return (
self.has_filter == other.has_filter
and self.total_tip_length == other.total_tip_length
and self.nominal_volume == other.nominal_volume
and self.maximal_volume == other.maximal_volume
and self.fitting_depth == other.fitting_depth
)
Expand Down
21 changes: 21 additions & 0 deletions pylabrobot/resources/tip_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ def test_serialize(self):
"name": "test_tip",
"has_filter": False,
"total_tip_length": 10.0,
"nominal_volume": 10.0,
"maximal_volume": 10.0,
"fitting_depth": 1.0,
},
Expand All @@ -46,6 +47,7 @@ def test_serialize_subclass(self):
"name": "test_tip",
"has_filter": False,
"total_tip_length": 10.0,
"nominal_volume": 10.0,
"maximal_volume": 10.0,
"pickup_method": "OUT_OF_RACK",
"tip_size": "HIGH_VOLUME",
Expand All @@ -62,3 +64,22 @@ def test_deserialize_subclass(self):
name="test_tip",
)
self.assertEqual(HamiltonTip.deserialize(tip.serialize()), tip)

def test_nominal_volume_defaults_to_maximal_volume(self):
"""An unspecified nominal_volume falls back to maximal_volume (the physical capacity)."""
tip = Tip(False, 10.0, 400.0, 1.0, name="test_tip")
self.assertEqual(tip.nominal_volume, 400.0)

def test_deserialize_legacy_without_nominal_volume(self):
"""A payload predating nominal_volume deserializes, falling back to maximal_volume."""
tip = HamiltonTip(
False,
10.0,
400.0,
TipSize.HIGH_VOLUME,
TipPickupMethod.OUT_OF_RACK,
name="test_tip",
)
legacy = tip.serialize()
del legacy["nominal_volume"]
self.assertEqual(HamiltonTip.deserialize(legacy).nominal_volume, 400.0)
Loading