pynode-flow 0.2.6__tar.gz → 0.2.8__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 (357) hide show
  1. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/.claude/skills/pynode-workflows/SKILL.md +26 -4
  2. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/Dockerfile +4 -2
  3. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/INSTALL.md +1 -1
  4. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/PKG-INFO +3 -2
  5. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/README.md +2 -1
  6. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/_version.py +3 -3
  7. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/api/sse.py +6 -1
  8. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/node_registry.py +1 -0
  9. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/ChangeNode/change_node.py +123 -24
  10. pynode_flow-0.2.8/pynode/nodes/ClassMapNode/__init__.py +3 -0
  11. pynode_flow-0.2.8/pynode/nodes/ClassMapNode/class_map_node.py +329 -0
  12. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/DebugNode/debug_node.py +13 -2
  13. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/DrawPredictionsNode/draw_predictions_node.py +18 -7
  14. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/ImageUploadNode/image_upload_node.py +21 -0
  15. pynode_flow-0.2.8/pynode/nodes/InferenceNode/InferenceEngine/device_detection.py +314 -0
  16. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/InferenceNode/InferenceEngine/engines/onnx_engine.py +21 -1
  17. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/InferenceNode/InferenceEngine/engines/ultralytics_engine.py +16 -8
  18. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/InferenceNode/inference_node.py +45 -12
  19. pynode_flow-0.2.8/pynode/nodes/InferenceNode/requirements.txt +16 -0
  20. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/Qwen3VLMNode/qwen3_vlm_node.py +13 -0
  21. pynode_flow-0.2.8/pynode/nodes/Supervision/__init__.py +25 -0
  22. pynode_flow-0.2.8/pynode/nodes/Supervision/requirements.txt +3 -0
  23. pynode_flow-0.2.8/pynode/nodes/Supervision/supervision_annotate_node.py +393 -0
  24. pynode_flow-0.2.8/pynode/nodes/Supervision/supervision_filter_node.py +278 -0
  25. pynode_flow-0.2.8/pynode/nodes/Supervision/supervision_line_counter_node.py +313 -0
  26. pynode_flow-0.2.8/pynode/nodes/Supervision/supervision_sink_node.py +223 -0
  27. pynode_flow-0.2.8/pynode/nodes/Supervision/supervision_smoother_node.py +146 -0
  28. pynode_flow-0.2.8/pynode/nodes/Supervision/supervision_tracker_node.py +229 -0
  29. pynode_flow-0.2.8/pynode/nodes/Supervision/supervision_zone_node.py +316 -0
  30. pynode_flow-0.2.8/pynode/nodes/TemplateNode/requirements.txt +1 -0
  31. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/UltralyticsNode/ultralytics_node.py +18 -7
  32. pynode_flow-0.2.8/pynode/nodes/detection_utils.py +207 -0
  33. pynode_flow-0.2.8/pynode/nodes/supervision_utils.py +375 -0
  34. pynode_flow-0.2.8/pynode/static/examples/18-track-trace.json +87 -0
  35. pynode_flow-0.2.8/pynode/static/examples/19-line-counter.json +110 -0
  36. pynode_flow-0.2.8/pynode/static/examples/20-zone-occupancy.json +112 -0
  37. pynode_flow-0.2.8/pynode/static/examples/21-annotator-gallery.json +170 -0
  38. pynode_flow-0.2.8/pynode/static/examples/22-heatmap.json +113 -0
  39. pynode_flow-0.2.8/pynode/static/examples/23-privacy-blur.json +95 -0
  40. pynode_flow-0.2.8/pynode/static/examples/24-detection-log.json +108 -0
  41. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/examples/README.md +29 -1
  42. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/examples/manifest.json +49 -0
  43. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/js/config.js +1 -0
  44. pynode_flow-0.2.8/pynode/static/js/geometry-editor.js +236 -0
  45. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/js/main.js +2 -0
  46. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/js/nodes.js +7 -2
  47. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/js/properties.js +56 -4
  48. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/style.css +134 -0
  49. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode_flow.egg-info/PKG-INFO +3 -2
  50. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode_flow.egg-info/SOURCES.txt +31 -3
  51. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode_flow.egg-info/scm_file_list.json +303 -275
  52. pynode_flow-0.2.8/pynode_flow.egg-info/scm_version.json +8 -0
  53. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pyproject.toml +3 -0
  54. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/setup.bat +55 -27
  55. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/setup.sh +38 -15
  56. pynode_flow-0.2.8/tests/test_change_node.py +256 -0
  57. pynode_flow-0.2.8/tests/test_class_map_node.py +250 -0
  58. pynode_flow-0.2.8/tests/test_debug_json_safety.py +89 -0
  59. pynode_flow-0.2.8/tests/test_detection_utils.py +268 -0
  60. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_device_detection.py +146 -1
  61. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_image_upload_node.py +28 -0
  62. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_node_types_api.py +1 -0
  63. pynode_flow-0.2.8/tests/test_supervision_annotate_node.py +259 -0
  64. pynode_flow-0.2.8/tests/test_supervision_phase3_nodes.py +293 -0
  65. pynode_flow-0.2.8/tests/test_supervision_tracker_node.py +226 -0
  66. pynode_flow-0.2.8/tests/test_supervision_utils.py +180 -0
  67. pynode_flow-0.2.8/tests/test_supervision_zone_nodes.py +323 -0
  68. pynode_flow-0.2.6/pynode/nodes/InferenceNode/InferenceEngine/device_detection.py +0 -149
  69. pynode_flow-0.2.6/pynode/nodes/InferenceNode/requirements.txt +0 -12
  70. pynode_flow-0.2.6/pynode/nodes/TrackerNode/__init__.py +0 -2
  71. pynode_flow-0.2.6/pynode/nodes/TrackerNode/supervision_tracker_node.py +0 -251
  72. pynode_flow-0.2.6/pynode/nodes/TrackerNode/tracker_node.py +0 -745
  73. pynode_flow-0.2.6/pynode_flow.egg-info/scm_version.json +0 -8
  74. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/.dockerignore +0 -0
  75. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/.github/workflows/ci.yml +0 -0
  76. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/.github/workflows/publish.yaml +0 -0
  77. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/.gitignore +0 -0
  78. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/DOCKER.md +0 -0
  79. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/Dockerfile.cpu +0 -0
  80. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/LICENSE +0 -0
  81. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/MANIFEST.in +0 -0
  82. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/docker-compose.yml +0 -0
  83. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/docker-entrypoint.sh +0 -0
  84. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/docs/CREATING_NODES.md +0 -0
  85. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/docs/CUSTOM_NODES.md +0 -0
  86. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/docs/DOCKER.md +0 -0
  87. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/docs/EXTENSIBILITY.md +0 -0
  88. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/docs/UI_COMPONENTS.md +0 -0
  89. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/examples/README.md +0 -0
  90. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/examples/camera_workflow.py +0 -0
  91. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/examples/camera_yolo_workflow.py +0 -0
  92. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/install_nodes.bat +0 -0
  93. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/install_nodes.sh +0 -0
  94. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/__init__.py +0 -0
  95. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/__main__.py +0 -0
  96. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/_perf.py +0 -0
  97. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/api/__init__.py +0 -0
  98. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/api/helpers.py +0 -0
  99. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/api/nodes.py +0 -0
  100. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/api/services.py +0 -0
  101. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/api/uploads.py +0 -0
  102. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/api/workflows.py +0 -0
  103. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/config.py +0 -0
  104. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/install_nodes.py +0 -0
  105. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/main.py +0 -0
  106. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/AutoSyncNode/auto_sync_node.py +0 -0
  107. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/BBoxMetricsNode/__init__.py +0 -0
  108. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/BBoxMetricsNode/bbox_metrics_node.py +0 -0
  109. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/BBoxMetricsNode/requirements.txt +0 -0
  110. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/BatchNode/__init__.py +0 -0
  111. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/BatchNode/batch_node.py +0 -0
  112. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/BatchNode/requirements.txt +0 -0
  113. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/CameraNode/__init__.py +0 -0
  114. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/CameraNode/camera_node.py +0 -0
  115. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/CameraNode/requirements.txt +0 -0
  116. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/ChangeNode/__init__.py +0 -0
  117. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/ChangeNode/requirements.txt +0 -0
  118. {pynode_flow-0.2.6/pynode/nodes/ConfidenceFilterNode → pynode_flow-0.2.8/pynode/nodes/ClassMapNode}/requirements.txt +0 -0
  119. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/ConfidenceFilterNode/__init__.py +0 -0
  120. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/ConfidenceFilterNode/confidence_filter_node.py +0 -0
  121. {pynode_flow-0.2.6/pynode/nodes/DelayNode → pynode_flow-0.2.8/pynode/nodes/ConfidenceFilterNode}/requirements.txt +0 -0
  122. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/ControlSliderNode/control_slider_node.py +0 -0
  123. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/CounterNode/__init__.py +0 -0
  124. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/CounterNode/counter_node.py +0 -0
  125. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/CropNode/__init__.py +0 -0
  126. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/CropNode/crop_node.py +0 -0
  127. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/CropNode/requirements.txt +0 -0
  128. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/DebugNode/__init__.py +0 -0
  129. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/DebugNode/requirements.txt +0 -0
  130. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/DelayNode/delay_node.py +0 -0
  131. {pynode_flow-0.2.6/pynode/nodes/ErrorNode → pynode_flow-0.2.8/pynode/nodes/DelayNode}/requirements.txt +0 -0
  132. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/DrawPredictionsNode/__init__.py +0 -0
  133. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/DrawPredictionsNode/requirements.txt +0 -0
  134. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/ErrorNode/error_node.py +0 -0
  135. {pynode_flow-0.2.6/pynode/nodes/FilterNode → pynode_flow-0.2.8/pynode/nodes/ErrorNode}/requirements.txt +0 -0
  136. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/FilterNode/__init__.py +0 -0
  137. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/FilterNode/filter_node.py +0 -0
  138. {pynode_flow-0.2.6/pynode/nodes/FunctionNode → pynode_flow-0.2.8/pynode/nodes/FilterNode}/requirements.txt +0 -0
  139. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/FrameSourceNode/__init__.py +0 -0
  140. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/FrameSourceNode/frame_source_node.py +0 -0
  141. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/FrameSourceNode/requirements.txt +0 -0
  142. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/FunctionNode/__init__.py +0 -0
  143. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/FunctionNode/function_node.py +0 -0
  144. {pynode_flow-0.2.6/pynode/nodes/GateNode → pynode_flow-0.2.8/pynode/nodes/FunctionNode}/requirements.txt +0 -0
  145. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/GateNode/gate_node.py +0 -0
  146. {pynode_flow-0.2.6/pynode/nodes/ImageWriterNode → pynode_flow-0.2.8/pynode/nodes/GateNode}/requirements.txt +0 -0
  147. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/ImageFormatNode/__init__.py +0 -0
  148. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/ImageFormatNode/image_format_node.py +0 -0
  149. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/ImageFormatNode/requirements.txt +0 -0
  150. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/ImageViewerNode/__init__.py +0 -0
  151. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/ImageViewerNode/image_viewer_node.py +0 -0
  152. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/ImageViewerNode/requirements.txt +0 -0
  153. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/ImageWriterNode/__init__.py +0 -0
  154. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/ImageWriterNode/imagewriter_node.py +0 -0
  155. {pynode_flow-0.2.6/pynode/nodes/InjectNode → pynode_flow-0.2.8/pynode/nodes/ImageWriterNode}/requirements.txt +0 -0
  156. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/InferenceNode/InferenceEngine/__init__.py +0 -0
  157. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/InferenceNode/InferenceEngine/engines/base_engine.py +0 -0
  158. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/InferenceNode/InferenceEngine/engines/example_engine_template.py +0 -0
  159. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/InferenceNode/InferenceEngine/engines/pass_engine.py +0 -0
  160. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/InferenceNode/InferenceEngine/inference_engine_factory.py +0 -0
  161. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/InferenceNode/InferenceEngine/readme.md +0 -0
  162. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/InferenceNode/InferenceEngine/result_converters.py +0 -0
  163. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/InjectNode/inject_node.py +0 -0
  164. {pynode_flow-0.2.6/pynode/nodes/JoinNode → pynode_flow-0.2.8/pynode/nodes/InjectNode}/requirements.txt +0 -0
  165. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/JoinNode/__init__.py +0 -0
  166. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/JoinNode/join_node.py +0 -0
  167. {pynode_flow-0.2.6/pynode/nodes/LabelFilterNode → pynode_flow-0.2.8/pynode/nodes/JoinNode}/requirements.txt +0 -0
  168. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/LabelFilterNode/__init__.py +0 -0
  169. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/LabelFilterNode/label_filter_node.py +0 -0
  170. {pynode_flow-0.2.6/pynode/nodes/LinkNode → pynode_flow-0.2.8/pynode/nodes/LabelFilterNode}/requirements.txt +0 -0
  171. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/LinkNode/__init__.py +0 -0
  172. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/LinkNode/link_bus.py +0 -0
  173. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/LinkNode/link_in_node.py +0 -0
  174. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/LinkNode/link_out_node.py +0 -0
  175. {pynode_flow-0.2.6/pynode/nodes/RangeNode → pynode_flow-0.2.8/pynode/nodes/LinkNode}/requirements.txt +0 -0
  176. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/MQTTNode/__init__.py +0 -0
  177. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/MQTTNode/mqtt_in_node.py +0 -0
  178. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/MQTTNode/mqtt_out_node.py +0 -0
  179. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/MQTTNode/mqtt_service.py +0 -0
  180. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/MQTTNode/requirements.txt +0 -0
  181. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/MessageWriterNode/__init__.py +0 -0
  182. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/MessageWriterNode/messagereader_node.py +0 -0
  183. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/MessageWriterNode/messagewriter_node.py +0 -0
  184. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OmronCameraNode/__init__.py +0 -0
  185. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OmronCameraNode/omron_camera_node.py +0 -0
  186. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OmronCameraNode/requirements.txt +0 -0
  187. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/__init__.py +0 -0
  188. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/arithmetic_node.py +0 -0
  189. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/bitwise_node.py +0 -0
  190. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/blend_node.py +0 -0
  191. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/blob_detector_node.py +0 -0
  192. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/blur_node.py +0 -0
  193. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/color_space_node.py +0 -0
  194. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/colormap_node.py +0 -0
  195. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/contours_node.py +0 -0
  196. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/denormalize_coords_node.py +0 -0
  197. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/draw_node.py +0 -0
  198. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/edge_detector_node.py +0 -0
  199. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/equirectangular_node.py +0 -0
  200. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/fft_node.py +0 -0
  201. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/focus_estimation_node.py +0 -0
  202. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/frequency_filter_node.py +0 -0
  203. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/histogram_node.py +0 -0
  204. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/hough_circles_node.py +0 -0
  205. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/hough_lines_node.py +0 -0
  206. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/in_range_node.py +0 -0
  207. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/morphology_node.py +0 -0
  208. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/normalize_coords_node.py +0 -0
  209. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/paste_node.py +0 -0
  210. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/perspective_node.py +0 -0
  211. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/realsense_depth_node.py +0 -0
  212. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/requirements.txt +0 -0
  213. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/resize_node.py +0 -0
  214. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/rotate_node.py +0 -0
  215. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/template_match_node.py +0 -0
  216. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/threshold_node.py +0 -0
  217. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/PointInShapeNode/__init__.py +0 -0
  218. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/PointInShapeNode/point_in_shape_node.py +0 -0
  219. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/PointInShapeNode/requirements.txt +0 -0
  220. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/PolygonMetricsNode/__init__.py +0 -0
  221. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/PolygonMetricsNode/polygon_metrics_node.py +0 -0
  222. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/PolygonMetricsNode/requirements.txt +0 -0
  223. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/QueueLengthProbeNode/__init__.py +0 -0
  224. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/QueueLengthProbeNode/queue_length_probe_node.py +0 -0
  225. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/Qwen3VLMNode/__init__.py +0 -0
  226. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/Qwen3VLMNode/requirements.txt +0 -0
  227. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/RESTEndpointNode/__init__.py +0 -0
  228. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/RESTEndpointNode/rest_endpoint_node.py +0 -0
  229. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/RangeNode/__init__.py +0 -0
  230. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/RangeNode/range_node.py +0 -0
  231. {pynode_flow-0.2.6/pynode/nodes/RateProbeNode → pynode_flow-0.2.8/pynode/nodes/RangeNode}/requirements.txt +0 -0
  232. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/RateProbeNode/__init__.py +0 -0
  233. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/RateProbeNode/rate_probe_node.py +0 -0
  234. {pynode_flow-0.2.6/pynode/nodes/SplitNode → pynode_flow-0.2.8/pynode/nodes/RateProbeNode}/requirements.txt +0 -0
  235. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/RoboflowUploadNode/__init__.py +0 -0
  236. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/RoboflowUploadNode/requirements.txt +0 -0
  237. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/RoboflowUploadNode/roboflow_upload_node.py +0 -0
  238. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SliceNode/README.md +0 -0
  239. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SliceNode/__init__.py +0 -0
  240. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SliceNode/merge_predictions_node.py +0 -0
  241. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SliceNode/requirements.txt +0 -0
  242. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SliceNode/slice_collector_node.py +0 -0
  243. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SliceNode/slice_image_node.py +0 -0
  244. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SocketNode/__init__.py +0 -0
  245. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SocketNode/interop/README.md +0 -0
  246. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SocketNode/interop/nodered-example-flow.json +0 -0
  247. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SocketNode/ndjson_protocol.py +0 -0
  248. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SocketNode/nodered_snippets.py +0 -0
  249. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SocketNode/requirements.txt +0 -0
  250. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SocketNode/tcp_in_node.py +0 -0
  251. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SocketNode/tcp_out_node.py +0 -0
  252. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SocketNode/udp_in_node.py +0 -0
  253. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SocketNode/udp_out_node.py +0 -0
  254. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SocketNode/udp_probe.py +0 -0
  255. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SocketNode/udp_protocol.py +0 -0
  256. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SplitNode/__init__.py +0 -0
  257. {pynode_flow-0.2.6/pynode/nodes/SwitchNode → pynode_flow-0.2.8/pynode/nodes/SplitNode}/requirements.txt +0 -0
  258. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SplitNode/split_node.py +0 -0
  259. {pynode_flow-0.2.6/pynode/nodes/TemplateNode → pynode_flow-0.2.8/pynode/nodes/SwitchNode}/requirements.txt +0 -0
  260. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SwitchNode/switch_node.py +0 -0
  261. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SyncNode/__init__.py +0 -0
  262. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SyncNode/sync_node.py +0 -0
  263. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/TemplateNode/template_node.py +0 -0
  264. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/UltralyticsNode/__init__.py +0 -0
  265. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/UltralyticsNode/model_paths.py +0 -0
  266. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/UltralyticsNode/requirements.txt +0 -0
  267. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/UnknownNode/__init__.py +0 -0
  268. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/VLLMNode/__init__.py +0 -0
  269. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/VLLMNode/vllm_node.py +0 -0
  270. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/VideoReaderNode/__init__.py +0 -0
  271. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/VideoReaderNode/video_reader_node.py +0 -0
  272. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/VideoWriterNode/__init__.py +0 -0
  273. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/VideoWriterNode/video_writer_node.py +0 -0
  274. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/WebhookNode/__init__.py +0 -0
  275. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/WebhookNode/webhook_node.py +0 -0
  276. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/__init__.py +0 -0
  277. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/base_node.py +0 -0
  278. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/image_utils.py +0 -0
  279. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/info.py +0 -0
  280. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/mDNSNode/__init__.py +0 -0
  281. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/mDNSNode/mdns_broadcast_node.py +0 -0
  282. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/mDNSNode/mdns_discovery_node.py +0 -0
  283. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/mDNSNode/mdns_manager.py +0 -0
  284. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/mDNSNode/requirements.txt +0 -0
  285. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/messages.py +0 -0
  286. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/pacing.py +0 -0
  287. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/server.py +0 -0
  288. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/examples/01-hello-world.json +0 -0
  289. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/examples/02-function-transform.json +0 -0
  290. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/examples/03-switch-routing.json +0 -0
  291. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/examples/04-flow-control.json +0 -0
  292. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/examples/05-webcam-viewer.json +0 -0
  293. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/examples/06-webcam-yolo.json +0 -0
  294. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/examples/07-blur-people.json +0 -0
  295. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/examples/08-video-yolo-recorder.json +0 -0
  296. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/examples/09-mqtt-pub-sub.json +0 -0
  297. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/examples/10-person-alert-mqtt.json +0 -0
  298. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/examples/16-socket-bridge.json +0 -0
  299. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/examples/17-slider-crop.json +0 -0
  300. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/images/favicon4.png +0 -0
  301. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/index.html +0 -0
  302. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/js/README.md +0 -0
  303. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/js/auth.js +0 -0
  304. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/js/clipboard.js +0 -0
  305. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/js/connections.js +0 -0
  306. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/js/debug.js +0 -0
  307. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/js/events.js +0 -0
  308. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/js/history.js +0 -0
  309. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/js/minimap.js +0 -0
  310. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/js/mqtt-services.js +0 -0
  311. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/js/palette.js +0 -0
  312. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/js/selection.js +0 -0
  313. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/js/state.js +0 -0
  314. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/js/ui-utils.js +0 -0
  315. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/js/viewport.js +0 -0
  316. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/js/workflow.js +0 -0
  317. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/js/workflows.js +0 -0
  318. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/workflow_engine.py +0 -0
  319. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/workflow_manager.py +0 -0
  320. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode_flow.egg-info/dependency_links.txt +0 -0
  321. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode_flow.egg-info/entry_points.txt +0 -0
  322. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode_flow.egg-info/requires.txt +0 -0
  323. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode_flow.egg-info/top_level.txt +0 -0
  324. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/requirements.txt +0 -0
  325. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/setup.cfg +0 -0
  326. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/setup.py +0 -0
  327. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/__init__.py +0 -0
  328. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/conftest.py +0 -0
  329. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_api_auth.py +0 -0
  330. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_api_errors.py +0 -0
  331. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_api_nodes.py +0 -0
  332. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_api_security.py +0 -0
  333. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_api_workflows.py +0 -0
  334. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_base_node_messaging.py +0 -0
  335. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_camera_node_backend.py +0 -0
  336. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_concurrency.py +0 -0
  337. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_config.py +0 -0
  338. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_control_slider_node.py +0 -0
  339. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_counter_sse.py +0 -0
  340. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_crop_node.py +0 -0
  341. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_debug_workflow_scope.py +0 -0
  342. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_delay_rate_limit.py +0 -0
  343. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_frame_pacing.py +0 -0
  344. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_function_node.py +0 -0
  345. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_link_nodes.py +0 -0
  346. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_message_reader_pickle.py +0 -0
  347. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_models_dir.py +0 -0
  348. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_mqtt_services.py +0 -0
  349. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_node_registration.py +0 -0
  350. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_persistence.py +0 -0
  351. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_range_node.py +0 -0
  352. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_send_copy_semantics.py +0 -0
  353. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_serialization.py +0 -0
  354. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_socket_nodes.py +0 -0
  355. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_video_reader_node.py +0 -0
  356. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_workflow_engine.py +0 -0
  357. {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_workflow_roundtrip.py +0 -0
@@ -84,11 +84,30 @@ Messages are dicts in Node-RED style: `payload`, `topic`, `_msgid`, plus extras.
84
84
  | `ImageViewerNode` | 1→0 | `width`: 320; `height`: 240; `image_path`: "payload.image"; `drop_messages`: true |
85
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
86
 
87
- Other vision nodes: `DrawPredictionsNode`, `CropNode`, `TrackerNode`,
87
+ Other vision nodes: `DrawPredictionsNode`, `CropNode`,
88
88
  `ImageFormatNode`, `SliceNode`, `BBoxMetricsNode`, `PolygonMetricsNode`,
89
89
  `InferenceNode` (multi-backend), `FrameSourceNode` (multi-source camera lib) —
90
90
  read their `DEFAULT_CONFIG`/`properties` in `pynode/nodes/<Name>/` before use.
91
91
 
92
+ ### Supervision
93
+ Nodes built on the `supervision` library (`[vision]` extra). They register even
94
+ without it installed, reporting an error and passing messages through.
95
+
96
+ | Type | Ports | Config |
97
+ |---|---|---|
98
+ | `SupervisionTrackerNode` | 1→1 | `track_thresh`: "0.25"; `track_buffer`: "30" (frames a lost track is kept, scaled by `frame_rate`); `match_thresh`: "0.8" (IoU); `min_consecutive_frames`: "1" (raise to 2-3 to kill ghost tracks); `frame_rate`: "30" (**set to the real source fps**). Adds `track_id` to each detection plus `payload.tracks`/`track_count`. Has a `reset` action — a new object needs 2 frames before it is reported. **Does not draw** — wire an `SupervisionAnnotateNode` after it. |
99
+ | `SupervisionAnnotateNode` | 1→1 | `annotators`: list from `box round_box box_corner ellipse circle triangle dot color label percentage_bar blur pixelate background_overlay trace heatmap` (applied in a fixed order: pixel effects → shapes → labels; default `["box","label"]`); `color_lookup`: `class`\|`track`\|`index` (`track` falls back to `class` without ids); `thickness`/`text_scale`: "auto" (resolution-scaled) or a number; `show_class`/`show_confidence`/`show_track_id`: bool (label content); `trace_length`: "30". `trace` needs a `SupervisionTrackerNode` upstream; `heatmap` accumulates over time. `reset` action clears trace/heatmap state. |
100
+ | `SupervisionLineCounterNode` | 1→**2** | `line`: "x1,y1,x2,y2" (image px; the GUI has a draw-on-frame editor); `anchors`: `corners`\|`center`\|`bottom_center`; `min_crossing_frames`: "1"; `draw`: "true"/"false". **Needs tracked detections** (SV Tracker upstream). Output 0 = every msg + `payload.line_in`/`line_out`/`line_in_by_class`/`line_out_by_class` and the line drawn; **output 1 = one msg per crossing** `{event:'line_crossing', direction, track_id, class_id, class_name, bbox, in_count, out_count}`. Actions: `reset` (zero counts), `set_geometry` (live line update, resets counts). |
101
+ | `SupervisionZoneNode` | 1→**2** | `polygon`: JSON "[[x,y],...]" (image px; draw-on-frame editor in GUI); `anchor`: `bottom_center`\|`center`\|`top_center`; `filter_to_zone`: bool (drop outside detections); `draw`: "true"/"false"; `opacity`: "0.15". Output 0 = every msg + `payload.zone_count` and `in_zone` on each detection; **output 1 = enter/exit events** (needs track ids) `{event:'enter'\|'exit', track_id, class_id, class_name, bbox, zone_count}`. Actions: `reset` (event state), `set_geometry` (live polygon update). |
102
+ | `SupervisionSmootherNode` | 1→1 | `length`: "5" (frames averaged per box). Smooths bbox jitter; **needs track ids** (SV Tracker upstream) — place between tracker and annotate/zones. Untracked input passes through with one warning. `reset` action clears history. |
103
+ | `SupervisionFilterNode` | 1→**2** | All criteria optional (empty = off): `min_confidence`/`max_confidence`; `allow_classes`/`deny_classes` (comma names or numeric ids); `min_area`/`max_area` (px²); `min_aspect`/`max_aspect` (w/h); `top_k`; `dedupe`: `off`\|`nms`\|`nmm` + `dedupe_threshold`: "0.5" + `overlap_metric`: `iou`\|`ios` + `class_agnostic`: bool. Output 0 = kept, output 1 = rejected by criteria (both always fire; NMS/NMM-suppressed are dropped, not rejected). |
104
+ | `SupervisionSinkNode` | 1→1 | `format`: `csv` (row per detection, safe for long runs)\|`json` (in-memory, written on stop/rotate); `path`: "./output"; `filename`: base → `<base>_<YYYYmmdd-HHMMSS>.<ext>`. Row = xyxy, class_id, confidence, tracker_id, class_name, frame, timestamp. Empty frames advance the frame counter but write no rows; the file opens on the first detection. Passes msgs through with `payload.sink_file`/`sink_rows`. `new_file` action rotates. |
105
+
106
+ Detections travel as `payload.detections` (list of dicts, read by every core
107
+ node) and, alongside it, `payload.sv` — the live `sv.Detections` object, which
108
+ also carries masks and tracker ids. Use `pynode/nodes/supervision_utils.py`
109
+ (`to_sv` / `write_back`) rather than converting by hand.
110
+
92
111
  ### Network
93
112
  | Type | Ports | Config |
94
113
  |---|---|---|
@@ -164,12 +183,15 @@ app = create_app({'WORKFLOWS_DIR': os.path.join(td, 'wf'),
164
183
  'WORKFLOW_FILE': os.path.join(td, 'wf', 'workflow.json'),
165
184
  'UPLOAD_BASE_DIR': os.path.join(td, 'up'), 'TESTING': True})
166
185
  client = app.test_client()
167
- data = json.load(open('pynode/static/examples/01-hello-world.json'))
168
- r = client.post('/api/workflow', json=data)
186
+ # A fresh app has NO workflows - create one first, then import into it.
187
+ r = client.post('/api/workflows', json={'name': 'validate'})
188
+ wf_id = r.get_json()['id']
189
+ data = json.load(open('pynode/static/examples/01-hello-world.json', encoding='utf-8'))
190
+ r = client.post(f'/api/workflow?workflow={wf_id}', json=data)
169
191
  assert r.status_code == 201, r.get_json()
170
192
  manager = app.extensions['workflow_manager']
171
193
  with manager.state_lock:
172
- eng = next(iter(manager.working_engines.values()))
194
+ eng = manager.working_engines[wf_id]
173
195
  unknown = [n.type for n in eng.nodes.values() if getattr(n, '_is_unknown_node', False)]
174
196
  assert not unknown, f"unknown node types: {unknown}"
175
197
  manager.shutdown()
@@ -1,9 +1,11 @@
1
- FROM docker.io/nvidia/cuda:12.6.0-runtime-ubuntu22.04
1
+ FROM docker.io/nvidia/cuda:12.8.1-runtime-ubuntu22.04
2
2
 
3
3
  # Set environment variables
4
4
  ENV DEBIAN_FRONTEND=noninteractive
5
5
  ENV PYTHONUNBUFFERED=1
6
- ENV CUDA_VERSION=12.6
6
+ # cu128 PyTorch wheels are the first to ship Blackwell (sm_120, e.g. RTX 50xx)
7
+ # kernels; older cu126 builds raise "no kernel image is available".
8
+ ENV CUDA_VERSION=12.8
7
9
 
8
10
  # Install system dependencies
9
11
  RUN apt-get update && apt-get install -y \
@@ -32,7 +32,7 @@ Install only the node groups you need:
32
32
 
33
33
  | Extra | Installs | Nodes it enables |
34
34
  |-------|----------|------------------|
35
- | `vision` | ultralytics, torch, torchvision, supervision | UltralyticsNode, TrackerNode, DrawPredictionsNode, … |
35
+ | `vision` | ultralytics, torch, torchvision, supervision | UltralyticsNode, DrawPredictionsNode, the Supervision nodes, … |
36
36
  | `mqtt` | paho-mqtt | MQTTNode |
37
37
  | `camera` | framesource[full] | FrameSourceNode |
38
38
  | `inference` | onnxruntime (+ `vision`) | InferenceNode |
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pynode-flow
3
- Version: 0.2.6
3
+ Version: 0.2.8
4
4
  Summary: Python based Node-RED-like visual workflow system
5
5
  Author: olkham
6
6
  License-Expression: MIT
@@ -94,7 +94,8 @@ carries its own documentation in the editor's **Information** panel (ℹ️ tab)
94
94
  | **Output** | Image Viewer, Image Writer, Video Writer, Message Writer, Roboflow Upload |
95
95
  | **Logic** | Switch (route on conditions), Gate, Sync, Auto Sync |
96
96
  | **Network** | MQTT In / Out, UDP In / Out, TCP In / Out, REST Endpoint, Webhook, mDNS Broadcast / Discovery |
97
- | **Vision** | YOLO, Inference, Tracker, Supervision Tracker, Crop, Slice Image / Slice Collector, Draw Predictions, Confidence Filter, Label Filter, Merge Predictions, Image Format, Qwen3-VL, vLLM |
97
+ | **Vision** | YOLO, Inference, Crop, Slice Image / Slice Collector, Draw Predictions, Class Map (relabel classes), Confidence Filter, Label Filter, Merge Predictions, Image Format, Qwen3-VL, vLLM |
98
+ | **Supervision** | SV Tracker (ByteTrack), SV Annotate (15 visual styles: boxes, labels, traces, heat maps, privacy blur/pixelate), SV Line Counter (in/out crossing counts + events), SV Polygon Zone (occupancy, enter/exit events) — zone geometry drawn directly on a live frame — SV Smoother (de-jitter boxes), SV Detection Filter (confidence/class/area/aspect/top-K + NMS/NMM), SV Sink (CSV/JSON detection logging) |
98
99
  | **Analysis** | BBox Metrics, Polygon Metrics, Point in Shape |
99
100
  | **OpenCV** | ~28 image-processing nodes: Blur, Threshold, Morphology, Edge Detector, Find Contours, Perspective, Resize, Rotate, Colormap, Histogram, FFT, Template Match, RealSense Depth, and more |
100
101
  | **Node Probes** | Rate Probe, Queue Length Probe, Counter |
@@ -29,7 +29,8 @@ carries its own documentation in the editor's **Information** panel (ℹ️ tab)
29
29
  | **Output** | Image Viewer, Image Writer, Video Writer, Message Writer, Roboflow Upload |
30
30
  | **Logic** | Switch (route on conditions), Gate, Sync, Auto Sync |
31
31
  | **Network** | MQTT In / Out, UDP In / Out, TCP In / Out, REST Endpoint, Webhook, mDNS Broadcast / Discovery |
32
- | **Vision** | YOLO, Inference, Tracker, Supervision Tracker, Crop, Slice Image / Slice Collector, Draw Predictions, Confidence Filter, Label Filter, Merge Predictions, Image Format, Qwen3-VL, vLLM |
32
+ | **Vision** | YOLO, Inference, Crop, Slice Image / Slice Collector, Draw Predictions, Class Map (relabel classes), Confidence Filter, Label Filter, Merge Predictions, Image Format, Qwen3-VL, vLLM |
33
+ | **Supervision** | SV Tracker (ByteTrack), SV Annotate (15 visual styles: boxes, labels, traces, heat maps, privacy blur/pixelate), SV Line Counter (in/out crossing counts + events), SV Polygon Zone (occupancy, enter/exit events) — zone geometry drawn directly on a live frame — SV Smoother (de-jitter boxes), SV Detection Filter (confidence/class/area/aspect/top-K + NMS/NMM), SV Sink (CSV/JSON detection logging) |
33
34
  | **Analysis** | BBox Metrics, Polygon Metrics, Point in Shape |
34
35
  | **OpenCV** | ~28 image-processing nodes: Blur, Threshold, Morphology, Edge Detector, Find Contours, Perspective, Resize, Rotate, Colormap, Histogram, FFT, Template Match, RealSense Depth, and more |
35
36
  | **Node Probes** | Rate Probe, Queue Length Probe, Counter |
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.2.6'
22
- __version_tuple__ = version_tuple = (0, 2, 6)
21
+ __version__ = version = '0.2.8'
22
+ __version_tuple__ = version_tuple = (0, 2, 8)
23
23
 
24
- __commit_id__ = commit_id = 'gfd6285bef'
24
+ __commit_id__ = commit_id = 'g7b8eff347'
@@ -40,7 +40,12 @@ def debug_stream():
40
40
  try:
41
41
  # Wait for data from broadcast thread
42
42
  data = q.get(timeout=1.0)
43
- yield f'data: {json.dumps(data)}\n\n'
43
+ # default=str: one non-JSON-serializable value (a numpy
44
+ # scalar, a live supervision object, ...) must degrade to
45
+ # its repr, not raise - a raise here closes the stream
46
+ # that carries ALL live UI updates (debug AND viewer
47
+ # frames) for this client.
48
+ yield f'data: {json.dumps(data, default=str)}\n\n'
44
49
  except queue.Empty:
45
50
  # Send keepalive
46
51
  yield 'data: {"type": "keepalive"}\n\n'
@@ -47,6 +47,7 @@ def build_node_types_cache():
47
47
  'logic',
48
48
  'network',
49
49
  'vision',
50
+ 'supervision',
50
51
  'analysis',
51
52
  'node probes',
52
53
  'opencv'
@@ -5,7 +5,7 @@ Similar to Node-RED's change node.
5
5
 
6
6
  import json
7
7
  import re
8
- from typing import Any, Dict
8
+ from typing import Any, Dict, List, Optional, Tuple
9
9
  from pynode.nodes.base_node import BaseNode, Info, MessageKeys
10
10
 
11
11
  _info = Info()
@@ -25,6 +25,18 @@ _info.add_bullets(
25
25
  ("Delete:", "Remove a property from the message."),
26
26
  ("Move:", "Move a property to a different location."),
27
27
  )
28
+ _info.add_header("Lists")
29
+ _info.add_text(
30
+ "Tick 'is list' on a rule when the property holds a list of objects, and "
31
+ "give the key to work on. The rule then applies to that key in every item, "
32
+ "however many there are - for example relabelling each detection:"
33
+ )
34
+ _info.add_code("in msg.payload.detections - is list, key class_name - search 2, replace drone").end()
35
+ _info.add_text(
36
+ "Search-and-replace works on substrings of text values. A value that is "
37
+ "not text (a number, a boolean) is replaced only when the search matches "
38
+ "the whole value, and the replacement keeps the type you selected."
39
+ )
28
40
 
29
41
 
30
42
  class ChangeNode(BaseNode):
@@ -52,6 +64,9 @@ class ChangeNode(BaseNode):
52
64
  ui_component = 'change-rules-editor'
53
65
  ui_component_config = {
54
66
  'operations': ['set', 'change', 'move', 'delete'],
67
+ # Per-rule list mode: apply the rule to `key` inside every item of the
68
+ # list at the rule's path (see _rule_targets).
69
+ 'list_fields': ['isList', 'key'],
55
70
  'value_types': [
56
71
  {'value': 'str', 'label': 'String'},
57
72
  {'value': 'num', 'label': 'Number'},
@@ -131,6 +146,87 @@ class ChangeNode(BaseNode):
131
146
 
132
147
  return False
133
148
 
149
+ def _rule_targets(self, msg: Dict, rule: Dict, path: str) -> List[Tuple[Optional[Dict], str]]:
150
+ """Work out the places a rule applies to.
151
+
152
+ Normally that is one place: the property path itself. With ``isList``
153
+ set, the path must hold a list, and the rule applies to ``key`` inside
154
+ every object in it - the way to rewrite a field on all detections
155
+ without knowing how many there are.
156
+
157
+ Args:
158
+ msg: The message being modified.
159
+ rule: The rule definition.
160
+ path: The rule's property path.
161
+
162
+ Returns:
163
+ ``(container, key)`` pairs. ``container`` is None for a plain
164
+ property path, in which case ``key`` is that path.
165
+ """
166
+ if not rule.get('isList', rule.get('is_list', False)):
167
+ return [(None, path)]
168
+
169
+ key = str(rule.get('key', rule.get('itemKey', '')) or '').strip()
170
+ if not key:
171
+ self.report_error(f"List rule on {path} needs an item key")
172
+ return []
173
+
174
+ items = self._get_nested_value(msg, path)
175
+ if not isinstance(items, (list, tuple)):
176
+ self.report_error(f"List rule on {path}: not a list")
177
+ return []
178
+
179
+ return [(item, key) for item in items if isinstance(item, dict)]
180
+
181
+ def _target_get(self, msg: Dict, target: Tuple[Optional[Dict], str]) -> Any:
182
+ """Read a target - a message path, or a key inside a list item."""
183
+ container, key = target
184
+ if container is None:
185
+ return self._get_nested_value(msg, key)
186
+ return container.get(key)
187
+
188
+ def _target_set(self, msg: Dict, target: Tuple[Optional[Dict], str], value: Any) -> None:
189
+ """Write a target."""
190
+ container, key = target
191
+ if container is None:
192
+ self._set_nested_value(msg, key, value)
193
+ else:
194
+ container[key] = value
195
+
196
+ def _target_delete(self, msg: Dict, target: Tuple[Optional[Dict], str]) -> bool:
197
+ """Remove a target, reporting whether anything was there."""
198
+ container, key = target
199
+ if container is None:
200
+ return self._delete_nested_value(msg, key)
201
+ if key in container:
202
+ del container[key]
203
+ return True
204
+ return False
205
+
206
+ def _apply_change(self, msg: Dict, target: Tuple[Optional[Dict], str],
207
+ search: Any, search_type: str,
208
+ replacement_text: str, replacement_value: Any) -> None:
209
+ """Search and replace inside one target.
210
+
211
+ Text is replaced by substring (or regex). Anything else - a number, a
212
+ boolean - has no substring to work on, so it is replaced only when the
213
+ search matches the whole value, and it takes the typed replacement.
214
+ """
215
+ current = self._target_get(msg, target)
216
+ if current is None:
217
+ return
218
+
219
+ if isinstance(current, str):
220
+ if search_type == 'regex':
221
+ try:
222
+ self._target_set(msg, target, re.sub(str(search), replacement_text, current))
223
+ except re.error:
224
+ self.report_error(f"Invalid regex pattern: {search}")
225
+ else:
226
+ self._target_set(msg, target, current.replace(str(search), replacement_text))
227
+ elif not isinstance(current, (dict, list, tuple)) and str(current) == str(search):
228
+ self._target_set(msg, target, replacement_value)
229
+
134
230
  def _resolve_value(self, msg: Dict, value: Any, value_type: str) -> Any:
135
231
  """
136
232
  Resolve a value based on its type.
@@ -189,6 +285,11 @@ class ChangeNode(BaseNode):
189
285
  property_path = rule.get('path', rule.get('p', rule.get('property', f'msg.{MessageKeys.PAYLOAD}')))
190
286
 
191
287
  try:
288
+ # One target for a plain path; one per list item in list mode.
289
+ targets = self._rule_targets(msg, rule, property_path)
290
+ if not targets:
291
+ continue
292
+
192
293
  if rule_type == 'set':
193
294
  # Set property to a value
194
295
  value = rule.get('value', rule.get('to', ''))
@@ -206,7 +307,8 @@ class ChangeNode(BaseNode):
206
307
  else:
207
308
  resolved_value = self._resolve_value(msg, value, value_type)
208
309
 
209
- self._set_nested_value(msg, property_path, resolved_value)
310
+ for target in targets:
311
+ self._target_set(msg, target, resolved_value)
210
312
 
211
313
  elif rule_type == 'change':
212
314
  # Search and replace within a property
@@ -215,39 +317,36 @@ class ChangeNode(BaseNode):
215
317
  replace = rule.get('replace', rule.get('to', ''))
216
318
  replace_type = rule.get('replaceType', rule.get('tot', 'str'))
217
319
 
218
- current_value = self._get_nested_value(msg, property_path)
219
-
220
- # Resolve replace value
320
+ # Resolve the replacement twice over: the text form drives
321
+ # substring and regex replacement, the typed form replaces a
322
+ # whole non-text value (see _apply_change).
221
323
  if replace_type == 'path':
222
- replace_value = self._get_nested_value(msg, 'msg.' + str(replace) if not str(replace).startswith('msg.') else replace)
324
+ replacement_value = self._get_nested_value(msg, 'msg.' + str(replace) if not str(replace).startswith('msg.') else replace)
223
325
  else:
224
- replace_value = str(replace)
326
+ replacement_value = self._resolve_value(msg, replace, replace_type)
327
+ replacement_text = str(replacement_value) if replacement_value is not None else ''
225
328
 
226
- if current_value is not None:
227
- if isinstance(current_value, str):
228
- if search_type == 'regex':
229
- try:
230
- new_value = re.sub(search, str(replace_value) if replace_value else '', current_value)
231
- self._set_nested_value(msg, property_path, new_value)
232
- except re.error as e:
233
- self.report_error(f"Invalid regex pattern: {search}")
234
- else:
235
- new_value = current_value.replace(str(search), str(replace_value) if replace_value else '')
236
- self._set_nested_value(msg, property_path, new_value)
329
+ for target in targets:
330
+ self._apply_change(msg, target, search, search_type,
331
+ replacement_text, replacement_value)
237
332
 
238
333
  elif rule_type == 'delete':
239
334
  # Delete a property
240
- self._delete_nested_value(msg, property_path)
335
+ for target in targets:
336
+ self._target_delete(msg, target)
241
337
 
242
338
  elif rule_type == 'move':
243
- # Move property to another location
339
+ # Move property to another location. In list mode the
340
+ # destination is a key inside the same item.
244
341
  to_path = rule.get('toPath', rule.get('to', rule.get('toProperty', '')))
245
342
 
246
343
  if to_path:
247
- current_value = self._get_nested_value(msg, property_path)
248
- if current_value is not None:
249
- self._set_nested_value(msg, to_path, current_value)
250
- self._delete_nested_value(msg, property_path)
344
+ for target in targets:
345
+ container, _ = target
346
+ current_value = self._target_get(msg, target)
347
+ if current_value is not None:
348
+ self._target_set(msg, (container, to_path), current_value)
349
+ self._target_delete(msg, target)
251
350
 
252
351
  except Exception as e:
253
352
  self.report_error(f"Error applying rule {rule_type} on {property_path}: {str(e)}")
@@ -0,0 +1,3 @@
1
+ from ..ClassMapNode.class_map_node import ClassMapNode
2
+
3
+ __all__ = ['ClassMapNode']