agenticros 0.0.1 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +192 -0
- package/README.md +90 -4
- package/dist/commands/config.d.ts +20 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +179 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/doctor.d.ts +33 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +232 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/down.d.ts +15 -0
- package/dist/commands/down.d.ts.map +1 -0
- package/dist/commands/down.js +91 -0
- package/dist/commands/down.js.map +1 -0
- package/dist/commands/init.d.ts +21 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +259 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/logs.d.ts +18 -0
- package/dist/commands/logs.d.ts.map +1 -0
- package/dist/commands/logs.js +67 -0
- package/dist/commands/logs.js.map +1 -0
- package/dist/commands/status.d.ts +12 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +56 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/up.d.ts +20 -0
- package/dist/commands/up.d.ts.map +1 -0
- package/dist/commands/up.js +70 -0
- package/dist/commands/up.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +107 -0
- package/dist/index.js.map +1 -0
- package/dist/menu.d.ts +9 -0
- package/dist/menu.d.ts.map +1 -0
- package/dist/menu.js +96 -0
- package/dist/menu.js.map +1 -0
- package/dist/runners/real-robot.d.ts +15 -0
- package/dist/runners/real-robot.d.ts.map +1 -0
- package/dist/runners/real-robot.js +46 -0
- package/dist/runners/real-robot.js.map +1 -0
- package/dist/runners/sim.d.ts +19 -0
- package/dist/runners/sim.d.ts.map +1 -0
- package/dist/runners/sim.js +53 -0
- package/dist/runners/sim.js.map +1 -0
- package/dist/util/env.d.ts +24 -0
- package/dist/util/env.d.ts.map +1 -0
- package/dist/util/env.js +53 -0
- package/dist/util/env.js.map +1 -0
- package/dist/util/logger.d.ts +24 -0
- package/dist/util/logger.d.ts.map +1 -0
- package/dist/util/logger.js +62 -0
- package/dist/util/logger.js.map +1 -0
- package/dist/util/paths.d.ts +57 -0
- package/dist/util/paths.d.ts.map +1 -0
- package/dist/util/paths.js +132 -0
- package/dist/util/paths.js.map +1 -0
- package/dist/util/pidfile.d.ts +16 -0
- package/dist/util/pidfile.d.ts.map +1 -0
- package/dist/util/pidfile.js +63 -0
- package/dist/util/pidfile.js.map +1 -0
- package/dist/util/state.d.ts +26 -0
- package/dist/util/state.d.ts.map +1 -0
- package/dist/util/state.js +55 -0
- package/dist/util/state.js.map +1 -0
- package/package.json +60 -1
- package/runtime/BUNDLE.json +11 -0
- package/runtime/LICENSE +192 -0
- package/runtime/README.md +273 -0
- package/runtime/docs/architecture.md +366 -0
- package/runtime/docs/cli.md +140 -0
- package/runtime/docs/memory.md +292 -0
- package/runtime/docs/robot-setup.md +347 -0
- package/runtime/package.json +28 -0
- package/runtime/packages/agenticros/agenticros-agenticros-0.0.1.tgz +0 -0
- package/runtime/packages/agenticros/openclaw.plugin.json +451 -0
- package/runtime/packages/agenticros/package.json +41 -0
- package/runtime/packages/agenticros/src/camera-snapshot-cache.ts +59 -0
- package/runtime/packages/agenticros/src/camera-snapshot-routes.ts +44 -0
- package/runtime/packages/agenticros/src/commands/estop.ts +41 -0
- package/runtime/packages/agenticros/src/commands/transport.ts +195 -0
- package/runtime/packages/agenticros/src/config-file.ts +136 -0
- package/runtime/packages/agenticros/src/config-page.ts +498 -0
- package/runtime/packages/agenticros/src/context/robot-context.ts +373 -0
- package/runtime/packages/agenticros/src/depth.ts +313 -0
- package/runtime/packages/agenticros/src/describer.ts +157 -0
- package/runtime/packages/agenticros/src/image-binary-trim.ts +16 -0
- package/runtime/packages/agenticros/src/index.ts +85 -0
- package/runtime/packages/agenticros/src/landing-page.ts +38 -0
- package/runtime/packages/agenticros/src/memory.ts +44 -0
- package/runtime/packages/agenticros/src/plugin-api.ts +173 -0
- package/runtime/packages/agenticros/src/plugin-image-base64.ts +69 -0
- package/runtime/packages/agenticros/src/preflight.ts +110 -0
- package/runtime/packages/agenticros/src/routes.ts +328 -0
- package/runtime/packages/agenticros/src/safety/validator.ts +43 -0
- package/runtime/packages/agenticros/src/service.ts +359 -0
- package/runtime/packages/agenticros/src/skill-api.ts +65 -0
- package/runtime/packages/agenticros/src/skill-loader.ts +146 -0
- package/runtime/packages/agenticros/src/teleop/page.ts +498 -0
- package/runtime/packages/agenticros/src/teleop/routes.ts +650 -0
- package/runtime/packages/agenticros/src/tools/index.ts +26 -0
- package/runtime/packages/agenticros/src/tools/ros2-action.ts +50 -0
- package/runtime/packages/agenticros/src/tools/ros2-camera.ts +221 -0
- package/runtime/packages/agenticros/src/tools/ros2-depth-distance.ts +58 -0
- package/runtime/packages/agenticros/src/tools/ros2-introspect.ts +62 -0
- package/runtime/packages/agenticros/src/tools/ros2-memory.ts +158 -0
- package/runtime/packages/agenticros/src/tools/ros2-param.ts +87 -0
- package/runtime/packages/agenticros/src/tools/ros2-publish.ts +52 -0
- package/runtime/packages/agenticros/src/tools/ros2-service.ts +46 -0
- package/runtime/packages/agenticros/src/tools/ros2-subscribe.ts +71 -0
- package/runtime/packages/agenticros/tsconfig.json +9 -0
- package/runtime/packages/agenticros-claude-code/README.md +260 -0
- package/runtime/packages/agenticros-claude-code/config.example.json +9 -0
- package/runtime/packages/agenticros-claude-code/dist/config.d.ts +8 -0
- package/runtime/packages/agenticros-claude-code/dist/config.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/config.js +93 -0
- package/runtime/packages/agenticros-claude-code/dist/config.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/depth.d.ts +20 -0
- package/runtime/packages/agenticros-claude-code/dist/depth.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/depth.js +126 -0
- package/runtime/packages/agenticros-claude-code/dist/depth.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/find-object/coco-classes.d.ts +6 -0
- package/runtime/packages/agenticros-claude-code/dist/find-object/coco-classes.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/find-object/coco-classes.js +36 -0
- package/runtime/packages/agenticros-claude-code/dist/find-object/coco-classes.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/find-object/find-object.d.ts +33 -0
- package/runtime/packages/agenticros-claude-code/dist/find-object/find-object.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/find-object/find-object.js +134 -0
- package/runtime/packages/agenticros-claude-code/dist/find-object/find-object.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/follow-me/controller.d.ts +43 -0
- package/runtime/packages/agenticros-claude-code/dist/follow-me/controller.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/follow-me/controller.js +73 -0
- package/runtime/packages/agenticros-claude-code/dist/follow-me/controller.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/follow-me/detector.d.ts +58 -0
- package/runtime/packages/agenticros-claude-code/dist/follow-me/detector.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/follow-me/detector.js +251 -0
- package/runtime/packages/agenticros-claude-code/dist/follow-me/detector.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/follow-me/loop.d.ts +61 -0
- package/runtime/packages/agenticros-claude-code/dist/follow-me/loop.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/follow-me/loop.js +268 -0
- package/runtime/packages/agenticros-claude-code/dist/follow-me/loop.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/index.d.ts +3 -0
- package/runtime/packages/agenticros-claude-code/dist/index.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/index.js +111 -0
- package/runtime/packages/agenticros-claude-code/dist/index.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/memory.d.ts +17 -0
- package/runtime/packages/agenticros-claude-code/dist/memory.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/memory.js +44 -0
- package/runtime/packages/agenticros-claude-code/dist/memory.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/safety.d.ts +10 -0
- package/runtime/packages/agenticros-claude-code/dist/safety.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/safety.js +34 -0
- package/runtime/packages/agenticros-claude-code/dist/safety.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/tools.d.ts +36 -0
- package/runtime/packages/agenticros-claude-code/dist/tools.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/tools.js +777 -0
- package/runtime/packages/agenticros-claude-code/dist/tools.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/transport.d.ts +17 -0
- package/runtime/packages/agenticros-claude-code/dist/transport.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/transport.js +46 -0
- package/runtime/packages/agenticros-claude-code/dist/transport.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/zero-shot/detector.d.ts +42 -0
- package/runtime/packages/agenticros-claude-code/dist/zero-shot/detector.d.ts.map +1 -0
- package/runtime/packages/agenticros-claude-code/dist/zero-shot/detector.js +114 -0
- package/runtime/packages/agenticros-claude-code/dist/zero-shot/detector.js.map +1 -0
- package/runtime/packages/agenticros-claude-code/package.json +29 -0
- package/runtime/packages/agenticros-claude-code/src/config.ts +96 -0
- package/runtime/packages/agenticros-claude-code/src/depth.ts +173 -0
- package/runtime/packages/agenticros-claude-code/src/find-object/coco-classes.ts +38 -0
- package/runtime/packages/agenticros-claude-code/src/find-object/find-object.ts +190 -0
- package/runtime/packages/agenticros-claude-code/src/follow-me/controller.ts +109 -0
- package/runtime/packages/agenticros-claude-code/src/follow-me/depth-loop.ts +420 -0
- package/runtime/packages/agenticros-claude-code/src/follow-me/detector.ts +303 -0
- package/runtime/packages/agenticros-claude-code/src/follow-me/loop.ts +330 -0
- package/runtime/packages/agenticros-claude-code/src/index.ts +125 -0
- package/runtime/packages/agenticros-claude-code/src/memory.ts +51 -0
- package/runtime/packages/agenticros-claude-code/src/safety.ts +44 -0
- package/runtime/packages/agenticros-claude-code/src/tools.ts +891 -0
- package/runtime/packages/agenticros-claude-code/src/transport.ts +58 -0
- package/runtime/packages/agenticros-claude-code/src/zero-shot/detector.ts +169 -0
- package/runtime/packages/agenticros-claude-code/tsconfig.json +9 -0
- package/runtime/packages/agenticros-claude-code/yolo-debug.mjs +106 -0
- package/runtime/packages/agenticros-gemini/README.md +139 -0
- package/runtime/packages/agenticros-gemini/package.json +28 -0
- package/runtime/packages/agenticros-gemini/scripts/smoke-api.mjs +42 -0
- package/runtime/packages/agenticros-gemini/src/chat.ts +139 -0
- package/runtime/packages/agenticros-gemini/src/config.ts +92 -0
- package/runtime/packages/agenticros-gemini/src/depth.ts +173 -0
- package/runtime/packages/agenticros-gemini/src/index.ts +58 -0
- package/runtime/packages/agenticros-gemini/src/memory.ts +32 -0
- package/runtime/packages/agenticros-gemini/src/safety.ts +44 -0
- package/runtime/packages/agenticros-gemini/src/tools.ts +516 -0
- package/runtime/packages/agenticros-gemini/src/transport.ts +58 -0
- package/runtime/packages/agenticros-gemini/tsconfig.json +8 -0
- package/runtime/packages/core/package.json +47 -0
- package/runtime/packages/core/src/banner.ts +32 -0
- package/runtime/packages/core/src/cmd-vel-twist.ts +31 -0
- package/runtime/packages/core/src/config.ts +279 -0
- package/runtime/packages/core/src/index.ts +54 -0
- package/runtime/packages/core/src/memory/__tests__/factory.test.ts +70 -0
- package/runtime/packages/core/src/memory/__tests__/local-provider.test.ts +195 -0
- package/runtime/packages/core/src/memory/__tests__/mem0-provider.test.ts +192 -0
- package/runtime/packages/core/src/memory/__tests__/smart-defaults.test.ts +46 -0
- package/runtime/packages/core/src/memory/factory.ts +63 -0
- package/runtime/packages/core/src/memory/index.ts +10 -0
- package/runtime/packages/core/src/memory/local/provider.ts +229 -0
- package/runtime/packages/core/src/memory/mem0/provider.ts +379 -0
- package/runtime/packages/core/src/memory/types.ts +96 -0
- package/runtime/packages/core/src/topic-utils.ts +95 -0
- package/runtime/packages/core/src/transport/factory.ts +47 -0
- package/runtime/packages/core/src/transport/local/conversion.ts +333 -0
- package/runtime/packages/core/src/transport/local/entities.ts +129 -0
- package/runtime/packages/core/src/transport/local/transport.ts +406 -0
- package/runtime/packages/core/src/transport/rosbridge/actions.ts +81 -0
- package/runtime/packages/core/src/transport/rosbridge/adapter.ts +157 -0
- package/runtime/packages/core/src/transport/rosbridge/client.ts +438 -0
- package/runtime/packages/core/src/transport/rosbridge/services.ts +41 -0
- package/runtime/packages/core/src/transport/rosbridge/topics.ts +60 -0
- package/runtime/packages/core/src/transport/rosbridge/types.ts +118 -0
- package/runtime/packages/core/src/transport/transport.ts +77 -0
- package/runtime/packages/core/src/transport/types.ts +137 -0
- package/runtime/packages/core/src/transport/webrtc/signaling-client.ts +196 -0
- package/runtime/packages/core/src/transport/webrtc/signaling-types.ts +130 -0
- package/runtime/packages/core/src/transport/webrtc/transport.ts +516 -0
- package/runtime/packages/core/src/transport/zenoh/adapter.ts +357 -0
- package/runtime/packages/core/src/transport/zenoh/cdr.ts +183 -0
- package/runtime/packages/core/src/transport/zenoh/keys.ts +51 -0
- package/runtime/packages/core/tsconfig.json +9 -0
- package/runtime/packages/ros-camera/package.json +30 -0
- package/runtime/packages/ros-camera/src/index.ts +13 -0
- package/runtime/packages/ros-camera/src/snapshot.ts +372 -0
- package/runtime/packages/ros-camera/tsconfig.json +9 -0
- package/runtime/pnpm-lock.yaml +5260 -0
- package/runtime/pnpm-workspace.yaml +2 -0
- package/runtime/ros2_ws/src/agenticros_agent/agenticros_agent/__init__.py +0 -0
- package/runtime/ros2_ws/src/agenticros_agent/agenticros_agent/agent_node.py +561 -0
- package/runtime/ros2_ws/src/agenticros_agent/package.xml +25 -0
- package/runtime/ros2_ws/src/agenticros_agent/resource/agenticros_agent +0 -0
- package/runtime/ros2_ws/src/agenticros_agent/setup.cfg +4 -0
- package/runtime/ros2_ws/src/agenticros_agent/setup.py +25 -0
- package/runtime/ros2_ws/src/agenticros_bringup/README.md +128 -0
- package/runtime/ros2_ws/src/agenticros_bringup/agenticros_bringup/__init__.py +1 -0
- package/runtime/ros2_ws/src/agenticros_bringup/agenticros_bringup/cmd_vel_relay.py +33 -0
- package/runtime/ros2_ws/src/agenticros_bringup/launch/cmd_vel_bridge.launch.py +58 -0
- package/runtime/ros2_ws/src/agenticros_bringup/launch/gazebo_turtlebot3.launch.py +69 -0
- package/runtime/ros2_ws/src/agenticros_bringup/launch/mode_a_gazebo.launch.py +55 -0
- package/runtime/ros2_ws/src/agenticros_bringup/launch/mode_a_gazebo_rviz.launch.py +48 -0
- package/runtime/ros2_ws/src/agenticros_bringup/launch/realsense_rosbridge.launch.py +154 -0
- package/runtime/ros2_ws/src/agenticros_bringup/launch/rosbridge_gazebo.launch.py +54 -0
- package/runtime/ros2_ws/src/agenticros_bringup/launch/rviz.launch.py +38 -0
- package/runtime/ros2_ws/src/agenticros_bringup/launch/turtlebot3_gazebo_rviz.launch.py +42 -0
- package/runtime/ros2_ws/src/agenticros_bringup/package.xml +24 -0
- package/runtime/ros2_ws/src/agenticros_bringup/resource/agenticros_bringup +0 -0
- package/runtime/ros2_ws/src/agenticros_bringup/rviz/turtlebot3_agenticros.rviz +174 -0
- package/runtime/ros2_ws/src/agenticros_bringup/setup.cfg +4 -0
- package/runtime/ros2_ws/src/agenticros_bringup/setup.py +28 -0
- package/runtime/ros2_ws/src/agenticros_discovery/agenticros_discovery/__init__.py +0 -0
- package/runtime/ros2_ws/src/agenticros_discovery/agenticros_discovery/discovery_node.py +172 -0
- package/runtime/ros2_ws/src/agenticros_discovery/package.xml +22 -0
- package/runtime/ros2_ws/src/agenticros_discovery/resource/agenticros_discovery +0 -0
- package/runtime/ros2_ws/src/agenticros_discovery/setup.cfg +5 -0
- package/runtime/ros2_ws/src/agenticros_discovery/setup.py +25 -0
- package/runtime/ros2_ws/src/agenticros_follow_me/README.md +66 -0
- package/runtime/ros2_ws/src/agenticros_follow_me/agenticros_follow_me/__init__.py +1 -0
- package/runtime/ros2_ws/src/agenticros_follow_me/agenticros_follow_me/__main__.py +5 -0
- package/runtime/ros2_ws/src/agenticros_follow_me/agenticros_follow_me/follow_me_node.py +278 -0
- package/runtime/ros2_ws/src/agenticros_follow_me/agenticros_follow_me/follower_controller.py +631 -0
- package/runtime/ros2_ws/src/agenticros_follow_me/agenticros_follow_me/person_tracker.py +635 -0
- package/runtime/ros2_ws/src/agenticros_follow_me/package.xml +22 -0
- package/runtime/ros2_ws/src/agenticros_follow_me/resource/agenticros_follow_me +0 -0
- package/runtime/ros2_ws/src/agenticros_follow_me/setup.py +25 -0
- package/runtime/ros2_ws/src/agenticros_msgs/CMakeLists.txt +26 -0
- package/runtime/ros2_ws/src/agenticros_msgs/msg/CapabilityManifest.msg +9 -0
- package/runtime/ros2_ws/src/agenticros_msgs/package.xml +22 -0
- package/runtime/ros2_ws/src/agenticros_msgs/srv/FollowMeGetStatus.srv +11 -0
- package/runtime/ros2_ws/src/agenticros_msgs/srv/FollowMeSetDistance.srv +4 -0
- package/runtime/ros2_ws/src/agenticros_msgs/srv/FollowMeSetTarget.srv +6 -0
- package/runtime/ros2_ws/src/agenticros_msgs/srv/FollowMeStart.srv +5 -0
- package/runtime/ros2_ws/src/agenticros_msgs/srv/FollowMeStop.srv +3 -0
- package/runtime/ros2_ws/src/agenticros_msgs/srv/GetCapabilities.srv +5 -0
- package/runtime/ros2_ws/src/agenticros_sim/CMakeLists.txt +24 -0
- package/runtime/ros2_ws/src/agenticros_sim/README.md +120 -0
- package/runtime/ros2_ws/src/agenticros_sim/config/agenticros-sim.config.json +28 -0
- package/runtime/ros2_ws/src/agenticros_sim/config/amr_bridge.yaml +111 -0
- package/runtime/ros2_ws/src/agenticros_sim/config/amr_view.rviz +172 -0
- package/runtime/ros2_ws/src/agenticros_sim/env-hooks/gz_resource_path.dsv.in +3 -0
- package/runtime/ros2_ws/src/agenticros_sim/env-hooks/gz_resource_path.sh.in +7 -0
- package/runtime/ros2_ws/src/agenticros_sim/launch/sim_amr.launch.py +159 -0
- package/runtime/ros2_ws/src/agenticros_sim/models/agenticros_amr/model.config +17 -0
- package/runtime/ros2_ws/src/agenticros_sim/models/agenticros_amr/model.sdf +244 -0
- package/runtime/ros2_ws/src/agenticros_sim/package.xml +27 -0
- package/runtime/ros2_ws/src/agenticros_sim/worlds/agenticros_indoor.sdf +183 -0
- package/runtime/scripts/activate_workspace.sh +285 -0
- package/runtime/scripts/agenticros-describer.policy.yaml +96 -0
- package/runtime/scripts/agenticros-proxy.cjs +99 -0
- package/runtime/scripts/agenticros-rosbridge.policy.yaml +62 -0
- package/runtime/scripts/check-cli-tarball-size.mjs +42 -0
- package/runtime/scripts/configure_agenticros.sh +200 -0
- package/runtime/scripts/configure_for_sim.sh +64 -0
- package/runtime/scripts/fix-openclaw-control-ui-path.sh +20 -0
- package/runtime/scripts/install_cli.sh +94 -0
- package/runtime/scripts/install_rosbridge_from_source.sh +67 -0
- package/runtime/scripts/lib/agenticros-banner.sh +28 -0
- package/runtime/scripts/onboard_robot.sh +75 -0
- package/runtime/scripts/openai.policy.yaml +77 -0
- package/runtime/scripts/openclaw-dashboard-url.cjs +49 -0
- package/runtime/scripts/pack-runtime.mjs +245 -0
- package/runtime/scripts/run_demo_native.sh +43 -0
- package/runtime/scripts/run_nemoclaw_host_stack.sh +91 -0
- package/runtime/scripts/run_robot_rosbridge.sh +36 -0
- package/runtime/scripts/sandbox_rosbridge_relay.py +137 -0
- package/runtime/scripts/setup-openclaw-local.cjs +75 -0
- package/runtime/scripts/setup_gateway_plugin.sh +329 -0
- package/runtime/scripts/setup_robot.sh +113 -0
- package/runtime/scripts/setup_workspace.sh +484 -0
- package/runtime/scripts/sim/run_sim.sh +146 -0
- package/runtime/scripts/smoke_test_nemoclaw.sh +123 -0
- package/runtime/scripts/start_demo.sh +55 -0
- package/runtime/scripts/sync-skill-tools.mjs +335 -0
- package/runtime/scripts/test-follow-me-sim.mjs +135 -0
- package/runtime/scripts/test-mcp-e2e.mjs +184 -0
- package/runtime/scripts/test-rclnodejs.mts +129 -0
- package/runtime/scripts/use-openclaw-2026.2.26.sh +19 -0
- package/runtime/scripts/use-openclaw-2026.3.11.sh +19 -0
- package/runtime/scripts/zenoh-bridge-ros2dds-robot.json5 +30 -0
- package/runtime/scripts/zenohd-agenticros.json5 +11 -0
- package/runtime/scripts/zenohd-rosclaw.json5 +11 -0
- package/runtime/tsconfig.base.json +19 -0
- package/index.js +0 -6
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PersonTracker - RealSense + MediaPipe Person Detection
|
|
3
|
+
|
|
4
|
+
Uses pyrealsense2 for depth + color frames and MediaPipe Pose
|
|
5
|
+
for fast person detection with 3D position estimation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import time
|
|
9
|
+
import numpy as np
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from typing import Optional
|
|
12
|
+
import threading
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
import pyrealsense2 as rs
|
|
16
|
+
REALSENSE_AVAILABLE = True
|
|
17
|
+
except ImportError:
|
|
18
|
+
REALSENSE_AVAILABLE = False
|
|
19
|
+
print("[WARN] pyrealsense2 not available - using mock camera")
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
import mediapipe as mp
|
|
23
|
+
# Check if using new Tasks API (0.10.x+) or legacy Solutions API
|
|
24
|
+
if hasattr(mp, 'solutions'):
|
|
25
|
+
MEDIAPIPE_API = 'solutions'
|
|
26
|
+
elif hasattr(mp, 'tasks'):
|
|
27
|
+
MEDIAPIPE_API = 'tasks'
|
|
28
|
+
else:
|
|
29
|
+
MEDIAPIPE_API = None
|
|
30
|
+
MEDIAPIPE_AVAILABLE = MEDIAPIPE_API is not None
|
|
31
|
+
if not MEDIAPIPE_AVAILABLE:
|
|
32
|
+
print("[WARN] mediapipe installed but no compatible API found")
|
|
33
|
+
except ImportError:
|
|
34
|
+
MEDIAPIPE_AVAILABLE = False
|
|
35
|
+
MEDIAPIPE_API = None
|
|
36
|
+
print("[WARN] mediapipe not available - using mock detection")
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
import cv2
|
|
40
|
+
CV2_AVAILABLE = True
|
|
41
|
+
except ImportError:
|
|
42
|
+
CV2_AVAILABLE = False
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class DetectedPerson:
|
|
47
|
+
"""Represents a detected person with 3D position."""
|
|
48
|
+
id: int
|
|
49
|
+
x: float # meters, positive = right of camera
|
|
50
|
+
y: float # meters, positive = down
|
|
51
|
+
z: float # meters, depth/distance from camera
|
|
52
|
+
confidence: float # 0.0 to 1.0
|
|
53
|
+
bbox: tuple[int, int, int, int] # x, y, width, height in pixels
|
|
54
|
+
landmarks: Optional[list] = None # MediaPipe pose landmarks
|
|
55
|
+
last_seen: float = field(default_factory=time.time)
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def distance(self) -> float:
|
|
59
|
+
"""Euclidean distance from camera."""
|
|
60
|
+
return np.sqrt(self.x**2 + self.y**2 + self.z**2)
|
|
61
|
+
|
|
62
|
+
def __repr__(self):
|
|
63
|
+
return f"Person #{self.id}: x={self.x:.2f}m, y={self.y:.2f}m, z={self.z:.2f}m (conf={self.confidence:.2f})"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class PersonTracker:
|
|
67
|
+
"""
|
|
68
|
+
Tracks people using RealSense depth camera and MediaPipe Pose.
|
|
69
|
+
|
|
70
|
+
Provides real-time detection at ~30 Hz with 3D position estimation.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
def __init__(self, use_camera: bool = True):
|
|
74
|
+
self.use_camera = use_camera and REALSENSE_AVAILABLE
|
|
75
|
+
self.running = False
|
|
76
|
+
self.lock = threading.Lock()
|
|
77
|
+
|
|
78
|
+
# Detected persons (thread-safe access via lock)
|
|
79
|
+
self._persons: list[DetectedPerson] = []
|
|
80
|
+
self._latest_color_frame: Optional[np.ndarray] = None
|
|
81
|
+
self._latest_depth_frame: Optional[np.ndarray] = None
|
|
82
|
+
|
|
83
|
+
# Tracking state
|
|
84
|
+
self._next_person_id = 1
|
|
85
|
+
self._tracking_history: dict[int, DetectedPerson] = {}
|
|
86
|
+
|
|
87
|
+
# Camera intrinsics (will be set from RealSense)
|
|
88
|
+
self.fx = 600.0 # focal length x (pixels)
|
|
89
|
+
self.fy = 600.0 # focal length y (pixels)
|
|
90
|
+
self.cx = 320.0 # principal point x
|
|
91
|
+
self.cy = 240.0 # principal point y
|
|
92
|
+
self.depth_scale = 0.001 # depth units to meters
|
|
93
|
+
|
|
94
|
+
# RealSense pipeline
|
|
95
|
+
self.pipeline = None
|
|
96
|
+
self.align = None
|
|
97
|
+
|
|
98
|
+
# MediaPipe
|
|
99
|
+
self.mp_pose = None
|
|
100
|
+
self.pose = None
|
|
101
|
+
|
|
102
|
+
if self.use_camera:
|
|
103
|
+
self._init_realsense()
|
|
104
|
+
|
|
105
|
+
if MEDIAPIPE_AVAILABLE:
|
|
106
|
+
self._init_mediapipe()
|
|
107
|
+
|
|
108
|
+
def _init_realsense(self):
|
|
109
|
+
"""Initialize RealSense camera pipeline."""
|
|
110
|
+
try:
|
|
111
|
+
self.pipeline = rs.pipeline()
|
|
112
|
+
config = rs.config()
|
|
113
|
+
|
|
114
|
+
# Configure streams
|
|
115
|
+
config.enable_stream(rs.stream.depth, 640, 480, rs.format.z16, 30)
|
|
116
|
+
config.enable_stream(rs.stream.color, 640, 480, rs.format.bgr8, 30)
|
|
117
|
+
|
|
118
|
+
# Start pipeline
|
|
119
|
+
profile = self.pipeline.start(config)
|
|
120
|
+
|
|
121
|
+
# Get depth scale
|
|
122
|
+
depth_sensor = profile.get_device().first_depth_sensor()
|
|
123
|
+
self.depth_scale = depth_sensor.get_depth_scale()
|
|
124
|
+
|
|
125
|
+
# Get camera intrinsics
|
|
126
|
+
depth_stream = profile.get_stream(rs.stream.depth)
|
|
127
|
+
intrinsics = depth_stream.as_video_stream_profile().get_intrinsics()
|
|
128
|
+
self.fx = intrinsics.fx
|
|
129
|
+
self.fy = intrinsics.fy
|
|
130
|
+
self.cx = intrinsics.ppx
|
|
131
|
+
self.cy = intrinsics.ppy
|
|
132
|
+
|
|
133
|
+
# Align depth to color
|
|
134
|
+
self.align = rs.align(rs.stream.color)
|
|
135
|
+
|
|
136
|
+
print(f"[INFO] RealSense initialized: {intrinsics.width}x{intrinsics.height}")
|
|
137
|
+
print(f"[INFO] Depth scale: {self.depth_scale}")
|
|
138
|
+
|
|
139
|
+
except Exception as e:
|
|
140
|
+
print(f"[ERROR] Failed to initialize RealSense: {e}")
|
|
141
|
+
self.use_camera = False
|
|
142
|
+
self.pipeline = None
|
|
143
|
+
|
|
144
|
+
def _init_mediapipe(self):
|
|
145
|
+
"""Initialize MediaPipe Pose."""
|
|
146
|
+
if MEDIAPIPE_API == 'solutions':
|
|
147
|
+
# Legacy API (mediapipe < 0.10)
|
|
148
|
+
self.mp_pose = mp.solutions.pose
|
|
149
|
+
self.pose = self.mp_pose.Pose(
|
|
150
|
+
static_image_mode=False,
|
|
151
|
+
model_complexity=1, # 0=lite, 1=full, 2=heavy
|
|
152
|
+
enable_segmentation=False,
|
|
153
|
+
min_detection_confidence=0.5,
|
|
154
|
+
min_tracking_confidence=0.5
|
|
155
|
+
)
|
|
156
|
+
print("[INFO] MediaPipe Pose initialized (solutions API)")
|
|
157
|
+
elif MEDIAPIPE_API == 'tasks':
|
|
158
|
+
# New Tasks API (mediapipe >= 0.10)
|
|
159
|
+
from mediapipe.tasks import python as mp_python
|
|
160
|
+
from mediapipe.tasks.python import vision as mp_vision
|
|
161
|
+
|
|
162
|
+
# For Tasks API, we need to download the model
|
|
163
|
+
# We'll use a simpler approach with pose landmarker
|
|
164
|
+
try:
|
|
165
|
+
base_options = mp_python.BaseOptions(
|
|
166
|
+
model_asset_path=self._get_pose_model_path()
|
|
167
|
+
)
|
|
168
|
+
options = mp_vision.PoseLandmarkerOptions(
|
|
169
|
+
base_options=base_options,
|
|
170
|
+
running_mode=mp_vision.RunningMode.VIDEO,
|
|
171
|
+
num_poses=5,
|
|
172
|
+
min_pose_detection_confidence=0.5,
|
|
173
|
+
min_tracking_confidence=0.5
|
|
174
|
+
)
|
|
175
|
+
self.pose = mp_vision.PoseLandmarker.create_from_options(options)
|
|
176
|
+
self.mp_pose = None # Not used in Tasks API
|
|
177
|
+
self._mp_vision = mp_vision
|
|
178
|
+
print("[INFO] MediaPipe Pose initialized (tasks API)")
|
|
179
|
+
except Exception as e:
|
|
180
|
+
print(f"[WARN] Failed to init MediaPipe Tasks: {e}")
|
|
181
|
+
print("[INFO] Falling back to simple detection")
|
|
182
|
+
self.pose = None
|
|
183
|
+
self.mp_pose = None
|
|
184
|
+
else:
|
|
185
|
+
self.pose = None
|
|
186
|
+
self.mp_pose = None
|
|
187
|
+
|
|
188
|
+
def _get_pose_model_path(self) -> str:
|
|
189
|
+
"""Get or download the pose landmarker model."""
|
|
190
|
+
import os
|
|
191
|
+
import urllib.request
|
|
192
|
+
|
|
193
|
+
model_dir = os.path.expanduser("~/.cache/mediapipe")
|
|
194
|
+
os.makedirs(model_dir, exist_ok=True)
|
|
195
|
+
model_path = os.path.join(model_dir, "pose_landmarker_lite.task")
|
|
196
|
+
|
|
197
|
+
if not os.path.exists(model_path):
|
|
198
|
+
print("[INFO] Downloading MediaPipe pose model...")
|
|
199
|
+
url = "https://storage.googleapis.com/mediapipe-models/pose_landmarker/pose_landmarker_lite/float16/1/pose_landmarker_lite.task"
|
|
200
|
+
urllib.request.urlretrieve(url, model_path)
|
|
201
|
+
print("[INFO] Model downloaded")
|
|
202
|
+
|
|
203
|
+
return model_path
|
|
204
|
+
|
|
205
|
+
def start(self):
|
|
206
|
+
"""Start the tracking loop in a background thread."""
|
|
207
|
+
if self.running:
|
|
208
|
+
return
|
|
209
|
+
|
|
210
|
+
self.running = True
|
|
211
|
+
self._thread = threading.Thread(target=self._tracking_loop, daemon=True)
|
|
212
|
+
self._thread.start()
|
|
213
|
+
print("[INFO] PersonTracker started")
|
|
214
|
+
|
|
215
|
+
def stop(self):
|
|
216
|
+
"""Stop the tracking loop."""
|
|
217
|
+
self.running = False
|
|
218
|
+
if hasattr(self, '_thread'):
|
|
219
|
+
self._thread.join(timeout=1.0)
|
|
220
|
+
|
|
221
|
+
if self.pipeline:
|
|
222
|
+
self.pipeline.stop()
|
|
223
|
+
|
|
224
|
+
if self.pose:
|
|
225
|
+
self.pose.close()
|
|
226
|
+
|
|
227
|
+
print("[INFO] PersonTracker stopped")
|
|
228
|
+
|
|
229
|
+
def _tracking_loop(self):
|
|
230
|
+
"""Main tracking loop running at ~30 Hz."""
|
|
231
|
+
while self.running:
|
|
232
|
+
try:
|
|
233
|
+
if self.use_camera and self.pipeline:
|
|
234
|
+
self._process_camera_frame()
|
|
235
|
+
else:
|
|
236
|
+
self._generate_mock_data()
|
|
237
|
+
|
|
238
|
+
time.sleep(1/30) # Target 30 Hz
|
|
239
|
+
|
|
240
|
+
except Exception as e:
|
|
241
|
+
print(f"[ERROR] Tracking loop error: {e}")
|
|
242
|
+
time.sleep(0.1)
|
|
243
|
+
|
|
244
|
+
def _process_camera_frame(self):
|
|
245
|
+
"""Process a frame from the RealSense camera."""
|
|
246
|
+
frames = self.pipeline.wait_for_frames()
|
|
247
|
+
|
|
248
|
+
# Align depth to color
|
|
249
|
+
aligned_frames = self.align.process(frames)
|
|
250
|
+
depth_frame = aligned_frames.get_depth_frame()
|
|
251
|
+
color_frame = aligned_frames.get_color_frame()
|
|
252
|
+
|
|
253
|
+
if not depth_frame or not color_frame:
|
|
254
|
+
return
|
|
255
|
+
|
|
256
|
+
# Convert to numpy
|
|
257
|
+
depth_image = np.asanyarray(depth_frame.get_data())
|
|
258
|
+
color_image = np.asanyarray(color_frame.get_data())
|
|
259
|
+
|
|
260
|
+
# Store latest frames
|
|
261
|
+
with self.lock:
|
|
262
|
+
self._latest_color_frame = color_image.copy()
|
|
263
|
+
self._latest_depth_frame = depth_image.copy()
|
|
264
|
+
|
|
265
|
+
# Detect people
|
|
266
|
+
persons = self._detect_persons(color_image, depth_image)
|
|
267
|
+
|
|
268
|
+
# Update tracked persons with ID continuity
|
|
269
|
+
self._update_tracking(persons)
|
|
270
|
+
|
|
271
|
+
def _detect_persons(self, color_image: np.ndarray, depth_image: np.ndarray) -> list[DetectedPerson]:
|
|
272
|
+
"""Detect persons in the frame using MediaPipe."""
|
|
273
|
+
if not MEDIAPIPE_AVAILABLE or self.pose is None:
|
|
274
|
+
# Fall back to simple detection if no MediaPipe
|
|
275
|
+
return self._simple_detect_persons(color_image, depth_image)
|
|
276
|
+
|
|
277
|
+
h, w = color_image.shape[:2]
|
|
278
|
+
|
|
279
|
+
if MEDIAPIPE_API == 'solutions':
|
|
280
|
+
return self._detect_persons_solutions(color_image, depth_image)
|
|
281
|
+
elif MEDIAPIPE_API == 'tasks':
|
|
282
|
+
return self._detect_persons_tasks(color_image, depth_image)
|
|
283
|
+
else:
|
|
284
|
+
return self._simple_detect_persons(color_image, depth_image)
|
|
285
|
+
|
|
286
|
+
def _detect_persons_solutions(self, color_image: np.ndarray, depth_image: np.ndarray) -> list[DetectedPerson]:
|
|
287
|
+
"""Detect using legacy Solutions API."""
|
|
288
|
+
# Convert BGR to RGB for MediaPipe
|
|
289
|
+
rgb_image = cv2.cvtColor(color_image, cv2.COLOR_BGR2RGB) if CV2_AVAILABLE else color_image
|
|
290
|
+
|
|
291
|
+
# Process with MediaPipe
|
|
292
|
+
results = self.pose.process(rgb_image)
|
|
293
|
+
|
|
294
|
+
if not results.pose_landmarks:
|
|
295
|
+
return []
|
|
296
|
+
|
|
297
|
+
persons = []
|
|
298
|
+
h, w = color_image.shape[:2]
|
|
299
|
+
|
|
300
|
+
# Get landmarks
|
|
301
|
+
landmarks = results.pose_landmarks.landmark
|
|
302
|
+
|
|
303
|
+
# Calculate bounding box from pose landmarks
|
|
304
|
+
x_coords = [lm.x * w for lm in landmarks if lm.visibility > 0.5]
|
|
305
|
+
y_coords = [lm.y * h for lm in landmarks if lm.visibility > 0.5]
|
|
306
|
+
|
|
307
|
+
if not x_coords or not y_coords:
|
|
308
|
+
return []
|
|
309
|
+
|
|
310
|
+
# Bounding box with padding
|
|
311
|
+
padding = 20
|
|
312
|
+
x_min = max(0, int(min(x_coords)) - padding)
|
|
313
|
+
y_min = max(0, int(min(y_coords)) - padding)
|
|
314
|
+
x_max = min(w, int(max(x_coords)) + padding)
|
|
315
|
+
y_max = min(h, int(max(y_coords)) + padding)
|
|
316
|
+
|
|
317
|
+
bbox = (x_min, y_min, x_max - x_min, y_max - y_min)
|
|
318
|
+
|
|
319
|
+
# Get center point (use hip center for more stable depth)
|
|
320
|
+
left_hip = landmarks[self.mp_pose.PoseLandmark.LEFT_HIP]
|
|
321
|
+
right_hip = landmarks[self.mp_pose.PoseLandmark.RIGHT_HIP]
|
|
322
|
+
|
|
323
|
+
center_x = int((left_hip.x + right_hip.x) / 2 * w)
|
|
324
|
+
center_y = int((left_hip.y + right_hip.y) / 2 * h)
|
|
325
|
+
|
|
326
|
+
# Clamp to image bounds
|
|
327
|
+
center_x = max(0, min(w - 1, center_x))
|
|
328
|
+
center_y = max(0, min(h - 1, center_y))
|
|
329
|
+
|
|
330
|
+
# Get depth at center (average over small region for stability)
|
|
331
|
+
depth_region = depth_image[
|
|
332
|
+
max(0, center_y - 5):min(h, center_y + 5),
|
|
333
|
+
max(0, center_x - 5):min(w, center_x + 5)
|
|
334
|
+
]
|
|
335
|
+
|
|
336
|
+
# Filter out zero/invalid depths
|
|
337
|
+
valid_depths = depth_region[depth_region > 0]
|
|
338
|
+
if len(valid_depths) == 0:
|
|
339
|
+
return []
|
|
340
|
+
|
|
341
|
+
depth_value = np.median(valid_depths) * self.depth_scale # Convert to meters
|
|
342
|
+
|
|
343
|
+
# Convert pixel to 3D coordinates
|
|
344
|
+
x_3d = (center_x - self.cx) * depth_value / self.fx
|
|
345
|
+
y_3d = (center_y - self.cy) * depth_value / self.fy
|
|
346
|
+
z_3d = depth_value
|
|
347
|
+
|
|
348
|
+
# Calculate confidence from landmark visibility
|
|
349
|
+
visibility_sum = sum(lm.visibility for lm in landmarks)
|
|
350
|
+
confidence = visibility_sum / len(landmarks)
|
|
351
|
+
|
|
352
|
+
person = DetectedPerson(
|
|
353
|
+
id=0, # Will be assigned in _update_tracking
|
|
354
|
+
x=x_3d,
|
|
355
|
+
y=y_3d,
|
|
356
|
+
z=z_3d,
|
|
357
|
+
confidence=confidence,
|
|
358
|
+
bbox=bbox,
|
|
359
|
+
landmarks=[(lm.x, lm.y, lm.z, lm.visibility) for lm in landmarks]
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
persons.append(person)
|
|
363
|
+
|
|
364
|
+
return persons
|
|
365
|
+
|
|
366
|
+
def _detect_persons_tasks(self, color_image: np.ndarray, depth_image: np.ndarray) -> list[DetectedPerson]:
|
|
367
|
+
"""Detect using new Tasks API (MediaPipe 0.10+)."""
|
|
368
|
+
h, w = color_image.shape[:2]
|
|
369
|
+
|
|
370
|
+
# Convert to RGB
|
|
371
|
+
rgb_image = cv2.cvtColor(color_image, cv2.COLOR_BGR2RGB) if CV2_AVAILABLE else color_image
|
|
372
|
+
|
|
373
|
+
# Create MediaPipe Image
|
|
374
|
+
mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=rgb_image)
|
|
375
|
+
|
|
376
|
+
# Get timestamp in milliseconds
|
|
377
|
+
timestamp_ms = int(time.time() * 1000)
|
|
378
|
+
|
|
379
|
+
# Detect poses
|
|
380
|
+
try:
|
|
381
|
+
results = self.pose.detect_for_video(mp_image, timestamp_ms)
|
|
382
|
+
except Exception as e:
|
|
383
|
+
print(f"[WARN] Pose detection failed: {e}")
|
|
384
|
+
return []
|
|
385
|
+
|
|
386
|
+
if not results.pose_landmarks:
|
|
387
|
+
return []
|
|
388
|
+
|
|
389
|
+
persons = []
|
|
390
|
+
|
|
391
|
+
# Process each detected pose
|
|
392
|
+
for pose_idx, pose_landmarks in enumerate(results.pose_landmarks):
|
|
393
|
+
landmarks = pose_landmarks
|
|
394
|
+
|
|
395
|
+
# Calculate bounding box
|
|
396
|
+
x_coords = [lm.x * w for lm in landmarks if lm.visibility > 0.5]
|
|
397
|
+
y_coords = [lm.y * h for lm in landmarks if lm.visibility > 0.5]
|
|
398
|
+
|
|
399
|
+
if not x_coords or not y_coords:
|
|
400
|
+
continue
|
|
401
|
+
|
|
402
|
+
padding = 20
|
|
403
|
+
x_min = max(0, int(min(x_coords)) - padding)
|
|
404
|
+
y_min = max(0, int(min(y_coords)) - padding)
|
|
405
|
+
x_max = min(w, int(max(x_coords)) + padding)
|
|
406
|
+
y_max = min(h, int(max(y_coords)) + padding)
|
|
407
|
+
|
|
408
|
+
bbox = (x_min, y_min, x_max - x_min, y_max - y_min)
|
|
409
|
+
|
|
410
|
+
# Get center point (use hip landmarks - indices 23 and 24 in Tasks API)
|
|
411
|
+
left_hip = landmarks[23] if len(landmarks) > 23 else landmarks[0]
|
|
412
|
+
right_hip = landmarks[24] if len(landmarks) > 24 else landmarks[0]
|
|
413
|
+
|
|
414
|
+
center_x = int((left_hip.x + right_hip.x) / 2 * w)
|
|
415
|
+
center_y = int((left_hip.y + right_hip.y) / 2 * h)
|
|
416
|
+
|
|
417
|
+
center_x = max(0, min(w - 1, center_x))
|
|
418
|
+
center_y = max(0, min(h - 1, center_y))
|
|
419
|
+
|
|
420
|
+
# Get depth
|
|
421
|
+
depth_region = depth_image[
|
|
422
|
+
max(0, center_y - 5):min(h, center_y + 5),
|
|
423
|
+
max(0, center_x - 5):min(w, center_x + 5)
|
|
424
|
+
]
|
|
425
|
+
|
|
426
|
+
valid_depths = depth_region[depth_region > 0]
|
|
427
|
+
if len(valid_depths) == 0:
|
|
428
|
+
continue
|
|
429
|
+
|
|
430
|
+
depth_value = np.median(valid_depths) * self.depth_scale
|
|
431
|
+
|
|
432
|
+
x_3d = (center_x - self.cx) * depth_value / self.fx
|
|
433
|
+
y_3d = (center_y - self.cy) * depth_value / self.fy
|
|
434
|
+
z_3d = depth_value
|
|
435
|
+
|
|
436
|
+
visibility_sum = sum(lm.visibility for lm in landmarks)
|
|
437
|
+
confidence = visibility_sum / len(landmarks)
|
|
438
|
+
|
|
439
|
+
person = DetectedPerson(
|
|
440
|
+
id=0,
|
|
441
|
+
x=x_3d,
|
|
442
|
+
y=y_3d,
|
|
443
|
+
z=z_3d,
|
|
444
|
+
confidence=confidence,
|
|
445
|
+
bbox=bbox,
|
|
446
|
+
landmarks=[(lm.x, lm.y, lm.z, lm.visibility) for lm in landmarks]
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
persons.append(person)
|
|
450
|
+
|
|
451
|
+
return persons
|
|
452
|
+
|
|
453
|
+
def _simple_detect_persons(self, color_image: np.ndarray, depth_image: np.ndarray) -> list[DetectedPerson]:
|
|
454
|
+
"""Simple depth-based person detection fallback."""
|
|
455
|
+
h, w = color_image.shape[:2]
|
|
456
|
+
|
|
457
|
+
# Simple approach: find the largest depth blob in the center region
|
|
458
|
+
# This is a basic fallback when MediaPipe is not available
|
|
459
|
+
|
|
460
|
+
# Focus on center 60% of the image
|
|
461
|
+
margin_x = int(w * 0.2)
|
|
462
|
+
margin_y = int(h * 0.1)
|
|
463
|
+
|
|
464
|
+
center_depth = depth_image[margin_y:h-margin_y, margin_x:w-margin_x]
|
|
465
|
+
|
|
466
|
+
# Find valid depth values between 0.5m and 4m
|
|
467
|
+
min_depth = int(0.5 / self.depth_scale)
|
|
468
|
+
max_depth = int(4.0 / self.depth_scale)
|
|
469
|
+
|
|
470
|
+
valid_mask = (center_depth > min_depth) & (center_depth < max_depth)
|
|
471
|
+
|
|
472
|
+
if not np.any(valid_mask):
|
|
473
|
+
return []
|
|
474
|
+
|
|
475
|
+
# Find the center of the valid region
|
|
476
|
+
y_indices, x_indices = np.where(valid_mask)
|
|
477
|
+
|
|
478
|
+
if len(x_indices) == 0:
|
|
479
|
+
return []
|
|
480
|
+
|
|
481
|
+
center_x = int(np.median(x_indices)) + margin_x
|
|
482
|
+
center_y = int(np.median(y_indices)) + margin_y
|
|
483
|
+
|
|
484
|
+
# Get depth at that point
|
|
485
|
+
depth_value = depth_image[center_y, center_x] * self.depth_scale
|
|
486
|
+
|
|
487
|
+
if depth_value <= 0:
|
|
488
|
+
return []
|
|
489
|
+
|
|
490
|
+
# Convert to 3D
|
|
491
|
+
x_3d = (center_x - self.cx) * depth_value / self.fx
|
|
492
|
+
y_3d = (center_y - self.cy) * depth_value / self.fy
|
|
493
|
+
z_3d = depth_value
|
|
494
|
+
|
|
495
|
+
# Create a rough bounding box
|
|
496
|
+
bbox = (margin_x, margin_y, w - 2*margin_x, h - 2*margin_y)
|
|
497
|
+
|
|
498
|
+
person = DetectedPerson(
|
|
499
|
+
id=0,
|
|
500
|
+
x=x_3d,
|
|
501
|
+
y=y_3d,
|
|
502
|
+
z=z_3d,
|
|
503
|
+
confidence=0.5, # Low confidence for simple detection
|
|
504
|
+
bbox=bbox,
|
|
505
|
+
landmarks=None
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
return [person]
|
|
509
|
+
|
|
510
|
+
def _update_tracking(self, new_persons: list[DetectedPerson]):
|
|
511
|
+
"""Update tracking with ID continuity based on position."""
|
|
512
|
+
current_time = time.time()
|
|
513
|
+
|
|
514
|
+
# Match new detections to existing tracked persons
|
|
515
|
+
for person in new_persons:
|
|
516
|
+
best_match_id = None
|
|
517
|
+
best_match_dist = float('inf')
|
|
518
|
+
|
|
519
|
+
# Find closest existing person
|
|
520
|
+
for tracked_id, tracked in self._tracking_history.items():
|
|
521
|
+
# Only match if seen recently (within 500ms)
|
|
522
|
+
if current_time - tracked.last_seen > 0.5:
|
|
523
|
+
continue
|
|
524
|
+
|
|
525
|
+
# Calculate 3D distance
|
|
526
|
+
dist = np.sqrt(
|
|
527
|
+
(person.x - tracked.x)**2 +
|
|
528
|
+
(person.y - tracked.y)**2 +
|
|
529
|
+
(person.z - tracked.z)**2
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
# Match if within 0.5m (person couldn't have moved further in one frame)
|
|
533
|
+
if dist < 0.5 and dist < best_match_dist:
|
|
534
|
+
best_match_dist = dist
|
|
535
|
+
best_match_id = tracked_id
|
|
536
|
+
|
|
537
|
+
if best_match_id is not None:
|
|
538
|
+
person.id = best_match_id
|
|
539
|
+
else:
|
|
540
|
+
person.id = self._next_person_id
|
|
541
|
+
self._next_person_id += 1
|
|
542
|
+
|
|
543
|
+
person.last_seen = current_time
|
|
544
|
+
self._tracking_history[person.id] = person
|
|
545
|
+
|
|
546
|
+
# Clean up old tracks
|
|
547
|
+
stale_ids = [
|
|
548
|
+
pid for pid, p in self._tracking_history.items()
|
|
549
|
+
if current_time - p.last_seen > 2.0 # Remove after 2 seconds
|
|
550
|
+
]
|
|
551
|
+
for pid in stale_ids:
|
|
552
|
+
del self._tracking_history[pid]
|
|
553
|
+
|
|
554
|
+
# Update current persons list
|
|
555
|
+
with self.lock:
|
|
556
|
+
self._persons = new_persons.copy()
|
|
557
|
+
|
|
558
|
+
def _generate_mock_data(self):
|
|
559
|
+
"""Generate mock person data for testing without camera."""
|
|
560
|
+
t = time.time()
|
|
561
|
+
|
|
562
|
+
# Simulate a person moving back and forth
|
|
563
|
+
x = 0.3 * np.sin(t * 0.5) # Side to side
|
|
564
|
+
z = 1.5 + 0.5 * np.sin(t * 0.3) # Forward/back
|
|
565
|
+
|
|
566
|
+
mock_person = DetectedPerson(
|
|
567
|
+
id=1,
|
|
568
|
+
x=x,
|
|
569
|
+
y=0.0,
|
|
570
|
+
z=z,
|
|
571
|
+
confidence=0.9,
|
|
572
|
+
bbox=(200, 100, 200, 300),
|
|
573
|
+
last_seen=t
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
with self.lock:
|
|
577
|
+
self._persons = [mock_person]
|
|
578
|
+
|
|
579
|
+
# Generate mock color frame
|
|
580
|
+
if CV2_AVAILABLE:
|
|
581
|
+
frame = np.zeros((480, 640, 3), dtype=np.uint8)
|
|
582
|
+
# Draw a simple rectangle for the person
|
|
583
|
+
cv2.rectangle(frame, (200, 100), (400, 400), (0, 255, 0), 2)
|
|
584
|
+
cv2.putText(frame, f"Person #1 z={z:.2f}m", (200, 90),
|
|
585
|
+
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
|
|
586
|
+
self._latest_color_frame = frame
|
|
587
|
+
|
|
588
|
+
@property
|
|
589
|
+
def persons(self) -> list[DetectedPerson]:
|
|
590
|
+
"""Get current list of detected persons (thread-safe)."""
|
|
591
|
+
with self.lock:
|
|
592
|
+
return self._persons.copy()
|
|
593
|
+
|
|
594
|
+
@property
|
|
595
|
+
def latest_frame(self) -> Optional[np.ndarray]:
|
|
596
|
+
"""Get latest color frame (thread-safe)."""
|
|
597
|
+
with self.lock:
|
|
598
|
+
if self._latest_color_frame is not None:
|
|
599
|
+
return self._latest_color_frame.copy()
|
|
600
|
+
return None
|
|
601
|
+
|
|
602
|
+
def get_annotated_frame(self) -> Optional[np.ndarray]:
|
|
603
|
+
"""Get color frame with detection annotations."""
|
|
604
|
+
frame = self.latest_frame
|
|
605
|
+
if frame is None or not CV2_AVAILABLE:
|
|
606
|
+
return frame
|
|
607
|
+
|
|
608
|
+
persons = self.persons
|
|
609
|
+
|
|
610
|
+
for person in persons:
|
|
611
|
+
x, y, w, h = person.bbox
|
|
612
|
+
|
|
613
|
+
# Draw bounding box
|
|
614
|
+
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
|
|
615
|
+
|
|
616
|
+
# Draw label
|
|
617
|
+
label = f"#{person.id} z={person.z:.2f}m"
|
|
618
|
+
cv2.putText(frame, label, (x, y - 10),
|
|
619
|
+
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
|
|
620
|
+
|
|
621
|
+
return frame
|
|
622
|
+
|
|
623
|
+
def get_person_by_id(self, person_id: int) -> Optional[DetectedPerson]:
|
|
624
|
+
"""Get a specific person by ID."""
|
|
625
|
+
for person in self.persons:
|
|
626
|
+
if person.id == person_id:
|
|
627
|
+
return person
|
|
628
|
+
return None
|
|
629
|
+
|
|
630
|
+
def get_closest_person(self) -> Optional[DetectedPerson]:
|
|
631
|
+
"""Get the closest detected person."""
|
|
632
|
+
persons = self.persons
|
|
633
|
+
if not persons:
|
|
634
|
+
return None
|
|
635
|
+
return min(persons, key=lambda p: p.distance)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<?xml version="1.0"?>
|
|
2
|
+
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
|
|
3
|
+
<package format="3">
|
|
4
|
+
<name>agenticros_follow_me</name>
|
|
5
|
+
<version>0.0.1</version>
|
|
6
|
+
<description>Follow Me mission: person tracking and follower control, exposes ROS2 services for AgenticROS</description>
|
|
7
|
+
<maintainer email="team@plaipin.com">PlaiPin</maintainer>
|
|
8
|
+
<license>Apache-2.0</license>
|
|
9
|
+
|
|
10
|
+
<depend>rclpy</depend>
|
|
11
|
+
<depend>std_msgs</depend>
|
|
12
|
+
<depend>geometry_msgs</depend>
|
|
13
|
+
<depend>agenticros_msgs</depend>
|
|
14
|
+
|
|
15
|
+
<test_depend>ament_copyright</test_depend>
|
|
16
|
+
<test_depend>ament_flake8</test_depend>
|
|
17
|
+
<test_depend>ament_pep257</test_depend>
|
|
18
|
+
|
|
19
|
+
<export>
|
|
20
|
+
<build_type>ament_python</build_type>
|
|
21
|
+
</export>
|
|
22
|
+
</package>
|
|
File without changes
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from setuptools import setup
|
|
2
|
+
|
|
3
|
+
package_name = "agenticros_follow_me"
|
|
4
|
+
|
|
5
|
+
setup(
|
|
6
|
+
name=package_name,
|
|
7
|
+
version="0.0.1",
|
|
8
|
+
packages=[package_name],
|
|
9
|
+
data_files=[
|
|
10
|
+
("share/ament_index/resource_index/packages", ["resource/" + package_name]),
|
|
11
|
+
("share/" + package_name, ["package.xml"]),
|
|
12
|
+
],
|
|
13
|
+
install_requires=["setuptools"],
|
|
14
|
+
zip_safe=True,
|
|
15
|
+
maintainer="PlaiPin",
|
|
16
|
+
maintainer_email="team@plaipin.com",
|
|
17
|
+
description="Follow Me mission: person tracking and follower control for AgenticROS",
|
|
18
|
+
license="Apache-2.0",
|
|
19
|
+
tests_require=["pytest"],
|
|
20
|
+
entry_points={
|
|
21
|
+
"console_scripts": [
|
|
22
|
+
"follow_me_node = agenticros_follow_me.follow_me_node:main",
|
|
23
|
+
],
|
|
24
|
+
},
|
|
25
|
+
)
|