pynode-flow 0.2.1__tar.gz → 0.2.3__tar.gz

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.
Files changed (304) hide show
  1. pynode_flow-0.2.3/.claude/skills/pynode-workflows/SKILL.md +185 -0
  2. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/.gitignore +2 -0
  3. pynode_flow-0.2.3/DOCKER.md +189 -0
  4. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/INSTALL.md +10 -0
  5. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/PKG-INFO +36 -1
  6. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/README.md +35 -0
  7. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/docker-compose.yml +6 -1
  8. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/examples/README.md +5 -0
  9. pynode_flow-0.2.3/plan-pynodeReview-2026-07-18.prompt.md +135 -0
  10. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/_version.py +3 -3
  11. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/api/helpers.py +9 -0
  12. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/api/nodes.py +22 -0
  13. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/api/services.py +40 -52
  14. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/api/workflows.py +54 -16
  15. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/config.py +42 -0
  16. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/main.py +9 -0
  17. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/CameraNode/camera_node.py +120 -45
  18. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/DebugNode/debug_node.py +9 -1
  19. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/DelayNode/delay_node.py +75 -37
  20. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/ImageViewerNode/image_viewer_node.py +16 -9
  21. pynode_flow-0.2.3/pynode/nodes/LinkNode/__init__.py +13 -0
  22. pynode_flow-0.2.3/pynode/nodes/LinkNode/link_bus.py +110 -0
  23. pynode_flow-0.2.3/pynode/nodes/LinkNode/link_in_node.py +104 -0
  24. pynode_flow-0.2.3/pynode/nodes/LinkNode/link_out_node.py +88 -0
  25. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/MQTTNode/mqtt_service.py +94 -30
  26. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/OmronCameraNode/omron_camera_node.py +13 -3
  27. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/RateProbeNode/rate_probe_node.py +37 -29
  28. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/SwitchNode/switch_node.py +8 -2
  29. pynode_flow-0.2.3/pynode/nodes/TemplateNode/requirements.txt +1 -0
  30. pynode_flow-0.2.3/pynode/nodes/UltralyticsNode/__init__.py +11 -0
  31. pynode_flow-0.2.3/pynode/nodes/UltralyticsNode/model_paths.py +115 -0
  32. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/UltralyticsNode/ultralytics_node.py +15 -9
  33. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/VideoReaderNode/video_reader_node.py +43 -22
  34. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/__init__.py +6 -5
  35. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/base_node.py +160 -52
  36. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/mDNSNode/mdns_manager.py +28 -3
  37. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/messages.py +1 -0
  38. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/server.py +17 -0
  39. pynode_flow-0.2.3/pynode/static/examples/01-hello-world.json +33 -0
  40. pynode_flow-0.2.3/pynode/static/examples/02-function-transform.json +45 -0
  41. pynode_flow-0.2.3/pynode/static/examples/03-switch-routing.json +80 -0
  42. pynode_flow-0.2.3/pynode/static/examples/04-flow-control.json +76 -0
  43. pynode_flow-0.2.3/pynode/static/examples/05-webcam-viewer.json +35 -0
  44. pynode_flow-0.2.3/pynode/static/examples/06-webcam-yolo.json +75 -0
  45. pynode_flow-0.2.3/pynode/static/examples/07-blur-people.json +95 -0
  46. pynode_flow-0.2.3/pynode/static/examples/08-video-yolo-recorder.json +73 -0
  47. pynode_flow-0.2.3/pynode/static/examples/09-mqtt-pub-sub.json +56 -0
  48. pynode_flow-0.2.3/pynode/static/examples/10-person-alert-mqtt.json +134 -0
  49. pynode_flow-0.2.3/pynode/static/examples/README.md +65 -0
  50. pynode_flow-0.2.3/pynode/static/examples/manifest.json +74 -0
  51. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/static/index.html +80 -0
  52. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/static/js/debug.js +280 -58
  53. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/static/js/events.js +72 -1
  54. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/static/js/main.js +5 -1
  55. pynode_flow-0.2.3/pynode/static/js/mqtt-services.js +346 -0
  56. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/static/js/nodes.js +9 -0
  57. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/static/js/properties.js +90 -283
  58. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/static/js/state.js +13 -4
  59. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/static/js/workflow.js +121 -60
  60. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/static/js/workflows.js +192 -89
  61. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/static/style.css +227 -32
  62. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/workflow_engine.py +7 -1
  63. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/workflow_manager.py +4 -0
  64. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode_flow.egg-info/PKG-INFO +36 -1
  65. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode_flow.egg-info/SOURCES.txt +28 -0
  66. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode_flow.egg-info/scm_file_list.json +28 -0
  67. pynode_flow-0.2.3/pynode_flow.egg-info/scm_version.json +8 -0
  68. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pyproject.toml +7 -0
  69. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/tests/test_api_nodes.py +49 -0
  70. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/tests/test_api_workflows.py +83 -0
  71. pynode_flow-0.2.3/tests/test_camera_node_backend.py +119 -0
  72. pynode_flow-0.2.3/tests/test_debug_workflow_scope.py +122 -0
  73. pynode_flow-0.2.3/tests/test_delay_rate_limit.py +296 -0
  74. pynode_flow-0.2.3/tests/test_link_nodes.py +208 -0
  75. pynode_flow-0.2.3/tests/test_models_dir.py +230 -0
  76. pynode_flow-0.2.3/tests/test_mqtt_services.py +158 -0
  77. pynode_flow-0.2.3/tests/test_send_copy_semantics.py +226 -0
  78. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/tests/test_video_reader_node.py +62 -6
  79. pynode_flow-0.2.1/DOCKER.md +0 -113
  80. pynode_flow-0.2.1/pynode/nodes/UltralyticsNode/__init__.py +0 -4
  81. pynode_flow-0.2.1/pynode_flow.egg-info/scm_version.json +0 -8
  82. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/.dockerignore +0 -0
  83. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/.github/workflows/ci.yml +0 -0
  84. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/.github/workflows/publish.yaml +0 -0
  85. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/Dockerfile +0 -0
  86. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/Dockerfile.cpu +0 -0
  87. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/LICENSE +0 -0
  88. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/MANIFEST.in +0 -0
  89. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/docs/CREATING_NODES.md +0 -0
  90. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/docs/CUSTOM_NODES.md +0 -0
  91. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/docs/DOCKER.md +0 -0
  92. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/docs/EXTENSIBILITY.md +0 -0
  93. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/docs/UI_COMPONENTS.md +0 -0
  94. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/examples/camera_workflow.py +0 -0
  95. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/examples/camera_yolo_workflow.py +0 -0
  96. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/install_nodes.bat +0 -0
  97. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/install_nodes.sh +0 -0
  98. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/plan-pynodeRepoReview.prompt.md +0 -0
  99. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/__init__.py +0 -0
  100. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/__main__.py +0 -0
  101. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/_perf.py +0 -0
  102. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/api/__init__.py +0 -0
  103. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/api/sse.py +0 -0
  104. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/api/uploads.py +0 -0
  105. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/install_nodes.py +0 -0
  106. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/node_registry.py +0 -0
  107. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/AutoSyncNode/auto_sync_node.py +0 -0
  108. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/BBoxMetricsNode/__init__.py +0 -0
  109. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/BBoxMetricsNode/bbox_metrics_node.py +0 -0
  110. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/BBoxMetricsNode/requirements.txt +0 -0
  111. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/BatchNode/__init__.py +0 -0
  112. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/BatchNode/batch_node.py +0 -0
  113. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/BatchNode/requirements.txt +0 -0
  114. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/CameraNode/__init__.py +0 -0
  115. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/CameraNode/requirements.txt +0 -0
  116. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/ChangeNode/__init__.py +0 -0
  117. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/ChangeNode/change_node.py +0 -0
  118. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/ChangeNode/requirements.txt +0 -0
  119. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/ConfidenceFilterNode/__init__.py +0 -0
  120. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/ConfidenceFilterNode/confidence_filter_node.py +0 -0
  121. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/ConfidenceFilterNode/requirements.txt +0 -0
  122. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/CounterNode/__init__.py +0 -0
  123. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/CounterNode/counter_node.py +0 -0
  124. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/CropNode/__init__.py +0 -0
  125. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/CropNode/crop_node.py +0 -0
  126. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/CropNode/requirements.txt +0 -0
  127. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/DebugNode/__init__.py +0 -0
  128. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/DebugNode/requirements.txt +0 -0
  129. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/DelayNode/requirements.txt +0 -0
  130. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/DrawPredictionsNode/__init__.py +0 -0
  131. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/DrawPredictionsNode/draw_predictions_node.py +0 -0
  132. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/DrawPredictionsNode/requirements.txt +0 -0
  133. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/ErrorNode/error_node.py +0 -0
  134. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/ErrorNode/requirements.txt +0 -0
  135. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/FilterNode/__init__.py +0 -0
  136. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/FilterNode/filter_node.py +0 -0
  137. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/FilterNode/requirements.txt +0 -0
  138. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/FrameSourceNode/__init__.py +0 -0
  139. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/FrameSourceNode/frame_source_node.py +0 -0
  140. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/FrameSourceNode/requirements.txt +0 -0
  141. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/FunctionNode/__init__.py +0 -0
  142. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/FunctionNode/function_node.py +0 -0
  143. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/FunctionNode/requirements.txt +0 -0
  144. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/GateNode/gate_node.py +0 -0
  145. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/GateNode/requirements.txt +0 -0
  146. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/ImageFormatNode/__init__.py +0 -0
  147. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/ImageFormatNode/image_format_node.py +0 -0
  148. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/ImageFormatNode/requirements.txt +0 -0
  149. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/ImageUploadNode/image_upload_node.py +0 -0
  150. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/ImageViewerNode/__init__.py +0 -0
  151. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/ImageViewerNode/requirements.txt +0 -0
  152. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/ImageWriterNode/__init__.py +0 -0
  153. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/ImageWriterNode/imagewriter_node.py +0 -0
  154. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/ImageWriterNode/requirements.txt +0 -0
  155. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/InferenceNode/InferenceEngine/__init__.py +0 -0
  156. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/InferenceNode/InferenceEngine/device_detection.py +0 -0
  157. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/InferenceNode/InferenceEngine/engines/base_engine.py +0 -0
  158. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/InferenceNode/InferenceEngine/engines/example_engine_template.py +0 -0
  159. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/InferenceNode/InferenceEngine/engines/onnx_engine.py +0 -0
  160. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/InferenceNode/InferenceEngine/engines/pass_engine.py +0 -0
  161. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/InferenceNode/InferenceEngine/engines/ultralytics_engine.py +0 -0
  162. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/InferenceNode/InferenceEngine/inference_engine_factory.py +0 -0
  163. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/InferenceNode/InferenceEngine/readme.md +0 -0
  164. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/InferenceNode/InferenceEngine/result_converters.py +0 -0
  165. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/InferenceNode/inference_node.py +0 -0
  166. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/InferenceNode/requirements.txt +0 -0
  167. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/InjectNode/inject_node.py +0 -0
  168. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/InjectNode/requirements.txt +0 -0
  169. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/JoinNode/__init__.py +0 -0
  170. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/JoinNode/join_node.py +0 -0
  171. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/JoinNode/requirements.txt +0 -0
  172. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/LabelFilterNode/__init__.py +0 -0
  173. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/LabelFilterNode/label_filter_node.py +0 -0
  174. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/LabelFilterNode/requirements.txt +0 -0
  175. {pynode_flow-0.2.1/pynode/nodes/RangeNode → pynode_flow-0.2.3/pynode/nodes/LinkNode}/requirements.txt +0 -0
  176. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/MQTTNode/__init__.py +0 -0
  177. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/MQTTNode/mqtt_in_node.py +0 -0
  178. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/MQTTNode/mqtt_out_node.py +0 -0
  179. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/MQTTNode/requirements.txt +0 -0
  180. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/MessageWriterNode/__init__.py +0 -0
  181. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/MessageWriterNode/messagereader_node.py +0 -0
  182. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/MessageWriterNode/messagewriter_node.py +0 -0
  183. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/OmronCameraNode/__init__.py +0 -0
  184. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/OmronCameraNode/requirements.txt +0 -0
  185. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/OpenCV/__init__.py +0 -0
  186. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/OpenCV/arithmetic_node.py +0 -0
  187. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/OpenCV/bitwise_node.py +0 -0
  188. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/OpenCV/blend_node.py +0 -0
  189. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/OpenCV/blob_detector_node.py +0 -0
  190. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/OpenCV/blur_node.py +0 -0
  191. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/OpenCV/color_space_node.py +0 -0
  192. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/OpenCV/colormap_node.py +0 -0
  193. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/OpenCV/contours_node.py +0 -0
  194. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/OpenCV/denormalize_coords_node.py +0 -0
  195. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/OpenCV/draw_node.py +0 -0
  196. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/OpenCV/edge_detector_node.py +0 -0
  197. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/OpenCV/equirectangular_node.py +0 -0
  198. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/OpenCV/fft_node.py +0 -0
  199. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/OpenCV/focus_estimation_node.py +0 -0
  200. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/OpenCV/frequency_filter_node.py +0 -0
  201. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/OpenCV/histogram_node.py +0 -0
  202. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/OpenCV/hough_circles_node.py +0 -0
  203. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/OpenCV/hough_lines_node.py +0 -0
  204. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/OpenCV/in_range_node.py +0 -0
  205. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/OpenCV/morphology_node.py +0 -0
  206. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/OpenCV/normalize_coords_node.py +0 -0
  207. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/OpenCV/paste_node.py +0 -0
  208. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/OpenCV/perspective_node.py +0 -0
  209. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/OpenCV/realsense_depth_node.py +0 -0
  210. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/OpenCV/requirements.txt +0 -0
  211. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/OpenCV/resize_node.py +0 -0
  212. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/OpenCV/rotate_node.py +0 -0
  213. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/OpenCV/template_match_node.py +0 -0
  214. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/OpenCV/threshold_node.py +0 -0
  215. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/PointInShapeNode/__init__.py +0 -0
  216. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/PointInShapeNode/point_in_shape_node.py +0 -0
  217. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/PointInShapeNode/requirements.txt +0 -0
  218. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/PolygonMetricsNode/__init__.py +0 -0
  219. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/PolygonMetricsNode/polygon_metrics_node.py +0 -0
  220. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/PolygonMetricsNode/requirements.txt +0 -0
  221. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/QueueLengthProbeNode/__init__.py +0 -0
  222. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/QueueLengthProbeNode/queue_length_probe_node.py +0 -0
  223. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/Qwen3VLMNode/__init__.py +0 -0
  224. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/Qwen3VLMNode/qwen3_vlm_node.py +0 -0
  225. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/Qwen3VLMNode/requirements.txt +0 -0
  226. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/RESTEndpointNode/__init__.py +0 -0
  227. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/RESTEndpointNode/rest_endpoint_node.py +0 -0
  228. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/RangeNode/__init__.py +0 -0
  229. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/RangeNode/range_node.py +0 -0
  230. {pynode_flow-0.2.1/pynode/nodes/RateProbeNode → pynode_flow-0.2.3/pynode/nodes/RangeNode}/requirements.txt +0 -0
  231. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/RateProbeNode/__init__.py +0 -0
  232. {pynode_flow-0.2.1/pynode/nodes/SplitNode → pynode_flow-0.2.3/pynode/nodes/RateProbeNode}/requirements.txt +0 -0
  233. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/RoboflowUploadNode/__init__.py +0 -0
  234. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/RoboflowUploadNode/requirements.txt +0 -0
  235. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/RoboflowUploadNode/roboflow_upload_node.py +0 -0
  236. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/SliceNode/README.md +0 -0
  237. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/SliceNode/__init__.py +0 -0
  238. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/SliceNode/merge_predictions_node.py +0 -0
  239. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/SliceNode/requirements.txt +0 -0
  240. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/SliceNode/slice_collector_node.py +0 -0
  241. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/SliceNode/slice_image_node.py +0 -0
  242. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/SplitNode/__init__.py +0 -0
  243. {pynode_flow-0.2.1/pynode/nodes/SwitchNode → pynode_flow-0.2.3/pynode/nodes/SplitNode}/requirements.txt +0 -0
  244. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/SplitNode/split_node.py +0 -0
  245. {pynode_flow-0.2.1/pynode/nodes/TemplateNode → pynode_flow-0.2.3/pynode/nodes/SwitchNode}/requirements.txt +0 -0
  246. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/SyncNode/__init__.py +0 -0
  247. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/SyncNode/sync_node.py +0 -0
  248. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/TemplateNode/template_node.py +0 -0
  249. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/TrackerNode/__init__.py +0 -0
  250. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/TrackerNode/supervision_tracker_node.py +0 -0
  251. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/TrackerNode/tracker_node.py +0 -0
  252. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/UltralyticsNode/requirements.txt +0 -0
  253. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/UnknownNode/__init__.py +0 -0
  254. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/VLLMNode/__init__.py +0 -0
  255. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/VLLMNode/vllm_node.py +0 -0
  256. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/VideoReaderNode/__init__.py +0 -0
  257. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/VideoWriterNode/__init__.py +0 -0
  258. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/VideoWriterNode/video_writer_node.py +0 -0
  259. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/WebhookNode/__init__.py +0 -0
  260. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/WebhookNode/webhook_node.py +0 -0
  261. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/image_utils.py +0 -0
  262. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/info.py +0 -0
  263. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/mDNSNode/__init__.py +0 -0
  264. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/mDNSNode/mdns_broadcast_node.py +0 -0
  265. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/mDNSNode/mdns_discovery_node.py +0 -0
  266. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/nodes/mDNSNode/requirements.txt +0 -0
  267. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/static/images/favicon4.png +0 -0
  268. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/static/js/README.md +0 -0
  269. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/static/js/auth.js +0 -0
  270. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/static/js/clipboard.js +0 -0
  271. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/static/js/config.js +0 -0
  272. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/static/js/connections.js +0 -0
  273. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/static/js/history.js +0 -0
  274. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/static/js/minimap.js +0 -0
  275. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/static/js/palette.js +0 -0
  276. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/static/js/selection.js +0 -0
  277. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/static/js/ui-utils.js +0 -0
  278. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode/static/js/viewport.js +0 -0
  279. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode_flow.egg-info/dependency_links.txt +0 -0
  280. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode_flow.egg-info/entry_points.txt +0 -0
  281. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode_flow.egg-info/requires.txt +0 -0
  282. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/pynode_flow.egg-info/top_level.txt +0 -0
  283. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/requirements.txt +0 -0
  284. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/setup.bat +0 -0
  285. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/setup.cfg +0 -0
  286. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/setup.py +0 -0
  287. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/setup.sh +0 -0
  288. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/tests/__init__.py +0 -0
  289. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/tests/conftest.py +0 -0
  290. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/tests/test_api_auth.py +0 -0
  291. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/tests/test_api_errors.py +0 -0
  292. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/tests/test_api_security.py +0 -0
  293. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/tests/test_base_node_messaging.py +0 -0
  294. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/tests/test_concurrency.py +0 -0
  295. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/tests/test_config.py +0 -0
  296. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/tests/test_counter_sse.py +0 -0
  297. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/tests/test_device_detection.py +0 -0
  298. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/tests/test_message_reader_pickle.py +0 -0
  299. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/tests/test_node_registration.py +0 -0
  300. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/tests/test_node_types_api.py +0 -0
  301. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/tests/test_persistence.py +0 -0
  302. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/tests/test_serialization.py +0 -0
  303. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/tests/test_workflow_engine.py +0 -0
  304. {pynode_flow-0.2.1 → pynode_flow-0.2.3}/tests/test_workflow_roundtrip.py +0 -0
@@ -0,0 +1,185 @@
1
+ ---
2
+ name: pynode-workflows
3
+ description: Build, edit and validate PyNode workflow JSON files (nodes + connections) that users can import via the GUI or REST API. Use when asked to create example flows, generate a workflow, wire nodes together, or debug a workflow definition.
4
+ ---
5
+
6
+ # Building PyNode Workflows
7
+
8
+ A PyNode workflow is a JSON document describing nodes and the connections
9
+ between them. The GUI's **Import** button (hamburger menu) accepts a flat
10
+ workflow file and loads it into a **new tab** named after the file. The same
11
+ document can be POSTed to `/api/workflow`.
12
+
13
+ ## Workflow file format
14
+
15
+ ```json
16
+ {
17
+ "nodes": [
18
+ {
19
+ "id": "inject_1",
20
+ "type": "InjectNode",
21
+ "name": "Every 2s",
22
+ "config": { "props": [{"property": "payload", "value": "hi", "valueType": "str"}], "repeat": "2" },
23
+ "x": 80, "y": 80
24
+ }
25
+ ],
26
+ "connections": [
27
+ { "source": "inject_1", "target": "debug_1", "sourceOutput": 0, "targetInput": 0 }
28
+ ]
29
+ }
30
+ ```
31
+
32
+ Rules:
33
+ - `type` is the **Python class name** of the node (e.g. `InjectNode`, `MqttOutNode`).
34
+ Unknown types import as disabled placeholder nodes — always validate.
35
+ - `id` is any unique string. Use readable slugs (`camera_1`, `yolo_1`).
36
+ - `enabled` is optional (default `true`). `x`/`y` are canvas pixels; space
37
+ columns ~260px apart (80, 340, 600, 860, 1120), rows ~140px apart.
38
+ - `sourceOutput`/`targetInput` are 0-based port indexes.
39
+ - Config values are lenient: many nodes read numbers from strings
40
+ (`"confidence": "0.25"`) — copy the conventions below exactly.
41
+ - The on-disk `workflows/workflow.json` uses a different multi-workflow
42
+ wrapper (`{version, activeWorkflow, workflows: [...]}`); **never** write that
43
+ file directly — import through the GUI or API instead.
44
+
45
+ ## Message conventions
46
+
47
+ Messages are dicts in Node-RED style: `payload`, `topic`, `_msgid`, plus extras.
48
+ - Images travel at `msg.payload.image` as a numpy BGR array (or JPEG bytes if a
49
+ source has `encode_jpeg: true`). Downstream nodes locate it via their
50
+ `image_path` config, default `payload.image`.
51
+ - Detections travel at `msg.payload.detections`: a list of
52
+ `{"bbox": [x1,y1,x2,y2], "bbox_format": "xyxy", "class_name": str,
53
+ "class_id": int, "confidence": float}`, with `payload.detection_count` and
54
+ `payload.bbox_format` alongside.
55
+ - `MqttOutNode` publishes `msg.payload`; **`msg.topic` overrides the node's
56
+ configured topic** — don't set a `topic` in InjectNode when a downstream
57
+ MQTT-out node should control the topic.
58
+
59
+ ## Node catalog (config keys verified against source)
60
+
61
+ ### Core / logic
62
+ | Type | Ports in→out | Config (defaults) |
63
+ |---|---|---|
64
+ | `InjectNode` | 0→1 | `props`: list of `{property: "payload", value, valueType: str\|num\|bool\|json\|date\|env}` (dot paths OK, e.g. `payload.count`); `topic`: str; `once`: seconds (str, empty=off); `repeat`: seconds (str, empty=off). Manual trigger button built in. |
65
+ | `DebugNode` | 1→0 | `console`: bool; `complete`: `"payload"` (default) or `"msg"` for whole message; `drop_messages`: bool |
66
+ | `FunctionNode` | 1→N | `func`: Python source (body of a function receiving `msg`, `node`, `time`; must `return msg` / a list for multiple outputs / `None` to drop); `outputs`: int (default 1). Full builtins and `import` are available inside the body. |
67
+ | `SwitchNode` | 1→N | `property`: dot path (`payload`, `payload.count`); `checkall`: bool; `rules`: list of `{operator, value, valueType}` — **rule index = output index**. Operators: `eq neq lt lte gt gte between contains matches true false null nnull else`. |
68
+ | `ChangeNode` | 1→1 | `rules`: list of `{type: set\|delete\|change\|move, path: "msg.payload...", value, valueType}` |
69
+ | `GateNode` | 1→1 | `{}` — passes messages only while enabled; toggled live from its card |
70
+ | `LinkOutNode` | 1→0 | `channel`: str — publishes each incoming msg to the named channel on a process-wide bus. Reaches every `LinkInNode` with the same channel, **including in other workflows**. Empty channel = disabled. `drop_messages` defaults `"false"`. |
71
+ | `LinkInNode` | 0→1 | `channel`: str — emits every msg published on the named channel (by any `LinkOutNode`, cross-flow). Deep-copied on delivery (flow isolation). Only receives while its flow is deployed/running and the node is enabled. Empty channel = disabled. |
72
+ | `DelayNode` | 1→1 | `mode`: `delay`\|`delay_count`\|`rate`; `timeout`: secs (delay mode); `rate` + `rate_time`: msgs per secs; `rate_drop`: `drop`\|`queue` |
73
+ | `CounterNode` | 1→1 | `initial_value`: "0"; `increment`: "1"; `retain_payload`: "false" |
74
+ | `RateProbeNode` | 1→1 | `window_size`: seconds (float) — passes msgs through, shows msg/s |
75
+
76
+ ### Vision
77
+ | Type | Ports | Config |
78
+ |---|---|---|
79
+ | `CameraNode` | 0→1 | `device_index`: 0; `fps`: 30; `width`: 640; `height`: 480; `encode_jpeg`: false; `jpeg_quality`: 75 |
80
+ | `VideoReaderNode` | 0→1 | `source`: path (upload via node properties in GUI); `fps`: 0 (=native); `loop`: bool; `jpeg_quality`: 80 |
81
+ | `UltralyticsNode` | 1→1 | `model`: "yolo11n.pt" (auto-downloads); `confidence`: "0.25"; `iou`: "0.45"; `draw_results`: "true"/"false"; `max_det`: "300"; `include_image`: true; `include_predictions`: true; `drop_messages`: "true" (keep on!); `device`: "cpu" (portable) or `cuda:0`/`intel:gpu` |
82
+ | `LabelFilterNode` | 1→2 | `labels`: "person, car" (comma list); `match_mode`: `any`\|`all`; `case_sensitive`: false; `filter_detections`: true (strips non-matching detections); `detections_path`: "payload.detections". **Output 0 = matched, output 1 = unmatched.** |
83
+ | `ConfidenceFilterNode` | 1→2 | `threshold_source`: `manual`\|`msg`; `threshold`: 0.5; `detections_path`: "payload.detections"; `confidence_field`: "confidence". **Output 0 = ≥ threshold, output 1 = < threshold.** |
84
+ | `ImageViewerNode` | 1→0 | `width`: 320; `height`: 240; `image_path`: "payload.image"; `drop_messages`: true |
85
+ | `VideoWriterNode` | 1→0 | `path`: "./output"; `filename`: "video_{counter}"; `codec`: `mp4v`\|`avc1`\|`xvid`...; `framerate`: 30.0; `width`/`height`; `auto_resolution`: bool (match input); `clip_length`: 0 (=unlimited); `naming_mode`: "counter"; `counter_digits`: 4 |
86
+
87
+ Other vision nodes: `DrawPredictionsNode`, `CropNode`, `TrackerNode`,
88
+ `ImageFormatNode`, `SliceNode`, `BBoxMetricsNode`, `PolygonMetricsNode`,
89
+ `InferenceNode` (multi-backend), `FrameSourceNode` (multi-source camera lib) —
90
+ read their `DEFAULT_CONFIG`/`properties` in `pynode/nodes/<Name>/` before use.
91
+
92
+ ### Network
93
+ | Type | Ports | Config |
94
+ |---|---|---|
95
+ | `MqttInNode` | 0→1 | `serviceId`: broker id from mqtt_services.json; `topic`: subscribe topic (wildcards `#`/`+` OK); `qos`: "0" |
96
+ | `MqttOutNode` | 1→0 | `serviceId`; `topic`: publish topic; `qos`: "0"; `retain`: "false" |
97
+
98
+ MQTT brokers are **services** stored in `mqtt_services.json` next to the
99
+ workflow data (managed in the GUI via the node's Broker dropdown → add). A
100
+ workflow referencing an unknown `serviceId` imports fine but the node reports
101
+ "No MQTT broker configured" until the user picks a broker.
102
+
103
+ Also available: `WebhookNode`, `RESTEndpointNode`, `mDNSNode`.
104
+
105
+ ## FunctionNode authoring rules
106
+
107
+ The `func` string is the **body** of `def user_function(msg, node, time)` —
108
+ top-level `return` is required to emit. `msg` is a deep copy. Imports work
109
+ inside the body (`import cv2`, `import numpy as np`, `import random`).
110
+ Return a list to fan out across `outputs`; `None` drops the message.
111
+
112
+ Example — blur every detection bbox in-place:
113
+
114
+ ```python
115
+ import cv2
116
+ import numpy as np
117
+ p = msg.get('payload') or {}
118
+ img = p.get('image')
119
+ dets = p.get('detections') or []
120
+ if not isinstance(img, np.ndarray):
121
+ return msg
122
+ h, w = img.shape[:2]
123
+ for d in dets:
124
+ bbox = d.get('bbox') or []
125
+ if len(bbox) != 4:
126
+ continue
127
+ x1, y1, x2, y2 = [int(round(v)) for v in bbox]
128
+ x1 = max(0, min(x1, w - 1)); x2 = max(x1 + 1, min(x2, w))
129
+ y1 = max(0, min(y1, h - 1)); y2 = max(y1 + 1, min(y2, h))
130
+ k = max(9, ((x2 - x1) // 4)) | 1
131
+ p['image'][y1:y2, x1:x2] = cv2.GaussianBlur(img[y1:y2, x1:x2], (k, k), 0)
132
+ return msg
133
+ ```
134
+
135
+ When embedding code in workflow JSON, join lines with `\n` and keep
136
+ indentation exact.
137
+
138
+ ## Loading a workflow
139
+
140
+ - **GUI (import)**: hamburger menu → *Import* → choose the `.json` file → it
141
+ opens as a new tab → press *Deploy* to activate.
142
+ - **GUI (bundled examples)**: hamburger menu → *Examples* → pick one. These are
143
+ the files in `pynode/static/examples/`, served statically and listed via
144
+ `pynode/static/examples/manifest.json`. **When adding a new bundled example,
145
+ drop the `.json` in that folder and add an entry to `manifest.json`** (id,
146
+ file, title, description, requires) or it won't appear in the menu.
147
+ - **API**: `POST /api/workflow` with the JSON body imports into the active
148
+ workflow (add `?workflow=<id>` to target another; `X-API-Key` header if the
149
+ server has an API key). `GET /api/workflow` exports the current one —
150
+ building a flow in the GUI and exporting it is the best way to learn configs.
151
+
152
+ ## Validating a workflow file (do this for every file you produce)
153
+
154
+ Run a sandboxed import — never against the real server or the repo's
155
+ `workflows/` dir (a leaked test once destroyed real data; see
156
+ tests/conftest.py):
157
+
158
+ ```python
159
+ import json, tempfile, os
160
+ from pynode.server import create_app # use a venv with the vision extras
161
+
162
+ td = tempfile.mkdtemp()
163
+ app = create_app({'WORKFLOWS_DIR': os.path.join(td, 'wf'),
164
+ 'WORKFLOW_FILE': os.path.join(td, 'wf', 'workflow.json'),
165
+ 'UPLOAD_BASE_DIR': os.path.join(td, 'up'), 'TESTING': True})
166
+ client = app.test_client()
167
+ data = json.load(open('pynode/static/examples/01-hello-world.json'))
168
+ r = client.post('/api/workflow', json=data)
169
+ assert r.status_code == 201, r.get_json()
170
+ manager = app.extensions['workflow_manager']
171
+ with manager.state_lock:
172
+ eng = next(iter(manager.working_engines.values()))
173
+ unknown = [n.type for n in eng.nodes.values() if getattr(n, '_is_unknown_node', False)]
174
+ assert not unknown, f"unknown node types: {unknown}"
175
+ manager.shutdown()
176
+ ```
177
+
178
+ Checklist:
179
+ - every connection's `source`/`target` id exists; port indexes < the node's
180
+ `input_count`/`output_count`
181
+ - node `type` strings match class names exactly (case-sensitive)
182
+ - vision chains keep `drop_messages` on for slow consumers (YOLO, viewers)
183
+ - prefer `device: "cpu"` and auto-downloading models (`yolo11n.pt`) so flows
184
+ run anywhere; note anything machine-specific (camera index, broker IP) in an
185
+ accompanying README
@@ -150,6 +150,8 @@ Thumbs.db
150
150
 
151
151
  # Project specific - ignore all JSON files (workflows and backups)
152
152
  *.json
153
+ # ...but keep the bundled, GUI-loadable example workflows
154
+ !pynode/static/examples/*.json
153
155
 
154
156
  # PyTorch model files
155
157
  *.pt
@@ -0,0 +1,189 @@
1
+ # Docker Setup for PyNode
2
+
3
+ ## Prerequisites
4
+
5
+ - Docker Engine with GPU support (NVIDIA Container Toolkit)
6
+ - Docker Compose v1.28+ (for GPU support)
7
+ - NVIDIA GPU with CUDA support
8
+
9
+ ## Quick Start
10
+
11
+ ### Build and Run
12
+
13
+ ```bash
14
+ docker-compose up --build
15
+ ```
16
+
17
+ ### Run in Background
18
+
19
+ ```bash
20
+ docker-compose up -d
21
+ ```
22
+
23
+ ### View Logs
24
+
25
+ ```bash
26
+ docker-compose logs -f
27
+ ```
28
+
29
+ ### Stop the Application
30
+
31
+ ```bash
32
+ docker-compose down
33
+ ```
34
+
35
+ ## Configuration
36
+
37
+ ### Ports
38
+
39
+ The application runs on port `5000` by default. To change the port, edit `docker-compose.yml`:
40
+
41
+ ```yaml
42
+ ports:
43
+ - "8080:5000" # Change 8080 to your desired port
44
+ ```
45
+
46
+ ### GPU Support
47
+
48
+ The Docker Compose file is configured to use all available NVIDIA GPUs. To limit GPU access:
49
+
50
+ ```yaml
51
+ deploy:
52
+ resources:
53
+ reservations:
54
+ devices:
55
+ - driver: nvidia
56
+ device_ids: ['0'] # Use only GPU 0
57
+ capabilities: [gpu]
58
+ ```
59
+
60
+ ### Persistent Storage
61
+
62
+ The following directories are mounted for persistence:
63
+ - `./workflows` - Saved workflows
64
+ - `./logs` - Application logs
65
+
66
+ ## Building
67
+
68
+ ### Custom CUDA Version
69
+
70
+ To use a different CUDA version, edit the `Dockerfile`:
71
+
72
+ ```dockerfile
73
+ FROM nvidia/cuda:12.8.0-runtime-ubuntu22.04 # Change CUDA version
74
+ ```
75
+
76
+ ### Without GPU Support
77
+
78
+ For CPU-only deployment, use the CPU Dockerfile:
79
+
80
+ ```bash
81
+ docker build -f Dockerfile.cpu -t pynode-cpu .
82
+ docker run -p 5000:5000 pynode-cpu
83
+ ```
84
+
85
+ ## Accessing the Application
86
+
87
+ Once running, access the web interface at:
88
+ - http://localhost:5000
89
+
90
+ ## mDNS / Service Discovery
91
+
92
+ ### The Limitation
93
+
94
+ The default `docker-compose.yml` uses Docker's standard bridge network. mDNS
95
+ (multicast DNS) relies on link-local multicast traffic (UDP `224.0.0.251:5353`),
96
+ which the bridge network's NAT does not forward in either direction. With the
97
+ default compose file, PyNode's mDNS nodes can broadcast and listen *inside*
98
+ the container, but that traffic never reaches the LAN — so other machines on
99
+ your network cannot discover the container, and the container cannot discover
100
+ services running elsewhere.
101
+
102
+ ### `HOST_IP`
103
+
104
+ The `HOST_IP` environment variable (see `docker-compose.yml`) only controls
105
+ the IP address **advertised** in mDNS service records - it does *not* make
106
+ multicast traffic reach the LAN. Setting `HOST_IP` without one of the fixes
107
+ below just means the (unreachable) mDNS records will advertise the right
108
+ address instead of the container's internal one; it does not fix discovery.
109
+
110
+ ### Fixing It
111
+
112
+ **Option 1: `network_mode: host` (Linux hosts only, full fix)**
113
+
114
+ On Linux, host networking removes the network isolation between the
115
+ container and the host, so multicast works exactly as it would for a
116
+ process running directly on the host:
117
+
118
+ ```yaml
119
+ services:
120
+ pynode:
121
+ network_mode: host
122
+ # ports: mapping is ignored in host mode - the container shares the
123
+ # host's network stack directly, so remove/comment out `ports:` above.
124
+ ```
125
+
126
+ This does **not** work on Docker Desktop for Windows/macOS - Docker Desktop
127
+ runs containers inside a lightweight VM, and `network_mode: host` there still
128
+ does not bridge multicast across the VM boundary.
129
+
130
+ **Option 2: macvlan network**
131
+
132
+ A macvlan network gives the container its own IP address directly on the LAN
133
+ (as if it were a separate physical device), which also allows multicast to
134
+ work normally. This works on Docker Desktop as well as Linux, but requires
135
+ more setup (a dedicated IP, and typically a way for the host itself to reach
136
+ the container since macvlan interfaces are not reachable from the host by
137
+ default). See the
138
+ [Docker macvlan network documentation](https://docs.docker.com/network/drivers/macvlan/)
139
+ for setup details.
140
+
141
+ **Option 3: mDNS reflector on the host**
142
+
143
+ When host networking isn't available (e.g. Docker Desktop), run an mDNS
144
+ reflector on the host to bridge multicast between the host's network and the
145
+ container's bridge network:
146
+
147
+ - **avahi-daemon** (Linux) - enable reflection in `/etc/avahi/avahi-daemon.conf`:
148
+ ```ini
149
+ [reflector]
150
+ enable-reflector=yes
151
+ ```
152
+ - **mdns-repeater** - run as a sidecar/host process that repeats mDNS packets
153
+ between interfaces.
154
+
155
+ This is the option that works when host networking isn't an option, at the
156
+ cost of an extra moving part to run and maintain.
157
+
158
+ ### Port Matching
159
+
160
+ Whichever option you use, the port PyNode advertises in its mDNS records
161
+ must match the port other machines can actually reach it on (the *published*
162
+ port), not just the container's internal port. If you remap ports (e.g.
163
+ `"8080:5000"`), make sure discovery consumers connect using the published
164
+ port (`8080`), not the internal one.
165
+
166
+ ## Troubleshooting
167
+
168
+ ### GPU Not Detected
169
+
170
+ Verify NVIDIA Container Toolkit is installed:
171
+ ```bash
172
+ docker run --rm --gpus all nvidia/cuda:12.6.0-base-ubuntu22.04 nvidia-smi
173
+ ```
174
+
175
+ ### Permission Issues
176
+
177
+ Ensure the mounted directories have proper permissions:
178
+ ```bash
179
+ mkdir -p workflows logs
180
+ chmod 777 workflows logs
181
+ ```
182
+
183
+ ### Rebuild from Scratch
184
+
185
+ ```bash
186
+ docker-compose down
187
+ docker-compose build --no-cache
188
+ docker-compose up
189
+ ```
@@ -16,6 +16,16 @@ pip install "pynode-flow[full]"
16
16
  Nodes whose optional dependencies are missing are simply skipped at startup, so
17
17
  a core-only install still runs fine.
18
18
 
19
+ > **Warning — conflicting `pynode` package:** an unrelated project on PyPI is
20
+ > named `pynode`, and it also installs a top-level `pynode` package. If it is
21
+ > present in the same environment the two clobber each other (symptoms include
22
+ > `ImportError: cannot import name '__version__' from 'pynode' (unknown
23
+ > location)`). Remove it before or after installing:
24
+ >
25
+ > ```bash
26
+ > pip uninstall pynode
27
+ > ```
28
+
19
29
  ### Optional extras
20
30
 
21
31
  Install only the node groups you need:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pynode-flow
3
- Version: 0.2.1
3
+ Version: 0.2.3
4
4
  Summary: Python based Node-RED-like visual workflow system
5
5
  Author: olkham
6
6
  License-Expression: MIT
@@ -84,6 +84,7 @@ https://github.com/user-attachments/assets/0b53085a-2cc6-4c26-bd43-e0de1e0716a2
84
84
  - **SwitchNode**: Route messages based on conditions
85
85
  - **DelayNode**: Delay message delivery
86
86
  - **GateNode**: Control message flow with real-time toggle
87
+ - **LinkNode**: Pass messages between flows via named channels (Link In / Link Out)
87
88
  - **RateProbeNode**: Monitor message throughput
88
89
  - **QueueLengthProbeNode**: Monitor queue lengths
89
90
  - **CounterNode**: Count messages
@@ -112,6 +113,12 @@ pip install "pynode-flow[full]"
112
113
  pynode
113
114
  ```
114
115
 
116
+ or specify a port:
117
+
118
+ ```bash
119
+ pynode --port 500
120
+ ```
121
+
115
122
  See [INSTALL.md](INSTALL.md) for the full list of extras (`vision`, `mqtt`,
116
123
  `camera`, `inference`, `vlm`, `upload`, `discovery`, `full`) and how to install
117
124
  per-node dependencies with `pynode-install-nodes`.
@@ -187,6 +194,15 @@ python -m pynode
187
194
 
188
195
  Navigate to `http://localhost:5000`
189
196
 
197
+ ### Load an Example Workflow
198
+
199
+ Ten ready-made learning workflows ship with PyNode — from a two-node hello
200
+ world up to a camera → YOLO → filter → MQTT alert pipeline (including a "blur
201
+ every detected person" privacy flow). Load any of them straight from the
202
+ editor: **☰ menu → Examples → pick one**, then press **Deploy**. See
203
+ [pynode/static/examples/README.md](pynode/static/examples/README.md) for the
204
+ guided tour and per-example prerequisites.
205
+
190
206
  ### Data Directory
191
207
 
192
208
  PyNode persists workflows under `<data dir>/workflows/` (`workflow.json` plus timestamped backups in `_backups/`). The data directory is resolved in this order:
@@ -198,6 +214,25 @@ PyNode persists workflows under `<data dir>/workflows/` (`workflow.json` plus ti
198
214
 
199
215
  The resolved location is logged at startup (`Workflow data directory: ...`).
200
216
 
217
+ ### Models Directory
218
+
219
+ Nodes that download or generate model weights (e.g. the YOLO node's `.pt`
220
+ files and exported OpenVINO models) write them into a shared **models
221
+ directory** instead of the process working directory. It is resolved in this
222
+ order:
223
+
224
+ 1. `pynode --models-dir <path>` CLI flag,
225
+ 2. `PYNODE_MODELS_DIR` environment variable,
226
+ 3. `<data dir>/models` otherwise — so `<repo>/models` for a source checkout and
227
+ `~/.pynode/models` for a regular `pip install`.
228
+
229
+ > **Upgrading:** older PyNode versions downloaded model files into whatever the
230
+ > working directory happened to be, so stray `.pt` files and
231
+ > `*_openvino_model/` folders may exist in the repo root, `pynode/models/` or
232
+ > `pynode/nodes/`. PyNode still reads models from those legacy locations, but
233
+ > nothing is migrated automatically — you can move them into the models
234
+ > directory manually when convenient.
235
+
201
236
  ## Securing PyNode
202
237
 
203
238
  **PyNode executes arbitrary Python by design** (e.g. FunctionNode runs whatever code is in the workflow), so anyone who can reach the API can run code on the host. Authentication is the trust boundary — secure the server before exposing it beyond your own machine:
@@ -19,6 +19,7 @@ https://github.com/user-attachments/assets/0b53085a-2cc6-4c26-bd43-e0de1e0716a2
19
19
  - **SwitchNode**: Route messages based on conditions
20
20
  - **DelayNode**: Delay message delivery
21
21
  - **GateNode**: Control message flow with real-time toggle
22
+ - **LinkNode**: Pass messages between flows via named channels (Link In / Link Out)
22
23
  - **RateProbeNode**: Monitor message throughput
23
24
  - **QueueLengthProbeNode**: Monitor queue lengths
24
25
  - **CounterNode**: Count messages
@@ -47,6 +48,12 @@ pip install "pynode-flow[full]"
47
48
  pynode
48
49
  ```
49
50
 
51
+ or specify a port:
52
+
53
+ ```bash
54
+ pynode --port 500
55
+ ```
56
+
50
57
  See [INSTALL.md](INSTALL.md) for the full list of extras (`vision`, `mqtt`,
51
58
  `camera`, `inference`, `vlm`, `upload`, `discovery`, `full`) and how to install
52
59
  per-node dependencies with `pynode-install-nodes`.
@@ -122,6 +129,15 @@ python -m pynode
122
129
 
123
130
  Navigate to `http://localhost:5000`
124
131
 
132
+ ### Load an Example Workflow
133
+
134
+ Ten ready-made learning workflows ship with PyNode — from a two-node hello
135
+ world up to a camera → YOLO → filter → MQTT alert pipeline (including a "blur
136
+ every detected person" privacy flow). Load any of them straight from the
137
+ editor: **☰ menu → Examples → pick one**, then press **Deploy**. See
138
+ [pynode/static/examples/README.md](pynode/static/examples/README.md) for the
139
+ guided tour and per-example prerequisites.
140
+
125
141
  ### Data Directory
126
142
 
127
143
  PyNode persists workflows under `<data dir>/workflows/` (`workflow.json` plus timestamped backups in `_backups/`). The data directory is resolved in this order:
@@ -133,6 +149,25 @@ PyNode persists workflows under `<data dir>/workflows/` (`workflow.json` plus ti
133
149
 
134
150
  The resolved location is logged at startup (`Workflow data directory: ...`).
135
151
 
152
+ ### Models Directory
153
+
154
+ Nodes that download or generate model weights (e.g. the YOLO node's `.pt`
155
+ files and exported OpenVINO models) write them into a shared **models
156
+ directory** instead of the process working directory. It is resolved in this
157
+ order:
158
+
159
+ 1. `pynode --models-dir <path>` CLI flag,
160
+ 2. `PYNODE_MODELS_DIR` environment variable,
161
+ 3. `<data dir>/models` otherwise — so `<repo>/models` for a source checkout and
162
+ `~/.pynode/models` for a regular `pip install`.
163
+
164
+ > **Upgrading:** older PyNode versions downloaded model files into whatever the
165
+ > working directory happened to be, so stray `.pt` files and
166
+ > `*_openvino_model/` folders may exist in the repo root, `pynode/models/` or
167
+ > `pynode/nodes/`. PyNode still reads models from those legacy locations, but
168
+ > nothing is migrated automatically — you can move them into the models
169
+ > directory manually when convenient.
170
+
136
171
  ## Securing PyNode
137
172
 
138
173
  **PyNode executes arbitrary Python by design** (e.g. FunctionNode runs whatever code is in the workflow), so anyone who can reach the API can run code on the host. Authentication is the trust boundary — secure the server before exposing it beyond your own machine:
@@ -5,7 +5,12 @@ services:
5
5
  dockerfile: Dockerfile
6
6
  container_name: pynode
7
7
  ports:
8
- - "500:5000"
8
+ - "5000:5000"
9
+ # Uncomment for mDNS support on Linux hosts (see DOCKER.md > mDNS /
10
+ # Service Discovery). Not effective on Docker Desktop for Windows/macOS.
11
+ # Remove the `ports:` mapping above when enabling this - it is ignored
12
+ # in host networking mode.
13
+ # network_mode: host
9
14
  volumes:
10
15
  # Mount persistent data
11
16
  - ./workflows:/app/workflows
@@ -2,6 +2,11 @@
2
2
 
3
3
  This directory contains example Python scripts that demonstrate how to use PyNode workflows programmatically without the UI.
4
4
 
5
+ > Looking for **GUI-loadable** examples instead? PyNode ships ten example
6
+ > workflows you can load from the editor's **☰ menu → Examples**. Their source
7
+ > and a guided tour live in
8
+ > [pynode/static/examples/](../pynode/static/examples/README.md).
9
+
5
10
  ## Examples
6
11
 
7
12
  ### 1. `camera_workflow.py` - Simple Camera Display