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.
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/.claude/skills/pynode-workflows/SKILL.md +26 -4
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/Dockerfile +4 -2
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/INSTALL.md +1 -1
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/PKG-INFO +3 -2
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/README.md +2 -1
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/_version.py +3 -3
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/api/sse.py +6 -1
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/node_registry.py +1 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/ChangeNode/change_node.py +123 -24
- pynode_flow-0.2.8/pynode/nodes/ClassMapNode/__init__.py +3 -0
- pynode_flow-0.2.8/pynode/nodes/ClassMapNode/class_map_node.py +329 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/DebugNode/debug_node.py +13 -2
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/DrawPredictionsNode/draw_predictions_node.py +18 -7
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/ImageUploadNode/image_upload_node.py +21 -0
- pynode_flow-0.2.8/pynode/nodes/InferenceNode/InferenceEngine/device_detection.py +314 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/InferenceNode/InferenceEngine/engines/onnx_engine.py +21 -1
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/InferenceNode/InferenceEngine/engines/ultralytics_engine.py +16 -8
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/InferenceNode/inference_node.py +45 -12
- pynode_flow-0.2.8/pynode/nodes/InferenceNode/requirements.txt +16 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/Qwen3VLMNode/qwen3_vlm_node.py +13 -0
- pynode_flow-0.2.8/pynode/nodes/Supervision/__init__.py +25 -0
- pynode_flow-0.2.8/pynode/nodes/Supervision/requirements.txt +3 -0
- pynode_flow-0.2.8/pynode/nodes/Supervision/supervision_annotate_node.py +393 -0
- pynode_flow-0.2.8/pynode/nodes/Supervision/supervision_filter_node.py +278 -0
- pynode_flow-0.2.8/pynode/nodes/Supervision/supervision_line_counter_node.py +313 -0
- pynode_flow-0.2.8/pynode/nodes/Supervision/supervision_sink_node.py +223 -0
- pynode_flow-0.2.8/pynode/nodes/Supervision/supervision_smoother_node.py +146 -0
- pynode_flow-0.2.8/pynode/nodes/Supervision/supervision_tracker_node.py +229 -0
- pynode_flow-0.2.8/pynode/nodes/Supervision/supervision_zone_node.py +316 -0
- pynode_flow-0.2.8/pynode/nodes/TemplateNode/requirements.txt +1 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/UltralyticsNode/ultralytics_node.py +18 -7
- pynode_flow-0.2.8/pynode/nodes/detection_utils.py +207 -0
- pynode_flow-0.2.8/pynode/nodes/supervision_utils.py +375 -0
- pynode_flow-0.2.8/pynode/static/examples/18-track-trace.json +87 -0
- pynode_flow-0.2.8/pynode/static/examples/19-line-counter.json +110 -0
- pynode_flow-0.2.8/pynode/static/examples/20-zone-occupancy.json +112 -0
- pynode_flow-0.2.8/pynode/static/examples/21-annotator-gallery.json +170 -0
- pynode_flow-0.2.8/pynode/static/examples/22-heatmap.json +113 -0
- pynode_flow-0.2.8/pynode/static/examples/23-privacy-blur.json +95 -0
- pynode_flow-0.2.8/pynode/static/examples/24-detection-log.json +108 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/examples/README.md +29 -1
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/examples/manifest.json +49 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/js/config.js +1 -0
- pynode_flow-0.2.8/pynode/static/js/geometry-editor.js +236 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/js/main.js +2 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/js/nodes.js +7 -2
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/js/properties.js +56 -4
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/style.css +134 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode_flow.egg-info/PKG-INFO +3 -2
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode_flow.egg-info/SOURCES.txt +31 -3
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode_flow.egg-info/scm_file_list.json +303 -275
- pynode_flow-0.2.8/pynode_flow.egg-info/scm_version.json +8 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pyproject.toml +3 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/setup.bat +55 -27
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/setup.sh +38 -15
- pynode_flow-0.2.8/tests/test_change_node.py +256 -0
- pynode_flow-0.2.8/tests/test_class_map_node.py +250 -0
- pynode_flow-0.2.8/tests/test_debug_json_safety.py +89 -0
- pynode_flow-0.2.8/tests/test_detection_utils.py +268 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_device_detection.py +146 -1
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_image_upload_node.py +28 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_node_types_api.py +1 -0
- pynode_flow-0.2.8/tests/test_supervision_annotate_node.py +259 -0
- pynode_flow-0.2.8/tests/test_supervision_phase3_nodes.py +293 -0
- pynode_flow-0.2.8/tests/test_supervision_tracker_node.py +226 -0
- pynode_flow-0.2.8/tests/test_supervision_utils.py +180 -0
- pynode_flow-0.2.8/tests/test_supervision_zone_nodes.py +323 -0
- pynode_flow-0.2.6/pynode/nodes/InferenceNode/InferenceEngine/device_detection.py +0 -149
- pynode_flow-0.2.6/pynode/nodes/InferenceNode/requirements.txt +0 -12
- pynode_flow-0.2.6/pynode/nodes/TrackerNode/__init__.py +0 -2
- pynode_flow-0.2.6/pynode/nodes/TrackerNode/supervision_tracker_node.py +0 -251
- pynode_flow-0.2.6/pynode/nodes/TrackerNode/tracker_node.py +0 -745
- pynode_flow-0.2.6/pynode_flow.egg-info/scm_version.json +0 -8
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/.dockerignore +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/.github/workflows/ci.yml +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/.github/workflows/publish.yaml +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/.gitignore +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/DOCKER.md +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/Dockerfile.cpu +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/LICENSE +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/MANIFEST.in +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/docker-compose.yml +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/docker-entrypoint.sh +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/docs/CREATING_NODES.md +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/docs/CUSTOM_NODES.md +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/docs/DOCKER.md +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/docs/EXTENSIBILITY.md +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/docs/UI_COMPONENTS.md +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/examples/README.md +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/examples/camera_workflow.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/examples/camera_yolo_workflow.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/install_nodes.bat +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/install_nodes.sh +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/__main__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/_perf.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/api/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/api/helpers.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/api/nodes.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/api/services.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/api/uploads.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/api/workflows.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/config.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/install_nodes.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/main.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/AutoSyncNode/auto_sync_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/BBoxMetricsNode/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/BBoxMetricsNode/bbox_metrics_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/BBoxMetricsNode/requirements.txt +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/BatchNode/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/BatchNode/batch_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/BatchNode/requirements.txt +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/CameraNode/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/CameraNode/camera_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/CameraNode/requirements.txt +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/ChangeNode/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/ChangeNode/requirements.txt +0 -0
- {pynode_flow-0.2.6/pynode/nodes/ConfidenceFilterNode → pynode_flow-0.2.8/pynode/nodes/ClassMapNode}/requirements.txt +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/ConfidenceFilterNode/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/ConfidenceFilterNode/confidence_filter_node.py +0 -0
- {pynode_flow-0.2.6/pynode/nodes/DelayNode → pynode_flow-0.2.8/pynode/nodes/ConfidenceFilterNode}/requirements.txt +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/ControlSliderNode/control_slider_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/CounterNode/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/CounterNode/counter_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/CropNode/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/CropNode/crop_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/CropNode/requirements.txt +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/DebugNode/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/DebugNode/requirements.txt +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/DelayNode/delay_node.py +0 -0
- {pynode_flow-0.2.6/pynode/nodes/ErrorNode → pynode_flow-0.2.8/pynode/nodes/DelayNode}/requirements.txt +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/DrawPredictionsNode/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/DrawPredictionsNode/requirements.txt +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/ErrorNode/error_node.py +0 -0
- {pynode_flow-0.2.6/pynode/nodes/FilterNode → pynode_flow-0.2.8/pynode/nodes/ErrorNode}/requirements.txt +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/FilterNode/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/FilterNode/filter_node.py +0 -0
- {pynode_flow-0.2.6/pynode/nodes/FunctionNode → pynode_flow-0.2.8/pynode/nodes/FilterNode}/requirements.txt +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/FrameSourceNode/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/FrameSourceNode/frame_source_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/FrameSourceNode/requirements.txt +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/FunctionNode/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/FunctionNode/function_node.py +0 -0
- {pynode_flow-0.2.6/pynode/nodes/GateNode → pynode_flow-0.2.8/pynode/nodes/FunctionNode}/requirements.txt +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/GateNode/gate_node.py +0 -0
- {pynode_flow-0.2.6/pynode/nodes/ImageWriterNode → pynode_flow-0.2.8/pynode/nodes/GateNode}/requirements.txt +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/ImageFormatNode/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/ImageFormatNode/image_format_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/ImageFormatNode/requirements.txt +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/ImageViewerNode/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/ImageViewerNode/image_viewer_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/ImageViewerNode/requirements.txt +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/ImageWriterNode/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/ImageWriterNode/imagewriter_node.py +0 -0
- {pynode_flow-0.2.6/pynode/nodes/InjectNode → pynode_flow-0.2.8/pynode/nodes/ImageWriterNode}/requirements.txt +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/InferenceNode/InferenceEngine/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/InferenceNode/InferenceEngine/engines/base_engine.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/InferenceNode/InferenceEngine/engines/example_engine_template.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/InferenceNode/InferenceEngine/engines/pass_engine.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/InferenceNode/InferenceEngine/inference_engine_factory.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/InferenceNode/InferenceEngine/readme.md +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/InferenceNode/InferenceEngine/result_converters.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/InjectNode/inject_node.py +0 -0
- {pynode_flow-0.2.6/pynode/nodes/JoinNode → pynode_flow-0.2.8/pynode/nodes/InjectNode}/requirements.txt +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/JoinNode/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/JoinNode/join_node.py +0 -0
- {pynode_flow-0.2.6/pynode/nodes/LabelFilterNode → pynode_flow-0.2.8/pynode/nodes/JoinNode}/requirements.txt +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/LabelFilterNode/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/LabelFilterNode/label_filter_node.py +0 -0
- {pynode_flow-0.2.6/pynode/nodes/LinkNode → pynode_flow-0.2.8/pynode/nodes/LabelFilterNode}/requirements.txt +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/LinkNode/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/LinkNode/link_bus.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/LinkNode/link_in_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/LinkNode/link_out_node.py +0 -0
- {pynode_flow-0.2.6/pynode/nodes/RangeNode → pynode_flow-0.2.8/pynode/nodes/LinkNode}/requirements.txt +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/MQTTNode/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/MQTTNode/mqtt_in_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/MQTTNode/mqtt_out_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/MQTTNode/mqtt_service.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/MQTTNode/requirements.txt +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/MessageWriterNode/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/MessageWriterNode/messagereader_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/MessageWriterNode/messagewriter_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OmronCameraNode/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OmronCameraNode/omron_camera_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OmronCameraNode/requirements.txt +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/arithmetic_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/bitwise_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/blend_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/blob_detector_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/blur_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/color_space_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/colormap_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/contours_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/denormalize_coords_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/draw_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/edge_detector_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/equirectangular_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/fft_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/focus_estimation_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/frequency_filter_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/histogram_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/hough_circles_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/hough_lines_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/in_range_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/morphology_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/normalize_coords_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/paste_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/perspective_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/realsense_depth_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/requirements.txt +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/resize_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/rotate_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/template_match_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/OpenCV/threshold_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/PointInShapeNode/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/PointInShapeNode/point_in_shape_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/PointInShapeNode/requirements.txt +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/PolygonMetricsNode/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/PolygonMetricsNode/polygon_metrics_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/PolygonMetricsNode/requirements.txt +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/QueueLengthProbeNode/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/QueueLengthProbeNode/queue_length_probe_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/Qwen3VLMNode/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/Qwen3VLMNode/requirements.txt +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/RESTEndpointNode/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/RESTEndpointNode/rest_endpoint_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/RangeNode/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/RangeNode/range_node.py +0 -0
- {pynode_flow-0.2.6/pynode/nodes/RateProbeNode → pynode_flow-0.2.8/pynode/nodes/RangeNode}/requirements.txt +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/RateProbeNode/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/RateProbeNode/rate_probe_node.py +0 -0
- {pynode_flow-0.2.6/pynode/nodes/SplitNode → pynode_flow-0.2.8/pynode/nodes/RateProbeNode}/requirements.txt +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/RoboflowUploadNode/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/RoboflowUploadNode/requirements.txt +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/RoboflowUploadNode/roboflow_upload_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SliceNode/README.md +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SliceNode/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SliceNode/merge_predictions_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SliceNode/requirements.txt +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SliceNode/slice_collector_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SliceNode/slice_image_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SocketNode/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SocketNode/interop/README.md +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SocketNode/interop/nodered-example-flow.json +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SocketNode/ndjson_protocol.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SocketNode/nodered_snippets.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SocketNode/requirements.txt +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SocketNode/tcp_in_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SocketNode/tcp_out_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SocketNode/udp_in_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SocketNode/udp_out_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SocketNode/udp_probe.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SocketNode/udp_protocol.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SplitNode/__init__.py +0 -0
- {pynode_flow-0.2.6/pynode/nodes/SwitchNode → pynode_flow-0.2.8/pynode/nodes/SplitNode}/requirements.txt +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SplitNode/split_node.py +0 -0
- {pynode_flow-0.2.6/pynode/nodes/TemplateNode → pynode_flow-0.2.8/pynode/nodes/SwitchNode}/requirements.txt +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SwitchNode/switch_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SyncNode/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/SyncNode/sync_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/TemplateNode/template_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/UltralyticsNode/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/UltralyticsNode/model_paths.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/UltralyticsNode/requirements.txt +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/UnknownNode/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/VLLMNode/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/VLLMNode/vllm_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/VideoReaderNode/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/VideoReaderNode/video_reader_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/VideoWriterNode/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/VideoWriterNode/video_writer_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/WebhookNode/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/WebhookNode/webhook_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/base_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/image_utils.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/info.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/mDNSNode/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/mDNSNode/mdns_broadcast_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/mDNSNode/mdns_discovery_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/mDNSNode/mdns_manager.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/mDNSNode/requirements.txt +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/messages.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/nodes/pacing.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/server.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/examples/01-hello-world.json +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/examples/02-function-transform.json +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/examples/03-switch-routing.json +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/examples/04-flow-control.json +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/examples/05-webcam-viewer.json +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/examples/06-webcam-yolo.json +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/examples/07-blur-people.json +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/examples/08-video-yolo-recorder.json +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/examples/09-mqtt-pub-sub.json +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/examples/10-person-alert-mqtt.json +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/examples/16-socket-bridge.json +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/examples/17-slider-crop.json +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/images/favicon4.png +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/index.html +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/js/README.md +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/js/auth.js +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/js/clipboard.js +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/js/connections.js +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/js/debug.js +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/js/events.js +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/js/history.js +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/js/minimap.js +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/js/mqtt-services.js +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/js/palette.js +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/js/selection.js +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/js/state.js +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/js/ui-utils.js +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/js/viewport.js +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/js/workflow.js +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/static/js/workflows.js +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/workflow_engine.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode/workflow_manager.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode_flow.egg-info/dependency_links.txt +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode_flow.egg-info/entry_points.txt +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode_flow.egg-info/requires.txt +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/pynode_flow.egg-info/top_level.txt +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/requirements.txt +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/setup.cfg +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/setup.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/__init__.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/conftest.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_api_auth.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_api_errors.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_api_nodes.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_api_security.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_api_workflows.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_base_node_messaging.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_camera_node_backend.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_concurrency.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_config.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_control_slider_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_counter_sse.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_crop_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_debug_workflow_scope.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_delay_rate_limit.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_frame_pacing.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_function_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_link_nodes.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_message_reader_pickle.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_models_dir.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_mqtt_services.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_node_registration.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_persistence.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_range_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_send_copy_semantics.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_serialization.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_socket_nodes.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_video_reader_node.py +0 -0
- {pynode_flow-0.2.6 → pynode_flow-0.2.8}/tests/test_workflow_engine.py +0 -0
- {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`,
|
|
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
|
-
|
|
168
|
-
r = client.post('/api/
|
|
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 =
|
|
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.
|
|
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
|
-
|
|
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,
|
|
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.
|
|
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,
|
|
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,
|
|
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.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 2,
|
|
21
|
+
__version__ = version = '0.2.8'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 2, 8)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
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
|
-
|
|
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'
|
|
@@ -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
|
-
|
|
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
|
-
|
|
219
|
-
|
|
220
|
-
#
|
|
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
|
-
|
|
324
|
+
replacement_value = self._get_nested_value(msg, 'msg.' + str(replace) if not str(replace).startswith('msg.') else replace)
|
|
223
325
|
else:
|
|
224
|
-
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
|
|
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
|
-
|
|
248
|
-
|
|
249
|
-
self.
|
|
250
|
-
|
|
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)}")
|