mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2026-02-07 15:35:10 +00:00
Compare commits
8 Commits
v0.10.1
...
d2dda6ee03
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2dda6ee03 | ||
|
|
208540b307 | ||
|
|
cb7c56a1d9 | ||
|
|
ea2e9c3e3a | ||
|
|
0452a68180 | ||
|
|
90a0f3db9b | ||
|
|
055d120ba8 | ||
|
|
a948f09f60 |
212
unilabos/devices/liquid_handling/biomek.py
Normal file
212
unilabos/devices/liquid_handling/biomek.py
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
import requests
|
||||||
|
from typing import List, Sequence, Optional, Union, Literal
|
||||||
|
from geometry_msgs.msg import Point
|
||||||
|
from unilabos_msgs.msg import Resource
|
||||||
|
|
||||||
|
from unilabos.ros.nodes.resource_tracker import DeviceNodeResourceTracker # type: ignore
|
||||||
|
from .liquid_handler_abstract import LiquidHandlerAbstract
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class LiquidHandlerBiomek(LiquidHandlerAbstract):
|
||||||
|
"""
|
||||||
|
Biomek液体处理器的实现类,继承自LiquidHandlerAbstract。
|
||||||
|
该类用于处理Biomek液体处理器的特定操作。
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self._status = "Idle" # 初始状态为 Idle
|
||||||
|
self._success = False # 初始成功状态为 False
|
||||||
|
self._status_queue = kwargs.get("status_queue", None) # 状态队列
|
||||||
|
self.temp_protocol = {}
|
||||||
|
self.py32_path = "/opt/py32" # Biomek的Python 3.2路径
|
||||||
|
|
||||||
|
def create_protocol(
|
||||||
|
self,
|
||||||
|
protocol_name: str,
|
||||||
|
protocol_description: str,
|
||||||
|
protocol_version: str,
|
||||||
|
protocol_author: str,
|
||||||
|
protocol_date: str,
|
||||||
|
none_keys: List[str] = [],
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
创建一个新的协议。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
protocol_name (str): 协议名称
|
||||||
|
protocol_description (str): 协议描述
|
||||||
|
protocol_version (str): 协议版本
|
||||||
|
protocol_author (str): 协议作者
|
||||||
|
protocol_date (str): 协议日期
|
||||||
|
protocol_type (str): 协议类型
|
||||||
|
none_keys (List[str]): 需要设置为None的键列表
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: 创建的协议字典
|
||||||
|
"""
|
||||||
|
self.temp_protocol = {
|
||||||
|
"meta": {
|
||||||
|
"name": protocol_name,
|
||||||
|
"description": protocol_description,
|
||||||
|
"version": protocol_version,
|
||||||
|
"author": protocol_author,
|
||||||
|
"date": protocol_date,
|
||||||
|
},
|
||||||
|
"labwares": [],
|
||||||
|
"steps": [],
|
||||||
|
}
|
||||||
|
return self.temp_protocol
|
||||||
|
|
||||||
|
def run_protocol(self):
|
||||||
|
"""
|
||||||
|
执行创建的实验流程。
|
||||||
|
工作站的完整执行流程是,
|
||||||
|
从 create_protocol 开始,创建新的 method,
|
||||||
|
随后执行 transfer_liquid 等操作向实验流程添加步骤,
|
||||||
|
最后 run_protocol 执行整个方法。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: 执行结果
|
||||||
|
"""
|
||||||
|
#use popen or subprocess to create py32 process and communicate send the temp protocol to it
|
||||||
|
if not self.temp_protocol:
|
||||||
|
raise ValueError("No protocol created. Please create a protocol first.")
|
||||||
|
|
||||||
|
# 模拟执行协议
|
||||||
|
self._status = "Running"
|
||||||
|
self._success = True
|
||||||
|
# 在这里可以添加实际执行协议的逻辑
|
||||||
|
|
||||||
|
response = requests.post("localhost:5000/api/protocols", json=self.temp_protocol)
|
||||||
|
|
||||||
|
def create_resource(
|
||||||
|
self,
|
||||||
|
resource_tracker: DeviceNodeResourceTracker,
|
||||||
|
resources: list[Resource],
|
||||||
|
bind_parent_id: str,
|
||||||
|
bind_location: dict[str, float],
|
||||||
|
liquid_input_slot: list[int],
|
||||||
|
liquid_type: list[str],
|
||||||
|
liquid_volume: list[int],
|
||||||
|
slot_on_deck: int,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
创建一个新的资源。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
device_id (str): 设备ID
|
||||||
|
res_id (str): 资源ID
|
||||||
|
class_name (str): 资源类名
|
||||||
|
parent (str): 父级ID
|
||||||
|
bind_locations (Point): 绑定位置
|
||||||
|
liquid_input_slot (list[int]): 液体输入槽列表
|
||||||
|
liquid_type (list[str]): 液体类型列表
|
||||||
|
liquid_volume (list[int]): 液体体积列表
|
||||||
|
slot_on_deck (int): 甲板上的槽位
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: 创建的资源字典
|
||||||
|
"""
|
||||||
|
# TODO:需要对好接口,下面这个是临时的
|
||||||
|
resource = {
|
||||||
|
"id": res_id,
|
||||||
|
"class": class_name,
|
||||||
|
"parent": parent,
|
||||||
|
"bind_locations": bind_locations.to_dict(),
|
||||||
|
"liquid_input_slot": liquid_input_slot,
|
||||||
|
"liquid_type": liquid_type,
|
||||||
|
"liquid_volume": liquid_volume,
|
||||||
|
"slot_on_deck": slot_on_deck,
|
||||||
|
}
|
||||||
|
self.temp_protocol["labwares"].append(resource)
|
||||||
|
return resource
|
||||||
|
|
||||||
|
def transfer_liquid(
|
||||||
|
self,
|
||||||
|
sources: Sequence[Container],
|
||||||
|
targets: Sequence[Container],
|
||||||
|
tip_racks: Sequence[TipRack],
|
||||||
|
solvent: Optional[str] = None,
|
||||||
|
TipLocation: Optional[str] = None,
|
||||||
|
*,
|
||||||
|
use_channels: Optional[List[int]] = None,
|
||||||
|
asp_vols: Union[List[float], float],
|
||||||
|
dis_vols: Union[List[float], float],
|
||||||
|
asp_flow_rates: Optional[List[Optional[float]]] = None,
|
||||||
|
dis_flow_rates: Optional[List[Optional[float]]] = None,
|
||||||
|
offsets: Optional[List[Coordinate]] = None,
|
||||||
|
touch_tip: bool = False,
|
||||||
|
liquid_height: Optional[List[Optional[float]]] = None,
|
||||||
|
blow_out_air_volume: Optional[List[Optional[float]]] = None,
|
||||||
|
spread: Literal["wide", "tight", "custom"] = "wide",
|
||||||
|
is_96_well: bool = False,
|
||||||
|
mix_stage: Optional[Literal["none", "before", "after", "both"]] = "none",
|
||||||
|
mix_times: Optional[List(int)] = None,
|
||||||
|
mix_vol: Optional[int] = None,
|
||||||
|
mix_rate: Optional[int] = None,
|
||||||
|
mix_liquid_height: Optional[float] = None,
|
||||||
|
delays: Optional[List[int]] = None,
|
||||||
|
none_keys: List[str] = []
|
||||||
|
):
|
||||||
|
|
||||||
|
transfer_params = {
|
||||||
|
"Span8": False,
|
||||||
|
"Pod": "Pod1",
|
||||||
|
"items": {},
|
||||||
|
"Wash": False,
|
||||||
|
"Dynamic?": True,
|
||||||
|
"AutoSelectActiveWashTechnique": False,
|
||||||
|
"ActiveWashTechnique": "",
|
||||||
|
"ChangeTipsBetweenDests": False,
|
||||||
|
"ChangeTipsBetweenSources": False,
|
||||||
|
"DefaultCaption": "",
|
||||||
|
"UseExpression": False,
|
||||||
|
"LeaveTipsOn": False,
|
||||||
|
"MandrelExpression": "",
|
||||||
|
"Repeats": "1",
|
||||||
|
"RepeatsByVolume": False,
|
||||||
|
"Replicates": "1",
|
||||||
|
"ShowTipHandlingDetails": False,
|
||||||
|
"ShowTransferDetails": True,
|
||||||
|
"Solvent": "Water",
|
||||||
|
"Span8Wash": False,
|
||||||
|
"Span8WashVolume": "2",
|
||||||
|
"Span8WasteVolume": "1",
|
||||||
|
"SplitVolume": False,
|
||||||
|
"SplitVolumeCleaning": False,
|
||||||
|
"Stop": "Destinations",
|
||||||
|
"TipLocation": "BC1025F",
|
||||||
|
"UseCurrentTips": False,
|
||||||
|
"UseDisposableTips": True,
|
||||||
|
"UseFixedTips": False,
|
||||||
|
"UseJIT": True,
|
||||||
|
"UseMandrelSelection": True,
|
||||||
|
"UseProbes": [True, True, True, True, True, True, True, True],
|
||||||
|
"WashCycles": "1",
|
||||||
|
"WashVolume": "110%",
|
||||||
|
"Wizard": False
|
||||||
|
}
|
||||||
|
|
||||||
|
items: dict = {}
|
||||||
|
for idx, (src, dst) in enumerate(zip(sources, targets)):
|
||||||
|
items[str(idx)] = {
|
||||||
|
"Source": str(src),
|
||||||
|
"Destination": str(dst),
|
||||||
|
"Volume": asp_vols[idx]
|
||||||
|
}
|
||||||
|
transfer_params["items"] = items
|
||||||
|
transfer_params["Solvent"] = solvent if solvent else "Water"
|
||||||
|
transfer_params["TipLocation"] = TipLocation
|
||||||
|
|
||||||
|
if len(tip_racks) == 1:
|
||||||
|
transfer_params['UseCurrentTips'] = True
|
||||||
|
elif len(tip_racks) > 1:
|
||||||
|
transfer_params["ChangeTipsBetweenDests"] = True
|
||||||
|
|
||||||
|
self.temp_protocol["steps"].append(transfer_params)
|
||||||
|
|
||||||
|
return
|
||||||
|
s
|
||||||
@@ -6,13 +6,8 @@ import asyncio
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from pylabrobot.liquid_handling import LiquidHandler
|
from pylabrobot.liquid_handling import LiquidHandler
|
||||||
from pylabrobot.resources import (
|
from pylabrobot.resources import Resource, TipRack, Container, Coordinate, Well
|
||||||
Resource,
|
|
||||||
TipRack,
|
|
||||||
Container,
|
|
||||||
Coordinate,
|
|
||||||
Well
|
|
||||||
)
|
|
||||||
|
|
||||||
class LiquidHandlerAbstract(LiquidHandler):
|
class LiquidHandlerAbstract(LiquidHandler):
|
||||||
"""Extended LiquidHandler with additional operations."""
|
"""Extended LiquidHandler with additional operations."""
|
||||||
@@ -21,6 +16,19 @@ class LiquidHandlerAbstract(LiquidHandler):
|
|||||||
# REMOVE LIQUID --------------------------------------------------
|
# REMOVE LIQUID --------------------------------------------------
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
|
|
||||||
|
async def create_protocol(
|
||||||
|
self,
|
||||||
|
protocol_name: str,
|
||||||
|
protocol_description: str,
|
||||||
|
protocol_version: str,
|
||||||
|
protocol_author: str,
|
||||||
|
protocol_date: str,
|
||||||
|
protocol_type: str,
|
||||||
|
none_keys: List[str] = [],
|
||||||
|
):
|
||||||
|
"""Create a new protocol with the given metadata."""
|
||||||
|
pass
|
||||||
|
|
||||||
async def remove_liquid(
|
async def remove_liquid(
|
||||||
self,
|
self,
|
||||||
vols: List[float],
|
vols: List[float],
|
||||||
@@ -35,26 +43,26 @@ class LiquidHandlerAbstract(LiquidHandler):
|
|||||||
spread: Optional[Literal["wide", "tight", "custom"]] = "wide",
|
spread: Optional[Literal["wide", "tight", "custom"]] = "wide",
|
||||||
delays: Optional[List[int]] = None,
|
delays: Optional[List[int]] = None,
|
||||||
is_96_well: Optional[bool] = False,
|
is_96_well: Optional[bool] = False,
|
||||||
top: Optional[List(float)] = None,
|
top: Optional[List[float]] = None,
|
||||||
none_keys: List[str] = []
|
none_keys: List[str] = [],
|
||||||
):
|
):
|
||||||
"""A complete *remove* (aspirate → waste) operation."""
|
"""A complete *remove* (aspirate → waste) operation."""
|
||||||
trash = self.deck.get_trash_area()
|
trash = self.deck.get_trash_area()
|
||||||
try:
|
try:
|
||||||
if is_96_well:
|
if is_96_well:
|
||||||
pass # This mode is not verified
|
pass # This mode is not verified
|
||||||
else:
|
else:
|
||||||
if len(vols) != len(sources):
|
if len(vols) != len(sources):
|
||||||
raise ValueError("Length of `vols` must match `sources`.")
|
raise ValueError("Length of `vols` must match `sources`.")
|
||||||
|
|
||||||
for src, vol in zip(sources, vols):
|
for src, vol in zip(sources, vols):
|
||||||
self.move_to(src, dis_to_top=top[0] if top else 0)
|
await self.move_to(src, dis_to_top=top[0] if top else 0)
|
||||||
tip = next(self.current_tip)
|
tip = next(self.current_tip)
|
||||||
await self.pick_up_tips(tip)
|
await self.pick_up_tips(tip)
|
||||||
await self.aspirate(
|
await self.aspirate(
|
||||||
resources=[src],
|
resources=[src],
|
||||||
vols=[vol],
|
vols=[vol],
|
||||||
use_channels=use_channels, # only aspirate96 used, default to None
|
use_channels=use_channels, # only aspirate96 used, default to None
|
||||||
flow_rates=[flow_rates[0]] if flow_rates else None,
|
flow_rates=[flow_rates[0]] if flow_rates else None,
|
||||||
offsets=[offsets[0]] if offsets else None,
|
offsets=[offsets[0]] if offsets else None,
|
||||||
liquid_height=[liquid_height[0]] if liquid_height else None,
|
liquid_height=[liquid_height[0]] if liquid_height else None,
|
||||||
@@ -64,15 +72,15 @@ class LiquidHandlerAbstract(LiquidHandler):
|
|||||||
await self.custom_delay(seconds=delays[0] if delays else 0)
|
await self.custom_delay(seconds=delays[0] if delays else 0)
|
||||||
await self.dispense(
|
await self.dispense(
|
||||||
resources=waste_liquid,
|
resources=waste_liquid,
|
||||||
vols=[vol],
|
vols=[vol],
|
||||||
use_channels=use_channels,
|
use_channels=use_channels,
|
||||||
flow_rates=[flow_rates[1]] if flow_rates else None,
|
flow_rates=[flow_rates[1]] if flow_rates else None,
|
||||||
offsets=[offsets[1]] if offsets else None,
|
offsets=[offsets[1]] if offsets else None,
|
||||||
liquid_height=[liquid_height[1]] if liquid_height else None,
|
liquid_height=[liquid_height[1]] if liquid_height else None,
|
||||||
blow_out_air_volume=blow_out_air_volume[1] if blow_out_air_volume else None,
|
blow_out_air_volume=blow_out_air_volume[1] if blow_out_air_volume else None,
|
||||||
spread=spread,
|
spread=spread,
|
||||||
)
|
)
|
||||||
await self.discard_tips() # For now, each of tips is discarded after use
|
await self.discard_tips() # For now, each of tips is discarded after use
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise RuntimeError(f"Liquid removal failed: {e}") from e
|
raise RuntimeError(f"Liquid removal failed: {e}") from e
|
||||||
@@ -100,13 +108,13 @@ class LiquidHandlerAbstract(LiquidHandler):
|
|||||||
mix_vol: Optional[int] = None,
|
mix_vol: Optional[int] = None,
|
||||||
mix_rate: Optional[int] = None,
|
mix_rate: Optional[int] = None,
|
||||||
mix_liquid_height: Optional[float] = None,
|
mix_liquid_height: Optional[float] = None,
|
||||||
none_keys: List[str] = []
|
none_keys: List[str] = [],
|
||||||
):
|
):
|
||||||
"""A complete *add* (aspirate reagent → dispense into targets) operation."""
|
"""A complete *add* (aspirate reagent → dispense into targets) operation."""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if is_96_well:
|
if is_96_well:
|
||||||
pass # This mode is not verified.
|
pass # This mode is not verified.
|
||||||
else:
|
else:
|
||||||
if len(asp_vols) != len(targets):
|
if len(asp_vols) != len(targets):
|
||||||
raise ValueError("Length of `vols` must match `targets`.")
|
raise ValueError("Length of `vols` must match `targets`.")
|
||||||
@@ -122,7 +130,7 @@ class LiquidHandlerAbstract(LiquidHandler):
|
|||||||
offsets=[offsets[0]] if offsets else None,
|
offsets=[offsets[0]] if offsets else None,
|
||||||
liquid_height=[liquid_height[0]] if liquid_height else None,
|
liquid_height=[liquid_height[0]] if liquid_height else None,
|
||||||
blow_out_air_volume=[blow_out_air_volume[0]] if blow_out_air_volume else None,
|
blow_out_air_volume=[blow_out_air_volume[0]] if blow_out_air_volume else None,
|
||||||
spread=spread
|
spread=spread,
|
||||||
)
|
)
|
||||||
if delays is not None:
|
if delays is not None:
|
||||||
await self.custom_delay(seconds=delays[0])
|
await self.custom_delay(seconds=delays[0])
|
||||||
@@ -144,7 +152,8 @@ class LiquidHandlerAbstract(LiquidHandler):
|
|||||||
mix_vol=mix_vol,
|
mix_vol=mix_vol,
|
||||||
offsets=offsets if offsets else None,
|
offsets=offsets if offsets else None,
|
||||||
height_to_bottom=mix_liquid_height if mix_liquid_height else None,
|
height_to_bottom=mix_liquid_height if mix_liquid_height else None,
|
||||||
mix_rate=mix_rate if mix_rate else None)
|
mix_rate=mix_rate if mix_rate else None,
|
||||||
|
)
|
||||||
if delays is not None:
|
if delays is not None:
|
||||||
await self.custom_delay(seconds=delays[1])
|
await self.custom_delay(seconds=delays[1])
|
||||||
await self.touch_tip(targets[_])
|
await self.touch_tip(targets[_])
|
||||||
@@ -158,13 +167,13 @@ class LiquidHandlerAbstract(LiquidHandler):
|
|||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
async def transfer_liquid(
|
async def transfer_liquid(
|
||||||
self,
|
self,
|
||||||
asp_vols: Union[List[float], float],
|
|
||||||
dis_vols: Union[List[float], float],
|
|
||||||
sources: Sequence[Container],
|
sources: Sequence[Container],
|
||||||
targets: Sequence[Container],
|
targets: Sequence[Container],
|
||||||
tip_racks: Sequence[TipRack],
|
tip_racks: Sequence[TipRack],
|
||||||
*,
|
*,
|
||||||
use_channels: Optional[List[int]] = None,
|
use_channels: Optional[List[int]] = None,
|
||||||
|
asp_vols: Union[List[float], float],
|
||||||
|
dis_vols: Union[List[float], float],
|
||||||
asp_flow_rates: Optional[List[Optional[float]]] = None,
|
asp_flow_rates: Optional[List[Optional[float]]] = None,
|
||||||
dis_flow_rates: Optional[List[Optional[float]]] = None,
|
dis_flow_rates: Optional[List[Optional[float]]] = None,
|
||||||
offsets: Optional[List[Coordinate]] = None,
|
offsets: Optional[List[Coordinate]] = None,
|
||||||
@@ -179,7 +188,7 @@ class LiquidHandlerAbstract(LiquidHandler):
|
|||||||
mix_rate: Optional[int] = None,
|
mix_rate: Optional[int] = None,
|
||||||
mix_liquid_height: Optional[float] = None,
|
mix_liquid_height: Optional[float] = None,
|
||||||
delays: Optional[List[int]] = None,
|
delays: Optional[List[int]] = None,
|
||||||
none_keys: List[str] = []
|
none_keys: List[str] = [],
|
||||||
):
|
):
|
||||||
"""Transfer liquid from each *source* well/plate to the corresponding *target*.
|
"""Transfer liquid from each *source* well/plate to the corresponding *target*.
|
||||||
|
|
||||||
@@ -201,14 +210,15 @@ class LiquidHandlerAbstract(LiquidHandler):
|
|||||||
# 96‑channel head mode
|
# 96‑channel head mode
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
if is_96_well:
|
if is_96_well:
|
||||||
pass # This mode is not verified
|
pass # This mode is not verified
|
||||||
else:
|
else:
|
||||||
if not (len(asp_vols) == len(sources) and len(dis_vols) == len(targets)):
|
if not (len(asp_vols) == len(sources) and len(dis_vols) == len(targets)):
|
||||||
raise ValueError("`sources`, `targets`, and `vols` must have the same length.")
|
raise ValueError("`sources`, `targets`, and `vols` must have the same length.")
|
||||||
|
|
||||||
tip_iter = self.iter_tips(tip_racks)
|
tip_iter = self.iter_tips(tip_racks)
|
||||||
for src, tgt, asp_vol, asp_flow_rate, dis_vol, dis_flow_rate in (
|
for src, tgt, asp_vol, asp_flow_rate, dis_vol, dis_flow_rate in zip(
|
||||||
zip(sources, targets, asp_vols, asp_flow_rates, dis_vols, dis_flow_rates)):
|
sources, targets, asp_vols, asp_flow_rates, dis_vols, dis_flow_rates
|
||||||
|
):
|
||||||
tip = next(tip_iter)
|
tip = next(tip_iter)
|
||||||
await self.pick_up_tips(tip)
|
await self.pick_up_tips(tip)
|
||||||
# Aspirate from source
|
# Aspirate from source
|
||||||
@@ -247,9 +257,9 @@ class LiquidHandlerAbstract(LiquidHandler):
|
|||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
raise RuntimeError(f"Liquid transfer failed: {exc}") from exc
|
raise RuntimeError(f"Liquid transfer failed: {exc}") from exc
|
||||||
|
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
# Helper utilities
|
# Helper utilities
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
|
|
||||||
async def custom_delay(self, seconds=0, msg=None):
|
async def custom_delay(self, seconds=0, msg=None):
|
||||||
"""
|
"""
|
||||||
@@ -266,28 +276,26 @@ class LiquidHandlerAbstract(LiquidHandler):
|
|||||||
print(f"Done: {msg}")
|
print(f"Done: {msg}")
|
||||||
print(f"Current time: {time.strftime('%H:%M:%S')}")
|
print(f"Current time: {time.strftime('%H:%M:%S')}")
|
||||||
|
|
||||||
async def touch_tip(self,
|
async def touch_tip(self, targets: Sequence[Container]):
|
||||||
targets: Sequence[Container],
|
|
||||||
):
|
|
||||||
"""Touch the tip to the side of the well."""
|
"""Touch the tip to the side of the well."""
|
||||||
await self.aspirate(
|
await self.aspirate(
|
||||||
resources=[targets],
|
resources=[targets],
|
||||||
vols=[0],
|
vols=[0],
|
||||||
use_channels=None,
|
use_channels=None,
|
||||||
flow_rates=None,
|
flow_rates=None,
|
||||||
offsets=[Coordinate(x=-targets.get_size_x()/2,y=0,z=0)],
|
offsets=[Coordinate(x=-targets.get_size_x() / 2, y=0, z=0)],
|
||||||
liquid_height=None,
|
liquid_height=None,
|
||||||
blow_out_air_volume=None
|
blow_out_air_volume=None,
|
||||||
)
|
)
|
||||||
#await self.custom_delay(seconds=1) # In the simulation, we do not need to wait
|
# await self.custom_delay(seconds=1) # In the simulation, we do not need to wait
|
||||||
await self.aspirate(
|
await self.aspirate(
|
||||||
resources=[targets],
|
resources=[targets],
|
||||||
vols=[0],
|
vols=[0],
|
||||||
use_channels=None,
|
use_channels=None,
|
||||||
flow_rates=None,
|
flow_rates=None,
|
||||||
offsets=[Coordinate(x=targets.get_size_x()/2,y=0,z=0)],
|
offsets=[Coordinate(x=targets.get_size_x() / 2, y=0, z=0)],
|
||||||
liquid_height=None,
|
liquid_height=None,
|
||||||
blow_out_air_volume=None
|
blow_out_air_volume=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def mix(
|
async def mix(
|
||||||
@@ -298,9 +306,9 @@ class LiquidHandlerAbstract(LiquidHandler):
|
|||||||
height_to_bottom: Optional[float] = None,
|
height_to_bottom: Optional[float] = None,
|
||||||
offsets: Optional[Coordinate] = None,
|
offsets: Optional[Coordinate] = None,
|
||||||
mix_rate: Optional[float] = None,
|
mix_rate: Optional[float] = None,
|
||||||
none_keys: List[str] = []
|
none_keys: List[str] = [],
|
||||||
):
|
):
|
||||||
if mix_time is None: # No mixing required
|
if mix_time is None: # No mixing required
|
||||||
return
|
return
|
||||||
"""Mix the liquid in the target wells."""
|
"""Mix the liquid in the target wells."""
|
||||||
for _ in range(mix_time):
|
for _ in range(mix_time):
|
||||||
@@ -333,7 +341,7 @@ class LiquidHandlerAbstract(LiquidHandler):
|
|||||||
tip_iter = self.iter_tips(tip_racks)
|
tip_iter = self.iter_tips(tip_racks)
|
||||||
self.current_tip = tip_iter
|
self.current_tip = tip_iter
|
||||||
|
|
||||||
async def move_to(self, well: Well, dis_to_top: float = 0 , channel: int = 0):
|
async def move_to(self, well: Well, dis_to_top: float = 0, channel: int = 0):
|
||||||
"""
|
"""
|
||||||
Move a single channel to a specific well with a given z-height.
|
Move a single channel to a specific well with a given z-height.
|
||||||
|
|
||||||
@@ -352,4 +360,3 @@ class LiquidHandlerAbstract(LiquidHandler):
|
|||||||
await self.move_channel_x(channel, abs_loc.x)
|
await self.move_channel_x(channel, abs_loc.x)
|
||||||
await self.move_channel_y(channel, abs_loc.y)
|
await self.move_channel_y(channel, abs_loc.y)
|
||||||
await self.move_channel_z(channel, abs_loc.z + well_height + dis_to_top)
|
await self.move_channel_z(channel, abs_loc.z + well_height + dis_to_top)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,154 @@
|
|||||||
|
|
||||||
|
import json
|
||||||
|
from typing import Sequence, Optional, List, Union, Literal
|
||||||
|
json_path = "/Users/guangxinzhang/Documents/Deep Potential/opentrons/convert/protocols/enriched_steps/sci-lucif-assay4.json"
|
||||||
|
|
||||||
|
with open(json_path, "r") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
transfer_example = data[0]
|
||||||
|
#print(transfer_example)
|
||||||
|
|
||||||
|
|
||||||
|
temp_protocol = []
|
||||||
|
TipLocation = "BC1025F" # Assuming this is a fixed tip location for the transfer
|
||||||
|
sources = transfer_example["sources"] # Assuming sources is a list of Container objects
|
||||||
|
targets = transfer_example["targets"] # Assuming targets is a list of Container objects
|
||||||
|
tip_racks = transfer_example["tip_racks"] # Assuming tip_racks is a list of TipRack objects
|
||||||
|
asp_vols = transfer_example["asp_vols"] # Assuming asp_vols is a list of volumes
|
||||||
|
solvent = "PBS"
|
||||||
|
|
||||||
|
def transfer_liquid(
|
||||||
|
#self,
|
||||||
|
sources,#: Sequence[Container],
|
||||||
|
targets,#: Sequence[Container],
|
||||||
|
tip_racks,#: Sequence[TipRack],
|
||||||
|
TipLocation,
|
||||||
|
# *,
|
||||||
|
# use_channels: Optional[List[int]] = None,
|
||||||
|
asp_vols: Union[List[float], float],
|
||||||
|
solvent: Optional[str] = None,
|
||||||
|
# dis_vols: Union[List[float], float],
|
||||||
|
# asp_flow_rates: Optional[List[Optional[float]]] = None,
|
||||||
|
# dis_flow_rates: Optional[List[Optional[float]]] = None,
|
||||||
|
# offsets,#: Optional[List[]] = None,
|
||||||
|
# touch_tip: bool = False,
|
||||||
|
# liquid_height: Optional[List[Optional[float]]] = None,
|
||||||
|
# blow_out_air_volume: Optional[List[Optional[float]]] = None,
|
||||||
|
# spread: Literal["wide", "tight", "custom"] = "wide",
|
||||||
|
# is_96_well: bool = False,
|
||||||
|
# mix_stage: Optional[Literal["none", "before", "after", "both"]] = "none",
|
||||||
|
# mix_times,#: Optional[list() = None,
|
||||||
|
# mix_vol: Optional[int] = None,
|
||||||
|
# mix_rate: Optional[int] = None,
|
||||||
|
# mix_liquid_height: Optional[float] = None,
|
||||||
|
# delays: Optional[List[int]] = None,
|
||||||
|
# none_keys: List[str] = []
|
||||||
|
):
|
||||||
|
# -------- Build Biomek transfer step --------
|
||||||
|
# 1) Construct default parameter scaffold (values mirror Biomek “Transfer” block).
|
||||||
|
|
||||||
|
transfer_params = {
|
||||||
|
"Span8": False,
|
||||||
|
"Pod": "Pod1",
|
||||||
|
"items": {}, # to be filled below
|
||||||
|
"Wash": False,
|
||||||
|
"Dynamic?": True,
|
||||||
|
"AutoSelectActiveWashTechnique": False,
|
||||||
|
"ActiveWashTechnique": "",
|
||||||
|
"ChangeTipsBetweenDests": False,
|
||||||
|
"ChangeTipsBetweenSources": False,
|
||||||
|
"DefaultCaption": "", # filled after we know first pair/vol
|
||||||
|
"UseExpression": False,
|
||||||
|
"LeaveTipsOn": False,
|
||||||
|
"MandrelExpression": "",
|
||||||
|
"Repeats": "1",
|
||||||
|
"RepeatsByVolume": False,
|
||||||
|
"Replicates": "1",
|
||||||
|
"ShowTipHandlingDetails": False,
|
||||||
|
"ShowTransferDetails": True,
|
||||||
|
"Solvent": "Water",
|
||||||
|
"Span8Wash": False,
|
||||||
|
"Span8WashVolume": "2",
|
||||||
|
"Span8WasteVolume": "1",
|
||||||
|
"SplitVolume": False,
|
||||||
|
"SplitVolumeCleaning": False,
|
||||||
|
"Stop": "Destinations",
|
||||||
|
"TipLocation": "BC1025F",
|
||||||
|
"UseCurrentTips": False,
|
||||||
|
"UseDisposableTips": True,
|
||||||
|
"UseFixedTips": False,
|
||||||
|
"UseJIT": True,
|
||||||
|
"UseMandrelSelection": True,
|
||||||
|
"UseProbes": [True, True, True, True, True, True, True, True],
|
||||||
|
"WashCycles": "1",
|
||||||
|
"WashVolume": "110%",
|
||||||
|
"Wizard": False
|
||||||
|
}
|
||||||
|
|
||||||
|
items: dict = {}
|
||||||
|
for idx, (src, dst) in enumerate(zip(sources, targets)):
|
||||||
|
items[str(idx)] = {
|
||||||
|
"Source": str(src),
|
||||||
|
"Destination": str(dst),
|
||||||
|
"Volume": asp_vols[idx]
|
||||||
|
}
|
||||||
|
transfer_params["items"] = items
|
||||||
|
transfer_params["Solvent"] = solvent if solvent else "Water"
|
||||||
|
transfer_params["TipLocation"] = TipLocation
|
||||||
|
|
||||||
|
if len(tip_racks) == 1:
|
||||||
|
transfer_params['UseCurrentTips'] = True
|
||||||
|
elif len(tip_racks) > 1:
|
||||||
|
transfer_params["ChangeTipsBetweenDests"] = True
|
||||||
|
|
||||||
|
return transfer_params
|
||||||
|
|
||||||
|
action = transfer_liquid(sources=sources,targets=targets,tip_racks=tip_racks, asp_vols=asp_vols,solvent = solvent, TipLocation=TipLocation)
|
||||||
|
print(json.dumps(action,indent=2))
|
||||||
|
# print(action)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
"transfer": {
|
||||||
|
|
||||||
|
"items": {},
|
||||||
|
"Wash": false,
|
||||||
|
"Dynamic?": true,
|
||||||
|
"AutoSelectActiveWashTechnique": false,
|
||||||
|
"ActiveWashTechnique": "",
|
||||||
|
"ChangeTipsBetweenDests": true,
|
||||||
|
"ChangeTipsBetweenSources": false,
|
||||||
|
"DefaultCaption": "Transfer 100 µL from P13 to P3",
|
||||||
|
"UseExpression": false,
|
||||||
|
"LeaveTipsOn": false,
|
||||||
|
"MandrelExpression": "",
|
||||||
|
"Repeats": "1",
|
||||||
|
"RepeatsByVolume": false,
|
||||||
|
"Replicates": "1",
|
||||||
|
"ShowTipHandlingDetails": true,
|
||||||
|
"ShowTransferDetails": true,
|
||||||
|
|
||||||
|
"Span8Wash": false,
|
||||||
|
"Span8WashVolume": "2",
|
||||||
|
"Span8WasteVolume": "1",
|
||||||
|
"SplitVolume": false,
|
||||||
|
"SplitVolumeCleaning": false,
|
||||||
|
"Stop": "Destinations",
|
||||||
|
"TipLocation": "BC1025F",
|
||||||
|
"UseCurrentTips": false,
|
||||||
|
"UseDisposableTips": false,
|
||||||
|
"UseFixedTips": false,
|
||||||
|
"UseJIT": true,
|
||||||
|
"UseMandrelSelection": true,
|
||||||
|
"UseProbes": [true, true, true, true, true, true, true, true],
|
||||||
|
"WashCycles": "3",
|
||||||
|
"WashVolume": "110%",
|
||||||
|
"Wizard": false
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -22,8 +22,8 @@ liquid_handler:
|
|||||||
is_96_well: is_96_well
|
is_96_well: is_96_well
|
||||||
top: top
|
top: top
|
||||||
none_keys: none_keys
|
none_keys: none_keys
|
||||||
feedback: { }
|
feedback: {}
|
||||||
result: { }
|
result: {}
|
||||||
add_liquid:
|
add_liquid:
|
||||||
type: LiquidHandlerAdd
|
type: LiquidHandlerAdd
|
||||||
goal:
|
goal:
|
||||||
@@ -43,8 +43,8 @@ liquid_handler:
|
|||||||
mix_rate: mix_rate
|
mix_rate: mix_rate
|
||||||
mix_liquid_height: mix_liquid_height
|
mix_liquid_height: mix_liquid_height
|
||||||
none_keys: none_keys
|
none_keys: none_keys
|
||||||
feedback: { }
|
feedback: {}
|
||||||
result: { }
|
result: {}
|
||||||
transfer_liquid:
|
transfer_liquid:
|
||||||
type: LiquidHandlerTransfer
|
type: LiquidHandlerTransfer
|
||||||
goal:
|
goal:
|
||||||
@@ -69,8 +69,8 @@ liquid_handler:
|
|||||||
mix_liquid_height: mix_liquid_height
|
mix_liquid_height: mix_liquid_height
|
||||||
delays: delays
|
delays: delays
|
||||||
none_keys: none_keys
|
none_keys: none_keys
|
||||||
feedback: { }
|
feedback: {}
|
||||||
result: { }
|
result: {}
|
||||||
mix:
|
mix:
|
||||||
type: LiquidHandlerMix
|
type: LiquidHandlerMix
|
||||||
goal:
|
goal:
|
||||||
@@ -81,16 +81,16 @@ liquid_handler:
|
|||||||
offsets: offsets
|
offsets: offsets
|
||||||
mix_rate: mix_rate
|
mix_rate: mix_rate
|
||||||
none_keys: none_keys
|
none_keys: none_keys
|
||||||
feedback: { }
|
feedback: {}
|
||||||
result: { }
|
result: {}
|
||||||
move_to:
|
move_to:
|
||||||
type: LiquidHandlerMoveTo
|
type: LiquidHandlerMoveTo
|
||||||
goal:
|
goal:
|
||||||
well: well
|
well: well
|
||||||
dis_to_top: dis_to_top
|
dis_to_top: dis_to_top
|
||||||
channel: channel
|
channel: channel
|
||||||
feedback: { }
|
feedback: {}
|
||||||
result: { }
|
result: {}
|
||||||
aspirate:
|
aspirate:
|
||||||
type: LiquidHandlerAspirate
|
type: LiquidHandlerAspirate
|
||||||
goal:
|
goal:
|
||||||
@@ -272,3 +272,60 @@ liquid_handler.revvity:
|
|||||||
status: status
|
status: status
|
||||||
result:
|
result:
|
||||||
success: success
|
success: success
|
||||||
|
|
||||||
|
liquid_handler.biomek:
|
||||||
|
description: Biomek液体处理器设备,基于pylabrobot控制
|
||||||
|
icon: icon_yiyezhan.webp
|
||||||
|
class:
|
||||||
|
module: unilabos.devices.liquid_handling.biomek:LiquidHandlerBiomek
|
||||||
|
type: python
|
||||||
|
status_types: {}
|
||||||
|
action_value_mappings:
|
||||||
|
create_protocol:
|
||||||
|
type: LiquidHandlerProtocolCreation
|
||||||
|
goal:
|
||||||
|
protocol_name: protocol_name
|
||||||
|
protocol_description: protocol_description
|
||||||
|
protocol_version: protocol_version
|
||||||
|
protocol_author: protocol_author
|
||||||
|
protocol_date: protocol_date
|
||||||
|
protocol_type: protocol_type
|
||||||
|
none_keys: none_keys
|
||||||
|
feedback: {}
|
||||||
|
result: {}
|
||||||
|
run_protocol:
|
||||||
|
type: EmptyIn
|
||||||
|
goal: {}
|
||||||
|
feedback: {}
|
||||||
|
result: {}
|
||||||
|
transfer_liquid:
|
||||||
|
type: LiquidHandlerTransfer
|
||||||
|
goal:
|
||||||
|
asp_vols: asp_vols
|
||||||
|
dis_vols: dis_vols
|
||||||
|
sources: sources
|
||||||
|
targets: targets
|
||||||
|
tip_racks: tip_racks
|
||||||
|
use_channels: use_channels
|
||||||
|
asp_flow_rates: asp_flow_rates
|
||||||
|
dis_flow_rates: dis_flow_rates
|
||||||
|
offsets: offsets
|
||||||
|
touch_tip: touch_tip
|
||||||
|
liquid_height: liquid_height
|
||||||
|
blow_out_air_volume: blow_out_air_volume
|
||||||
|
spread: spread
|
||||||
|
is_96_well: is_96_well
|
||||||
|
mix_stage: mix_stage
|
||||||
|
mix_times: mix_times
|
||||||
|
mix_vol: mix_vol
|
||||||
|
mix_rate: mix_rate
|
||||||
|
mix_liquid_height: mix_liquid_height
|
||||||
|
delays: delays
|
||||||
|
none_keys: none_keys
|
||||||
|
feedback: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
additionalProperties: false
|
||||||
|
|||||||
@@ -349,6 +349,20 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
response = rclient.call(request)
|
response = rclient.call(request)
|
||||||
# 应该先add_resource了
|
# 应该先add_resource了
|
||||||
res.response = "OK"
|
res.response = "OK"
|
||||||
|
# 如果driver自己就有assign的方法,那就使用driver自己的assign方法
|
||||||
|
if hasattr(self.driver_instance, "create_resource"):
|
||||||
|
create_resource_func = getattr(self.driver_instance, "create_resource")
|
||||||
|
create_resource_func(
|
||||||
|
resource_tracker=self.resource_tracker,
|
||||||
|
resources=request.resources,
|
||||||
|
bind_parent_id=bind_parent_id,
|
||||||
|
bind_location=location,
|
||||||
|
liquid_input_slot=LIQUID_INPUT_SLOT,
|
||||||
|
liquid_type=ADD_LIQUID_TYPE,
|
||||||
|
liquid_volume=LIQUID_VOLUME,
|
||||||
|
slot_on_deck=slot,
|
||||||
|
)
|
||||||
|
return res
|
||||||
# 接下来该根据bind_parent_id进行assign了,目前只有plr可以进行assign,不然没有办法输入到物料系统中
|
# 接下来该根据bind_parent_id进行assign了,目前只有plr可以进行assign,不然没有办法输入到物料系统中
|
||||||
resource = self.resource_tracker.figure_resource({"name": bind_parent_id})
|
resource = self.resource_tracker.figure_resource({"name": bind_parent_id})
|
||||||
# request.resources = [convert_to_ros_msg(Resource, resources)]
|
# request.resources = [convert_to_ros_msg(Resource, resources)]
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ set(action_files
|
|||||||
"action/HeatChillStart.action"
|
"action/HeatChillStart.action"
|
||||||
"action/HeatChillStop.action"
|
"action/HeatChillStop.action"
|
||||||
|
|
||||||
|
"action/LiquidHandlerProtocolCreation.action"
|
||||||
|
|
||||||
"action/LiquidHandlerAspirate.action"
|
"action/LiquidHandlerAspirate.action"
|
||||||
"action/LiquidHandlerDiscardTips.action"
|
"action/LiquidHandlerDiscardTips.action"
|
||||||
"action/LiquidHandlerDispense.action"
|
"action/LiquidHandlerDispense.action"
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
string protocol_name
|
||||||
|
string protocol_description
|
||||||
|
string protocol_version
|
||||||
|
string protocol_author
|
||||||
|
string protocol_date
|
||||||
|
string protocol_type
|
||||||
|
string[] none_keys
|
||||||
|
---
|
||||||
|
---
|
||||||
Reference in New Issue
Block a user