Spaces:
Running
Running
Download processor/app/glove_sources.py from Stouching-Robotics/DataFlowing: direct link, hf CLI and curl.
- Browser
- Download file 6.78 kB
-
https://huggingface.co/spaces/Stouching-Robotics/DataFlowing/resolve/main/processor/app/glove_sources.py
- Command line
-
hf download hf://spaces/Stouching-Robotics/DataFlowing/processor/app/glove_sources.py
-
curl -L -o glove_sources.py https://huggingface.co/spaces/Stouching-Robotics/DataFlowing/resolve/main/processor/app/glove_sources.py
6.78 kB
| """Helpers for treating the two SenseGlove channels as one input device. | |
| The recorder stores the left and right channels separately because they are | |
| two data streams. That storage detail must not leak into the workflow UI: | |
| one glove device can still carry both ``left_glove`` and ``right_glove`` | |
| source keys in its node configuration. | |
| """ | |
| from __future__ import annotations | |
| import re | |
| _SIDE_TOKEN = re.compile(r"(^|[_\-.])(left|right)(?=[_\-.]|$)", re.IGNORECASE) | |
| def _glove_base(key: str) -> str | None: | |
| """Return a stable pair key for a left/right glove channel.""" | |
| value = str(key or "").strip() | |
| lower = value.lower() | |
| if "glove" not in lower: | |
| return None | |
| match = _SIDE_TOKEN.search(value) | |
| if not match: | |
| return None | |
| # Remove the separator together with the side token. For example, | |
| # left_glove_joint/right_glove_joint both become ``glove_joint``. | |
| base = _SIDE_TOKEN.sub("_", value, count=1).strip("_-. ").lower() | |
| return base or "glove" | |
| def group_glove_source_keys(values) -> list[list[str]]: | |
| """Group paired glove channels while preserving singleton channels. | |
| The result is deterministic and ordered left, right. Non-glove sensor | |
| names are returned as singleton groups so this helper is safe to use on | |
| a complete ``info.json`` sensor list. | |
| """ | |
| unique: list[str] = [] | |
| seen: set[str] = set() | |
| for raw in values or []: | |
| key = str(raw or "").strip() | |
| if key and key.lower() not in seen: | |
| unique.append(key) | |
| seen.add(key.lower()) | |
| grouped: dict[str, dict[str, str]] = {} | |
| singleton_order: list[str] = [] | |
| for key in unique: | |
| base = _glove_base(key) | |
| match = _SIDE_TOKEN.search(key) if base else None | |
| side = match.group(2).lower() if match else "" | |
| if not base or side not in {"left", "right"}: | |
| singleton_order.append(key) | |
| continue | |
| bucket = grouped.setdefault(base, {}) | |
| bucket.setdefault(side, key) | |
| result: list[list[str]] = [] | |
| result.extend([key] for key in singleton_order) | |
| # Pair ordering is based on the first appearance of each base, while the | |
| # channels inside a pair always use left then right. | |
| bases: list[str] = [] | |
| for key in unique: | |
| base = _glove_base(key) | |
| if base and base not in bases: | |
| bases.append(base) | |
| paired: list[list[str]] = [] | |
| for base in bases: | |
| bucket = grouped.get(base, {}) | |
| keys = [bucket[side] for side in ("left", "right") if side in bucket] | |
| if keys: | |
| paired.append(keys) | |
| # Keep original singleton order, followed by grouped glove channels. In | |
| # the normal recorder layout this gives left_glove,right_glove together. | |
| return result + paired | |
| def is_paired_glove_source(keys: list[str]) -> bool: | |
| """Whether a group contains both left and right glove channels.""" | |
| sides = { | |
| (match.group(2).lower() if match else "") | |
| for key in keys | |
| for match in [_SIDE_TOKEN.search(str(key))] | |
| if match and "glove" in str(key).lower() | |
| } | |
| return sides == {"left", "right"} | |
| def merge_paired_glove_nodes(graph: dict) -> bool: | |
| """Collapse legacy left/right glove input nodes in a workflow graph. | |
| Older auto-generated workflows contain one node per channel. The first | |
| node is kept, both source keys are written to its config, and outgoing | |
| edges from the removed node are redirected to it. Returns whether the | |
| graph changed. | |
| """ | |
| if not isinstance(graph, dict): | |
| return False | |
| nodes = list(graph.get("nodes") or []) | |
| by_base: dict[str, list[dict]] = {} | |
| for node in nodes: | |
| data = node.get("data") or {} | |
| if data.get("nodeType") != "glove_sensor": | |
| continue | |
| config = data.get("config") or {} | |
| raw = config.get("source_keys") or config.get("source_key") or config.get("position") | |
| values = raw.split(",") if isinstance(raw, str) else raw or [] | |
| keys = [str(value).strip() for value in values if str(value).strip()] | |
| bases = {_glove_base(key) for key in keys} | |
| for base in bases: | |
| if base: | |
| by_base.setdefault(base, []).append(node) | |
| changed = False | |
| redirects: dict[str, str] = {} | |
| remove_ids: set[str] = set() | |
| for duplicate_nodes in by_base.values(): | |
| if len(duplicate_nodes) < 2: | |
| continue | |
| keeper = duplicate_nodes[0] | |
| keeper_data = keeper.setdefault("data", {}) | |
| keeper_config = keeper_data.setdefault("config", {}) | |
| all_keys: list[str] = [] | |
| for node in duplicate_nodes: | |
| config = (node.get("data") or {}).get("config") or {} | |
| raw = config.get("source_keys") or config.get("source_key") or config.get("position") | |
| values = raw.split(",") if isinstance(raw, str) else raw or [] | |
| for key in values: | |
| key = str(key).strip() | |
| if key and key.lower() not in {item.lower() for item in all_keys}: | |
| all_keys.append(key) | |
| merged = group_glove_source_keys(all_keys) | |
| paired = next((group for group in merged if is_paired_glove_source(group)), None) | |
| if not paired: | |
| continue | |
| keeper_config["source_key"] = paired[0] | |
| keeper_config["source_keys"] = ",".join(paired) | |
| keeper_config.pop("position", None) | |
| keeper_data["label"] = keeper_data.get("label") or "Glove Sensor" | |
| for node in duplicate_nodes[1:]: | |
| node_id = str(node.get("id") or "") | |
| if node_id: | |
| redirects[node_id] = str(keeper.get("id") or "") | |
| remove_ids.add(node_id) | |
| changed = True | |
| if not changed: | |
| return False | |
| graph["nodes"] = [node for node in nodes | |
| if str(node.get("id") or "") not in remove_ids] | |
| seen_edges: set[tuple[str, str, str, str]] = set() | |
| rewritten: list[dict] = [] | |
| for edge in graph.get("edges") or []: | |
| source = redirects.get(str(edge.get("source") or ""), edge.get("source")) | |
| target = redirects.get(str(edge.get("target") or ""), edge.get("target")) | |
| if not source or not target: | |
| continue | |
| edge = dict(edge) | |
| edge["source"] = source | |
| edge["target"] = target | |
| identity = (str(source), str(edge.get("sourceHandle") or ""), | |
| str(target), str(edge.get("targetHandle") or "")) | |
| if identity in seen_edges: | |
| continue | |
| seen_edges.add(identity) | |
| edge["id"] = ( | |
| f"xy-edge__{source}{edge.get('sourceHandle') or ''}-" | |
| f"{target}{edge.get('targetHandle') or ''}") | |
| rewritten.append(edge) | |
| graph["edges"] = rewritten | |
| return True | |