mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2025-12-17 21:11:12 +00:00
对于PRCXI9320的transfer_group,一对多和多对多
This commit is contained in:
@@ -16,15 +16,15 @@
|
||||
"_resource_child_name": "PRCXI_Deck",
|
||||
"_resource_type": "unilabos.devices.liquid_handling.prcxi.prcxi:PRCXI9300Deck"
|
||||
},
|
||||
"host": "10.181.102.13",
|
||||
"host": "172.21.5.75",
|
||||
"port": 9999,
|
||||
"timeout": 10.0,
|
||||
"axis": "Right",
|
||||
"channel_num": 1,
|
||||
"setup": false,
|
||||
"debug": false,
|
||||
"simulator": false,
|
||||
"matrix_id": "fd383e6d-2d0e-40b5-9c01-1b2870b1f1b1"
|
||||
"debug": true,
|
||||
"simulator": true,
|
||||
"matrix_id": "c1d0d5dc-40f2-4f24-97ac-9cc49c68496c"
|
||||
},
|
||||
"data": {},
|
||||
"children": [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import traceback
|
||||
from typing import List, Sequence, Optional, Literal, Union, Iterator, Dict, Any, Callable, Set, cast
|
||||
from collections import Counter
|
||||
@@ -558,14 +558,16 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
|
||||
# ---------------------------------------------------------------
|
||||
|
||||
def set_group(self, group_name: str, wells: List[Well], volumes: List[float]):
|
||||
if len(wells) != 8:
|
||||
if self.channel_num == 8 and len(wells) != 8:
|
||||
raise RuntimeError(f"Expected 8 wells, got {len(wells)}")
|
||||
self.group_info[group_name] = wells
|
||||
self.set_liquid(wells, [group_name] * len(wells), volumes)
|
||||
|
||||
async def transfer_group(self, source_group_name: str, target_group_name: str, unit_volume: float):
|
||||
|
||||
source_wells = self.group_info.get(source_group_name, [])
|
||||
target_wells = self.group_info.get(target_group_name, [])
|
||||
|
||||
rack_info = dict()
|
||||
for child in self.deck.children:
|
||||
if issubclass(child.__class__, TipRack):
|
||||
@@ -576,6 +578,7 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
|
||||
break
|
||||
else:
|
||||
rack_info[rack.name] = (rack, tip.maximal_volume - unit_volume)
|
||||
|
||||
if len(rack_info) == 0:
|
||||
raise ValueError(f"No tip rack can support volume {unit_volume}.")
|
||||
|
||||
@@ -583,25 +586,41 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
|
||||
for child in self.deck.children:
|
||||
if child.name == rack_info[0][0]:
|
||||
target_rack = child
|
||||
|
||||
target_rack = cast(TipRack, target_rack)
|
||||
|
||||
available_tips = {}
|
||||
for (idx, name), tipSpot in zip(target_rack._ordering.items(), target_rack.get_all_items()):
|
||||
for (idx, tipSpot) in enumerate(target_rack.get_all_items()):
|
||||
if tipSpot.has_tip():
|
||||
available_tips[idx] = tipSpot
|
||||
continue
|
||||
# 一般移动液体有两种方式,一对多和多对多
|
||||
if self.channel_num == 8:
|
||||
|
||||
colnum_list = [int(holename[1:]) for holename in available_tips.keys()]
|
||||
tip_prefix = list(available_tips.values())[0].name.split('_')[0]
|
||||
colnum_list = [int(tip.name.split('_')[-1][1:]) for tip in available_tips.values()]
|
||||
available_cols = [colnum for colnum, count in dict(Counter(colnum_list)).items() if count == 8]
|
||||
available_cols.sort() # 这样就确定了列号
|
||||
tips_to_use = [available_tips[f"{chr(65 + i)}{available_cols[0]}"] for i in range(8)]
|
||||
|
||||
available_cols.sort()
|
||||
available_tips_dict = {tip.name: tip for tip in available_tips.values()}
|
||||
tips_to_use = [available_tips_dict[f"{tip_prefix}_{chr(65 + i)}{available_cols[0]}"] for i in range(8)]
|
||||
await self.pick_up_tips(tips_to_use, use_channels=list(range(0, 8)))
|
||||
await self.aspirate(source_wells, [10] * 8, use_channels=list(range(0, 8)))
|
||||
await self.dispense(target_wells, [10] * 8, use_channels=list(range(0, 8)))
|
||||
await self.aspirate(source_wells, [unit_volume] * 8, use_channels=list(range(0, 8)))
|
||||
await self.dispense(target_wells, [unit_volume] * 8, use_channels=list(range(0, 8)))
|
||||
await self.discard_tips(use_channels=list(range(0, 8)))
|
||||
|
||||
elif self.channel_num == 1:
|
||||
|
||||
for num_well in range(len(target_wells)):
|
||||
tip_to_use = available_tips[list(available_tips.keys())[num_well]]
|
||||
await self.pick_up_tips([tip_to_use], use_channels=[0])
|
||||
if len(source_wells) == 1:
|
||||
await self.aspirate([source_wells[0]], [unit_volume], use_channels=[0])
|
||||
else:
|
||||
await self.aspirate([source_wells[num_well]], [unit_volume], use_channels=[0])
|
||||
await self.dispense([target_wells[num_well]], [unit_volume], use_channels=[0])
|
||||
await self.discard_tips(use_channels=[0])
|
||||
|
||||
else:
|
||||
raise ValueError(f"Unsupported channel number {self.channel_num}.")
|
||||
|
||||
async def create_protocol(
|
||||
self,
|
||||
protocol_name: str,
|
||||
|
||||
568
unilabos/devices/liquid_handling/prcxi/abstract_protocol.py
Normal file
568
unilabos/devices/liquid_handling/prcxi/abstract_protocol.py
Normal file
@@ -0,0 +1,568 @@
|
||||
import asyncio
|
||||
import collections
|
||||
import contextlib
|
||||
import json
|
||||
import socket
|
||||
import time
|
||||
from typing import Any, List, Dict, Optional, TypedDict, Union, Sequence, Iterator, Literal
|
||||
|
||||
from pylabrobot.liquid_handling import (
|
||||
LiquidHandlerBackend,
|
||||
Pickup,
|
||||
SingleChannelAspiration,
|
||||
Drop,
|
||||
SingleChannelDispense,
|
||||
PickupTipRack,
|
||||
DropTipRack,
|
||||
MultiHeadAspirationPlate, ChatterBoxBackend, LiquidHandlerChatterboxBackend,
|
||||
)
|
||||
from pylabrobot.liquid_handling.standard import (
|
||||
MultiHeadAspirationContainer,
|
||||
MultiHeadDispenseContainer,
|
||||
MultiHeadDispensePlate,
|
||||
ResourcePickup,
|
||||
ResourceMove,
|
||||
ResourceDrop,
|
||||
)
|
||||
from pylabrobot.resources import Tip, Deck, Plate, Well, TipRack, Resource, Container, Coordinate, TipSpot, Trash
|
||||
|
||||
from unilabos.devices.liquid_handling.liquid_handler_abstract import LiquidHandlerAbstract
|
||||
|
||||
|
||||
|
||||
|
||||
class MaterialResource:
|
||||
"""统一的液体/反应器资源,支持多孔(wells)场景:
|
||||
- wells: 列表,每个元素代表一个物料孔(unit);
|
||||
- units: 与 wells 对齐的列表,每个元素是 {liquid_id: volume};
|
||||
- 若传入 liquid_id + volume 或 composition,总量将**等分**到各 unit;
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
resource_name: str,
|
||||
slot: int,
|
||||
well: List[int],
|
||||
composition: Optional[Dict[str, float]] = None,
|
||||
liquid_id: Optional[str] = None,
|
||||
volume: Union[float, int] = 0.0,
|
||||
is_supply: Optional[bool] = None,
|
||||
):
|
||||
self.resource_name = resource_name
|
||||
self.slot = int(slot)
|
||||
self.well = list(well or [])
|
||||
self.is_supply = bool(is_supply) if is_supply is not None else (bool(composition) or (liquid_id is not None))
|
||||
|
||||
# 规范化:至少有 1 个 unit
|
||||
n = max(1, len(self.well))
|
||||
self.units: List[Dict[str, float]] = [dict() for _ in range(n)]
|
||||
|
||||
# 初始化内容:等分到各 unit
|
||||
if composition:
|
||||
for k, v in composition.items():
|
||||
share = float(v) / n
|
||||
for u in self.units:
|
||||
if share > 0:
|
||||
u[k] = u.get(k, 0.0) + share
|
||||
elif liquid_id is not None and float(volume) > 0:
|
||||
share = float(volume) / n
|
||||
for u in self.units:
|
||||
u[liquid_id] = u.get(liquid_id, 0.0) + share
|
||||
|
||||
# 位置描述
|
||||
def location(self) -> Dict[str, Any]:
|
||||
return {"slot": self.slot, "well": self.well}
|
||||
|
||||
def unit_count(self) -> int:
|
||||
return len(self.units)
|
||||
|
||||
def unit_volume(self, idx: int) -> float:
|
||||
return float(sum(self.units[idx].values()))
|
||||
|
||||
def total_volume(self) -> float:
|
||||
return float(sum(self.unit_volume(i) for i in range(self.unit_count())))
|
||||
|
||||
def add_to_unit(self, idx: int, liquid_id: str, vol: Union[float, int]):
|
||||
v = float(vol)
|
||||
if v < 0:
|
||||
return
|
||||
u = self.units[idx]
|
||||
if liquid_id not in u:
|
||||
u[liquid_id] = 0.0
|
||||
if v > 0:
|
||||
u[liquid_id] += v
|
||||
|
||||
def remove_from_unit(self, idx: int, total: Union[float, int]) -> Dict[str, float]:
|
||||
take = float(total)
|
||||
if take <= 0: return {}
|
||||
u = self.units[idx]
|
||||
avail = sum(u.values())
|
||||
if avail <= 0: return {}
|
||||
take = min(take, avail)
|
||||
ratio = take / avail
|
||||
removed: Dict[str, float] = {}
|
||||
for k, v in list(u.items()):
|
||||
dv = v * ratio
|
||||
nv = v - dv
|
||||
if nv < 1e-9: nv = 0.0
|
||||
u[k] = nv
|
||||
removed[k] = dv
|
||||
|
||||
self.units[idx] = {k: v for k, v in u.items() if v > 0}
|
||||
return removed
|
||||
|
||||
def transfer_unit_to(self, src_idx: int, other: "MaterialResource", dst_idx: int, total: Union[float, int]):
|
||||
moved = self.remove_from_unit(src_idx, total)
|
||||
for k, v in moved.items():
|
||||
other.add_to_unit(dst_idx, k, v)
|
||||
|
||||
def get_resource(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"resource_name": self.resource_name,
|
||||
"slot": self.slot,
|
||||
"well": self.well,
|
||||
"units": [dict(u) for u in self.units],
|
||||
"total_volume": self.total_volume(),
|
||||
"is_supply": self.is_supply,
|
||||
}
|
||||
|
||||
def transfer_liquid(
|
||||
sources: MaterialResource,
|
||||
targets: MaterialResource,
|
||||
unit_volume: Optional[Union[float, int]] = None,
|
||||
tip: Optional[str] = None, #这里应该是指定种类的
|
||||
) -> Dict[str, Any]:
|
||||
try:
|
||||
vol_each = float(unit_volume)
|
||||
except (TypeError, ValueError):
|
||||
return {"action": "transfer_liquid", "error": "invalid unit_volume"}
|
||||
if vol_each <= 0:
|
||||
return {"action": "transfer_liquid", "error": "non-positive volume"}
|
||||
|
||||
ns, nt = sources.unit_count(), targets.unit_count()
|
||||
# one-to-many: 从单个 source unit(0) 扇出到目标各 unit
|
||||
if ns == 1 and nt >= 1:
|
||||
for j in range(nt):
|
||||
sources.transfer_unit_to(0, targets, j, vol_each)
|
||||
# many-to-many: 数量相同,逐一对应
|
||||
elif ns == nt and ns > 0:
|
||||
for i in range(ns):
|
||||
sources.transfer_unit_to(i, targets, i, vol_each)
|
||||
else:
|
||||
raise ValueError(f"Unsupported mapping: sources={ns} units, targets={nt} units. Only 1->N or N->N are allowed.")
|
||||
|
||||
return {
|
||||
"action": "transfer_liquid",
|
||||
"sources": sources.get_resource(),
|
||||
"targets": targets.get_resource(),
|
||||
"unit_volume": unit_volume,
|
||||
"tip": tip,
|
||||
}
|
||||
|
||||
def plan_transfer(pm: "ProtocolManager", **kwargs) -> Dict[str, Any]:
|
||||
"""Shorthand to add a non-committing transfer to a ProtocolManager.
|
||||
Accepts the same kwargs as ProtocolManager.add_transfer.
|
||||
"""
|
||||
return pm.add_transfer(**kwargs)
|
||||
|
||||
class ProtocolManager:
|
||||
"""Plan/track transfers and back‑solve minimum initial volumes.
|
||||
|
||||
Use add_transfer(...) to register steps (no mutation).
|
||||
Use compute_min_initials(...) to infer the minimal starting volume of each liquid
|
||||
per resource required to execute the plan in order.
|
||||
"""
|
||||
|
||||
# ---------- lifecycle ----------
|
||||
def __init__(self):
|
||||
# queued logical steps (keep live refs to MaterialResource)
|
||||
self.steps: List[Dict[str, Any]] = []
|
||||
|
||||
# simple tip catalog; choose the smallest that meets min_aspirate and capacity*safety
|
||||
self.tip_catalog = [
|
||||
{"name": "TIP_10uL", "capacity": 10.0, "min_aspirate": 0.5},
|
||||
{"name": "TIP_20uL", "capacity": 20.0, "min_aspirate": 1.0},
|
||||
{"name": "TIP_50uL", "capacity": 50.0, "min_aspirate": 2.0},
|
||||
{"name": "TIP_200uL", "capacity": 200.0, "min_aspirate": 5.0},
|
||||
{"name": "TIP_300uL", "capacity": 300.0, "min_aspirate": 10.0},
|
||||
{"name": "TIP_1000uL", "capacity": 1000.0, "min_aspirate": 20.0},
|
||||
]
|
||||
|
||||
# stable labels for unknown liquids per resource (A, B, C, ..., AA, AB, ...)
|
||||
self._unknown_labels: Dict[MaterialResource, str] = {}
|
||||
self._unknown_label_counter: int = 0
|
||||
|
||||
# ---------- public API ----------
|
||||
def recommend_tip(self, unit_volume: float, safety: float = 1.10) -> str:
|
||||
v = float(unit_volume)
|
||||
# prefer: meets min_aspirate and capacity with safety margin; else fallback to capacity-only; else max capacity
|
||||
eligible = [t for t in self.tip_catalog if t["min_aspirate"] <= v and t["capacity"] >= v * safety]
|
||||
if not eligible:
|
||||
eligible = [t for t in self.tip_catalog if t["capacity"] >= v]
|
||||
return min(eligible or self.tip_catalog, key=lambda t: t["capacity"]) ["name"]
|
||||
|
||||
def get_tip_capacity(self, tip_name: str) -> Optional[float]:
|
||||
for t in self.tip_catalog:
|
||||
if t["name"] == tip_name:
|
||||
return t["capacity"]
|
||||
return None
|
||||
|
||||
def add_transfer(
|
||||
self,
|
||||
sources: MaterialResource,
|
||||
targets: MaterialResource,
|
||||
unit_volume: Union[float, int],
|
||||
tip: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
step = {
|
||||
"action": "transfer_liquid",
|
||||
"sources": sources,
|
||||
"targets": targets,
|
||||
"unit_volume": float(unit_volume),
|
||||
"tip": tip or self.recommend_tip(unit_volume),
|
||||
}
|
||||
self.steps.append(step)
|
||||
# return a serializable shadow (no mutation)
|
||||
return {
|
||||
"action": "transfer_liquid",
|
||||
"sources": sources.get_resource(),
|
||||
"targets": targets.get_resource(),
|
||||
"unit_volume": step["unit_volume"],
|
||||
"tip": step["tip"],
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _liquid_keys_of(resource: MaterialResource) -> List[str]:
|
||||
keys: set[str] = set()
|
||||
for u in resource.units:
|
||||
keys.update(u.keys())
|
||||
return sorted(keys)
|
||||
|
||||
@staticmethod
|
||||
def _fanout_multiplier(ns: int, nt: int) -> Optional[int]:
|
||||
"""Return the number of liquid movements for a mapping shape.
|
||||
1->N: N moves; N->N: N moves; otherwise unsupported (None).
|
||||
"""
|
||||
if ns == 1 and nt >= 1:
|
||||
return nt
|
||||
if ns == nt and ns > 0:
|
||||
return ns
|
||||
return None
|
||||
|
||||
# ---------- planning core ----------
|
||||
def compute_min_initials(
|
||||
self,
|
||||
use_initial: bool = False,
|
||||
external_only: bool = True,
|
||||
) -> Dict[str, Dict[str, float]]:
|
||||
"""Simulate the plan (non‑mutating) and return minimal starting volumes per resource/liquid."""
|
||||
ledger: Dict[MaterialResource, Dict[str, float]] = {}
|
||||
min_seen: Dict[MaterialResource, Dict[str, float]] = {}
|
||||
|
||||
def _ensure(res: MaterialResource) -> None:
|
||||
if res in ledger:
|
||||
return
|
||||
declared = self._liquid_keys_of(res)
|
||||
if use_initial:
|
||||
# sum actual held amounts across units
|
||||
totals = {k: 0.0 for k in declared}
|
||||
for u in res.units:
|
||||
for k, v in u.items():
|
||||
totals[k] = totals.get(k, 0.0) + float(v)
|
||||
ledger[res] = totals
|
||||
else:
|
||||
ledger[res] = {k: 0.0 for k in declared}
|
||||
min_seen[res] = {k: ledger[res].get(k, 0.0) for k in ledger[res]}
|
||||
|
||||
def _proportions(src: MaterialResource, src_bal: Dict[str, float]) -> tuple[List[str], Dict[str, float]]:
|
||||
keys = list(src_bal.keys())
|
||||
total_pos = sum(x for x in src_bal.values() if x > 0)
|
||||
|
||||
# if ledger has no keys yet, seed from declared types on the resource
|
||||
if not keys:
|
||||
keys = self._liquid_keys_of(src)
|
||||
for k in keys:
|
||||
src_bal.setdefault(k, 0.0)
|
||||
min_seen[src].setdefault(k, 0.0)
|
||||
|
||||
if total_pos > 0:
|
||||
# proportional to current positive balances
|
||||
props = {k: (src_bal.get(k, 0.0) / total_pos) for k in keys}
|
||||
return keys, props
|
||||
|
||||
# no material currently: evenly from known keys, or assign an unknown label
|
||||
if keys:
|
||||
eq = 1.0 / len(keys)
|
||||
return keys, {k: eq for k in keys}
|
||||
|
||||
unk = self._label_for_unknown(src)
|
||||
keys = [unk]
|
||||
src_bal.setdefault(unk, 0.0)
|
||||
min_seen[src].setdefault(unk, 0.0)
|
||||
return keys, {unk: 1.0}
|
||||
|
||||
for step in self.steps:
|
||||
if step.get("action") != "transfer_liquid":
|
||||
continue
|
||||
|
||||
src: MaterialResource = step["sources"]
|
||||
dst: MaterialResource = step["targets"]
|
||||
vol = float(step["unit_volume"])
|
||||
if vol <= 0:
|
||||
continue
|
||||
|
||||
_ensure(src)
|
||||
_ensure(dst)
|
||||
|
||||
mult = self._fanout_multiplier(src.unit_count(), dst.unit_count())
|
||||
if not mult:
|
||||
continue # unsupported mapping shape for this planner
|
||||
|
||||
eff_vol = vol * mult
|
||||
src_bal = ledger[src]
|
||||
keys, props = _proportions(src, src_bal)
|
||||
|
||||
# subtract from src; track minima; accumulate to dst
|
||||
moved: Dict[str, float] = {}
|
||||
for k in keys:
|
||||
dv = eff_vol * props[k]
|
||||
src_bal[k] = src_bal.get(k, 0.0) - dv
|
||||
moved[k] = dv
|
||||
prev_min = min_seen[src].get(k, 0.0)
|
||||
if src_bal[k] < prev_min:
|
||||
min_seen[src][k] = src_bal[k]
|
||||
|
||||
dst_bal = ledger[dst]
|
||||
for k, dv in moved.items():
|
||||
dst_bal[k] = dst_bal.get(k, 0.0) + dv
|
||||
min_seen[dst].setdefault(k, dst_bal[k])
|
||||
|
||||
# convert minima (negative) to required initials
|
||||
result: Dict[str, Dict[str, float]] = {}
|
||||
for res, mins in min_seen.items():
|
||||
if external_only and not getattr(res, "is_supply", False):
|
||||
continue
|
||||
need = {liq: max(0.0, -mn) for liq, mn in mins.items() if mn < 0.0}
|
||||
if need:
|
||||
result[res.resource_name] = need
|
||||
return result
|
||||
|
||||
def compute_tip_consumption(self) -> Dict[str, Any]:
|
||||
"""Compute how many tips are consumed at each transfer step, and aggregate by tip type.
|
||||
Rule: each liquid movement (source unit -> target unit) consumes one tip.
|
||||
For supported shapes: 1->N uses N tips; N->N uses N tips.
|
||||
"""
|
||||
per_step: List[Dict[str, Any]] = []
|
||||
totals_by_tip: Dict[str, int] = {}
|
||||
|
||||
for i, s in enumerate(self.steps):
|
||||
if s.get("action") != "transfer_liquid":
|
||||
continue
|
||||
ns = s["sources"].unit_count()
|
||||
nt = s["targets"].unit_count()
|
||||
moves = self._fanout_multiplier(ns, nt) or 0
|
||||
tip_name = s.get("tip") or self.recommend_tip(s["unit_volume"]) # per-step tip may vary
|
||||
per_step.append({
|
||||
"idx": i,
|
||||
"tip": tip_name,
|
||||
"tips_used": moves,
|
||||
"moves": moves,
|
||||
})
|
||||
totals_by_tip[tip_name] = totals_by_tip.get(tip_name, 0) + int(moves)
|
||||
|
||||
return {"per_step": per_step, "totals_by_tip": totals_by_tip}
|
||||
|
||||
def compute_min_initials_with_tips(
|
||||
self,
|
||||
use_initial: bool = False,
|
||||
external_only: bool = True,
|
||||
) -> Dict[str, Any]:
|
||||
needs = self.compute_min_initials(use_initial=use_initial, external_only=external_only)
|
||||
step_tips: List[Dict[str, Any]] = []
|
||||
totals_by_tip: Dict[str, int] = {}
|
||||
|
||||
for i, s in enumerate(self.steps):
|
||||
if s.get("action") != "transfer_liquid":
|
||||
continue
|
||||
ns = s["sources"].unit_count()
|
||||
nt = s["targets"].unit_count()
|
||||
moves = self._fanout_multiplier(ns, nt) or 0
|
||||
tip_name = s.get("tip") or self.recommend_tip(s["unit_volume"]) # step-specific tip
|
||||
totals_by_tip[self.get_tip_capacity(tip_name)] = totals_by_tip.get(tip_name, 0) + int(moves)
|
||||
|
||||
step_tips.append({
|
||||
"idx": i,
|
||||
"tip": tip_name,
|
||||
"tip_capacity": self.get_tip_capacity(tip_name),
|
||||
"unit_volume": s["unit_volume"],
|
||||
"tips_used": moves,
|
||||
})
|
||||
return {"liquid_setup": needs, "step_tips": step_tips, "totals_by_tip": totals_by_tip}
|
||||
|
||||
# ---------- unknown labels ----------
|
||||
def _index_to_letters(self, idx: int) -> str:
|
||||
"""0->A, 1->B, ... 25->Z, 26->AA, 27->AB ... (Excel-like)"""
|
||||
s: List[str] = []
|
||||
idx = int(idx)
|
||||
while True:
|
||||
idx, r = divmod(idx, 26)
|
||||
s.append(chr(ord('A') + r))
|
||||
if idx == 0:
|
||||
break
|
||||
idx -= 1 # Excel-style carry
|
||||
return "".join(reversed(s))
|
||||
|
||||
def _label_for_unknown(self, res: MaterialResource) -> str:
|
||||
"""Assign a stable unknown-liquid label (A/B/C/...) per resource."""
|
||||
if res not in self._unknown_labels:
|
||||
lab = self._index_to_letters(self._unknown_label_counter)
|
||||
self._unknown_label_counter += 1
|
||||
self._unknown_labels[res] = lab
|
||||
return self._unknown_labels[res]
|
||||
|
||||
|
||||
# 在这一步传输目前有的物料
|
||||
class LabResource:
|
||||
def __init__(self):
|
||||
self.tipracks = []
|
||||
self.plates = []
|
||||
self.trash = []
|
||||
|
||||
def add_tipracks(self, tiprack: List[TipRack]):
|
||||
self.tipracks.extend(tiprack)
|
||||
def add_plates(self, plate: List[Plate]):
|
||||
self.plates.extend(plate)
|
||||
def add_trash(self, trash: List[Plate]):
|
||||
self.trash.extend(trash)
|
||||
def get_resources_info(self) -> Dict[str, Any]:
|
||||
tipracks = [{"name": tr.name, "max_volume": tr.children[0].tracker._tip.maximal_volume, "count": len(tr.children)} for tr in self.tipracks]
|
||||
plates = [{"name": pl.name, "max_volume": pl.children[0].max_volume, "count": len(pl.children)} for pl in self.plates]
|
||||
trash = [{"name": t.name, "max_volume": t.children[0].max_volume, "count": len(t.children)} for t in self.trash]
|
||||
return {
|
||||
"tipracks": tipracks,
|
||||
"plates": plates,
|
||||
"trash": trash
|
||||
}
|
||||
|
||||
from typing import Dict, Any
|
||||
|
||||
class DefaultLayout:
|
||||
|
||||
def __init__(self, product_name: str = "PRCXI9300"):
|
||||
self.labresource = None
|
||||
if product_name not in ["PRCXI9300", "PRCXI9320"]:
|
||||
raise ValueError(f"Unsupported product_name: {product_name}. Only 'PRCXI9300' and 'PRCXI9320' are supported.")
|
||||
|
||||
if product_name == "PRCXI9300":
|
||||
self.rows = 2
|
||||
self.columns = 3
|
||||
self.layout = [1, 2, 3, 4, 5, 6]
|
||||
self.trash_slot = 3
|
||||
self.waste_liquid_slot = 6
|
||||
elif product_name == "PRCXI9320":
|
||||
self.rows = 3
|
||||
self.columns = 4
|
||||
self.layout = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
|
||||
self.trash_slot = 3
|
||||
self.waste_liquid_slot = 12
|
||||
|
||||
def get_layout(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"rows": self.rows,
|
||||
"columns": self.columns,
|
||||
"layout": self.layout,
|
||||
"trash_slot": self.trash_slot,
|
||||
"waste_liquid_slot": self.waste_liquid_slot
|
||||
}
|
||||
|
||||
def get_trash_slot(self) -> int:
|
||||
return self.trash_slot
|
||||
|
||||
def get_waste_liquid_slot(self) -> int:
|
||||
return self.waste_liquid_slot
|
||||
|
||||
def set_liquid_handler_layout(self, product_name: str):
|
||||
if product_name == "PRCXI9300":
|
||||
self.rows = 2
|
||||
self.columns = 3
|
||||
self.layout = [1, 2, 3, 4, 5, 6]
|
||||
self.trash_slot = 3
|
||||
self.waste_liquid_slot = 6
|
||||
|
||||
elif product_name == "PRCXI9320":
|
||||
self.rows = 3
|
||||
self.columns = 4
|
||||
self.layout = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
|
||||
self.trash_slot = 3
|
||||
self.waste_liquid_slot = 12
|
||||
|
||||
def set_trash_slot(self, slot: int):
|
||||
self.trash_slot = slot
|
||||
|
||||
def set_waste_liquid_slot(self, slot: int):
|
||||
self.waste_liquid_slot = slot
|
||||
|
||||
def add_lab_resource(self, lab_resource: LabResource):
|
||||
self.labresource = lab_resource.get_resources_info()
|
||||
|
||||
def recommend_layout(self, needs: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""根据 needs 推荐布局"""
|
||||
liquid_info = needs['liquid_setup']
|
||||
tip_info = needs['totals_by_tip'] # 修改这里:直接访问 totals_by_tip
|
||||
print("当前实验所需物料信息:", liquid_info)
|
||||
print("当前实验所需枪头信息:", tip_info)
|
||||
print(self.labresource)
|
||||
|
||||
for liquid in liquid_info:
|
||||
# total_volume = liquid.values()
|
||||
print(liquid)
|
||||
#print(f"资源 {liquid} 需要的总体积: {total_volume}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
# ---- 资源:SUP 供液(X),中间板 R1(4 孔空),目标板 R2(4 孔空)----
|
||||
sup = MaterialResource("SUP", slot=5, well=[1], liquid_id="X", volume=10000)
|
||||
r1 = MaterialResource("R1", slot=6, well=[1,2,3,4,5,6,7,8])
|
||||
r2 = MaterialResource("R2", slot=7, well=[1,2,3,4,5,6,7,8])
|
||||
|
||||
pm = ProtocolManager()
|
||||
# 步骤1:SUP -> R1,1->N 扇出,每孔 50 uL(总 200 uL)
|
||||
pm.add_transfer(sup, r1, unit_volume=10.0)
|
||||
# 步骤2:R1 -> R2,N->N 对应,每对 25 uL(总 100 uL;来自 R1 中已存在的混合物 X)
|
||||
pm.add_transfer(r1, r2, unit_volume=120.0)
|
||||
|
||||
out = pm.compute_min_initials_with_tips()
|
||||
|
||||
|
||||
|
||||
|
||||
# layout_planer = DefaultLayout('PRCXI9320')
|
||||
# print(layout_planer.get_layout())
|
||||
# print("回推最小需求:", out["liquid_setup"]) # {'SUP': {'X': 200.0}}
|
||||
# print("步骤枪头建议:", out["step_tips"]) # [{'idx':0,'tip':'TIP_200uL','unit_volume':50.0}, {'idx':1,'tip':'TIP_50uL','unit_volume':25.0}]
|
||||
|
||||
# # 实际执行(可选)
|
||||
# transfer_liquid(sup, r1, unit_volume=50.0)
|
||||
# transfer_liquid(r1, r2, unit_volume=25.0)
|
||||
# print("执行后 SUP:", sup.get_resource()) # 总体积 -200
|
||||
# print("执行后 R1:", r1.get_resource()) # 每孔 25 uL(50 进 -25 出)
|
||||
# print("执行后 R2:", r2.get_resource()) # 每孔 25 uL
|
||||
|
||||
|
||||
from pylabrobot.resources.opentrons.tube_racks import *
|
||||
from pylabrobot.resources.opentrons.plates import *
|
||||
from pylabrobot.resources.opentrons.tip_racks import *
|
||||
from pylabrobot.resources.opentrons.reservoirs import *
|
||||
|
||||
plate = [locals()['nest_96_wellplate_2ml_deep'](name="thermoscientificnunc_96_wellplate_2000ul"), locals()['corning_96_wellplate_360ul_flat'](name="corning_96_wellplate_360ul_flat")]
|
||||
tiprack = [locals()['opentrons_96_tiprack_300ul'](name="opentrons_96_tiprack_300ul"), locals()['opentrons_96_tiprack_1000ul'](name="opentrons_96_tiprack_1000ul")]
|
||||
trash = [locals()['axygen_1_reservoir_90ml'](name="axygen_1_reservoir_90ml")]
|
||||
|
||||
from pprint import pprint
|
||||
|
||||
lab_resource = LabResource()
|
||||
lab_resource.add_tipracks(tiprack)
|
||||
lab_resource.add_plates(plate)
|
||||
lab_resource.add_trash(trash)
|
||||
|
||||
layout_planer = DefaultLayout('PRCXI9300')
|
||||
layout_planer.add_lab_resource(lab_resource)
|
||||
layout_planer.recommend_layout(out)
|
||||
@@ -981,121 +981,119 @@ if __name__ == "__main__":
|
||||
# 4.
|
||||
|
||||
|
||||
deck = PRCXI9300Deck(name="PRCXI_Deck_9300", size_x=100, size_y=100, size_z=100)
|
||||
# deck = PRCXI9300Deck(name="PRCXI_Deck_9300", size_x=100, size_y=100, size_z=100)
|
||||
|
||||
from pylabrobot.resources.opentrons.tip_racks import opentrons_96_tiprack_300ul,opentrons_96_tiprack_10ul
|
||||
from pylabrobot.resources.opentrons.plates import corning_96_wellplate_360ul_flat, nest_96_wellplate_2ml_deep
|
||||
# from pylabrobot.resources.opentrons.tip_racks import opentrons_96_tiprack_300ul,opentrons_96_tiprack_10ul
|
||||
# from pylabrobot.resources.opentrons.plates import corning_96_wellplate_360ul_flat, nest_96_wellplate_2ml_deep
|
||||
|
||||
def get_well_container(name: str) -> PRCXI9300Container:
|
||||
well_containers = corning_96_wellplate_360ul_flat(name).serialize()
|
||||
plate = PRCXI9300Container(name=name, size_x=50, size_y=50, size_z=10, category="plate",
|
||||
ordering=well_containers["ordering"])
|
||||
plate_serialized = plate.serialize()
|
||||
plate_serialized["parent_name"] = deck.name
|
||||
well_containers.update({k: v for k, v in plate_serialized.items() if k not in ["children"]})
|
||||
new_plate: PRCXI9300Container = PRCXI9300Container.deserialize(well_containers)
|
||||
return new_plate
|
||||
# def get_well_container(name: str) -> PRCXI9300Container:
|
||||
# well_containers = corning_96_wellplate_360ul_flat(name).serialize()
|
||||
# plate = PRCXI9300Container(name=name, size_x=50, size_y=50, size_z=10, category="plate",
|
||||
# ordering=well_containers["ordering"])
|
||||
# plate_serialized = plate.serialize()
|
||||
# plate_serialized["parent_name"] = deck.name
|
||||
# well_containers.update({k: v for k, v in plate_serialized.items() if k not in ["children"]})
|
||||
# new_plate: PRCXI9300Container = PRCXI9300Container.deserialize(well_containers)
|
||||
# return new_plate
|
||||
|
||||
def get_tip_rack(name: str) -> PRCXI9300Container:
|
||||
tip_racks = opentrons_96_tiprack_300ul("name").serialize()
|
||||
tip_rack = PRCXI9300Container(name=name, size_x=50, size_y=50, size_z=10, category="tip_rack",
|
||||
ordering=tip_racks["ordering"])
|
||||
tip_rack_serialized = tip_rack.serialize()
|
||||
tip_rack_serialized["parent_name"] = deck.name
|
||||
tip_racks.update({k: v for k, v in tip_rack_serialized.items() if k not in ["children"]})
|
||||
new_tip_rack: PRCXI9300Container = PRCXI9300Container.deserialize(tip_racks)
|
||||
return new_tip_rack
|
||||
# def get_tip_rack(name: str) -> PRCXI9300Container:
|
||||
# tip_racks = opentrons_96_tiprack_300ul("name").serialize()
|
||||
# tip_rack = PRCXI9300Container(name=name, size_x=50, size_y=50, size_z=10, category="tip_rack",
|
||||
# ordering=tip_racks["ordering"])
|
||||
# tip_rack_serialized = tip_rack.serialize()
|
||||
# tip_rack_serialized["parent_name"] = deck.name
|
||||
# tip_racks.update({k: v for k, v in tip_rack_serialized.items() if k not in ["children"]})
|
||||
# new_tip_rack: PRCXI9300Container = PRCXI9300Container.deserialize(tip_racks)
|
||||
# return new_tip_rack
|
||||
|
||||
plate1 = get_tip_rack("RackT1")
|
||||
plate1.load_state({
|
||||
"Material": {
|
||||
"uuid": "076250742950465b9d6ea29a225dfb00",
|
||||
"Code": "ZX-001-300",
|
||||
"Name": "300μL Tip头"
|
||||
}
|
||||
})
|
||||
# plate1 = get_tip_rack("RackT1")
|
||||
# plate1.load_state({
|
||||
# "Material": {
|
||||
# "uuid": "076250742950465b9d6ea29a225dfb00",
|
||||
# "Code": "ZX-001-300",
|
||||
# "Name": "300μL Tip头"
|
||||
# }
|
||||
# })
|
||||
|
||||
plate2 = get_well_container("PlateT2")
|
||||
plate2.load_state({
|
||||
"Material": {
|
||||
"uuid": "57b1e4711e9e4a32b529f3132fc5931f",
|
||||
"Code": "ZX-019-2.2",
|
||||
"Name": "96深孔板"
|
||||
}
|
||||
})
|
||||
# plate2 = get_well_container("PlateT2")
|
||||
# plate2.load_state({
|
||||
# "Material": {
|
||||
# "uuid": "57b1e4711e9e4a32b529f3132fc5931f",
|
||||
# "Code": "ZX-019-2.2",
|
||||
# "Name": "96深孔板"
|
||||
# }
|
||||
# })
|
||||
|
||||
|
||||
plate3 = PRCXI9300Trash("trash", size_x=50, size_y=100, size_z=10, category="trash")
|
||||
plate3.load_state({
|
||||
"Material": {
|
||||
"uuid": "730067cf07ae43849ddf4034299030e9"
|
||||
}
|
||||
})
|
||||
# plate3 = PRCXI9300Trash("trash", size_x=50, size_y=100, size_z=10, category="trash")
|
||||
# plate3.load_state({
|
||||
# "Material": {
|
||||
# "uuid": "730067cf07ae43849ddf4034299030e9"
|
||||
# }
|
||||
# })
|
||||
|
||||
plate4 = get_well_container("PlateT4")
|
||||
plate4.load_state({
|
||||
"Material": {
|
||||
"uuid": "57b1e4711e9e4a32b529f3132fc5931f",
|
||||
"Code": "ZX-019-2.2",
|
||||
"Name": "96深孔板"
|
||||
}
|
||||
})
|
||||
# plate4 = get_well_container("PlateT4")
|
||||
# plate4.load_state({
|
||||
# "Material": {
|
||||
# "uuid": "57b1e4711e9e4a32b529f3132fc5931f",
|
||||
# "Code": "ZX-019-2.2",
|
||||
# "Name": "96深孔板"
|
||||
# }
|
||||
# })
|
||||
|
||||
plate5 = get_well_container("PlateT5")
|
||||
plate5.load_state({
|
||||
"Material": {
|
||||
"uuid": "57b1e4711e9e4a32b529f3132fc5931f",
|
||||
"Code": "ZX-019-2.2",
|
||||
"Name": "96深孔板"
|
||||
}
|
||||
})
|
||||
plate6 = get_well_container("PlateT6")
|
||||
# plate5 = get_well_container("PlateT5")
|
||||
# plate5.load_state({
|
||||
# "Material": {
|
||||
# "uuid": "57b1e4711e9e4a32b529f3132fc5931f",
|
||||
# "Code": "ZX-019-2.2",
|
||||
# "Name": "96深孔板"
|
||||
# }
|
||||
# })
|
||||
# plate6 = get_well_container("PlateT6")
|
||||
|
||||
plate6.load_state({
|
||||
"Material": {
|
||||
"uuid": "57b1e4711e9e4a32b529f3132fc5931f",
|
||||
"Code": "ZX-019-2.2",
|
||||
"Name": "96深孔板"
|
||||
}
|
||||
})
|
||||
# plate6.load_state({
|
||||
# "Material": {
|
||||
# "uuid": "57b1e4711e9e4a32b529f3132fc5931f",
|
||||
# "Code": "ZX-019-2.2",
|
||||
# "Name": "96深孔板"
|
||||
# }
|
||||
# })
|
||||
|
||||
deck.assign_child_resource(plate1, location=Coordinate(0, 0, 0))
|
||||
deck.assign_child_resource(plate2, location=Coordinate(0, 0, 0))
|
||||
deck.assign_child_resource(plate3, location=Coordinate(0, 0, 0))
|
||||
deck.assign_child_resource(plate4, location=Coordinate(0, 0, 0))
|
||||
deck.assign_child_resource(plate5, location=Coordinate(0, 0, 0))
|
||||
deck.assign_child_resource(plate6, location=Coordinate(0, 0, 0))
|
||||
# deck.assign_child_resource(plate1, location=Coordinate(0, 0, 0))
|
||||
# deck.assign_child_resource(plate2, location=Coordinate(0, 0, 0))
|
||||
# deck.assign_child_resource(plate3, location=Coordinate(0, 0, 0))
|
||||
# deck.assign_child_resource(plate4, location=Coordinate(0, 0, 0))
|
||||
# deck.assign_child_resource(plate5, location=Coordinate(0, 0, 0))
|
||||
# deck.assign_child_resource(plate6, location=Coordinate(0, 0, 0))
|
||||
|
||||
# plate_2_liquids = [[('water', 500)]]*96
|
||||
# # # plate_2_liquids = [[('water', 500)]]*96
|
||||
|
||||
# plate2.set_well_liquids(plate_2_liquids)
|
||||
# # # plate2.set_well_liquids(plate_2_liquids)
|
||||
|
||||
|
||||
|
||||
|
||||
handler = PRCXI9300Handler(deck=deck, host="10.181.214.132", port=9999,
|
||||
timeout=10.0, setup=False, debug=False,
|
||||
simulator=True,
|
||||
matrix_id="71593",
|
||||
channel_num=8, axis="Left") # Initialize the handler with the deck and host settings
|
||||
# handler = PRCXI9300Handler(deck=deck, host="10.181.214.132", port=9999,
|
||||
# timeout=10.0, setup=False, debug=False,
|
||||
# simulator=True,
|
||||
# matrix_id="71593",
|
||||
# channel_num=8, axis="Left") # Initialize the handler with the deck and host settings
|
||||
|
||||
# plate_2_liquids = handler.set_group("water", plate2.children[:8], [200]*8)
|
||||
|
||||
# plate5_liquids = handler.set_group("master_mix", plate5.children[:8], [100]*8)
|
||||
|
||||
|
||||
plate_2_liquids = handler.set_group("water", plate2.children[:8], [200]*8)
|
||||
plate5_liquids = handler.set_group("master_mix", plate5.children[:8], [100]*8)
|
||||
|
||||
handler.set_tiprack([plate1])
|
||||
asyncio.run(handler.setup()) # Initialize the handler and setup the connection
|
||||
from pylabrobot.resources import set_volume_tracking
|
||||
# handler.set_tiprack([plate1])
|
||||
# asyncio.run(handler.setup()) # Initialize the handler and setup the connection
|
||||
# from pylabrobot.resources import set_volume_tracking
|
||||
# from pylabrobot.resources import set_tip_tracking
|
||||
set_volume_tracking(enabled=True)
|
||||
# set_volume_tracking(enabled=True)
|
||||
# from unilabos.resources.graphio import *
|
||||
# # A = tree_to_list([resource_plr_to_ulab(deck)])
|
||||
# # with open("deck_9300_new.json", "w", encoding="utf-8") as f:
|
||||
# # json.dump(A, f, indent=4, ensure_ascii=False)
|
||||
asyncio.run(handler.create_protocol(protocol_name="Test Protocol")) # Initialize the backend and setup the connection
|
||||
asyncio.run(handler.transfer_group("water", "master_mix", 100)) # Reset tip tracking
|
||||
# asyncio.run(handler.create_protocol(protocol_name="Test Protocol")) # Initialize the backend and setup the connection
|
||||
# asyncio.run(handler.transfer_group("water", "master_mix", 100)) # Reset tip tracking
|
||||
|
||||
# asyncio.run(handler.pick_up_tips(plate1.children[:8],[0,1,2,3,4,5,6,7]))
|
||||
# print(plate1.children[:8])
|
||||
@@ -1166,164 +1164,172 @@ if __name__ == "__main__":
|
||||
### 9320 ###
|
||||
|
||||
|
||||
# deck = PRCXI9300Deck(name="PRCXI_Deck", size_x=100, size_y=100, size_z=100)
|
||||
deck = PRCXI9300Deck(name="PRCXI_Deck", size_x=100, size_y=100, size_z=100)
|
||||
|
||||
# from pylabrobot.resources.opentrons.tip_racks import tipone_96_tiprack_200ul,opentrons_96_tiprack_10ul
|
||||
# from pylabrobot.resources.opentrons.plates import corning_96_wellplate_360ul_flat, nest_96_wellplate_2ml_deep
|
||||
from pylabrobot.resources.opentrons.tip_racks import tipone_96_tiprack_200ul,opentrons_96_tiprack_10ul
|
||||
from pylabrobot.resources.opentrons.plates import corning_96_wellplate_360ul_flat, nest_96_wellplate_2ml_deep
|
||||
|
||||
# def get_well_container(name: str) -> PRCXI9300Container:
|
||||
# well_containers = corning_96_wellplate_360ul_flat(name).serialize()
|
||||
# plate = PRCXI9300Container(name=name, size_x=50, size_y=50, size_z=10, category="plate",
|
||||
# ordering=collections.OrderedDict())
|
||||
# plate_serialized = plate.serialize()
|
||||
# plate_serialized["parent_name"] = deck.name
|
||||
# well_containers.update({k: v for k, v in plate_serialized.items() if k not in ["children"]})
|
||||
# new_plate: PRCXI9300Container = PRCXI9300Container.deserialize(well_containers)
|
||||
# return new_plate
|
||||
def get_well_container(name: str) -> PRCXI9300Container:
|
||||
well_containers = corning_96_wellplate_360ul_flat(name).serialize()
|
||||
plate = PRCXI9300Container(name=name, size_x=50, size_y=50, size_z=10, category="plate",
|
||||
ordering=collections.OrderedDict())
|
||||
plate_serialized = plate.serialize()
|
||||
plate_serialized["parent_name"] = deck.name
|
||||
well_containers.update({k: v for k, v in plate_serialized.items() if k not in ["children"]})
|
||||
new_plate: PRCXI9300Container = PRCXI9300Container.deserialize(well_containers)
|
||||
return new_plate
|
||||
|
||||
# def get_tip_rack(name: str) -> PRCXI9300Container:
|
||||
# tip_racks = opentrons_96_tiprack_10ul("name").serialize()
|
||||
# tip_rack = PRCXI9300Container(name=name, size_x=50, size_y=50, size_z=10, category="tip_rack",
|
||||
# ordering=collections.OrderedDict())
|
||||
# tip_rack_serialized = tip_rack.serialize()
|
||||
# tip_rack_serialized["parent_name"] = deck.name
|
||||
# tip_racks.update({k: v for k, v in tip_rack_serialized.items() if k not in ["children"]})
|
||||
# new_tip_rack: PRCXI9300Container = PRCXI9300Container.deserialize(tip_racks)
|
||||
# return new_tip_rack
|
||||
def get_tip_rack(name: str) -> PRCXI9300Container:
|
||||
tip_racks = opentrons_96_tiprack_10ul("name").serialize()
|
||||
tip_rack = PRCXI9300Container(name=name, size_x=50, size_y=50, size_z=10, category="tip_rack",
|
||||
ordering=collections.OrderedDict())
|
||||
tip_rack_serialized = tip_rack.serialize()
|
||||
tip_rack_serialized["parent_name"] = deck.name
|
||||
tip_racks.update({k: v for k, v in tip_rack_serialized.items() if k not in ["children"]})
|
||||
new_tip_rack: PRCXI9300Container = PRCXI9300Container.deserialize(tip_racks)
|
||||
return new_tip_rack
|
||||
|
||||
# plate1 = get_well_container("HPLCPlateT1")
|
||||
# plate1.load_state({
|
||||
# "Material": {
|
||||
# "uuid": "548bbc3df0d4447586f2c19d2c0c0c55",
|
||||
# "Code": "HPLC01",
|
||||
# "Name": "HPLC料盘"
|
||||
# }
|
||||
# })
|
||||
# plate2 = get_well_container("PlateT2")
|
||||
# plate2.load_state({
|
||||
# "Material": {
|
||||
# "uuid": "04211a2dc93547fe9bf6121eac533650",
|
||||
# }
|
||||
# })
|
||||
# plate3 = get_well_container("PlateT3")
|
||||
# plate3.load_state({
|
||||
# "Material": {
|
||||
# "uuid": "04211a2dc93547fe9bf6121eac533650",
|
||||
# }
|
||||
# })
|
||||
# trash = PRCXI9300Trash(name="trash", size_x=50, size_y=50, size_z=10, category="trash")
|
||||
# trash.load_state({
|
||||
# "Material": {
|
||||
# "uuid": "730067cf07ae43849ddf4034299030e9"
|
||||
# }
|
||||
# })
|
||||
# plate5 = get_well_container("PlateT5")
|
||||
# plate5.load_state({
|
||||
# "Material": {
|
||||
# "uuid": "04211a2dc93547fe9bf6121eac533650",
|
||||
# }
|
||||
# })
|
||||
# plate6 = get_well_container("PlateT6")
|
||||
# plate6.load_state({
|
||||
# "Material": {
|
||||
# "uuid": "04211a2dc93547fe9bf6121eac533650"
|
||||
# }
|
||||
# })
|
||||
# plate7 = PRCXI9300Container(name="plateT7", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict())
|
||||
# plate7.load_state({
|
||||
# "Material": {
|
||||
# "uuid": "04211a2dc93547fe9bf6121eac533650"
|
||||
# }
|
||||
# })
|
||||
# plate8 = get_tip_rack("RackT8")
|
||||
# plate8.load_state({
|
||||
# "Material": {
|
||||
# "uuid": "068b3815e36b4a72a59bae017011b29f",
|
||||
# "Code": "ZX-001-10+",
|
||||
# "Name": "10μL加长 Tip头"
|
||||
# }
|
||||
# })
|
||||
# plate9 = get_well_container("PlateT9")
|
||||
# plate9.load_state({
|
||||
# "Material": {
|
||||
# "uuid": "04211a2dc93547fe9bf6121eac533650"
|
||||
# }
|
||||
# })
|
||||
# plate10 = get_well_container("PlateT10")
|
||||
# plate10.load_state({
|
||||
# "Material": {
|
||||
# "uuid": "04211a2dc93547fe9bf6121eac533650"
|
||||
# }
|
||||
# })
|
||||
# plate11 = get_well_container("PlateT11")
|
||||
# plate11.load_state({
|
||||
# "Material": {
|
||||
# "uuid": "57b1e4711e9e4a32b529f3132fc5931f",
|
||||
# }
|
||||
# })
|
||||
# plate12 = get_well_container("PlateT12")
|
||||
# plate12.load_state({
|
||||
# "Material": {
|
||||
# "uuid": "04211a2dc93547fe9bf6121eac533650"
|
||||
# }
|
||||
# })
|
||||
# plate13 = get_well_container("PlateT13")
|
||||
# plate13.load_state({
|
||||
# "Material": {
|
||||
# "uuid": "04211a2dc93547fe9bf6121eac533650"
|
||||
# }
|
||||
# })
|
||||
plate1 = get_well_container("HPLCPlateT1")
|
||||
plate1.load_state({
|
||||
"Material": {
|
||||
"uuid": "548bbc3df0d4447586f2c19d2c0c0c55",
|
||||
"Code": "HPLC01",
|
||||
"Name": "HPLC料盘"
|
||||
}
|
||||
})
|
||||
plate2 = get_well_container("PlateT2")
|
||||
plate2.load_state({
|
||||
"Material": {
|
||||
"uuid": "04211a2dc93547fe9bf6121eac533650",
|
||||
}
|
||||
})
|
||||
plate3 = get_well_container("PlateT3")
|
||||
plate3.load_state({
|
||||
"Material": {
|
||||
"uuid": "04211a2dc93547fe9bf6121eac533650",
|
||||
}
|
||||
})
|
||||
trash = PRCXI9300Trash(name="trash", size_x=50, size_y=50, size_z=10, category="trash")
|
||||
trash.load_state({
|
||||
"Material": {
|
||||
"uuid": "730067cf07ae43849ddf4034299030e9"
|
||||
}
|
||||
})
|
||||
plate5 = get_well_container("PlateT5")
|
||||
plate5.load_state({
|
||||
"Material": {
|
||||
"uuid": "04211a2dc93547fe9bf6121eac533650",
|
||||
}
|
||||
})
|
||||
plate6 = get_well_container("PlateT6")
|
||||
plate6.load_state({
|
||||
"Material": {
|
||||
"uuid": "04211a2dc93547fe9bf6121eac533650"
|
||||
}
|
||||
})
|
||||
plate7 = PRCXI9300Container(name="plateT7", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict())
|
||||
plate7.load_state({
|
||||
"Material": {
|
||||
"uuid": "04211a2dc93547fe9bf6121eac533650"
|
||||
}
|
||||
})
|
||||
plate8 = get_tip_rack("RackT8")
|
||||
plate8.load_state({
|
||||
"Material": {
|
||||
"uuid": "068b3815e36b4a72a59bae017011b29f",
|
||||
"Code": "ZX-001-10+",
|
||||
"Name": "10μL加长 Tip头"
|
||||
}
|
||||
})
|
||||
plate9 = get_well_container("PlateT9")
|
||||
plate9.load_state({
|
||||
"Material": {
|
||||
"uuid": "04211a2dc93547fe9bf6121eac533650"
|
||||
}
|
||||
})
|
||||
plate10 = get_well_container("PlateT10")
|
||||
plate10.load_state({
|
||||
"Material": {
|
||||
"uuid": "04211a2dc93547fe9bf6121eac533650"
|
||||
}
|
||||
})
|
||||
plate11 = get_well_container("PlateT11")
|
||||
plate11.load_state({
|
||||
"Material": {
|
||||
"uuid": "57b1e4711e9e4a32b529f3132fc5931f",
|
||||
}
|
||||
})
|
||||
plate12 = get_well_container("PlateT12")
|
||||
plate12.load_state({
|
||||
"Material": {
|
||||
"uuid": "04211a2dc93547fe9bf6121eac533650"
|
||||
}
|
||||
})
|
||||
plate13 = get_well_container("PlateT13")
|
||||
plate13.load_state({
|
||||
"Material": {
|
||||
"uuid": "04211a2dc93547fe9bf6121eac533650"
|
||||
}
|
||||
})
|
||||
|
||||
# # container_for_nothing = PRCXI9300Container(name="container_for_nothing", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict())
|
||||
# container_for_nothing = PRCXI9300Container(name="container_for_nothing", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict())
|
||||
|
||||
# deck.assign_child_resource(plate1, location=Coordinate(0, 0, 0))
|
||||
# deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing1", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
|
||||
# deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing2", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
|
||||
# deck.assign_child_resource(trash, location=Coordinate(0, 0, 0))
|
||||
# deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing3", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
|
||||
# deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
|
||||
# deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing4", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
|
||||
# deck.assign_child_resource(plate8, location=Coordinate(0, 0, 0))
|
||||
# deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing5", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
|
||||
# deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing6", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
|
||||
# deck.assign_child_resource(plate11, location=Coordinate(0, 0, 0))
|
||||
# deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing7", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
|
||||
# deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing8", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
|
||||
deck.assign_child_resource(plate1, location=Coordinate(0, 0, 0))
|
||||
deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing1", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
|
||||
deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing2", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
|
||||
deck.assign_child_resource(trash, location=Coordinate(0, 0, 0))
|
||||
deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing3", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
|
||||
deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
|
||||
deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing4", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
|
||||
deck.assign_child_resource(plate8, location=Coordinate(0, 0, 0))
|
||||
deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing5", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
|
||||
deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing6", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
|
||||
deck.assign_child_resource(plate11, location=Coordinate(0, 0, 0))
|
||||
deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing7", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
|
||||
deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing8", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
|
||||
|
||||
# handler = PRCXI9300Handler(deck=deck, host="10.181.102.13", port=9999,
|
||||
# timeout=10.0, setup=False, debug=False,
|
||||
# matrix_id="fd383e6d-2d0e-40b5-9c01-1b2870b1f1b1",
|
||||
# channel_num=8, axis="Right") # Initialize the handler with the deck and host settings
|
||||
handler = PRCXI9300Handler(deck=deck, host="172.21.5.75", port=9999,
|
||||
timeout=10.0, setup=False, debug=True,
|
||||
matrix_id="c1d0d5dc-40f2-4f24-97ac-9cc49c68496c",
|
||||
channel_num=1, axis="Left",simulator=True) # Initialize the handler with the deck and host settings
|
||||
|
||||
# handler.set_tiprack([plate8]) # Set the tip rack for the handler
|
||||
# asyncio.run(handler.setup()) # Initialize the handler and setup the connection
|
||||
# from pylabrobot.resources import set_volume_tracking
|
||||
# # from pylabrobot.resources import set_tip_tracking
|
||||
# set_volume_tracking(enabled=True)
|
||||
handler.set_tiprack([plate8]) # Set the tip rack for the handler
|
||||
asyncio.run(handler.setup()) # Initialize the handler and setup the connection
|
||||
from pylabrobot.resources import set_volume_tracking
|
||||
# from pylabrobot.resources import set_tip_tracking
|
||||
set_volume_tracking(enabled=True)
|
||||
|
||||
# plate11.set_well_liquids([("Water", 100) if (i % 8 == 0 and i // 8 < 6) else (None, 100) for i in range(96)]) # Set liquids for every 8 wells in plate8
|
||||
plate_2_liquids = handler.set_group("water", [plate2.children[0]], [300])
|
||||
#print(plate_2_liquids)
|
||||
plate5_liquids = handler.set_group("master_mix", plate5.children[:23], [100]*23)
|
||||
#print(plate5_liquids)
|
||||
|
||||
# from unilabos.resources.graphio import *
|
||||
# plate11.set_well_liquids([("Water", 100) if (i % 8 == 0 and i // 8 < 6) else (None, 100) for i in range(96)]) # Set liquids for every 8 wells in plate8
|
||||
|
||||
from unilabos.resources.graphio import *
|
||||
|
||||
# A = tree_to_list([resource_plr_to_ulab(deck)])
|
||||
# # with open("deck.json", "w", encoding="utf-8") as f:
|
||||
# # json.dump(A, f, indent=4, ensure_ascii=False)
|
||||
|
||||
# print(plate11.get_well(0).tracker.get_used_volume())
|
||||
# asyncio.run(handler.create_protocol(protocol_name="Test Protocol")) # Initialize the backend and setup the connection
|
||||
asyncio.run(handler.create_protocol(protocol_name="Test Protocol")) # Initialize the backend and setup the connection
|
||||
|
||||
# # asyncio.run(handler.pick_up_tips([plate8.children[8]],[0]))
|
||||
# # print(plate8.children[8])
|
||||
# # # asyncio.run(handler.run_protocol())
|
||||
# # asyncio.run(handler.aspirate([plate11.children[0]],[10], [0]))
|
||||
# # print(plate11.children[0])
|
||||
# # # asyncio.run(handler.run_protocol())
|
||||
# # asyncio.run(handler.dispense([plate1.children[0]],[10],[0]))
|
||||
# # print(plate1.children[0])
|
||||
# # # asyncio.run(handler.run_protocol())
|
||||
# # asyncio.run(handler.mix([plate1.children[0]], mix_time=3, mix_vol=5, height_to_bottom=0.5, offsets=Coordinate(0, 0, 0), mix_rate=100))
|
||||
# # print(plate1.children[0])
|
||||
# # asyncio.run(handler.discard_tips())
|
||||
asyncio.run(handler.transfer_group("water", "master_mix", 10)) # Reset tip tracking
|
||||
|
||||
|
||||
# asyncio.run(handler.pick_up_tips([plate8.children[8]],[0]))
|
||||
# print(plate8.children[8])
|
||||
# asyncio.run(handler.run_protocol())
|
||||
# asyncio.run(handler.aspirate([plate11.children[0]],[10], [0]))
|
||||
# print(plate11.children[0])
|
||||
# # asyncio.run(handler.run_protocol())
|
||||
# asyncio.run(handler.dispense([plate1.children[0]],[10],[0]))
|
||||
# print(plate1.children[0])
|
||||
# # asyncio.run(handler.run_protocol())
|
||||
# asyncio.run(handler.mix([plate1.children[0]], mix_time=3, mix_vol=5, height_to_bottom=0.5, offsets=Coordinate(0, 0, 0), mix_rate=100))
|
||||
# print(plate1.children[0])
|
||||
# asyncio.run(handler.discard_tips([0]))
|
||||
|
||||
# asyncio.run(handler.add_liquid(
|
||||
# asp_vols=[10]*7,
|
||||
@@ -1341,10 +1347,7 @@ if __name__ == "__main__":
|
||||
# spread="custom",
|
||||
# ))
|
||||
|
||||
# asyncio.run(handler.run_protocol()) # Run the protocol
|
||||
|
||||
|
||||
|
||||
# asyncio.run(handler.run_protocol()) # Run the protocol
|
||||
|
||||
|
||||
|
||||
@@ -1413,7 +1416,7 @@ if __name__ == "__main__":
|
||||
# # # ))
|
||||
# # print(json.dumps(handler._unilabos_backend.steps_todo_list, indent=2)) # Print matrix info
|
||||
# # # input("pick_up_tips add step")
|
||||
# # #asyncio.run(handler.run_protocol()) # Run the protocol
|
||||
#asyncio.run(handler.run_protocol()) # Run the protocol
|
||||
# # # input("Running protocol...")
|
||||
# # # input("Press Enter to continue...") # Wait for user input before proceeding
|
||||
# # # print("PRCXI9300Handler initialized with deck and host settings.")
|
||||
|
||||
Reference in New Issue
Block a user