物料教程(Resource)
本教程面向 Uni-Lab-OS 的开发者,讲解“物料”的核心概念、3种物料格式(UniLab、PyLabRobot、奔耀Bioyond)及其相互转换方法,并说明4种 children 结构表现形式及使用场景。
1. 物料是什么
物料(Resource):指实验工作站中的实体对象,包括设备(device)、操作甲板 (deck)、试剂、实验耗材,也包括设备上承载的具体物料或者包含的容器(如container/plate/well/瓶/孔/片等)。
物料基本信息(以 UniLab list格式为例):
{
"id": "plate", // 某一类物料的唯一名称
"name": "50ml瓶装试剂托盘", // 在云端显示的名称
"sample_id": null, // 同类物料的不同样品
"children": [
"50ml试剂瓶" // 表示托盘上有一个 50ml 试剂瓶
],
"parent": "deck", // 此物料放置在 deck 上
"type": "plate", // 物料类型
"class": "plate", // 物料对应的注册/类名
"position": {
"x": 0, // 初始放置位置
"y": 0,
"z": 0
},
"config": { // 固有配置(尺寸、旋转等)
"size_x": 400.0,
"size_y": 400.0,
"size_z": 400.0,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
}
},
"data": {
"bottle_number": 1 // 动态数据(可变化)
}
}
2. 3种物料格式概览(UniLab、PyLabRobot、奔耀Bioyond)
2.1 UniLab 物料格式(云端/项目内通用)
结构特征:顶层通常是
nodes
列表;每个节点是扁平字典,children
是子节点id
列表;parent
为父节点id
或null
。用途:
云端数据存储、前端可视化、与图结构算法互操作
在上传/下载/部署配置时作为标准交换格式
示例片段(UniLab 物料格式):
{
"nodes": [
{
"id": "a",
"name": "name_a",
"sample_id": 1,
"type": "deck",
"class": "deck",
"parent": null,
"children": ["b1"],
"position": {"x": 0, "y": 0, "z": 0},
"config": {},
"data": {}
},
{
"id": "b1",
"name": "name_b1",
"sample_id": 1,
"type": "plate",
"class": "plate",
"parent": "a1",
"children": [],
"position": {"x": 0, "y": 0, "z": 0},
"config": {},
"data": {}
}
]
}
2.2 PyLabRobot(PLR)物料格式(实验流程运行时)
结构特征:严格的层级树,
children
为“子资源字典列表”(每个子节点本身是完整对象)。用途:
实验流程执行与调度,PLR 运行时期望的资源对象格式
通过
Resource.deserialize/serialize
、load_all_state/serialize_all_state
与对象交互
示例片段(PRL 物料格式)::
{
"name": "deck",
"type": "Deck",
"category": "deck",
"location": {"x": 0, "y": 0, "z": 0, "type": "Coordinate"},
"rotation": {"x": 0, "y": 0, "z": 0, "type": "Rotation"},
"parent_name": null,
"children": [
{
"name": "plate_1",
"type": "Plate",
"category": "plate_96",
"location": {"x": 100, "y": 0, "z": 0, "type": "Coordinate"},
"rotation": {"x": 0, "y": 0, "z": 0, "type": "Rotation"},
"parent_name": "deck",
"children": [
{
"name": "A1",
"type": "Well",
"category": "well",
"location": {"x": 0, "y": 0, "z": 0, "type": "Coordinate"},
"rotation": {"x": 0, "y": 0, "z": 0, "type": "Rotation"},
"parent_name": "plate_1",
"children": []
}
]
}
]
}
2.3 奔耀 Bioyond 物料格式(第三方来源)
一般是厂商自己定义的json格式和字段,信息需要提取和对应。以下为示例说明。
结构特征:顶层
data
列表,每项包含typeName
、code
、barCode
、name
、quantity
、unit
、locations
(仓位whName
、x/y/z
)、detail
(细粒度内容,如瓶内液体或孔位物料)。用途:
第三方 WMS/设备的物料清单输入
需要自定义映射表将
typeName
→ PLR 类名,对locations
/detail
进行落位/赋值
示例片段(奔耀Bioyond 物料格式):
{
"data": [
{
"id": "3a1b5c10-d4f3-01ac-1e64-5b4be2add4b1",
"typeName": "液",
"code": "0006-00014",
"barCode": "",
"name": "EMC",
"quantity": 50,
"lockQuantity": 2.057,
"unit": "瓶",
"status": 1,
"isUse": false,
"locations": [
{
"id": "3a19da43-57b5-5e75-552f-8dbd0ad1075f",
"whid": "3a19da43-57b4-a2a8-3f52-91dbbeb836db",
"whName": "配液站内试剂仓库",
"code": "0003-0003",
"x": 1,
"y": 3,
"z": 1,
"quantity": 0
}
],
"detail": [
{
"code": "0006-00014-01",
"name": "EMC-瓶-1",
"x": 1,
"y": 3,
"z": 1,
"quantity": 500.0
}
]
}
],
"code": 1,
"message": "",
"timestamp": 0
}
2.4 3种物料格式关键字段对应(UniLab、PyLabRobot、奔耀Bioyond)
含义 |
UniLab |
PyLabRobot (PLR) |
奔耀 Bioyond |
---|---|---|---|
节点唯一名 |
|
|
|
父节点引用 |
|
|
|
子节点集合 |
|
|
|
类型(抽象类别) |
|
|
|
运行/业务数据 |
|
通过 |
|
固有配置 |
|
资源字典中的同名键(反序列化时按构造签名取用) |
厂商自定义字段(需映射入 PLR/UniLab 的 |
空间位置 |
|
|
|
条码/标识 |
|
常放在配置键中(如 |
|
数量单位 |
无固定键,通常在 |
无固定键,通常在配置或状态中 |
|
物料编码 |
通常在 |
通常在配置中自定义 |
|
说明:
Bioyond 不提供显式的树形父子关系,通常通过
locations
将物料落位到某仓位/坐标。用detail
表示子级明细。
3. children 的四种结构表示
list(扁平列表):每个节点是扁平字典,
children
为子节点id
数组。示例:UniLabnodes
中的单个节点。
{
"nodes": [
{ "id": "root", "parent": null, "children": ["child1"] },
{ "id": "child1", "parent": "root", "children": [] }
]
}
dict(嵌套字典):节点的
children
是{ child_id: child_node_dict }
字典。
{
"id": "root",
"parent": null,
"children": {
"child1": { "id": "child1", "parent": "root", "children": {} }
}
}
tree(树形列表):顶层是
[root_node, ...]
,每个node.children
是“子节点对象列表”(而非 id 列表)。
[
{
"id": "root",
"parent": null,
"children": [
{ "id": "child1", "parent": "root", "children": [] }
]
}
]
nestdict(顶层嵌套字典):顶层是
{root_id: root_node, ...}
,或者根节点自身带children: {id: node}
形态。
{
"root": {
"id": "root",
"parent": null,
"children": {
"child1": { "id": "child1", "parent": "root", "children": {} }
}
}
}
这些结构之间可使用 graphio.py
中的工具函数互转(见下一节)。
4. 转换函数及调用
核心代码文件:unilabos/resources/graphio.py
4.1 结构互转(list/dict/tree/nestdict)
代码引用:
def dict_to_tree(nodes: dict, devices_only: bool = False) -> list[dict]:
# ... 由扁平 dict(id->node)生成树(children 为对象列表)
def dict_to_nested_dict(nodes: dict, devices_only: bool = False) -> dict:
# ... 由扁平 dict 生成嵌套字典(children 为 {id:node})
def list_to_nested_dict(nodes: list[dict]) -> dict:
# ... 由扁平列表(children 为 id 列表)转嵌套字典
def tree_to_list(tree: list[dict]) -> list[dict]:
# ... 由树形列表转回扁平列表(children 还原为 id 列表)
def nested_dict_to_list(nested_dict: dict) -> list[dict]:
# ... 由嵌套字典转回扁平列表
常见路径:
UniLab 扁平列表 → 树:
dict_to_tree({r["id"]: r for r in resources})
树 → UniLab 扁平列表:
tree_to_list(resources_tree)
扁平列表 ↔ 嵌套字典:
list_to_nested_dict
/nested_dict_to_list
4.2 UniLab ↔ PyLabRobot(PLR)
高层封装:
def convert_resources_to_type(resources_list: list[dict], resource_type: Union[type, list[type]], *, plr_model: bool = False):
# UniLab -> (NestedDict or PLR)
def convert_resources_from_type(resources_list, resource_type: Union[type, list[type]], *, is_plr: bool = False):
# (NestedDict or PLR) -> UniLab 扁平列表
底层转换:
def resource_ulab_to_plr(resource: dict, plr_model=False) -> "ResourcePLR":
# UniLab 单节点(树根) -> PLR Resource 对象
def resource_plr_to_ulab(resource_plr: "ResourcePLR", parent_name: str = None, with_children=True):
# PLR Resource -> UniLab 单节点(dict)
示例:
from unilabos.resources.graphio import convert_resources_to_type, convert_resources_from_type
from pylabrobot.resources.resource import Resource as ResourcePLR
# UniLab 扁平列表 -> PLR 根资源对象
plr_root = convert_resources_to_type(resources_list=ulab_list, resource_type=ResourcePLR)
# PLR 资源对象 -> UniLab 扁平列表(用于保存/上传)
ulab_flat = convert_resources_from_type(resources_list=plr_root, resource_type=ResourcePLR)
可选项:
plr_model=True
:保留model
字段(默认会移除)。with_children=False
:resource_plr_to_ulab
仅转换当前节点。
4.3 奔耀(Bioyond)→ PLR(及进一步到 UniLab)
转换入口:
def resource_bioyond_to_plr(bioyond_materials: list[dict], type_mapping: dict = {}, deck: Any = None) -> list[dict]:
# Bioyond 列表 -> PLR 资源列表,并可根据 deck.warehouses 将资源落位
使用示例:
import json
from unilabos.resources.graphio import resource_bioyond_to_plr, convert_resources_from_type
from pylabrobot.resources.resource import Resource as ResourcePLR
resp = json.load(open("unilabos/devices/workstation/bioyond_cell/bioyond_test_yibin.json", encoding="utf-8"))
materials = resp["data"]
# 将第三方类型name映射到 PLR 资源类名(需根据现场定义)
type_mapping = {
"液": "RegularContainer",
"加样头(大)": "RegularContainer"
}
plr_list = resource_bioyond_to_plr(materials, type_mapping=type_mapping, deck=None)
# 如需上传云端(UniLab 扁平格式):
ulab_flat = convert_resources_from_type(plr_list, [ResourcePLR])
说明:
type_mapping
必须由开发者根据设备/物料种类人工维护。如传入
deck
,且deck.warehouses
命名与whName
对应,可将物料安放到仓库坐标(x/y/z)。
5. 何时使用哪种格式
云端/持久化:使用 UniLab 物料格式(扁平
nodes
列表,children 为 id 列表)。便于版本化、可视化与网络传输。实验工作流执行:使用 PyLabRobot(PLR)格式。PLR 运行时依赖严格的树形资源结构与对象 API。
第三方设备/系统(Bioyond)输入:保持来源格式不变,使用
resource_bioyond_to_plr
+ 人工type_mapping
将其转换为 PLR(必要时再转 UniLab)。
6. 常见问题与注意事项
children 形态不一致:不同函数期望不同 children 形态,注意在进入转换前先用“结构互转”工具函数标准化形态。
devices_only:
dict_to_tree/dict_to_nested_dict
支持仅保留type == device
的节点。模型/类型字段:PLR 对象序列化参数有所差异,
resource_ulab_to_plr
内部会根据构造签名移除不兼容字段(如category
)。驱动初始化:
initialize_resource(s)
支持从注册表/类路径创建 PLR/UniLab 资源或列表。
参考代码:
def initialize_resource(resource_config: dict, resource_type: Any = None) -> Union[list[dict], ResourcePLR]:
# 从注册类/模块反射创建资源,或将 UniLab 字典包装为列表
def initialize_resources(resources_config) -> list[dict]:
# 批量初始化