-
Notifications
You must be signed in to change notification settings - Fork 46
/
tube_rack.py
126 lines (97 loc) · 4.06 KB
/
tube_rack.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
from typing import List, Optional, Sequence, Tuple, Union, cast
from pylabrobot.resources.itemized_resource import ItemizedResource
from pylabrobot.resources.tube import Tube
from .itemized_resource import ItemizedResource
from .resource import Resource, Coordinate
from .liquid import Liquid
class TubeRack(ItemizedResource[Tube]):
""" Tube rack resource. """
def __init__(
self,
name: str,
size_x: float,
size_y: float,
size_z: float,
items: List[List[Tube]],
model: Optional[str] = None,
):
""" Initialize a TubeRack resource.
Args:
name: Name of the tube rack.
size_x: Size of the tube rack in the x direction.
size_y: Size of the tube rack in the y direction.
size_z: Size of the tube rack in the z direction.
items: List of lists of wells.
model: Model of the tube rack.
"""
super().__init__(
name=name,
size_x=size_x,
size_y=size_y,
size_z=size_z,
items=items,
model=model)
def serialize(self) -> dict:
return {**super().serialize()}
def assign_child_resource(
self,
resource: Resource,
location: Optional[Coordinate] = None,
reassign: bool = True
):
assert location is not None, "Location must be specified for resource."
return super().assign_child_resource(resource, location=location, reassign=reassign)
def __repr__(self) -> str:
return (f"{self.__class__.__name__}(name={self.name}, size_x={self._size_x}, "
f"size_y={self._size_y}, size_z={self._size_z}, location={self.location})")
def get_tube(self, identifier: Union[str, int, Tuple[int, int]]) -> Tube:
""" Get the item with the given identifier.
See :meth:`~.get_item` for more information.
"""
return super().get_item(identifier)
def get_tubes(self,
identifier: Union[str, Sequence[int]]) -> List[Tube]:
""" Get the tubes with the given identifier.
See :meth:`~.get_items` for more information.
"""
return super().get_items(identifier)
def set_tube_liquids(
self,
liquids: Union[
List[List[Tuple[Optional["Liquid"], Union[int, float]]]],
List[Tuple[Optional["Liquid"], Union[int, float]]],
Tuple[Optional["Liquid"], Union[int, float]]]
) -> None:
""" Update the liquid in the volume tracker for each tube in the rack.
Args:
liquids: A list of liquids, one for each tube in the rack. The list can be a list of lists,
where each inner list contains the liquids for each tube in a column. If a single tuple is
given, the volume is assumed to be the same for all tubes. Liquids are in uL.
Raises:
ValueError: If the number of liquids does not match the number of tubes in the rack.
Example:
Set the volume of each tube in a 4x6 rack to 1000 uL.
>>> rack = TubeRack("rack", 127.0, 86.0, 14.5, num_items_x=6, num_items_y=4)
>>> rack.set_tube_liquids((Liquid.WATER, 1000))
"""
if isinstance(liquids, tuple):
liquids = [liquids] * self.num_items
elif isinstance(liquids, list) and all(isinstance(column, list) for column in liquids):
# mypy doesn't know that all() checks the type
liquids = cast(List[List[Tuple[Optional["Liquid"], float]]], liquids)
liquids = [list(column) for column in zip(*liquids)] # transpose the list of lists
liquids = [volume for column in liquids for volume in column] # flatten the list of lists
if len(liquids) != self.num_items:
raise ValueError(f"Number of liquids ({len(liquids)}) does not match number of tubes "
f"({self.num_items}) in rack '{self.name}'.")
for i, (liquid, volume) in enumerate(liquids):
tube = self.get_tube(i)
tube.tracker.set_liquids([(liquid, volume)]) # type: ignore
def disable_volume_trackers(self) -> None:
""" Disable volume tracking for all tubes in the rack. """
for tube in self.get_all_items():
tube.tracker.disable()
def enable_volume_trackers(self) -> None:
""" Enable volume tracking for all tubes in the rack. """
for tube in self.get_all_items():
tube.tracker.enable()