mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2026-02-04 13:25:13 +00:00
Compare commits
20 Commits
v0.10.3
...
b957ad2f71
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b957ad2f71 | ||
|
|
e1a7c3a103 | ||
|
|
e63c15997c | ||
|
|
c5a495f409 | ||
|
|
5b240cb0ea | ||
|
|
147b8f47c0 | ||
|
|
6d2489af5f | ||
|
|
807dcdd226 | ||
|
|
8a29bc5597 | ||
|
|
6f6c70ee57 | ||
|
|
478a85951c | ||
|
|
0f2555c90c | ||
|
|
d2dda6ee03 | ||
|
|
208540b307 | ||
|
|
cb7c56a1d9 | ||
|
|
ea2e9c3e3a | ||
|
|
0452a68180 | ||
|
|
90a0f3db9b | ||
|
|
055d120ba8 | ||
|
|
a948f09f60 |
834
unilabos/devices/liquid_handling/biomek.py
Normal file
834
unilabos/devices/liquid_handling/biomek.py
Normal file
@@ -0,0 +1,834 @@
|
||||
import requests
|
||||
from typing import List, Sequence, Optional, Union, Literal
|
||||
from geometry_msgs.msg import Point
|
||||
#from unilabos_msgs.msg import Resource
|
||||
|
||||
from pylabrobot.resources import (
|
||||
TipRack,
|
||||
Container,
|
||||
Coordinate,
|
||||
)
|
||||
|
||||
from unilabos.ros.nodes.resource_tracker import DeviceNodeResourceTracker # type: ignore
|
||||
from .liquid_handler_abstract import LiquidHandlerAbstract
|
||||
|
||||
import json
|
||||
from typing import Sequence, Optional, List, Union, Literal
|
||||
|
||||
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路径
|
||||
self.aspirate_techniques = {
|
||||
'MC P300 high':{
|
||||
"Solvent": "Water",
|
||||
}
|
||||
}
|
||||
self.dispense_techniques = {
|
||||
'MC P300 high':{
|
||||
"Span8": False,
|
||||
"Pod": "Pod1",
|
||||
"Wash": False,
|
||||
"Dynamic?": True,
|
||||
"AutoSelectActiveWashTechnique": False,
|
||||
"ActiveWashTechnique": "",
|
||||
"ChangeTipsBetweenDests": True,
|
||||
"ChangeTipsBetweenSources": False,
|
||||
"DefaultCaption": "",
|
||||
"UseExpression": False,
|
||||
"LeaveTipsOn": False,
|
||||
"MandrelExpression": "",
|
||||
"Repeats": "1",
|
||||
"RepeatsByVolume": False,
|
||||
"Replicates": "1",
|
||||
"ShowTipHandlingDetails": False,
|
||||
"ShowTransferDetails": True,
|
||||
"Span8Wash": False,
|
||||
"Span8WashVolume": "2",
|
||||
"Span8WasteVolume": "1",
|
||||
"SplitVolume": False,
|
||||
"SplitVolumeCleaning": False,
|
||||
"Stop": "Destinations",
|
||||
"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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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] = [],
|
||||
):
|
||||
"""
|
||||
创建一个新的协议。
|
||||
|
||||
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,
|
||||
"type": protocol_type,
|
||||
},
|
||||
"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:需要对好接口,下面这个是临时的
|
||||
for resource in resources:
|
||||
res_id = resource.id
|
||||
class_name = resource.class_name
|
||||
parent = bind_parent_id
|
||||
bind_locations = Coordinate.from_point(resource.bind_location)
|
||||
liquid_input_slot = liquid_input_slot
|
||||
liquid_type = liquid_type
|
||||
liquid_volume = liquid_volume
|
||||
slot_on_deck = slot_on_deck
|
||||
|
||||
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],
|
||||
*,
|
||||
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[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": "Well Content",
|
||||
"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": dis_vols[idx]
|
||||
}
|
||||
transfer_params["items"] = items
|
||||
|
||||
transfer_params["Solvent"] = solvent if solvent else "Water"
|
||||
TipLocation = tip_racks[0].name
|
||||
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
|
||||
|
||||
def transfer_biomek(
|
||||
self,
|
||||
source: str,
|
||||
target: str,
|
||||
tip_rack: str,
|
||||
volume: float,
|
||||
aspirate_techniques: str,
|
||||
dispense_techniques: str,
|
||||
):
|
||||
"""
|
||||
处理Biomek的液体转移操作。
|
||||
|
||||
"""
|
||||
|
||||
asp_params = self.aspirate_techniques.get(aspirate_techniques, {})
|
||||
dis_params = self.dispense_techniques.get(dispense_techniques, {})
|
||||
|
||||
transfer_params = {
|
||||
"Span8": False,
|
||||
"Pod": "Pod1",
|
||||
"items": {},
|
||||
"Wash": False,
|
||||
"Dynamic?": True,
|
||||
"AutoSelectActiveWashTechnique": False,
|
||||
"ActiveWashTechnique": "",
|
||||
"ChangeTipsBetweenDests": False,
|
||||
"ChangeTipsBetweenSources": True,
|
||||
"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 = {}
|
||||
items["Source"] = source
|
||||
items["Destination"] = target
|
||||
items["Volume"] = volume
|
||||
transfer_params["items"] = items
|
||||
transfer_params["Solvent"] = asp_params['Solvent']
|
||||
transfer_params["TipLocation"] = tip_rack
|
||||
transfer_params.update(asp_params)
|
||||
transfer_params.update(dis_params)
|
||||
self.temp_protocol["steps"].append(transfer_params)
|
||||
|
||||
return
|
||||
|
||||
steps_info = '''
|
||||
{
|
||||
"steps": [
|
||||
{
|
||||
"step_number": 1,
|
||||
"operation": "transfer",
|
||||
"description": "转移PCR产物或酶促反应液至0.05ml 96孔板中",
|
||||
"parameters": {
|
||||
"source": "P1",
|
||||
"target": "P11",
|
||||
"tip_rack": "BC230",
|
||||
"volume": 50
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 2,
|
||||
"operation": "transfer",
|
||||
"description": "加入2倍体积Bind Beads BC至产物中",
|
||||
"parameters": {
|
||||
"source": "P2",
|
||||
"target": "P11",
|
||||
"tip_rack": "BC230",
|
||||
"volume": 100
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 3,
|
||||
"operation": "move_labware",
|
||||
"description": "移动P11至Orbital1用于振荡混匀",
|
||||
"parameters": {
|
||||
"source": "P11",
|
||||
"target": "Orbital1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 4,
|
||||
"operation": "oscillation",
|
||||
"description": "在Orbital1上振荡混匀Bind Beads BC与PCR产物(700-900rpm,300秒)",
|
||||
"parameters": {
|
||||
"rpm": 800,
|
||||
"time": 300
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 5,
|
||||
"operation": "move_labware",
|
||||
"description": "移动混匀后的板回P11",
|
||||
"parameters": {
|
||||
"source": "Orbital1",
|
||||
"target": "P11"
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 6,
|
||||
"operation": "move_labware",
|
||||
"description": "将P11移动到磁力架(P12)吸附3分钟",
|
||||
"parameters": {
|
||||
"source": "P11",
|
||||
"target": "P12"
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 7,
|
||||
"operation": "incubation",
|
||||
"description": "磁力架上室温静置3分钟完成吸附",
|
||||
"parameters": {
|
||||
"time": 180
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 8,
|
||||
"operation": "transfer",
|
||||
"description": "去除上清液至废液槽",
|
||||
"parameters": {
|
||||
"source": "P12",
|
||||
"target": "P22",
|
||||
"tip_rack": "BC230",
|
||||
"volume": 150
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 9,
|
||||
"operation": "transfer",
|
||||
"description": "加入300-500μl 75%乙醇清洗",
|
||||
"parameters": {
|
||||
"source": "P3",
|
||||
"target": "P12",
|
||||
"tip_rack": "BC230",
|
||||
"volume": 400
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 10,
|
||||
"operation": "move_labware",
|
||||
"description": "移动清洗板到Orbital1进行振荡",
|
||||
"parameters": {
|
||||
"source": "P12",
|
||||
"target": "Orbital1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 11,
|
||||
"operation": "oscillation",
|
||||
"description": "乙醇清洗液振荡混匀(700-900rpm, 45秒)",
|
||||
"parameters": {
|
||||
"rpm": 800,
|
||||
"time": 45
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 12,
|
||||
"operation": "move_labware",
|
||||
"description": "振荡后将板移回磁力架P12吸附",
|
||||
"parameters": {
|
||||
"source": "Orbital1",
|
||||
"target": "P12"
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 13,
|
||||
"operation": "incubation",
|
||||
"description": "吸附3分钟",
|
||||
"parameters": {
|
||||
"time": 180
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 14,
|
||||
"operation": "transfer",
|
||||
"description": "去除乙醇上清液至废液槽",
|
||||
"parameters": {
|
||||
"source": "P12",
|
||||
"target": "P22",
|
||||
"tip_rack": "BC230",
|
||||
"volume": 400
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 15,
|
||||
"operation": "transfer",
|
||||
"description": "第二次加入300-500μl 75%乙醇清洗",
|
||||
"parameters": {
|
||||
"source": "P3",
|
||||
"target": "P12",
|
||||
"tip_rack": "BC230",
|
||||
"volume": 400
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 16,
|
||||
"operation": "move_labware",
|
||||
"description": "再次移动清洗板到Orbital1振荡",
|
||||
"parameters": {
|
||||
"source": "P12",
|
||||
"target": "Orbital1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 17,
|
||||
"operation": "oscillation",
|
||||
"description": "再次乙醇清洗液振荡混匀(700-900rpm, 45秒)",
|
||||
"parameters": {
|
||||
"rpm": 800,
|
||||
"time": 45
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 18,
|
||||
"operation": "move_labware",
|
||||
"description": "振荡后板送回磁力架P12吸附",
|
||||
"parameters": {
|
||||
"source": "Orbital1",
|
||||
"target": "P12"
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 19,
|
||||
"operation": "incubation",
|
||||
"description": "再次吸附3分钟",
|
||||
"parameters": {
|
||||
"time": 180
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 20,
|
||||
"operation": "transfer",
|
||||
"description": "去除乙醇上清液至废液槽",
|
||||
"parameters": {
|
||||
"source": "P12",
|
||||
"target": "P22",
|
||||
"tip_rack": "BC230",
|
||||
"volume": 400
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 21,
|
||||
"operation": "incubation",
|
||||
"description": "空气干燥15分钟",
|
||||
"parameters": {
|
||||
"time": 900
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 22,
|
||||
"operation": "transfer",
|
||||
"description": "加30-50μl Elution Buffer洗脱",
|
||||
"parameters": {
|
||||
"source": "P4",
|
||||
"target": "P12",
|
||||
"tip_rack": "BC230",
|
||||
"volume": 40
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 23,
|
||||
"operation": "move_labware",
|
||||
"description": "移动到Orbital1振荡混匀(60秒)",
|
||||
"parameters": {
|
||||
"source": "P12",
|
||||
"target": "Orbital1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 24,
|
||||
"operation": "oscillation",
|
||||
"description": "Elution Buffer振荡混匀(700-900rpm, 60秒)",
|
||||
"parameters": {
|
||||
"rpm": 800,
|
||||
"time": 60
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 25,
|
||||
"operation": "move_labware",
|
||||
"description": "振荡后送回磁力架P12",
|
||||
"parameters": {
|
||||
"source": "Orbital1",
|
||||
"target": "P12"
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 26,
|
||||
"operation": "incubation",
|
||||
"description": "室温静置3分钟(洗脱反应)",
|
||||
"parameters": {
|
||||
"time": 180
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 27,
|
||||
"operation": "transfer",
|
||||
"description": "将上清液(DNA)转移到新板(P13)",
|
||||
"parameters": {
|
||||
"source": "P12",
|
||||
"target": "P13",
|
||||
"tip_rack": "BC230",
|
||||
"volume": 40
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
'''
|
||||
|
||||
|
||||
labware_with_liquid = '''
|
||||
[ {
|
||||
"id": "stock plate on P1",
|
||||
"parent": "deck",
|
||||
"slot_on_deck": "P1",
|
||||
"class_name": "nest_12_reservoir_15ml",
|
||||
"liquid_type": [
|
||||
"master_mix"
|
||||
],
|
||||
"liquid_volume": [10000],
|
||||
"liquid_input_wells": [
|
||||
"A1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "Tip Rack BC230 TL2",
|
||||
"parent": "deck",
|
||||
"slot_on_deck": "TL2",
|
||||
"class_name": "BC230",
|
||||
"liquid_type": [],
|
||||
"liquid_volume": [],
|
||||
"liquid_input_wells": [
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "Tip Rack BC230 on TL3",
|
||||
"parent": "deck",
|
||||
"slot_on_deck": "TL3",
|
||||
"class_name": "BC230",
|
||||
"liquid_type": [],
|
||||
"liquid_volume": [],
|
||||
"liquid_input_wells": [
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "Tip Rack BC230 on TL4",
|
||||
"parent": "deck",
|
||||
"slot_on_deck": "TL4",
|
||||
"class_name": "BC230",
|
||||
"liquid_type": [],
|
||||
"liquid_volume": [],
|
||||
"liquid_input_wells": [
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "Tip Rack BC230 on TL5",
|
||||
"parent": "deck",
|
||||
"slot_on_deck": "TL5",
|
||||
"class_name": "BC230",
|
||||
"liquid_type": [],
|
||||
"liquid_volume": [],
|
||||
"liquid_input_wells": [
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "Tip Rack BC230 on P5",
|
||||
"parent": "deck",
|
||||
"slot_on_deck": "P5",
|
||||
"class_name": "BC230",
|
||||
"liquid_type": [],
|
||||
"liquid_volume": [],
|
||||
"liquid_input_wells": [
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "Tip Rack BC230 on P6",
|
||||
"parent": "deck",
|
||||
"slot_on_deck": "P6",
|
||||
"class_name": "BC230",
|
||||
"liquid_type": [],
|
||||
"liquid_volume": [],
|
||||
"liquid_input_wells": [
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "Tip Rack BC230 on P7",
|
||||
"parent": "deck",
|
||||
"slot_on_deck": "P7",
|
||||
"class_name": "BC230",
|
||||
"liquid_type": [],
|
||||
"liquid_volume": [],
|
||||
"liquid_input_wells": [
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "Tip Rack BC230 on P8",
|
||||
"parent": "deck",
|
||||
"slot_on_deck": "P8",
|
||||
"class_name": "BC230",
|
||||
"liquid_type": [],
|
||||
"liquid_volume": [],
|
||||
"liquid_input_wells": [
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "Tip Rack BC230 P16",
|
||||
"parent": "deck",
|
||||
"slot_on_deck": "P16",
|
||||
"class_name": "BC230",
|
||||
"liquid_type": [],
|
||||
"liquid_volume": [],
|
||||
"liquid_input_wells": [
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "stock plate on 4",
|
||||
"parent": "deck",
|
||||
"slot_on_deck": "P2",
|
||||
"class_name": "nest_12_reservoir_15ml",
|
||||
"liquid_type": [
|
||||
"bind beads"
|
||||
],
|
||||
"liquid_volume": [10000],
|
||||
"liquid_input_wells": [
|
||||
"A1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "stock plate on P2",
|
||||
"parent": "deck",
|
||||
"slot_on_deck": "P2",
|
||||
"class_name": "nest_12_reservoir_15ml",
|
||||
"liquid_type": [
|
||||
"bind beads"
|
||||
],
|
||||
"liquid_volume": [10000],
|
||||
"liquid_input_wells": [
|
||||
"A1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "stock plate on P3",
|
||||
"parent": "deck",
|
||||
"slot_on_deck": "P3",
|
||||
"class_name": "nest_12_reservoir_15ml",
|
||||
"liquid_type": [
|
||||
"ethyl alcohol"
|
||||
],
|
||||
"liquid_volume": [10000],
|
||||
"liquid_input_wells": [
|
||||
"A1"
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"id": "oscillation",
|
||||
"parent": "deck",
|
||||
"slot_on_deck": "Orbital1",
|
||||
"class_name": "Orbital",
|
||||
"liquid_type": [],
|
||||
"liquid_volume": [],
|
||||
"liquid_input_wells": [
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "working plate on P11",
|
||||
"parent": "deck",
|
||||
"slot_on_deck": "P11",
|
||||
"class_name": "NEST 2ml Deep Well Plate",
|
||||
"liquid_type": [
|
||||
],
|
||||
"liquid_volume": [],
|
||||
"liquid_input_wells": [
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "magnetics module on P12",
|
||||
"parent": "deck",
|
||||
"slot_on_deck": "P12",
|
||||
"class_name": "magnetics module",
|
||||
"liquid_type": [],
|
||||
"liquid_volume": [],
|
||||
"liquid_input_wells": [
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "working plate on P13",
|
||||
"parent": "deck",
|
||||
"slot_on_deck": "P13",
|
||||
"class_name": "NEST 2ml Deep Well Plate",
|
||||
"liquid_type": [
|
||||
],
|
||||
"liquid_volume": [],
|
||||
"liquid_input_wells": [
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "waste on P22",
|
||||
"parent": "deck",
|
||||
"slot_on_deck": "P22",
|
||||
"class_name": "nest_1_reservoir_195ml",
|
||||
"liquid_type": [
|
||||
],
|
||||
"liquid_volume": [],
|
||||
"liquid_input_wells": [
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
'''
|
||||
|
||||
handler = LiquidHandlerBiomek()
|
||||
handler.temp_protocol = {
|
||||
"meta": {},
|
||||
"labwares": [],
|
||||
"steps": []
|
||||
}
|
||||
|
||||
input_steps = json.loads(steps_info)
|
||||
labwares = json.loads(labware_with_liquid)
|
||||
|
||||
for step in input_steps['steps']:
|
||||
if step['operation'] != 'transfer':
|
||||
continue
|
||||
parameters = step['parameters']
|
||||
tip_rack=parameters['tip_rack']
|
||||
# 找到labwares中与tip_rack匹配的项的id
|
||||
tip_rack_id = [lw['id'] for lw in labwares if lw['class_name'] == tip_rack][0]
|
||||
|
||||
handler.transfer_biomek(source=parameters['source'],
|
||||
target=parameters['target'],
|
||||
volume=parameters['volume'],
|
||||
tip_rack=tip_rack_id,
|
||||
aspirate_techniques='MC P300 high',
|
||||
dispense_techniques='MC P300 high'
|
||||
)
|
||||
print(json.dumps(handler.temp_protocol['steps'],indent=4, ensure_ascii=False))
|
||||
|
||||
747
unilabos/devices/liquid_handling/biomek_test.py
Normal file
747
unilabos/devices/liquid_handling/biomek_test.py
Normal file
@@ -0,0 +1,747 @@
|
||||
import requests
|
||||
from typing import List, Sequence, Optional, Union, Literal
|
||||
# from geometry_msgs.msg import Point
|
||||
# from unilabos_msgs.msg import Resource
|
||||
from pylabrobot.resources import (
|
||||
Resource,
|
||||
TipRack,
|
||||
Container,
|
||||
Coordinate,
|
||||
Well
|
||||
)
|
||||
# from unilabos.ros.nodes.resource_tracker import DeviceNodeResourceTracker # type: ignore
|
||||
# from .liquid_handler_abstract import LiquidHandlerAbstract
|
||||
|
||||
import json
|
||||
from typing import Sequence, Optional, List, Union, Literal
|
||||
|
||||
steps_info = '''
|
||||
{
|
||||
"steps": [
|
||||
{
|
||||
"step_number": 1,
|
||||
"operation": "transfer",
|
||||
"description": "转移PCR产物或酶促反应液至0.05ml 96孔板中",
|
||||
"parameters": {
|
||||
"source": "P1",
|
||||
"target": "P11",
|
||||
"tip_rack": "BC230",
|
||||
"volume": 50
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 2,
|
||||
"operation": "transfer",
|
||||
"description": "加入2倍体积Bind Beads BC至产物中",
|
||||
"parameters": {
|
||||
"source": "P2",
|
||||
"target": "P11",
|
||||
"tip_rack": "BC230",
|
||||
"volume": 100
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 3,
|
||||
"operation": "move_labware",
|
||||
"description": "移动P11至Orbital1用于振荡混匀",
|
||||
"parameters": {
|
||||
"source": "P11",
|
||||
"target": "Orbital1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 4,
|
||||
"operation": "oscillation",
|
||||
"description": "在Orbital1上振荡混匀Bind Beads BC与PCR产物(700-900rpm,300秒)",
|
||||
"parameters": {
|
||||
"rpm": 800,
|
||||
"time": 300
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 5,
|
||||
"operation": "move_labware",
|
||||
"description": "移动混匀后的板回P11",
|
||||
"parameters": {
|
||||
"source": "Orbital1",
|
||||
"target": "P11"
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 6,
|
||||
"operation": "move_labware",
|
||||
"description": "将P11移动到磁力架(P12)吸附3分钟",
|
||||
"parameters": {
|
||||
"source": "P11",
|
||||
"target": "P12"
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 7,
|
||||
"operation": "incubation",
|
||||
"description": "磁力架上室温静置3分钟完成吸附",
|
||||
"parameters": {
|
||||
"time": 180
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 8,
|
||||
"operation": "transfer",
|
||||
"description": "去除上清液至废液槽",
|
||||
"parameters": {
|
||||
"source": "P12",
|
||||
"target": "P22",
|
||||
"tip_rack": "BC230",
|
||||
"volume": 150
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 9,
|
||||
"operation": "transfer",
|
||||
"description": "加入300-500μl 75%乙醇清洗",
|
||||
"parameters": {
|
||||
"source": "P3",
|
||||
"target": "P12",
|
||||
"tip_rack": "BC230",
|
||||
"volume": 400
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 10,
|
||||
"operation": "move_labware",
|
||||
"description": "移动清洗板到Orbital1进行振荡",
|
||||
"parameters": {
|
||||
"source": "P12",
|
||||
"target": "Orbital1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 11,
|
||||
"operation": "oscillation",
|
||||
"description": "乙醇清洗液振荡混匀(700-900rpm, 45秒)",
|
||||
"parameters": {
|
||||
"rpm": 800,
|
||||
"time": 45
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 12,
|
||||
"operation": "move_labware",
|
||||
"description": "振荡后将板移回磁力架P12吸附",
|
||||
"parameters": {
|
||||
"source": "Orbital1",
|
||||
"target": "P12"
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 13,
|
||||
"operation": "incubation",
|
||||
"description": "吸附3分钟",
|
||||
"parameters": {
|
||||
"time": 180
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 14,
|
||||
"operation": "transfer",
|
||||
"description": "去除乙醇上清液至废液槽",
|
||||
"parameters": {
|
||||
"source": "P12",
|
||||
"target": "P22",
|
||||
"tip_rack": "BC230",
|
||||
"volume": 400
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 15,
|
||||
"operation": "transfer",
|
||||
"description": "第二次加入300-500μl 75%乙醇清洗",
|
||||
"parameters": {
|
||||
"source": "P3",
|
||||
"target": "P12",
|
||||
"tip_rack": "BC230",
|
||||
"volume": 400
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 16,
|
||||
"operation": "move_labware",
|
||||
"description": "再次移动清洗板到Orbital1振荡",
|
||||
"parameters": {
|
||||
"source": "P12",
|
||||
"target": "Orbital1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 17,
|
||||
"operation": "oscillation",
|
||||
"description": "再次乙醇清洗液振荡混匀(700-900rpm, 45秒)",
|
||||
"parameters": {
|
||||
"rpm": 800,
|
||||
"time": 45
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 18,
|
||||
"operation": "move_labware",
|
||||
"description": "振荡后板送回磁力架P12吸附",
|
||||
"parameters": {
|
||||
"source": "Orbital1",
|
||||
"target": "P12"
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 19,
|
||||
"operation": "incubation",
|
||||
"description": "再次吸附3分钟",
|
||||
"parameters": {
|
||||
"time": 180
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 20,
|
||||
"operation": "transfer",
|
||||
"description": "去除乙醇上清液至废液槽",
|
||||
"parameters": {
|
||||
"source": "P12",
|
||||
"target": "P22",
|
||||
"tip_rack": "BC230",
|
||||
"volume": 400
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 21,
|
||||
"operation": "incubation",
|
||||
"description": "空气干燥15分钟",
|
||||
"parameters": {
|
||||
"time": 900
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 22,
|
||||
"operation": "transfer",
|
||||
"description": "加30-50μl Elution Buffer洗脱",
|
||||
"parameters": {
|
||||
"source": "P4",
|
||||
"target": "P12",
|
||||
"tip_rack": "BC230",
|
||||
"volume": 40
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 23,
|
||||
"operation": "move_labware",
|
||||
"description": "移动到Orbital1振荡混匀(60秒)",
|
||||
"parameters": {
|
||||
"source": "P12",
|
||||
"target": "Orbital1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 24,
|
||||
"operation": "oscillation",
|
||||
"description": "Elution Buffer振荡混匀(700-900rpm, 60秒)",
|
||||
"parameters": {
|
||||
"rpm": 800,
|
||||
"time": 60
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 25,
|
||||
"operation": "move_labware",
|
||||
"description": "振荡后送回磁力架P12",
|
||||
"parameters": {
|
||||
"source": "Orbital1",
|
||||
"target": "P12"
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 26,
|
||||
"operation": "incubation",
|
||||
"description": "室温静置3分钟(洗脱反应)",
|
||||
"parameters": {
|
||||
"time": 180
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_number": 27,
|
||||
"operation": "transfer",
|
||||
"description": "将上清液(DNA)转移到新板(P13)",
|
||||
"parameters": {
|
||||
"source": "P12",
|
||||
"target": "P13",
|
||||
"tip_rack": "BC230",
|
||||
"volume": 40
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
'''
|
||||
|
||||
|
||||
|
||||
#class LiquidHandlerBiomek(LiquidHandlerAbstract):
|
||||
|
||||
|
||||
class LiquidHandlerBiomek:
|
||||
"""
|
||||
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路径
|
||||
self.aspirate_techniques = {
|
||||
'MC P300 high':{
|
||||
"Solvent": "Water",
|
||||
}
|
||||
}
|
||||
self.dispense_techniques = {
|
||||
'MC P300 high':{
|
||||
"Span8": False,
|
||||
"Pod": "Pod1",
|
||||
"Wash": False,
|
||||
"Dynamic?": True,
|
||||
"AutoSelectActiveWashTechnique": False,
|
||||
"ActiveWashTechnique": "",
|
||||
"ChangeTipsBetweenDests": True,
|
||||
"ChangeTipsBetweenSources": False,
|
||||
"DefaultCaption": "",
|
||||
"UseExpression": False,
|
||||
"LeaveTipsOn": False,
|
||||
"MandrelExpression": "",
|
||||
"Repeats": "1",
|
||||
"RepeatsByVolume": False,
|
||||
"Replicates": "1",
|
||||
"ShowTipHandlingDetails": False,
|
||||
"ShowTransferDetails": True,
|
||||
"Span8Wash": False,
|
||||
"Span8WashVolume": "2",
|
||||
"Span8WasteVolume": "1",
|
||||
"SplitVolume": False,
|
||||
"SplitVolumeCleaning": False,
|
||||
"Stop": "Destinations",
|
||||
"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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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] = [],
|
||||
):
|
||||
"""
|
||||
创建一个新的协议。
|
||||
|
||||
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,
|
||||
"type": protocol_type,
|
||||
},
|
||||
"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,
|
||||
# res_id,
|
||||
# class_name,
|
||||
# bind_locations,
|
||||
# parent
|
||||
# ):
|
||||
# """
|
||||
# 创建一个新的资源。
|
||||
|
||||
# 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_biomek(
|
||||
self,
|
||||
source: str,
|
||||
target: str,
|
||||
tip_rack: str,
|
||||
volume: float,
|
||||
aspirate_techniques: str,
|
||||
dispense_techniques: str,
|
||||
):
|
||||
"""
|
||||
处理Biomek的液体转移操作。
|
||||
|
||||
"""
|
||||
|
||||
asp_params = self.aspirate_techniques.get(aspirate_techniques, {})
|
||||
dis_params = self.dispense_techniques.get(dispense_techniques, {})
|
||||
|
||||
transfer_params = {
|
||||
"Span8": False,
|
||||
"Pod": "Pod1",
|
||||
"items": {},
|
||||
"Wash": False,
|
||||
"Dynamic?": True,
|
||||
"AutoSelectActiveWashTechnique": False,
|
||||
"ActiveWashTechnique": "",
|
||||
"ChangeTipsBetweenDests": False,
|
||||
"ChangeTipsBetweenSources": True,
|
||||
"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 = {}
|
||||
items["Source"] = source
|
||||
items["Destination"] = target
|
||||
items["Volume"] = volume
|
||||
transfer_params["items"] = items
|
||||
transfer_params["Solvent"] = asp_params['Solvent']
|
||||
transfer_params["TipLocation"] = tip_rack
|
||||
transfer_params.update(asp_params)
|
||||
transfer_params.update(dis_params)
|
||||
self.temp_protocol["steps"].append(transfer_params)
|
||||
|
||||
return
|
||||
|
||||
labware_with_liquid = '''
|
||||
[ {
|
||||
"id": "stock plate on P1",
|
||||
"parent": "deck",
|
||||
"slot_on_deck": "P1",
|
||||
"class_name": "nest_12_reservoir_15ml",
|
||||
"liquid_type": [
|
||||
"master_mix"
|
||||
],
|
||||
"liquid_volume": [10000],
|
||||
"liquid_input_wells": [
|
||||
"A1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "Tip Rack BC230 TL2",
|
||||
"parent": "deck",
|
||||
"slot_on_deck": "TL2",
|
||||
"class_name": "BC230",
|
||||
"liquid_type": [],
|
||||
"liquid_volume": [],
|
||||
"liquid_input_wells": [
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "Tip Rack BC230 on TL3",
|
||||
"parent": "deck",
|
||||
"slot_on_deck": "TL3",
|
||||
"class_name": "BC230",
|
||||
"liquid_type": [],
|
||||
"liquid_volume": [],
|
||||
"liquid_input_wells": [
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "Tip Rack BC230 on TL4",
|
||||
"parent": "deck",
|
||||
"slot_on_deck": "TL4",
|
||||
"class_name": "BC230",
|
||||
"liquid_type": [],
|
||||
"liquid_volume": [],
|
||||
"liquid_input_wells": [
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "Tip Rack BC230 on TL5",
|
||||
"parent": "deck",
|
||||
"slot_on_deck": "TL5",
|
||||
"class_name": "BC230",
|
||||
"liquid_type": [],
|
||||
"liquid_volume": [],
|
||||
"liquid_input_wells": [
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "Tip Rack BC230 on P5",
|
||||
"parent": "deck",
|
||||
"slot_on_deck": "P5",
|
||||
"class_name": "BC230",
|
||||
"liquid_type": [],
|
||||
"liquid_volume": [],
|
||||
"liquid_input_wells": [
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "Tip Rack BC230 on P6",
|
||||
"parent": "deck",
|
||||
"slot_on_deck": "P6",
|
||||
"class_name": "BC230",
|
||||
"liquid_type": [],
|
||||
"liquid_volume": [],
|
||||
"liquid_input_wells": [
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "Tip Rack BC230 on P7",
|
||||
"parent": "deck",
|
||||
"slot_on_deck": "P7",
|
||||
"class_name": "BC230",
|
||||
"liquid_type": [],
|
||||
"liquid_volume": [],
|
||||
"liquid_input_wells": [
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "Tip Rack BC230 on P8",
|
||||
"parent": "deck",
|
||||
"slot_on_deck": "P8",
|
||||
"class_name": "BC230",
|
||||
"liquid_type": [],
|
||||
"liquid_volume": [],
|
||||
"liquid_input_wells": [
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "Tip Rack BC230 P16",
|
||||
"parent": "deck",
|
||||
"slot_on_deck": "P16",
|
||||
"class_name": "BC230",
|
||||
"liquid_type": [],
|
||||
"liquid_volume": [],
|
||||
"liquid_input_wells": [
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "stock plate on 4",
|
||||
"parent": "deck",
|
||||
"slot_on_deck": "P2",
|
||||
"class_name": "nest_12_reservoir_15ml",
|
||||
"liquid_type": [
|
||||
"bind beads"
|
||||
],
|
||||
"liquid_volume": [10000],
|
||||
"liquid_input_wells": [
|
||||
"A1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "stock plate on P2",
|
||||
"parent": "deck",
|
||||
"slot_on_deck": "P2",
|
||||
"class_name": "nest_12_reservoir_15ml",
|
||||
"liquid_type": [
|
||||
"bind beads"
|
||||
],
|
||||
"liquid_volume": [10000],
|
||||
"liquid_input_wells": [
|
||||
"A1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "stock plate on P3",
|
||||
"parent": "deck",
|
||||
"slot_on_deck": "P3",
|
||||
"class_name": "nest_12_reservoir_15ml",
|
||||
"liquid_type": [
|
||||
"ethyl alcohol"
|
||||
],
|
||||
"liquid_volume": [10000],
|
||||
"liquid_input_wells": [
|
||||
"A1"
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"id": "oscillation",
|
||||
"parent": "deck",
|
||||
"slot_on_deck": "Orbital1",
|
||||
"class_name": "Orbital",
|
||||
"liquid_type": [],
|
||||
"liquid_volume": [],
|
||||
"liquid_input_wells": [
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "working plate on P11",
|
||||
"parent": "deck",
|
||||
"slot_on_deck": "P11",
|
||||
"class_name": "NEST 2ml Deep Well Plate",
|
||||
"liquid_type": [
|
||||
],
|
||||
"liquid_volume": [],
|
||||
"liquid_input_wells": [
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "magnetics module on P12",
|
||||
"parent": "deck",
|
||||
"slot_on_deck": "P12",
|
||||
"class_name": "magnetics module",
|
||||
"liquid_type": [],
|
||||
"liquid_volume": [],
|
||||
"liquid_input_wells": [
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "working plate on P13",
|
||||
"parent": "deck",
|
||||
"slot_on_deck": "P13",
|
||||
"class_name": "NEST 2ml Deep Well Plate",
|
||||
"liquid_type": [
|
||||
],
|
||||
"liquid_volume": [],
|
||||
"liquid_input_wells": [
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "waste on P22",
|
||||
"parent": "deck",
|
||||
"slot_on_deck": "P22",
|
||||
"class_name": "nest_1_reservoir_195ml",
|
||||
"liquid_type": [
|
||||
],
|
||||
"liquid_volume": [],
|
||||
"liquid_input_wells": [
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
'''
|
||||
|
||||
|
||||
|
||||
handler = LiquidHandlerBiomek()
|
||||
|
||||
handler.temp_protocol = {
|
||||
"meta": {},
|
||||
"labwares": [],
|
||||
"steps": []
|
||||
}
|
||||
|
||||
input_steps = json.loads(steps_info)
|
||||
labwares = json.loads(labware_with_liquid)
|
||||
|
||||
for step in input_steps['steps']:
|
||||
if step['operation'] != 'transfer':
|
||||
continue
|
||||
parameters = step['parameters']
|
||||
|
||||
|
||||
handler.transfer_biomek(source=parameters['source'],
|
||||
target=parameters['target'],
|
||||
volume=parameters['volume'],
|
||||
tip_rack=parameters['tip_rack'],
|
||||
aspirate_techniques='MC P300 high',
|
||||
dispense_techniques='MC P300 high'
|
||||
)
|
||||
|
||||
print(json.dumps(handler.temp_protocol['steps'],indent=4, ensure_ascii=False))
|
||||
|
||||
@@ -6,13 +6,8 @@ import asyncio
|
||||
import time
|
||||
|
||||
from pylabrobot.liquid_handling import LiquidHandler
|
||||
from pylabrobot.resources import (
|
||||
Resource,
|
||||
TipRack,
|
||||
Container,
|
||||
Coordinate,
|
||||
Well
|
||||
)
|
||||
from pylabrobot.resources import Resource, TipRack, Container, Coordinate, Well
|
||||
|
||||
|
||||
class LiquidHandlerAbstract(LiquidHandler):
|
||||
"""Extended LiquidHandler with additional operations."""
|
||||
@@ -21,6 +16,19 @@ class LiquidHandlerAbstract(LiquidHandler):
|
||||
# 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(
|
||||
self,
|
||||
vols: List[float],
|
||||
@@ -35,26 +43,26 @@ class LiquidHandlerAbstract(LiquidHandler):
|
||||
spread: Optional[Literal["wide", "tight", "custom"]] = "wide",
|
||||
delays: Optional[List[int]] = None,
|
||||
is_96_well: Optional[bool] = False,
|
||||
top: Optional[List(float)] = None,
|
||||
none_keys: List[str] = []
|
||||
top: Optional[List[float]] = None,
|
||||
none_keys: List[str] = [],
|
||||
):
|
||||
"""A complete *remove* (aspirate → waste) operation."""
|
||||
trash = self.deck.get_trash_area()
|
||||
try:
|
||||
if is_96_well:
|
||||
pass # This mode is not verified
|
||||
pass # This mode is not verified
|
||||
else:
|
||||
if len(vols) != len(sources):
|
||||
raise ValueError("Length of `vols` must match `sources`.")
|
||||
|
||||
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)
|
||||
await self.pick_up_tips(tip)
|
||||
await self.aspirate(
|
||||
resources=[src],
|
||||
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,
|
||||
offsets=[offsets[0]] if offsets 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.dispense(
|
||||
resources=waste_liquid,
|
||||
vols=[vol],
|
||||
use_channels=use_channels,
|
||||
flow_rates=[flow_rates[1]] if flow_rates else None,
|
||||
offsets=[offsets[1]] if offsets 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,
|
||||
spread=spread,
|
||||
)
|
||||
await self.discard_tips() # For now, each of tips is discarded after use
|
||||
vols=[vol],
|
||||
use_channels=use_channels,
|
||||
flow_rates=[flow_rates[1]] if flow_rates else None,
|
||||
offsets=[offsets[1]] if offsets 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,
|
||||
spread=spread,
|
||||
)
|
||||
await self.discard_tips() # For now, each of tips is discarded after use
|
||||
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"Liquid removal failed: {e}") from e
|
||||
@@ -100,13 +108,13 @@ class LiquidHandlerAbstract(LiquidHandler):
|
||||
mix_vol: Optional[int] = None,
|
||||
mix_rate: Optional[int] = None,
|
||||
mix_liquid_height: Optional[float] = None,
|
||||
none_keys: List[str] = []
|
||||
none_keys: List[str] = [],
|
||||
):
|
||||
"""A complete *add* (aspirate reagent → dispense into targets) operation."""
|
||||
|
||||
try:
|
||||
if is_96_well:
|
||||
pass # This mode is not verified.
|
||||
pass # This mode is not verified.
|
||||
else:
|
||||
if len(asp_vols) != len(targets):
|
||||
raise ValueError("Length of `vols` must match `targets`.")
|
||||
@@ -122,7 +130,7 @@ class LiquidHandlerAbstract(LiquidHandler):
|
||||
offsets=[offsets[0]] if offsets 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,
|
||||
spread=spread
|
||||
spread=spread,
|
||||
)
|
||||
if delays is not None:
|
||||
await self.custom_delay(seconds=delays[0])
|
||||
@@ -144,7 +152,8 @@ class LiquidHandlerAbstract(LiquidHandler):
|
||||
mix_vol=mix_vol,
|
||||
offsets=offsets if offsets 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:
|
||||
await self.custom_delay(seconds=delays[1])
|
||||
await self.touch_tip(targets[_])
|
||||
@@ -158,13 +167,13 @@ class LiquidHandlerAbstract(LiquidHandler):
|
||||
# ---------------------------------------------------------------
|
||||
async def transfer_liquid(
|
||||
self,
|
||||
asp_vols: Union[List[float], float],
|
||||
dis_vols: Union[List[float], float],
|
||||
sources: Sequence[Container],
|
||||
targets: Sequence[Container],
|
||||
tip_racks: Sequence[TipRack],
|
||||
*,
|
||||
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,
|
||||
@@ -179,7 +188,7 @@ class LiquidHandlerAbstract(LiquidHandler):
|
||||
mix_rate: Optional[int] = None,
|
||||
mix_liquid_height: Optional[float] = 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*.
|
||||
|
||||
@@ -201,14 +210,15 @@ class LiquidHandlerAbstract(LiquidHandler):
|
||||
# 96‑channel head mode
|
||||
# ------------------------------------------------------------------
|
||||
if is_96_well:
|
||||
pass # This mode is not verified
|
||||
pass # This mode is not verified
|
||||
else:
|
||||
if not (len(asp_vols) == len(sources) and len(dis_vols) == len(targets)):
|
||||
raise ValueError("`sources`, `targets`, and `vols` must have the same length.")
|
||||
|
||||
tip_iter = self.iter_tips(tip_racks)
|
||||
for src, tgt, asp_vol, asp_flow_rate, dis_vol, dis_flow_rate in (
|
||||
zip(sources, targets, asp_vols, asp_flow_rates, dis_vols, dis_flow_rates)):
|
||||
for src, tgt, asp_vol, asp_flow_rate, dis_vol, dis_flow_rate in zip(
|
||||
sources, targets, asp_vols, asp_flow_rates, dis_vols, dis_flow_rates
|
||||
):
|
||||
tip = next(tip_iter)
|
||||
await self.pick_up_tips(tip)
|
||||
# Aspirate from source
|
||||
@@ -247,9 +257,9 @@ class LiquidHandlerAbstract(LiquidHandler):
|
||||
except Exception as exc:
|
||||
raise RuntimeError(f"Liquid transfer failed: {exc}") from exc
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# Helper utilities
|
||||
# ---------------------------------------------------------------
|
||||
# ---------------------------------------------------------------
|
||||
# Helper utilities
|
||||
# ---------------------------------------------------------------
|
||||
|
||||
async def custom_delay(self, seconds=0, msg=None):
|
||||
"""
|
||||
@@ -266,28 +276,26 @@ class LiquidHandlerAbstract(LiquidHandler):
|
||||
print(f"Done: {msg}")
|
||||
print(f"Current time: {time.strftime('%H:%M:%S')}")
|
||||
|
||||
async def touch_tip(self,
|
||||
targets: Sequence[Container],
|
||||
):
|
||||
async def touch_tip(self, targets: Sequence[Container]):
|
||||
"""Touch the tip to the side of the well."""
|
||||
await self.aspirate(
|
||||
resources=[targets],
|
||||
vols=[0],
|
||||
use_channels=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,
|
||||
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(
|
||||
resources=[targets],
|
||||
vols=[0],
|
||||
use_channels=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,
|
||||
blow_out_air_volume=None
|
||||
blow_out_air_volume=None,
|
||||
)
|
||||
|
||||
async def mix(
|
||||
@@ -298,9 +306,9 @@ class LiquidHandlerAbstract(LiquidHandler):
|
||||
height_to_bottom: Optional[float] = None,
|
||||
offsets: Optional[Coordinate] = 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
|
||||
"""Mix the liquid in the target wells."""
|
||||
for _ in range(mix_time):
|
||||
@@ -333,7 +341,7 @@ class LiquidHandlerAbstract(LiquidHandler):
|
||||
tip_iter = self.iter_tips(tip_racks)
|
||||
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.
|
||||
|
||||
@@ -352,4 +360,3 @@ class LiquidHandlerAbstract(LiquidHandler):
|
||||
await self.move_channel_x(channel, abs_loc.x)
|
||||
await self.move_channel_y(channel, abs_loc.y)
|
||||
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
@@ -5,22 +5,22 @@ class SolenoidValveMock:
|
||||
def __init__(self, port: str = "COM6"):
|
||||
self._status = "Idle"
|
||||
self._valve_position = "OPEN"
|
||||
|
||||
|
||||
@property
|
||||
def status(self) -> str:
|
||||
return self._status
|
||||
|
||||
|
||||
@property
|
||||
def valve_position(self) -> str:
|
||||
return self._valve_position
|
||||
|
||||
def get_valve_position(self) -> str:
|
||||
return self._valve_position
|
||||
|
||||
|
||||
def set_valve_position(self, position):
|
||||
self._status = "Busy"
|
||||
time.sleep(5)
|
||||
|
||||
|
||||
self._valve_position = position
|
||||
time.sleep(5)
|
||||
self._status = "Idle"
|
||||
|
||||
@@ -4,17 +4,17 @@ import time
|
||||
class VacuumPumpMock:
|
||||
def __init__(self, port: str = "COM6"):
|
||||
self._status = "OPEN"
|
||||
|
||||
|
||||
@property
|
||||
def status(self) -> str:
|
||||
return self._status
|
||||
|
||||
def get_status(self) -> str:
|
||||
return self._status
|
||||
|
||||
|
||||
def set_status(self, position):
|
||||
time.sleep(5)
|
||||
|
||||
|
||||
self._status = position
|
||||
time.sleep(5)
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
liquid_handler:
|
||||
description: Liquid handler device controlled by pylabrobot
|
||||
icon: icon_yiyezhan.webp
|
||||
class:
|
||||
module: unilabos.devices.liquid_handling.liquid_handler_abstract:LiquidHandlerAbstract
|
||||
type: python
|
||||
@@ -22,8 +23,8 @@ liquid_handler:
|
||||
is_96_well: is_96_well
|
||||
top: top
|
||||
none_keys: none_keys
|
||||
feedback: { }
|
||||
result: { }
|
||||
feedback: {}
|
||||
result: {}
|
||||
add_liquid:
|
||||
type: LiquidHandlerAdd
|
||||
goal:
|
||||
@@ -43,8 +44,8 @@ liquid_handler:
|
||||
mix_rate: mix_rate
|
||||
mix_liquid_height: mix_liquid_height
|
||||
none_keys: none_keys
|
||||
feedback: { }
|
||||
result: { }
|
||||
feedback: {}
|
||||
result: {}
|
||||
transfer_liquid:
|
||||
type: LiquidHandlerTransfer
|
||||
goal:
|
||||
@@ -69,8 +70,8 @@ liquid_handler:
|
||||
mix_liquid_height: mix_liquid_height
|
||||
delays: delays
|
||||
none_keys: none_keys
|
||||
feedback: { }
|
||||
result: { }
|
||||
feedback: {}
|
||||
result: {}
|
||||
mix:
|
||||
type: LiquidHandlerMix
|
||||
goal:
|
||||
@@ -81,16 +82,16 @@ liquid_handler:
|
||||
offsets: offsets
|
||||
mix_rate: mix_rate
|
||||
none_keys: none_keys
|
||||
feedback: { }
|
||||
result: { }
|
||||
feedback: {}
|
||||
result: {}
|
||||
move_to:
|
||||
type: LiquidHandlerMoveTo
|
||||
goal:
|
||||
well: well
|
||||
dis_to_top: dis_to_top
|
||||
channel: channel
|
||||
feedback: { }
|
||||
result: { }
|
||||
feedback: {}
|
||||
result: {}
|
||||
aspirate:
|
||||
type: LiquidHandlerAspirate
|
||||
goal:
|
||||
@@ -245,6 +246,21 @@ liquid_handler:
|
||||
target_vols: target_vols
|
||||
aspiration_flow_rate: aspiration_flow_rate
|
||||
dispense_flow_rates: dispense_flow_rates
|
||||
handles:
|
||||
input:
|
||||
- handler_key: liquid-input
|
||||
label: Liquid Input
|
||||
data_type: resource
|
||||
io_type: target
|
||||
data_source: handle
|
||||
data_key: liquid
|
||||
output:
|
||||
- handler_key: liquid-output
|
||||
label: Liquid Output
|
||||
data_type: resource
|
||||
io_type: source
|
||||
data_source: executor
|
||||
data_key: liquid
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
@@ -272,3 +288,60 @@ liquid_handler.revvity:
|
||||
status: status
|
||||
result:
|
||||
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
|
||||
|
||||
@@ -23,20 +23,51 @@ syringe_pump_with_valve.runze:
|
||||
type: string
|
||||
description: The position of the valve
|
||||
required:
|
||||
- status
|
||||
- position
|
||||
- valve_position
|
||||
- status
|
||||
- position
|
||||
- valve_position
|
||||
additionalProperties: false
|
||||
|
||||
|
||||
solenoid_valve.mock:
|
||||
description: Mock solenoid valve
|
||||
class:
|
||||
module: unilabos.devices.pump_and_valve.solenoid_valve_mock:SolenoidValveMock
|
||||
type: python
|
||||
status_types:
|
||||
status: String
|
||||
valve_position: String
|
||||
action_value_mappings:
|
||||
open:
|
||||
type: EmptyIn
|
||||
goal: {}
|
||||
feedback: {}
|
||||
result: {}
|
||||
close:
|
||||
type: EmptyIn
|
||||
goal: {}
|
||||
feedback: {}
|
||||
result: {}
|
||||
handles:
|
||||
input:
|
||||
- handler_key: fluid-input
|
||||
label: Fluid Input
|
||||
data_type: fluid
|
||||
output:
|
||||
- handler_key: fluid-output
|
||||
label: Fluid Output
|
||||
data_type: fluid
|
||||
init_param_schema:
|
||||
type: object
|
||||
properties:
|
||||
port:
|
||||
type: string
|
||||
description: "通信端口"
|
||||
default: "COM6"
|
||||
required:
|
||||
- port
|
||||
|
||||
solenoid_valve:
|
||||
description: Solenoid valve
|
||||
class:
|
||||
module: unilabos.devices.pump_and_valve.solenoid_valve:SolenoidValve
|
||||
type: python
|
||||
type: python
|
||||
|
||||
@@ -22,9 +22,76 @@ vacuum_pump.mock:
|
||||
string: string
|
||||
feedback: {}
|
||||
result: {}
|
||||
handles:
|
||||
input:
|
||||
- handler_key: fluid-input
|
||||
label: Fluid Input
|
||||
data_type: fluid
|
||||
io_type: target
|
||||
data_source: handle
|
||||
data_key: fluid_in
|
||||
output:
|
||||
- handler_key: fluid-output
|
||||
label: Fluid Output
|
||||
data_type: fluid
|
||||
io_type: source
|
||||
data_source: executor
|
||||
data_key: fluid_out
|
||||
init_param_schema:
|
||||
type: object
|
||||
properties:
|
||||
port:
|
||||
type: string
|
||||
description: "通信端口"
|
||||
default: "COM6"
|
||||
required:
|
||||
- port
|
||||
|
||||
gas_source.mock:
|
||||
description: Mock gas source
|
||||
class:
|
||||
module: unilabos.devices.pump_and_valve.vacuum_pump_mock:VacuumPumpMock
|
||||
type: python
|
||||
status_types:
|
||||
status: String
|
||||
action_value_mappings:
|
||||
open:
|
||||
type: EmptyIn
|
||||
goal: {}
|
||||
feedback: {}
|
||||
result: {}
|
||||
close:
|
||||
type: EmptyIn
|
||||
goal: {}
|
||||
feedback: {}
|
||||
result: {}
|
||||
set_status:
|
||||
type: StrSingleInput
|
||||
goal:
|
||||
string: string
|
||||
feedback: {}
|
||||
result: {}
|
||||
handles:
|
||||
input:
|
||||
- handler_key: fluid-input
|
||||
label: Fluid Input
|
||||
data_type: fluid
|
||||
io_type: target
|
||||
data_source: handle
|
||||
data_key: fluid_in
|
||||
output:
|
||||
- handler_key: fluid-output
|
||||
label: Fluid Output
|
||||
data_type: fluid
|
||||
io_type: source
|
||||
data_source: executor
|
||||
data_key: fluid_out
|
||||
init_param_schema:
|
||||
type: object
|
||||
properties:
|
||||
port:
|
||||
type: string
|
||||
description: "通信端口"
|
||||
default: "COM6"
|
||||
required:
|
||||
- port
|
||||
|
||||
@@ -4,4 +4,4 @@ workstation:
|
||||
module: unilabos.ros.nodes.presets.protocol_node:ROS2ProtocolNode
|
||||
type: ros2
|
||||
schema:
|
||||
properties: {}
|
||||
properties: {}
|
||||
|
||||
@@ -25,9 +25,7 @@ class Registry:
|
||||
self.ResourceCreateFromOuterEasy = self._replace_type_with_class(
|
||||
"ResourceCreateFromOuterEasy", "host_node", f"动作 create_resource"
|
||||
)
|
||||
self.EmptyIn = self._replace_type_with_class(
|
||||
"EmptyIn", "host_node", f""
|
||||
)
|
||||
self.EmptyIn = self._replace_type_with_class("EmptyIn", "host_node", f"")
|
||||
self.device_type_registry = {}
|
||||
self.resource_type_registry = {}
|
||||
self._setup_called = False # 跟踪setup是否已调用
|
||||
@@ -99,6 +97,8 @@ class Registry:
|
||||
},
|
||||
"icon": "icon_device.webp",
|
||||
"registry_type": "device",
|
||||
"handles": [],
|
||||
"init_param_schema": {},
|
||||
"schema": {"properties": {}, "additionalProperties": False, "type": "object"},
|
||||
"file_path": "/",
|
||||
}
|
||||
@@ -132,6 +132,10 @@ class Registry:
|
||||
resource_info["description"] = ""
|
||||
if "icon" not in resource_info:
|
||||
resource_info["icon"] = ""
|
||||
if "handles" not in resource_info:
|
||||
resource_info["handles"] = []
|
||||
if "init_param_schema" not in resource_info:
|
||||
resource_info["init_param_schema"] = {}
|
||||
resource_info["registry_type"] = "resource"
|
||||
self.resource_type_registry.update(data)
|
||||
logger.debug(
|
||||
@@ -194,6 +198,10 @@ class Registry:
|
||||
device_config["description"] = ""
|
||||
if "icon" not in device_config:
|
||||
device_config["icon"] = ""
|
||||
if "handles" not in device_config:
|
||||
device_config["handles"] = []
|
||||
if "init_param_schema" not in device_config:
|
||||
device_config["init_param_schema"] = {}
|
||||
device_config["registry_type"] = "device"
|
||||
if "class" in device_config:
|
||||
# 处理状态类型
|
||||
|
||||
@@ -131,7 +131,7 @@ _msg_converter: Dict[Type, Any] = {
|
||||
Bool: lambda x: Bool(data=bool(x)),
|
||||
str: str,
|
||||
String: lambda x: String(data=str(x)),
|
||||
Point: lambda x: Point(x=x.x, y=x.y, z=x.z),
|
||||
Point: lambda x: Point(x=x.x, y=x.y, z=x.z) if not isinstance(x, dict) else Point(x=x.get("x", 0), y=x.get("y", 0), z=x.get("z", 0)),
|
||||
Resource: lambda x: Resource(
|
||||
id=x.get("id", ""),
|
||||
name=x.get("name", ""),
|
||||
|
||||
@@ -349,6 +349,20 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
||||
response = rclient.call(request)
|
||||
# 应该先add_resource了
|
||||
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,不然没有办法输入到物料系统中
|
||||
resource = self.resource_tracker.figure_resource({"name": bind_parent_id})
|
||||
# request.resources = [convert_to_ros_msg(Resource, resources)]
|
||||
|
||||
@@ -29,6 +29,8 @@ set(action_files
|
||||
"action/HeatChillStart.action"
|
||||
"action/HeatChillStop.action"
|
||||
|
||||
"action/LiquidHandlerProtocolCreation.action"
|
||||
|
||||
"action/LiquidHandlerAspirate.action"
|
||||
"action/LiquidHandlerDiscardTips.action"
|
||||
"action/LiquidHandlerDispense.action"
|
||||
@@ -43,6 +45,7 @@ set(action_files
|
||||
"action/LiquidHandlerReturnTips96.action"
|
||||
"action/LiquidHandlerStamp.action"
|
||||
"action/LiquidHandlerTransfer.action"
|
||||
"action/LiquidHandlerTransferBiomek.action"
|
||||
|
||||
"action/LiquidHandlerAdd.action"
|
||||
"action/LiquidHandlerMix.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
|
||||
---
|
||||
---
|
||||
9
unilabos_msgs/action/LiquidHandlerTransferBiomek.action
Normal file
9
unilabos_msgs/action/LiquidHandlerTransferBiomek.action
Normal file
@@ -0,0 +1,9 @@
|
||||
Resource source
|
||||
Resource target
|
||||
Resource tip_rack
|
||||
string aspirate_technique
|
||||
string dispense_technique
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
Reference in New Issue
Block a user