Skip to content

Conversation

@q434343
Copy link
Collaborator

@q434343 q434343 commented Nov 28, 2025

修改上传大小方法

Summary by Sourcery

Update liquid handling backends, deck definitions, and resource metadata to support richer configuration, visualization, and result reporting.

New Features:

  • Allow LiquidHandlerAbstract to construct backend instances from a configuration dictionary specifying a backend type string and parameters.
  • Expose PRCXI9300Deck slot layout with coordinates and site metadata through a new serialize method for UI/visualization.
  • Add registry entries and 3D model paths for additional resource containers and Laiyu deck models, including new plate and tip rack variants.

Bug Fixes:

  • Normalize resource registry categories for plates and tip racks to singular forms used elsewhere in the system.
  • Ensure resource instances always include a pose field derived from position and size information when missing.
  • Adjust mix_times configuration in liquid handler YAMLs to accept arrays of integers with appropriate defaults instead of a single scalar value.

Enhancements:

  • Pass through arbitrary backend keyword arguments in LiquidHandlerAbstract initialization to support backend-specific options.
  • Extend PRCXI9300Deck to support 12 slots with defined coordinates instead of 6.
  • Include samples in serialized result info strings for richer task execution reporting.

zhangshixiang and others added 22 commits September 26, 2025 20:09
修改移液枪的状态判断方法,
添加三轴的表定点与零点之间的转换
添加三轴真实移动的backend
简化移动方法,
取消软件限制位置,
修改当值使用Z轴时也需要重新复位Z轴的问题
1,现在可以直接通过修改backend,适配其他的移液站,主类依旧使用LiquidHandler,不用重新编写

2,修改枪头判断标准,使用枪头自身判断而不是类的判断,

3,将归零参数用毫米计算,方便手动调整,

4,修改归零方式,上电使用机械归零,确定机械零点,手动归零设置工作区域零点方便计算,二者互不干涉
@sourcery-ai
Copy link

sourcery-ai bot commented Nov 28, 2025

Reviewer's Guide

Extends liquid handling device flexibility and visualization by allowing dict-configured backends, enriching deck and resource metadata (including 3D model paths and slot layout), changing mix_times from scalar to array in multiple device schemas, and tightening category/pose handling for PRCXI and Laiyu resources.

Sequence diagram for dict-configured backend resolution in LiquidHandlerAbstract

sequenceDiagram
  participant Caller
  participant LiquidHandlerAbstract
  participant Globals
  participant Importlib
  participant Pylabrobot
  participant SuperMiddleware as LiquidHandlerMiddleware

  Caller->>LiquidHandlerAbstract: __init__(backend=dict_with_type, deck, simulator, channel_num, backend_kwargs)
  activate LiquidHandlerAbstract
  LiquidHandlerAbstract->>LiquidHandlerAbstract: check isinstance(backend, dict) and has key type
  alt type in globals
    LiquidHandlerAbstract->>Globals: lookup backend_cls = globals[type_str]
  else dotted notation
    LiquidHandlerAbstract->>Importlib: import_module(module_name)
    Importlib-->>LiquidHandlerAbstract: module or error
    LiquidHandlerAbstract->>Importlib: getattr(mod, class_name)
  else pylabrobot namespace
    LiquidHandlerAbstract->>Pylabrobot: getattr(pylabrobot, type_str)
    Pylabrobot-->>LiquidHandlerAbstract: backend_cls or None
  end
  alt backend_cls is valid type
    LiquidHandlerAbstract->>LiquidHandlerAbstract: backend_type = backend_cls(**backend_dict)
  else failure
    LiquidHandlerAbstract->>Caller: raise RuntimeError
  end
  LiquidHandlerAbstract->>LiquidHandlerMiddleware: super().__init__(backend_type, deck, simulator, channel_num, backend_kwargs)
  deactivate LiquidHandlerAbstract
Loading

Class diagram for updated LiquidHandlerAbstract backend initialization

classDiagram
  class LiquidHandlerBackend
  class Deck
  class LiquidHandlerMiddleware {
    <<abstract>>
  }
  class LiquidHandlerAbstract {
    +bool support_touch_tip
    +BaseROS2DeviceNode _ros_node
    -bool _simulator
    -dict group_info
    +LiquidHandlerAbstract(backend, deck, simulator, channel_num, backend_kwargs)
    +post_init(ros_node)
  }

  LiquidHandlerAbstract --|> LiquidHandlerMiddleware
  LiquidHandlerAbstract --> LiquidHandlerBackend : uses
  LiquidHandlerAbstract --> Deck : uses

  class LiquidHandlerAbstractInitFlow {
    -backend_type
    +resolve_backend(backend)
  }

  LiquidHandlerAbstract ..> LiquidHandlerAbstractInitFlow : initialization_logic
Loading

Class diagram for updated PRCXI9300Deck with slot_locations and serialize

classDiagram
  class Coordinate {
    +float x
    +float y
    +float z
  }

  class Deck {
    +string name
    +float size_x
    +float size_y
    +float size_z
    +serialize()
  }

  class PRCXI9300Deck {
    +list slots
    +list slot_locations
    +PRCXI9300Deck(name, size_x, size_y, size_z, kwargs)
    +serialize()
  }

  PRCXI9300Deck --|> Deck
  PRCXI9300Deck "12" o-- Coordinate : slot_locations

  class SiteSerializationItem {
    +string label
    +bool visible
    +position_x
    +position_y
    +position_z
    +size_width
    +size_height
    +size_depth
    +list content_type
  }

  PRCXI9300Deck ..> SiteSerializationItem : builds_sites_in_serialize
Loading

File-Level Changes

Change Details Files
Allow LiquidHandlerAbstract to accept a dict backend configuration and forward extra backend kwargs.
  • Add **backend_kwargs to LiquidHandlerAbstract.init signature and super() call
  • When backend is a dict with a type field, dynamically resolve the backend class from globals, dotted module path, or pylabrobot, and instantiate it with remaining keys as kwargs
  • Fallback to using the backend object directly when not a dict; raise RuntimeError on resolution failure
unilabos/devices/liquid_handling/liquid_handler_abstract.py
Extend PRCXI deck representation with a 12-slot grid layout and serializable site metadata, and loosen PRCXI device init signature.
  • Increase PRCXI9300Deck slot count from 6 to 12 and define explicit Coordinate-based slot_locations for a 3x4 grid
  • Override serialize() to include a sites list describing each slot’s label, visibility, position and permitted content_types, then return the enriched dict
  • Add **kwargs to PRCXI9300 device init to tolerate additional configuration arguments
unilabos/devices/liquid_handling/prcxi/prcxi.py
Adjust resource tracking to derive pose from position/config and ensure size information is populated.
  • If position exists, build a pose object using config.pose as base, copying nested position if available or defaulting to zeros
  • Populate pose.size from config.size_x/size_y/size_z when missing, and assign the computed pose back to content
unilabos/ros/nodes/resource_tracker.py
Change mix_times from a scalar integer to an array of integers across multiple liquid handler schemas and defaults, and add PRCXI liquid transfer placeholders.
  • Update default mix_times values from 0 to a single-element list [0] in several liquid_handler variants
  • Change JSON schema definitions of mix_times from type: integer with bounds to type: array of bounded integer items
  • Add placeholder_keys liquid_names and volumes for PRCXI liquid_handler transfer configuration
unilabos/registry/devices/liquid_handler.yaml
unilabos/registry/devices/laiyu_liquid.yaml
Refine resource registry metadata for various decks, plates, racks, and add xacro model paths.
  • Add model.path URLs to several resource_container and opentrons plate/tip rack models (hplc_plate, plate_96_high, tiprack_96_high, tecan_nested_tip_rack-based resources, etc.)
  • Introduce new tiprack_box and plate_96 resource_container entries with children_mesh, children_mesh_path, and associated transforms
  • Add a second TransformXYZDeck entry with device model and xacro path for Laiyu deck
  • Normalize PRCXI resource categories (e.g., plates→plate, tip_racks→tip_rack, dropping extra prcxi category)
unilabos/registry/resources/common/resource_container.yaml
unilabos/registry/resources/laiyu/deck.yaml
unilabos/registry/resources/prcxi/tip_racks.yaml
unilabos/registry/resources/prcxi/plates.yaml
unilabos/registry/resources/opentrons/plates.yaml
unilabos/registry/resources/opentrons/tip_racks.yaml
Extend result serialization utility to include sample data.
  • Add optional samples parameter to get_result_info_str and include it in the result_info dict
  • Preserve existing error, suc, and return_value fields and JSON serialization behavior
unilabos/utils/type_check.py
Add a new PRCXI 9320 experiment configuration JSON and rename the old one.
  • Introduce prcxi_9320_new.json in experiments tests (contents not shown in diff)
  • Remove or supersede the previous prcxi_9320.json reference in the test directory
unilabos/test/experiments/prcxi_9320.json
unilabos/test/experiments/prcxi_9320_new.json

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

  • In resource_container.yaml and laiyu/deck.yaml you now have duplicate top‑level keys (tiprack_box, TransformXYZDeck) defined twice, which in YAML will silently override one definition; please consolidate each into a single entry to avoid confusing or unintended behavior.
  • The new get_resource_instance_from_dict logic in resource_tracker.py assumes content["config"] always exists and only sets pose when "position" in content; consider using content.get("config", {}) and preserving the previous fallback behavior when position is missing to avoid KeyError and missing pose in some inputs.
  • In PRCXI9300Deck.serialize there are print debug statements that will run on every serialization; these should be removed or replaced with proper logging to avoid noisy stdout in production.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `resource_container.yaml` and `laiyu/deck.yaml` you now have duplicate top‑level keys (`tiprack_box`, `TransformXYZDeck`) defined twice, which in YAML will silently override one definition; please consolidate each into a single entry to avoid confusing or unintended behavior.
- The new `get_resource_instance_from_dict` logic in `resource_tracker.py` assumes `content["config"]` always exists and only sets `pose` when `"position" in content`; consider using `content.get("config", {})` and preserving the previous fallback behavior when `position` is missing to avoid `KeyError` and missing `pose` in some inputs.
- In `PRCXI9300Deck.serialize` there are `print` debug statements that will run on every serialization; these should be removed or replaced with proper logging to avoid noisy stdout in production.

## Individual Comments

### Comment 1
<location> `unilabos/registry/resources/common/resource_container.yaml:236-234` </location>
<code_context>
   registry_type: resource
   version: 1.0.0
+
+tiprack_box:
+  category:
+  - resource_container
+  class:
+    module: unilabos.devices.resource_container.container:TipRackContainer
+    type: python
+  description: 96针头盒
+  handles: []
+  icon: ''
+  init_param_schema: {}
+  model:
+    children_mesh: tip/meshes/tip.stl
+    children_mesh_path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/tip/modal.xacro
+    children_mesh_tf:
+    - 0.0045
+    - 0.0045
+    - 0
+    - 0
+    - 0
+    - 0
+    mesh: tiprack_box/meshes/tiprack_box.stl
+    path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/tiprack_box/modal.xacro
+    mesh_tf:
+    - 0
+    - 0
+    - 0
+    - 0
+    - 0
+    - 0
+    type: resource
+  registry_type: resource
+  version: 1.0.0
+
+plate_96:
</code_context>

<issue_to_address>
**issue (bug_risk):** The `tiprack_box` entry is defined twice, which in YAML means the earlier definition is silently overwritten.

Because YAML keeps only the last value for a given key, this new `tiprack_box` block will replace the earlier one entirely. Please confirm whether you meant to fully replace it; otherwise, consider renaming this resource or merging these fields into the original definition so existing configuration isn’t lost.
</issue_to_address>

### Comment 2
<location> `unilabos/devices/liquid_handling/prcxi/prcxi.py:98-100` </location>
<code_context>
+                "content_type": ["plate", "tip_rack", "tube_rack"],
+                } for i, slot in enumerate(self.slot_locations)
+            ]
+        print("----"*10)
+        print(super_serialized)
+        return super_serialized

 class PRCXI9300Container(Plate, TipRack):
</code_context>

<issue_to_address>
**issue (performance):** Debug `print` statements in `serialize()` will produce unwanted console output in production.

Since `serialize()` is likely called often, these `print` calls will spam stdout and add unnecessary overhead. Please remove them or switch to the project’s logger at an appropriate level (e.g. `debug`).
</issue_to_address>

### Comment 3
<location> `unilabos/ros/nodes/resource_tracker.py:149-158` </location>
<code_context>
-        if "pose" not in content:
-            content["pose"] = content.get("position", {})
+
+        if "position" in content:
+            pose = content["config"].get("pose",{})
+            if "position" not in pose :
+                if "position" in content["position"]:
+                    pose["position"] = content["position"]["position"]
+                else:
+                    pose["position"] = {"x": 0, "y": 0, "z": 0}
+            if "size" not in pose:
+                pose["size"] = {
+                    "width": content["config"].get("size_x", 0), 
+                    "height": content["config"].get("size_y", 0), 
+                    "depth": content["config"].get("size_z", 0)
+                }
+            content["pose"] = pose
         return ResourceDictInstance(ResourceDict.model_validate(content))

</code_context>

<issue_to_address>
**issue (bug_risk):** New pose construction logic assumes `content["config"]` always exists, which can raise a `KeyError`.

Here `pose = content["config"].get("pose", {})` will raise if `"config"` is absent. The previous logic didn’t depend on `config`, so this is a potential regression. Use `content.get("config", {})` (and similar for other `config` reads) or guard this block with a `"config" in content` check.
</issue_to_address>

### Comment 4
<location> `unilabos/registry/resources/laiyu/deck.yaml:18-16` </location>
<code_context>
   registry_type: resource
   version: 1.0.0
+
+TransformXYZDeck:
+  category:
+  - deck
+  class:
+    module: unilabos.devices.liquid_handling.laiyu.laiyu:TransformXYZDeck
+    type: pylabrobot
+  description: Laiyu deck
+  handles: []
+  icon: ''
+  init_param_schema: {}
+  model:
+    mesh: liquid_transform_xyz
+    path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/devices/liquid_transform_xyz/macro_device.xacro
+    type: device
+  registry_type: resource
+  version: 1.0.0
+
</code_context>

<issue_to_address>
**issue (bug_risk):** The `TransformXYZDeck` resource is declared twice, so the second definition will override the first.

Because YAML only keeps the last occurrence of a duplicate top-level key, the earlier `TransformXYZDeck` definition will be silently discarded. If you meant to extend or modify the existing resource, please merge these fields into the original block rather than redefining the key, so you don’t lose any configuration from the first definition.
</issue_to_address>

### Comment 5
<location> `unilabos/devices/liquid_handling/liquid_handler_abstract.py:592` </location>
<code_context>
    def __init__(self, backend: LiquidHandlerBackend, deck: Deck, simulator: bool=False, channel_num:int = 8,**backend_kwargs):
        """Initialize a LiquidHandler.

        Args:
          backend: Backend to use.
          deck: Deck to use.
        """
        backend_type = None
        if isinstance(backend, dict) and "type" in backend:
            backend_dict = backend.copy()
            type_str = backend_dict.pop("type")
            try:
                # Try to get class from string using globals (current module), or fallback to pylabrobot or unilabos namespaces
                backend_cls = None
                if type_str in globals():
                    backend_cls = globals()[type_str]
                else:
                    # Try resolving dotted notation, e.g. "xxx.yyy.ClassName"
                    components = type_str.split(".")
                    mod = None
                    if len(components) > 1:
                        module_name = ".".join(components[:-1])
                        try:
                            import importlib
                            mod = importlib.import_module(module_name)
                        except ImportError:
                            mod = None
                        if mod is not None:
                            backend_cls = getattr(mod, components[-1], None)
                    if backend_cls is None:
                        # Try pylabrobot style import (if available)
                        try:
                            import pylabrobot
                            backend_cls = getattr(pylabrobot, type_str, None)
                        except Exception:
                            backend_cls = None
                if backend_cls is not None and isinstance(backend_cls, type):
                    backend_type = backend_cls(**backend_dict)  # pass the rest of dict as kwargs
            except Exception as exc:
                raise RuntimeError(f"Failed to convert backend type '{type_str}' to class: {exc}")
        else:
            backend_type = backend
        self._simulator = simulator
        self.group_info = dict()
        super().__init__(backend_type, deck, simulator, channel_num,**backend_kwargs)

</code_context>

<issue_to_address>
**suggestion (code-quality):** We've found these issues:

- Explicitly raise from a previous error ([`raise-from-previous-error`](https://docs.sourcery.ai/Reference/Default-Rules/suggestions/raise-from-previous-error/))
- Low code quality found in LiquidHandlerAbstract.\_\_init\_\_ - 24% ([`low-code-quality`](https://docs.sourcery.ai/Reference/Default-Rules/comments/low-code-quality/))

```suggestion
                raise RuntimeError(
                    f"Failed to convert backend type '{type_str}' to class: {exc}"
                ) from exc
```

<br/><details><summary>Explanation</summary>
The quality score for this function is below the quality threshold of 25%.
This score is a combination of the method length, cognitive complexity and working memory.

How can you solve this?

It might be worth refactoring this function to make it shorter and more readable.

- Reduce the function length by extracting pieces of functionality out into
  their own functions. This is the most important thing you can do - ideally a
  function should be less than 10 lines.
- Reduce nesting, perhaps by introducing guard clauses to return early.
- Ensure that variables are tightly scoped, so that code using related concepts
  sits together within the function rather than being scattered.</details>
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/resources/tiprack_box/modal.xacro
type: resource
registry_type: resource
version: 1.0.0
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): The tiprack_box entry is defined twice, which in YAML means the earlier definition is silently overwritten.

Because YAML keeps only the last value for a given key, this new tiprack_box block will replace the earlier one entirely. Please confirm whether you meant to fully replace it; otherwise, consider renaming this resource or merging these fields into the original definition so existing configuration isn’t lost.

path: https://uni-lab.oss-cn-zhangjiakou.aliyuncs.com/uni-lab/devices/liquid_transform_xyz/macro_device.xacro
type: device
registry_type: resource
version: 1.0.0
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): The TransformXYZDeck resource is declared twice, so the second definition will override the first.

Because YAML only keeps the last occurrence of a duplicate top-level key, the earlier TransformXYZDeck definition will be silently discarded. If you meant to extend or modify the existing resource, please merge these fields into the original block rather than redefining the key, so you don’t lose any configuration from the first definition.

@TablewareBox TablewareBox changed the title Commit Canonicalize liquid_handler code Nov 29, 2025
@TablewareBox TablewareBox merged commit 350067b into deepmodeling:dev Dec 23, 2025
6 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants