mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2026-02-04 13:25:13 +00:00
workflow upload & prcxi transfer liquid
This commit is contained in:
@@ -1,15 +1,11 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
from typing import List, Sequence, Optional, Literal, Union, Iterator, Dict, Any, Callable, Set, cast
|
from typing import List, Sequence, Optional, Literal, Union, Iterator, Dict, Any, Callable, Set, cast
|
||||||
|
|
||||||
from typing_extensions import TypedDict
|
|
||||||
from pylabrobot.liquid_handling import LiquidHandler, LiquidHandlerBackend, LiquidHandlerChatterboxBackend, Strictness
|
from pylabrobot.liquid_handling import LiquidHandler, LiquidHandlerBackend, LiquidHandlerChatterboxBackend, Strictness
|
||||||
from unilabos.devices.liquid_handling.rviz_backend import UniLiquidHandlerRvizBackend
|
|
||||||
from unilabos.devices.liquid_handling.laiyu.backend.laiyu_v_backend import UniLiquidHandlerLaiyuBackend
|
|
||||||
from pylabrobot.liquid_handling.liquid_handler import TipPresenceProbingMethod
|
from pylabrobot.liquid_handling.liquid_handler import TipPresenceProbingMethod
|
||||||
from pylabrobot.liquid_handling.standard import GripDirection
|
from pylabrobot.liquid_handling.standard import GripDirection
|
||||||
from pylabrobot.resources import (
|
from pylabrobot.resources import (
|
||||||
@@ -27,26 +23,33 @@ from pylabrobot.resources import (
|
|||||||
Trash,
|
Trash,
|
||||||
Tip,
|
Tip,
|
||||||
)
|
)
|
||||||
|
from typing_extensions import TypedDict
|
||||||
|
|
||||||
|
from unilabos.devices.liquid_handling.rviz_backend import UniLiquidHandlerRvizBackend
|
||||||
from unilabos.registry.placeholder_type import ResourceSlot
|
from unilabos.registry.placeholder_type import ResourceSlot
|
||||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
from unilabos.resources.resource_tracker import ResourceTreeSet, ResourceDict
|
||||||
from unilabos.resources.resource_tracker import ResourceTreeSet
|
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode, ROS2DeviceNode
|
||||||
|
|
||||||
|
|
||||||
class SimpleReturn(TypedDict):
|
class SimpleReturn(TypedDict):
|
||||||
samples: list
|
samples: List[List[ResourceDict]]
|
||||||
volumes: list
|
volumes: List[float]
|
||||||
|
|
||||||
|
|
||||||
class SetLiquidReturn(TypedDict):
|
class SetLiquidReturn(TypedDict):
|
||||||
wells: list
|
wells: List[List[ResourceDict]]
|
||||||
volumes: list
|
volumes: List[float]
|
||||||
|
|
||||||
|
|
||||||
class SetLiquidFromPlateReturn(TypedDict):
|
class SetLiquidFromPlateReturn(TypedDict):
|
||||||
plate: list
|
plate: List[List[ResourceDict]]
|
||||||
wells: list
|
wells: List[List[ResourceDict]]
|
||||||
volumes: list
|
volumes: List[float]
|
||||||
|
|
||||||
|
|
||||||
|
class TransferLiquidReturn(TypedDict):
|
||||||
|
sources: List[List[ResourceDict]]
|
||||||
|
targets: List[List[ResourceDict]]
|
||||||
|
|
||||||
|
|
||||||
class LiquidHandlerMiddleware(LiquidHandler):
|
class LiquidHandlerMiddleware(LiquidHandler):
|
||||||
@@ -682,9 +685,8 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
|
|||||||
wells=ResourceTreeSet.from_plr_resources(wells, known_newly_created=False).dump(), volumes=res_volumes # type: ignore
|
wells=ResourceTreeSet.from_plr_resources(wells, known_newly_created=False).dump(), volumes=res_volumes # type: ignore
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def set_liquid_from_plate(
|
def set_liquid_from_plate(
|
||||||
cls, plate: List[ResourceSlot], well_names: list[str], liquid_names: list[str], volumes: list[float]
|
self, plate: List[ResourceSlot], well_names: list[str], liquid_names: list[str], volumes: list[float]
|
||||||
) -> SetLiquidFromPlateReturn:
|
) -> SetLiquidFromPlateReturn:
|
||||||
"""Set the liquid in wells of a plate by well names (e.g., A1, A2, B3).
|
"""Set the liquid in wells of a plate by well names (e.g., A1, A2, B3).
|
||||||
|
|
||||||
@@ -710,6 +712,14 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
|
|||||||
well.set_liquids([(liquid_name, volume)]) # type: ignore
|
well.set_liquids([(liquid_name, volume)]) # type: ignore
|
||||||
res_volumes.append(volume)
|
res_volumes.append(volume)
|
||||||
|
|
||||||
|
task = ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{"resources": wells})
|
||||||
|
submit_time = time.time()
|
||||||
|
while not task.done():
|
||||||
|
if time.time() - submit_time > 10:
|
||||||
|
self._ros_node.lab_logger().info(f"set_liquid_from_plate {plate} 超时")
|
||||||
|
break
|
||||||
|
time.sleep(0.01)
|
||||||
|
|
||||||
return SetLiquidFromPlateReturn(
|
return SetLiquidFromPlateReturn(
|
||||||
plate=ResourceTreeSet.from_plr_resources([plate], known_newly_created=False).dump(), # type: ignore
|
plate=ResourceTreeSet.from_plr_resources([plate], known_newly_created=False).dump(), # type: ignore
|
||||||
wells=ResourceTreeSet.from_plr_resources(wells, known_newly_created=False).dump(), # type: ignore
|
wells=ResourceTreeSet.from_plr_resources(wells, known_newly_created=False).dump(), # type: ignore
|
||||||
@@ -1115,7 +1125,7 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
|
|||||||
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] = [],
|
||||||
):
|
) -> TransferLiquidReturn:
|
||||||
"""Transfer liquid with automatic mode detection.
|
"""Transfer liquid with automatic mode detection.
|
||||||
|
|
||||||
Supports three transfer modes:
|
Supports three transfer modes:
|
||||||
@@ -1255,6 +1265,11 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
|
|||||||
"Supported modes: 1->N, N->1, or N->N."
|
"Supported modes: 1->N, N->1, or N->N."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return TransferLiquidReturn(
|
||||||
|
sources=ResourceTreeSet.from_plr_resources(list(sources), known_newly_created=False).dump(), # type: ignore
|
||||||
|
targets=ResourceTreeSet.from_plr_resources(list(targets), known_newly_created=False).dump(), # type: ignore
|
||||||
|
)
|
||||||
|
|
||||||
async def _transfer_one_to_one(
|
async def _transfer_one_to_one(
|
||||||
self,
|
self,
|
||||||
sources: Sequence[Container],
|
sources: Sequence[Container],
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ from unilabos.devices.liquid_handling.liquid_handler_abstract import (
|
|||||||
SimpleReturn,
|
SimpleReturn,
|
||||||
SetLiquidReturn,
|
SetLiquidReturn,
|
||||||
SetLiquidFromPlateReturn,
|
SetLiquidFromPlateReturn,
|
||||||
|
TransferLiquidReturn,
|
||||||
)
|
)
|
||||||
from unilabos.registry.placeholder_type import ResourceSlot
|
from unilabos.registry.placeholder_type import ResourceSlot
|
||||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
||||||
@@ -154,25 +155,29 @@ class PRCXI9300Plate(Plate):
|
|||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
# 如果 ordered_items 不为 None,直接使用
|
# 如果 ordered_items 不为 None,直接使用
|
||||||
|
items = None
|
||||||
|
ordering_param = None
|
||||||
if ordered_items is not None:
|
if ordered_items is not None:
|
||||||
items = ordered_items
|
items = ordered_items
|
||||||
elif ordering is not None:
|
elif ordering is not None:
|
||||||
# 检查 ordering 中的值是否是字符串(从 JSON 反序列化时的情况)
|
# 检查 ordering 中的值是否是字符串(从 JSON 反序列化时的情况)
|
||||||
# 如果是字符串,说明这是位置名称,需要让 Plate 自己创建 Well 对象
|
# 如果是字符串,说明这是位置名称,需要让 Plate 自己创建 Well 对象
|
||||||
# 我们只传递位置信息(键),不传递值,使用 ordering 参数
|
# 我们只传递位置信息(键),不传递值,使用 ordering 参数
|
||||||
if ordering and isinstance(next(iter(ordering.values()), None), str):
|
if ordering:
|
||||||
# ordering 的值是字符串,只使用键(位置信息)创建新的 OrderedDict
|
values = list(ordering.values())
|
||||||
# 传递 ordering 参数而不是 ordered_items,让 Plate 自己创建 Well 对象
|
value = values[0]
|
||||||
items = None
|
if isinstance(value, str):
|
||||||
# 使用 ordering 参数,只包含位置信息(键)
|
# ordering 的值是字符串,只使用键(位置信息)创建新的 OrderedDict
|
||||||
ordering_param = collections.OrderedDict((k, None) for k in ordering.keys())
|
# 传递 ordering 参数而不是 ordered_items,让 Plate 自己创建 Well 对象
|
||||||
|
items = None
|
||||||
|
# 使用 ordering 参数,只包含位置信息(键)
|
||||||
|
ordering_param = collections.OrderedDict((k, None) for k in ordering.keys())
|
||||||
|
elif value is None:
|
||||||
|
ordering_param = ordering
|
||||||
else:
|
else:
|
||||||
# ordering 的值已经是对象,可以直接使用
|
# ordering 的值已经是对象,可以直接使用
|
||||||
items = ordering
|
items = ordering
|
||||||
ordering_param = None
|
ordering_param = None
|
||||||
else:
|
|
||||||
items = None
|
|
||||||
ordering_param = None
|
|
||||||
|
|
||||||
# 根据情况传递不同的参数
|
# 根据情况传递不同的参数
|
||||||
if items is not None:
|
if items is not None:
|
||||||
@@ -713,7 +718,7 @@ class PRCXI9300Handler(LiquidHandlerAbstract):
|
|||||||
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] = [],
|
||||||
):
|
) -> TransferLiquidReturn:
|
||||||
return await super().transfer_liquid(
|
return await super().transfer_liquid(
|
||||||
sources,
|
sources,
|
||||||
targets,
|
targets,
|
||||||
|
|||||||
@@ -638,7 +638,7 @@ liquid_handler:
|
|||||||
placeholder_keys: {}
|
placeholder_keys: {}
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: 吸头迭代函数。用于自动管理和切换吸头架中的吸头,实现批量实验中的吸头自动分配和追踪。该函数监控吸头使用状态,自动切换到下一个可用吸头位置,确保实验流程的连续性。适用于高通量实验、批量处理、自动化流水线等需要大量吸头管理的应用场景。
|
description: 吸头迭代函数。用于自动管理和切换枪头盒中的吸头,实现批量实验中的吸头自动分配和追踪。该函数监控吸头使用状态,自动切换到下一个可用吸头位置,确保实验流程的连续性。适用于高通量实验、批量处理、自动化流水线等需要大量吸头管理的应用场景。
|
||||||
properties:
|
properties:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
@@ -712,6 +712,43 @@ liquid_handler:
|
|||||||
title: set_group参数
|
title: set_group参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
|
auto-set_liquid_from_plate:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
liquid_names: null
|
||||||
|
plate: null
|
||||||
|
volumes: null
|
||||||
|
well_names: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
liquid_names:
|
||||||
|
type: string
|
||||||
|
plate:
|
||||||
|
type: string
|
||||||
|
volumes:
|
||||||
|
type: string
|
||||||
|
well_names:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- plate
|
||||||
|
- well_names
|
||||||
|
- liquid_names
|
||||||
|
- volumes
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: set_liquid_from_plate参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
auto-set_tiprack:
|
auto-set_tiprack:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal: {}
|
goal: {}
|
||||||
@@ -721,7 +758,7 @@ liquid_handler:
|
|||||||
placeholder_keys: {}
|
placeholder_keys: {}
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: 吸头架设置函数。用于配置和初始化液体处理系统的吸头架信息,包括吸头架位置、类型、容量等参数。该函数建立吸头资源管理系统,为后续的吸头选择和使用提供基础配置。适用于系统初始化、吸头架更换、实验配置等需要吸头资源管理的操作场景。
|
description: 枪头盒设置函数。用于配置和初始化液体处理系统的枪头盒信息,包括枪头盒位置、类型、容量等参数。该函数建立吸头资源管理系统,为后续的吸头选择和使用提供基础配置。适用于系统初始化、枪头盒更换、实验配置等需要吸头资源管理的操作场景。
|
||||||
properties:
|
properties:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
@@ -4093,32 +4130,32 @@ liquid_handler:
|
|||||||
- 0
|
- 0
|
||||||
handles:
|
handles:
|
||||||
input:
|
input:
|
||||||
- data_key: liquid
|
- data_key: sources
|
||||||
data_source: handle
|
data_source: handle
|
||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: sources
|
handler_key: sources
|
||||||
label: sources
|
label: 待移动液体
|
||||||
- data_key: liquid
|
- data_key: targets
|
||||||
data_source: executor
|
|
||||||
data_type: resource
|
|
||||||
handler_key: targets
|
|
||||||
label: targets
|
|
||||||
- data_key: liquid
|
|
||||||
data_source: executor
|
|
||||||
data_type: resource
|
|
||||||
handler_key: tip_rack
|
|
||||||
label: tip_rack
|
|
||||||
output:
|
|
||||||
- data_key: liquid
|
|
||||||
data_source: handle
|
data_source: handle
|
||||||
data_type: resource
|
data_type: resource
|
||||||
|
handler_key: targets
|
||||||
|
label: 转移目标
|
||||||
|
- data_key: tip_racks
|
||||||
|
data_source: handle
|
||||||
|
data_type: resource
|
||||||
|
handler_key: tip_rack
|
||||||
|
label: 枪头盒
|
||||||
|
output:
|
||||||
|
- data_key: sources.@flatten
|
||||||
|
data_source: executor
|
||||||
|
data_type: resource
|
||||||
handler_key: sources_out
|
handler_key: sources_out
|
||||||
label: sources
|
label: 移液后源孔
|
||||||
- data_key: liquid
|
- data_key: targets.@flatten
|
||||||
data_source: executor
|
data_source: executor
|
||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: targets_out
|
handler_key: targets_out
|
||||||
label: targets
|
label: 移液后目标孔
|
||||||
placeholder_keys:
|
placeholder_keys:
|
||||||
sources: unilabos_resources
|
sources: unilabos_resources
|
||||||
targets: unilabos_resources
|
targets: unilabos_resources
|
||||||
@@ -5114,19 +5151,34 @@ liquid_handler.biomek:
|
|||||||
- 0
|
- 0
|
||||||
handles:
|
handles:
|
||||||
input:
|
input:
|
||||||
- data_key: liquid
|
- data_key: sources
|
||||||
data_source: handle
|
data_source: handle
|
||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: liquid-input
|
handler_key: sources
|
||||||
io_type: target
|
io_type: target
|
||||||
label: Liquid Input
|
label: 待移动液体
|
||||||
|
- data_key: targets
|
||||||
|
data_source: handle
|
||||||
|
data_type: resource
|
||||||
|
handler_key: targets
|
||||||
|
label: 转移目标
|
||||||
|
- data_key: tip_racks
|
||||||
|
data_source: handle
|
||||||
|
data_type: resource
|
||||||
|
handler_key: tip_rack
|
||||||
|
label: 枪头盒
|
||||||
output:
|
output:
|
||||||
- data_key: liquid
|
- data_key: sources.@flatten
|
||||||
data_source: executor
|
data_source: executor
|
||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: liquid-output
|
handler_key: sources_out
|
||||||
io_type: source
|
io_type: source
|
||||||
label: Liquid Output
|
label: 移液后源孔
|
||||||
|
- data_key: targets.@flatten
|
||||||
|
data_source: executor
|
||||||
|
data_type: resource
|
||||||
|
handler_key: targets_out
|
||||||
|
label: 移液后目标孔
|
||||||
placeholder_keys:
|
placeholder_keys:
|
||||||
sources: unilabos_resources
|
sources: unilabos_resources
|
||||||
targets: unilabos_resources
|
targets: unilabos_resources
|
||||||
@@ -9924,18 +9976,18 @@ liquid_handler.prcxi:
|
|||||||
data_source: handle
|
data_source: handle
|
||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: tip_rack_identifier
|
handler_key: tip_rack_identifier
|
||||||
label: 墙头盒
|
label: 枪头盒
|
||||||
output:
|
output:
|
||||||
- data_key: liquid
|
- data_key: sources.@flatten
|
||||||
data_source: handle
|
data_source: executor
|
||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: sources_out
|
handler_key: sources_out
|
||||||
label: sources
|
label: 移液后源孔
|
||||||
- data_key: liquid
|
- data_key: targets.@flatten
|
||||||
data_source: executor
|
data_source: executor
|
||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: targets_out
|
handler_key: targets_out
|
||||||
label: targets
|
label: 移液后目标孔
|
||||||
placeholder_keys:
|
placeholder_keys:
|
||||||
sources: unilabos_resources
|
sources: unilabos_resources
|
||||||
targets: unilabos_resources
|
targets: unilabos_resources
|
||||||
|
|||||||
@@ -1581,7 +1581,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
f"转换ResourceSlot列表参数 {arg_name} 失败: {e}\n{traceback.format_exc()}"
|
f"转换ResourceSlot列表参数 {arg_name} 失败: {e}\n{traceback.format_exc()}"
|
||||||
)
|
)
|
||||||
raise JsonCommandInitError(f"ResourceSlot列表参数转换失败: {arg_name}")
|
raise JsonCommandInitError(f"ResourceSlot列表参数转换失败: {arg_name}")
|
||||||
|
# todo: 默认反报送
|
||||||
return function(**function_args)
|
return function(**function_args)
|
||||||
except KeyError as ex:
|
except KeyError as ex:
|
||||||
raise JsonCommandInitError(
|
raise JsonCommandInitError(
|
||||||
@@ -1614,8 +1614,8 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
timeout = 30.0
|
timeout = 30.0
|
||||||
elapsed = 0.0
|
elapsed = 0.0
|
||||||
while not future.done() and elapsed < timeout:
|
while not future.done() and elapsed < timeout:
|
||||||
time.sleep(0.05)
|
time.sleep(0.02)
|
||||||
elapsed += 0.05
|
elapsed += 0.02
|
||||||
|
|
||||||
if not future.done():
|
if not future.done():
|
||||||
raise Exception(f"资源查询超时: {uuids_list}")
|
raise Exception(f"资源查询超时: {uuids_list}")
|
||||||
|
|||||||
@@ -807,7 +807,7 @@ class HostNode(BaseROS2DeviceNode):
|
|||||||
assign_sample_id(action_kwargs)
|
assign_sample_id(action_kwargs)
|
||||||
goal_msg = convert_to_ros_msg(action_client._action_type.Goal(), action_kwargs)
|
goal_msg = convert_to_ros_msg(action_client._action_type.Goal(), action_kwargs)
|
||||||
|
|
||||||
self.lab_logger().info(f"[Host Node] Sending goal for {action_id}: {str(goal_msg)[:1000]}")
|
# self.lab_logger().trace(f"[Host Node] Sending goal for {action_id}: {str(goal_msg)[:1000]}")
|
||||||
self.lab_logger().trace(f"[Host Node] Sending goal for {action_id}: {action_kwargs}")
|
self.lab_logger().trace(f"[Host Node] Sending goal for {action_id}: {action_kwargs}")
|
||||||
self.lab_logger().trace(f"[Host Node] Sending goal for {action_id}: {goal_msg}")
|
self.lab_logger().trace(f"[Host Node] Sending goal for {action_id}: {goal_msg}")
|
||||||
action_client.wait_for_server()
|
action_client.wait_for_server()
|
||||||
|
|||||||
Reference in New Issue
Block a user