Skip to content

Commit 5c19945

Browse files
committed
support passing single well plates directly to {aspirate,dispense}96
1 parent dbdf7e1 commit 5c19945

File tree

1 file changed

+40
-38
lines changed

1 file changed

+40
-38
lines changed

pylabrobot/liquid_handling/liquid_handler.py

Lines changed: 40 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1574,29 +1574,38 @@ async def aspirate96(
15741574
flow_rate = float(flow_rate) if flow_rate is not None else None
15751575
blow_out_air_volume = float(blow_out_air_volume) if blow_out_air_volume is not None else None
15761576

1577+
# Convert Plate to either one Container (single well) or a list of Wells
15771578
containers: Sequence[Container]
1578-
if isinstance(resource, Container):
1579+
if isinstance(resource, Plate):
1580+
if resource.has_lid():
1581+
raise ValueError("Aspirating from plate with lid")
1582+
containers = resource.get_all_items() if resource.num_items > 1 else [resource.get_item(0)]
1583+
elif isinstance(resource, Container):
1584+
containers = [resource]
1585+
1586+
if len(containers) == 1: # single container
1587+
container = containers[0]
15791588
if (
1580-
resource.get_absolute_size_x() < 108.0 or resource.get_absolute_size_y() < 70.0
1589+
container.get_absolute_size_x() < 108.0 or container.get_absolute_size_y() < 70.0
15811590
): # TODO: analyze as attr
15821591
raise ValueError("Container too small to accommodate 96 head")
15831592

15841593
for channel in self.head96.values():
15851594
# superfluous to have append in two places but the type checker is very angry and does not
15861595
# understand that Optional[Liquid] (remove_liquid) is the same as None from the first case
15871596
liquids: List[Tuple[Optional[Liquid], float]]
1588-
if resource.tracker.is_disabled or not does_volume_tracking():
1597+
if container.tracker.is_disabled or not does_volume_tracking():
15891598
liquids = [(None, volume)]
15901599
all_liquids.append(liquids)
15911600
else:
1592-
liquids = resource.tracker.remove_liquid(volume=volume) # type: ignore
1601+
liquids = container.tracker.remove_liquid(volume=volume) # type: ignore
15931602
all_liquids.append(liquids)
15941603

15951604
for liquid, vol in reversed(liquids):
15961605
channel.get_tip().tracker.add_liquid(liquid=liquid, volume=vol)
15971606

15981607
aspiration = MultiHeadAspirationContainer(
1599-
container=resource,
1608+
container=container,
16001609
volume=volume,
16011610
offset=offset,
16021611
flow_rate=flow_rate,
@@ -1605,24 +1614,15 @@ async def aspirate96(
16051614
blow_out_air_volume=blow_out_air_volume,
16061615
liquids=cast(List[List[Tuple[Optional[Liquid], float]]], all_liquids), # stupid
16071616
)
1608-
1609-
containers = [resource]
1610-
else:
1611-
if isinstance(resource, Plate):
1612-
if resource.has_lid():
1613-
raise ValueError("Aspirating from plate with lid")
1614-
containers = resource.get_all_items()
1615-
else:
1616-
containers = resource
1617-
1618-
# ensure that wells are all in the same plate
1619-
plate = containers[0].parent
1620-
for well in containers:
1621-
if well.parent != plate:
1622-
raise ValueError("All wells must be in the same plate")
1617+
else: # multiple containers
1618+
# ensure that wells are all in the same plate
1619+
plate = containers[0].parent
1620+
for well in containers:
1621+
if well.parent != plate:
1622+
raise ValueError("All wells must be in the same plate")
16231623

16241624
if not len(containers) == 96:
1625-
raise ValueError(f"aspirate96 expects 96 wells, got {len(containers)}")
1625+
raise ValueError(f"aspirate96 expects 96 containers when a list, got {len(containers)}")
16261626

16271627
for well, channel in zip(containers, self.head96.values()):
16281628
# superfluous to have append in two places but the type checker is very angry and does not
@@ -1725,29 +1725,38 @@ async def dispense96(
17251725
flow_rate = float(flow_rate) if flow_rate is not None else None
17261726
blow_out_air_volume = float(blow_out_air_volume) if blow_out_air_volume is not None else None
17271727

1728+
# Convert Plate to either one Container (single well) or a list of Wells
17281729
containers: Sequence[Container]
1729-
if isinstance(resource, Container):
1730+
if isinstance(resource, Plate):
1731+
if resource.has_lid():
1732+
raise ValueError("Aspirating from plate with lid")
1733+
containers = resource.get_all_items() if resource.num_items > 1 else [resource.get_item(0)]
1734+
elif isinstance(resource, Container):
1735+
containers = [resource]
1736+
1737+
if len(containers) == 1: # single container
1738+
container = containers[0]
17301739
if (
1731-
resource.get_absolute_size_x() < 108.0 or resource.get_absolute_size_y() < 70.0
1740+
container.get_absolute_size_x() < 108.0 or container.get_absolute_size_y() < 70.0
17321741
): # TODO: analyze as attr
17331742
raise ValueError("Container too small to accommodate 96 head")
17341743

17351744
for channel in self.head96.values():
17361745
# superfluous to have append in two places but the type checker is very angry and does not
17371746
# understand that Optional[Liquid] (remove_liquid) is the same as None from the first case
17381747
reversed_liquids: List[Tuple[Optional[Liquid], float]]
1739-
if resource.tracker.is_disabled or not does_volume_tracking():
1748+
if container.tracker.is_disabled or not does_volume_tracking():
17401749
reversed_liquids = [(None, volume)]
17411750
all_liquids.append(reversed_liquids)
17421751
else:
1743-
reversed_liquids = resource.tracker.remove_liquid(volume=volume) # type: ignore
1752+
reversed_liquids = container.tracker.remove_liquid(volume=volume) # type: ignore
17441753
all_liquids.append(reversed_liquids)
17451754

17461755
for liquid, vol in reversed(reversed_liquids):
17471756
channel.get_tip().tracker.add_liquid(liquid=liquid, volume=vol)
17481757

17491758
dispense = MultiHeadDispenseContainer(
1750-
container=resource,
1759+
container=container,
17511760
volume=volume,
17521761
offset=offset,
17531762
flow_rate=flow_rate,
@@ -1759,18 +1768,11 @@ async def dispense96(
17591768

17601769
containers = [resource]
17611770
else:
1762-
if isinstance(resource, Plate):
1763-
if resource.has_lid():
1764-
raise ValueError("Aspirating from plate with lid")
1765-
containers = resource.get_all_items()
1766-
else: # List[Well]
1767-
containers = resource
1768-
1769-
# ensure that wells are all in the same plate
1770-
plate = containers[0].parent
1771-
for well in containers:
1772-
if well.parent != plate:
1773-
raise ValueError("All wells must be in the same plate")
1771+
# ensure that wells are all in the same plate
1772+
plate = containers[0].parent
1773+
for well in containers:
1774+
if well.parent != plate:
1775+
raise ValueError("All wells must be in the same plate")
17741776

17751777
if not len(containers) == 96:
17761778
raise ValueError(f"dispense96 expects 96 wells, got {len(containers)}")

0 commit comments

Comments
 (0)