Skip to content

Commit

Permalink
Merge branch 'master' into issue-42
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelosthege authored Jul 18, 2023
2 parents d6db8fb + 556b827 commit 74b7e85
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 70 deletions.
2 changes: 1 addition & 1 deletion robotools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@
)
from .utils import DilutionPlan, get_trough_wells

__version__ = "1.7.1"
__version__ = "1.7.2"
39 changes: 32 additions & 7 deletions robotools/evotools/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
"evo_wash",
)

MAX_DILUTOR_VOLUME = 950
""""Maximum dilutor volume in µL"""


def evo_make_selection_array(rows: int, columns: int, wells: Union[Iterable[str], np.ndarray]) -> np.ndarray:
"""Translate well IDs to a numpy array with 1s (selected) and 0s (not selected).
Expand Down Expand Up @@ -99,7 +102,8 @@ def prepare_evo_aspirate_dispense_parameters(
volume: Union[float, Sequence[float], int],
liquid_class: str,
tips: Union[Sequence[Tip], Sequence[int]],
max_volume: Optional[int] = None,
arm: int,
max_volume: Optional[Union[int, float]] = None,
) -> Tuple[List[str], Tuple[int, int], List[float], str, List[Tip]]:
# wells, labware_position, volume, liquid_class, tecan_tips
"""Validates and prepares aspirate/dispense parameters.
Expand All @@ -117,6 +121,8 @@ def prepare_evo_aspirate_dispense_parameters(
Overwrites the liquid class for this step (max 32 characters)
tips : list of int
Tip(s) that will be selected (out of tips 1-8)
arm : int
Which LiHa to use, if more than one is available
max_volume : int, optional
Maximum allowed volume
Expand Down Expand Up @@ -197,6 +203,11 @@ def prepare_evo_aspirate_dispense_parameters(
tip = int_to_tip(tip)
tecan_tips.append(tip)

if arm is None:
raise ValueError("Missing required paramter: arm")
if not arm == 0 and not arm == 1:
raise ValueError("Parameter arm has to be 0 (LiHa 1) or 1 (LiHa 2).")

return wells_list, labware_position, volume_list, liquid_class, tecan_tips


Expand All @@ -209,7 +220,8 @@ def evo_aspirate(
volume: Union[float, Sequence[float], int],
liquid_class: str,
tips: Union[Sequence[Tip], Sequence[int]],
max_volume: int,
arm: int = 0,
max_volume: Optional[Union[int, float]] = np.nan,
) -> str:
"""Command for aspirating with the EvoWARE aspirate command WITHOUT digital volume tracking.
Expand All @@ -234,16 +246,23 @@ def evo_aspirate(
Overwrites the liquid class for this step (max 32 characters)
tips : list
Tip(s) that will be selected; use either a list with integers from 1 - 8 or with tip.T1 - tip.T8
arm : int
Which LiHa to use, if more than one is available
max_volume
Maximum allowed dilutor volume.
"""
# update max_volume (if no value was given) according to the maximum dilutor volume stated at the top
if np.isnan(max_volume):
max_volume = MAX_DILUTOR_VOLUME

# perform consistency checks
(wells, labware_position, volume, liquid_class, tips,) = prepare_evo_aspirate_dispense_parameters(
wells=wells,
labware_position=labware_position,
volume=volume,
liquid_class=liquid_class,
tips=tips,
arm=arm,
max_volume=max_volume,
)

Expand All @@ -265,7 +284,7 @@ def evo_aspirate(
selected = evo_make_selection_array(n_rows, n_columns, wells)
# create code string containing information about target well(s)
code_string = evo_get_selection(n_rows, n_columns, selected)
return f'B;Aspirate({tip_selection},"{liquid_class}",{tip_volumes}0,0,0,0,{labware_position[0]},{labware_position[1]},1,"{code_string}",0,0);'
return f'B;Aspirate({tip_selection},"{liquid_class}",{tip_volumes}0,0,0,0,{labware_position[0]},{labware_position[1]},1,"{code_string}",0,{arm});'


def evo_dispense(
Expand All @@ -277,7 +296,8 @@ def evo_dispense(
volume: Union[float, Sequence[float], int],
liquid_class: str,
tips: Union[Sequence[Tip], Sequence[int]],
max_volume: int,
arm: int = 0,
max_volume: Optional[Union[int, float]] = np.nan,
) -> str:
"""Command for dispensing using the EvoWARE dispense command WITHOUT digital volume tracking.
Expand All @@ -302,16 +322,23 @@ def evo_dispense(
Overwrites the liquid class for this step (max 32 characters)
tips : list
Tip(s) that will be selected; use either a list with integers from 1 - 8 or with tip.T1 - tip.T8
arm : int
Which LiHa to use, if more than one is available
max_volume
Maximum allowed dilutor volume.
"""
# update max_volume (if no value was given) according to the maximum dilutor volume stated at the top
if np.isnan(max_volume):
max_volume = MAX_DILUTOR_VOLUME

# perform consistency checks
(wells, labware_position, volume, liquid_class, tips,) = prepare_evo_aspirate_dispense_parameters(
wells=wells,
labware_position=labware_position,
volume=volume,
liquid_class=liquid_class,
tips=tips,
arm=arm,
max_volume=max_volume,
)

Expand All @@ -333,7 +360,7 @@ def evo_dispense(
selected = evo_make_selection_array(n_rows, n_columns, wells)
# create code string containing information about target well(s)
code_string = evo_get_selection(n_rows, n_columns, selected)
return f'B;Dispense({tip_selection},"{liquid_class}",{tip_volumes}0,0,0,0,{labware_position[0]},{labware_position[1]},1,"{code_string}",0,0);'
return f'B;Dispense({tip_selection},"{liquid_class}",{tip_volumes}0,0,0,0,{labware_position[0]},{labware_position[1]},1,"{code_string}",0,{arm});'


def prepare_evo_wash_parameters(
Expand Down Expand Up @@ -442,8 +469,6 @@ def prepare_evo_wash_parameters(

if arm is None:
raise ValueError("Missing required paramter: arm")
if not isinstance(arm, int):
raise ValueError("Parameter arm is not int.")
if not arm == 0 and not arm == 1:
raise ValueError("Parameter arm has to be 0 (LiHa 1) or 1 (LiHa 2).")

Expand Down
11 changes: 11 additions & 0 deletions robotools/evotools/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ def test_prepare_evo_aspirate_dispense_parameters(self):
volume=15,
liquid_class="Water_DispZmax-1_AspZmax-1",
tips=[1, 2],
arm=0,
)
# test labware_position argument checks
with pytest.raises(ValueError, match="second number in labware_position"):
Expand All @@ -57,6 +58,7 @@ def test_prepare_evo_aspirate_dispense_parameters(self):
volume=15,
liquid_class="Water_DispZmax-1_AspZmax-1",
tips=[1, 2],
arm=0,
)
with pytest.raises(ValueError, match="first number in labware_position"):
prepare_evo_aspirate_dispense_parameters(
Expand All @@ -65,6 +67,7 @@ def test_prepare_evo_aspirate_dispense_parameters(self):
volume=15,
liquid_class="Water_DispZmax-1_AspZmax-1",
tips=[1, 2],
arm=0,
)
# test liquid_class argument checks
with pytest.raises(ValueError, match="Invalid liquid_class:"):
Expand All @@ -74,6 +77,7 @@ def test_prepare_evo_aspirate_dispense_parameters(self):
volume=15,
liquid_class=["Water_DispZmax-1_AspZmax-1"],
tips=[1, 2],
arm=0,
)
with pytest.raises(ValueError, match="Invalid liquid_class:"):
prepare_evo_aspirate_dispense_parameters(
Expand All @@ -82,6 +86,7 @@ def test_prepare_evo_aspirate_dispense_parameters(self):
volume=15,
liquid_class="Water;DispZmax-1;AspZmax-1",
tips=[1, 2],
arm=0,
)
# test tips argument checks
with pytest.raises(ValueError, match="Invalid type of tips:"):
Expand All @@ -91,13 +96,15 @@ def test_prepare_evo_aspirate_dispense_parameters(self):
volume=15,
liquid_class="Water_DispZmax-1_AspZmax-1",
tips=[1, "2"],
arm=0,
)
_, _, _, _, tips = prepare_evo_aspirate_dispense_parameters(
wells=["A01", "B01"],
labware_position=(38, 2),
volume=15,
liquid_class="Water_DispZmax-1_AspZmax-1",
tips=[1, 2],
arm=0,
)
if not all(isinstance(n, Tip) for n in tips):
raise TypeError(
Expand All @@ -111,6 +118,7 @@ def test_prepare_evo_aspirate_dispense_parameters(self):
volume="volume",
liquid_class="Water_DispZmax-1_AspZmax-1",
tips=[1, 2],
arm=0,
)
with pytest.raises(ValueError, match="Invalid volume:"):
prepare_evo_aspirate_dispense_parameters(
Expand All @@ -119,6 +127,7 @@ def test_prepare_evo_aspirate_dispense_parameters(self):
volume=-10,
liquid_class="Water_DispZmax-1_AspZmax-1",
tips=[1, 2],
arm=0,
)
with pytest.raises(ValueError, match="Invalid volume:"):
prepare_evo_aspirate_dispense_parameters(
Expand All @@ -127,6 +136,7 @@ def test_prepare_evo_aspirate_dispense_parameters(self):
volume=7158279,
liquid_class="Water_DispZmax-1_AspZmax-1",
tips=[1, 2],
arm=0,
)

# test complete prepare_evo_aspirate_dispense_parameters() command
Expand All @@ -136,6 +146,7 @@ def test_prepare_evo_aspirate_dispense_parameters(self):
volume=750,
liquid_class="Water_DispZmax_AspZmax",
tips=[5, 6, 7],
arm=0,
)
expected = (
["E01", "F01", "G01"],
Expand Down
84 changes: 22 additions & 62 deletions robotools/evotools/worklist.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def _prepare_aspirate_dispense_parameters(
tube_id: str = "",
rack_type: str = "",
forced_rack_type: str = "",
max_volume: typing.Optional[int] = None,
max_volume: typing.Optional[typing.Union[int, float]] = None,
) -> Tuple[str, int, str, str, Union[Tip, int, collections.abc.Iterable], str, str, str, str]:
"""Validates and prepares aspirate/dispense parameters.
Expand Down Expand Up @@ -190,7 +190,7 @@ def _optimize_partition_by(
return partition_by


def _partition_volume(volume: float, *, max_volume: int) -> typing.List[float]:
def _partition_volume(volume: float, *, max_volume: typing.Union[int, float]) -> typing.List[float]:
"""Partitions a pipetting volume into zero or more integer-valued volumes that are <= max_volume.
Parameters
Expand Down Expand Up @@ -276,7 +276,10 @@ class Worklist(list):
"""Context manager for the creation of Worklists."""

def __init__(
self, filepath: Optional[Union[str, Path]] = None, max_volume: int = 950, auto_split: bool = True
self,
filepath: Optional[Union[str, Path]] = None,
max_volume: typing.Union[int, float] = 950,
auto_split: bool = True,
) -> None:
"""Creates a worklist writer.
Expand Down Expand Up @@ -478,35 +481,6 @@ def aspirate_well(
)
return

def evo_aspirate_well(
self,
*,
labware: liquidhandling.Labware,
wells: typing.Union[str, typing.List[str]],
labware_position: typing.Tuple[int, int],
volume: typing.Union[float, typing.List[float], int],
liquid_class: str,
tips: typing.Union[typing.List[Tip], typing.List[int]],
) -> None:
warnings.warn(
"The `evo_aspirate_well` method is deprecated because it's just a wrapper for the `evo_aspirate` function."
"Replace your `evo_aspirate_well(...)` call with `wl.append(robotools.evotools.evo_aspirate(...))`.",
DeprecationWarning,
stacklevel=2,
)
cmd = commands.evo_aspirate(
n_rows=labware.n_rows,
n_columns=labware.n_columns,
wells=wells,
labware_position=labware_position,
volume=volume,
liquid_class=liquid_class,
tips=tips,
max_volume=self.max_volume,
)
self.append(cmd)
return

def dispense_well(
self,
rack_label: str,
Expand Down Expand Up @@ -575,35 +549,6 @@ def dispense_well(
)
return

def evo_dispense_well(
self,
*,
labware: liquidhandling.Labware,
wells: typing.Union[str, typing.List[str]],
labware_position: typing.Tuple[int, int],
volume: typing.Union[float, typing.List[float], int],
liquid_class: str,
tips: typing.Union[typing.List[Tip], typing.List[int]],
) -> None:
warnings.warn(
"The `evo_dispense_well` method is deprecated because it's just a wrapper for the `evo_dispense` function."
"Replace your `evo_dispense_well(...)` call with `wl.append(robotools.evotools.evo_dispense(...))`.",
DeprecationWarning,
stacklevel=2,
)
cmd = commands.evo_dispense(
n_rows=labware.n_rows,
n_columns=labware.n_columns,
wells=wells,
labware_position=labware_position,
volume=volume,
liquid_class=liquid_class,
tips=tips,
max_volume=self.max_volume,
)
self.append(cmd)
return

def evo_wash(
self,
*,
Expand Down Expand Up @@ -840,6 +785,7 @@ def evo_aspirate(
volumes: typing.Union[float, typing.List[float]],
liquid_class: str,
*,
arm: int = 0,
label: typing.Optional[str] = None,
) -> None:
"""Performs aspiration from the provided labware. Is identical to the aspirate command inside the EvoWARE.
Expand All @@ -859,6 +805,10 @@ def evo_aspirate(
Volume(s) in microliters (will be rounded to 2 decimal places); if several tips are used, these tips may aspirate individual volumes -> use list in these cases
liquid_class : str, optional
Overwrites the liquid class for this step (max 32 characters)
arm : int
Which LiHa to use, if more than one is available
label : str
Label of the operation to log into labware history
"""
# diferentiate between what is needed for volume calculation and for pipetting commands
wells_calc = numpy.array(wells).flatten("F")
Expand All @@ -875,6 +825,7 @@ def evo_aspirate(
volume=volumes,
liquid_class=liquid_class,
tips=tips,
arm=arm,
max_volume=self.max_volume,
)
self.append(cmd)
Expand Down Expand Up @@ -929,7 +880,9 @@ def evo_dispense(
volumes: typing.Union[float, typing.List[float]],
liquid_class: str,
*,
arm: int = 0,
label: typing.Optional[str] = None,
compositions: typing.Optional[typing.List[typing.Optional[typing.Dict[str, float]]]] = None,
) -> None:
"""Performs dispensation from the provided labware. Is identical to the dispense command inside the EvoWARE.
Thus, several wells in a single column can be targeted.
Expand All @@ -948,13 +901,19 @@ def evo_dispense(
Volume(s) in microliters (will be rounded to 2 decimal places); if several tips are used, these tips may aspirate individual volumes -> use list in these cases
liquid_class : str, optional
Overwrites the liquid class for this step (max 32 characters)
arm : int
Which LiHa to use, if more than one is available
label : str
Label of the operation to log into labware history
compositions : list
Iterable of liquid compositions
"""
# diferentiate between what is needed for volume calculation and for pipetting commands
wells_calc = numpy.array(wells).flatten("F")
volumes_calc = numpy.array(volumes).flatten("F")
if len(volumes_calc) == 1:
volumes_calc = numpy.repeat(volumes_calc, len(wells_calc))
labware.remove(wells_calc, volumes_calc, label)
labware.add(wells_calc, volumes_calc, label, compositions=compositions)
self.comment(label)
cmd = commands.evo_dispense(
n_rows=labware.n_rows,
Expand All @@ -964,6 +923,7 @@ def evo_dispense(
volume=volumes,
liquid_class=liquid_class,
tips=tips,
arm=arm,
max_volume=self.max_volume,
)
self.append(cmd)
Expand Down

0 comments on commit 74b7e85

Please sign in to comment.