pynode-flow 0.2.8__tar.gz → 0.2.9__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.8 → pynode_flow-0.2.9}/.claude/skills/pynode-workflows/SKILL.md +4 -3
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/.dockerignore +3 -0
- pynode_flow-0.2.9/.github/workflows/ci.yml +90 -0
- pynode_flow-0.2.9/.github/workflows/git-hooks.yml +28 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/.gitignore +3 -0
- pynode_flow-0.2.9/.pre-commit-config.yml +9 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/DOCKER.md +3 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/Dockerfile +1 -1
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/Dockerfile.cpu +1 -1
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/INSTALL.md +10 -1
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/PKG-INFO +30 -4
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/README.md +22 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/docker-compose.yml +8 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/docker-entrypoint.sh +1 -1
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/docs/CREATING_NODES.md +41 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/docs/EXTENSIBILITY.md +25 -22
- pynode_flow-0.2.9/docs/video_thumb_play.png +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/examples/camera_workflow.py +2 -4
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/examples/camera_yolo_workflow.py +2 -4
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/_version.py +3 -3
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/api/__init__.py +4 -1
- pynode_flow-0.2.9/pynode/api/credentials.py +104 -0
- pynode_flow-0.2.9/pynode/api/health.py +68 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/api/services.py +16 -5
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/config.py +8 -0
- pynode_flow-0.2.9/pynode/credential_store.py +475 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/main.py +113 -17
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/AutoSyncNode/auto_sync_node.py +5 -5
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/BBoxMetricsNode/bbox_metrics_node.py +2 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/BatchNode/batch_node.py +2 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/CameraNode/camera_node.py +3 -4
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/ChangeNode/__init__.py +2 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/ChangeNode/change_node.py +9 -9
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/ClassMapNode/class_map_node.py +14 -14
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/ConfidenceFilterNode/confidence_filter_node.py +2 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/ControlSliderNode/control_slider_node.py +2 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/CounterNode/counter_node.py +2 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/CropNode/crop_node.py +3 -5
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/DebugNode/debug_node.py +2 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/DelayNode/delay_node.py +6 -6
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/DrawPredictionsNode/draw_predictions_node.py +5 -5
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/ErrorNode/error_node.py +3 -3
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/FilterNode/filter_node.py +2 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/FrameSourceNode/frame_source_node.py +2 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/FunctionNode/function_node.py +3 -3
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/GateNode/gate_node.py +3 -3
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/ImageFormatNode/image_format_node.py +4 -4
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/ImageUploadNode/image_upload_node.py +1 -2
- pynode_flow-0.2.9/pynode/nodes/ImageViewerNode/image_viewer_node.py +271 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/ImageWriterNode/imagewriter_node.py +3 -4
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/InferenceNode/InferenceEngine/__init__.py +5 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/InferenceNode/InferenceEngine/device_detection.py +13 -14
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/InferenceNode/InferenceEngine/engines/base_engine.py +4 -4
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/InferenceNode/InferenceEngine/engines/example_engine_template.py +1 -1
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/InferenceNode/InferenceEngine/engines/onnx_engine.py +11 -11
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/InferenceNode/InferenceEngine/engines/pass_engine.py +7 -7
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/InferenceNode/InferenceEngine/engines/ultralytics_engine.py +2 -3
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/InferenceNode/InferenceEngine/result_converters.py +2 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/InferenceNode/inference_node.py +7 -11
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/JoinNode/join_node.py +2 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/LabelFilterNode/label_filter_node.py +3 -3
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/LinkNode/link_in_node.py +4 -4
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/LinkNode/link_out_node.py +2 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/MQTTNode/mqtt_in_node.py +3 -4
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/MQTTNode/mqtt_out_node.py +3 -3
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/MQTTNode/mqtt_service.py +247 -91
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/MessageWriterNode/messagereader_node.py +11 -12
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/MessageWriterNode/messagewriter_node.py +5 -5
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/OmronCameraNode/omron_camera_node.py +5 -6
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/OpenCV/arithmetic_node.py +8 -8
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/OpenCV/bitwise_node.py +2 -3
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/OpenCV/blend_node.py +2 -3
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/OpenCV/blob_detector_node.py +2 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/OpenCV/blur_node.py +2 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/OpenCV/color_space_node.py +2 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/OpenCV/colormap_node.py +2 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/OpenCV/contours_node.py +2 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/OpenCV/denormalize_coords_node.py +5 -5
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/OpenCV/draw_node.py +2 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/OpenCV/edge_detector_node.py +2 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/OpenCV/equirectangular_node.py +4 -4
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/OpenCV/fft_node.py +2 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/OpenCV/focus_estimation_node.py +2 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/OpenCV/frequency_filter_node.py +2 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/OpenCV/histogram_node.py +2 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/OpenCV/hough_circles_node.py +2 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/OpenCV/hough_lines_node.py +2 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/OpenCV/in_range_node.py +2 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/OpenCV/morphology_node.py +2 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/OpenCV/normalize_coords_node.py +5 -5
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/OpenCV/paste_node.py +7 -7
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/OpenCV/perspective_node.py +2 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/OpenCV/realsense_depth_node.py +2 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/OpenCV/resize_node.py +2 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/OpenCV/rotate_node.py +2 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/OpenCV/template_match_node.py +2 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/OpenCV/threshold_node.py +2 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/PointInShapeNode/point_in_shape_node.py +2 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/PolygonMetricsNode/polygon_metrics_node.py +2 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/QueueLengthProbeNode/queue_length_probe_node.py +3 -3
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/Qwen3VLMNode/qwen3_vlm_node.py +35 -18
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/RESTEndpointNode/rest_endpoint_node.py +84 -38
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/RangeNode/range_node.py +2 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/RateProbeNode/rate_probe_node.py +6 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/RoboflowUploadNode/roboflow_upload_node.py +42 -14
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/SliceNode/merge_predictions_node.py +13 -14
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/SliceNode/slice_collector_node.py +16 -16
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/SliceNode/slice_image_node.py +9 -9
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/SocketNode/ndjson_protocol.py +4 -4
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/SocketNode/nodered_snippets.py +5 -5
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/SocketNode/tcp_in_node.py +3 -4
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/SocketNode/tcp_out_node.py +2 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/SocketNode/udp_in_node.py +4 -4
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/SocketNode/udp_out_node.py +2 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/SocketNode/udp_protocol.py +15 -15
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/SplitNode/split_node.py +3 -3
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/Supervision/__init__.py +5 -0
- pynode_flow-0.2.9/pynode/nodes/Supervision/reid_gallery.py +227 -0
- pynode_flow-0.2.9/pynode/nodes/Supervision/requirements.txt +6 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/Supervision/supervision_annotate_node.py +17 -8
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/Supervision/supervision_filter_node.py +3 -3
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/Supervision/supervision_line_counter_node.py +13 -5
- pynode_flow-0.2.9/pynode/nodes/Supervision/supervision_reid_node.py +578 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/Supervision/supervision_sink_node.py +2 -3
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/Supervision/supervision_smoother_node.py +2 -0
- pynode_flow-0.2.9/pynode/nodes/Supervision/supervision_tracker_node.py +487 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/Supervision/supervision_zone_node.py +11 -8
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/SwitchNode/switch_node.py +4 -4
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/SyncNode/sync_node.py +7 -7
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/TemplateNode/template_node.py +2 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/UltralyticsNode/ultralytics_node.py +4 -4
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/VLLMNode/vllm_node.py +9 -9
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/VideoReaderNode/video_reader_node.py +8 -8
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/WebhookNode/webhook_node.py +94 -22
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/base_node.py +217 -67
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/detection_utils.py +9 -8
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/image_utils.py +9 -8
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/info.py +3 -4
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/mDNSNode/mdns_broadcast_node.py +0 -1
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/mDNSNode/mdns_manager.py +3 -4
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/messages.py +2 -2
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/pacing.py +3 -3
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/supervision_utils.py +142 -25
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/server.py +98 -13
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/examples/18-track-trace.json +2 -1
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/examples/19-line-counter.json +2 -1
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/examples/20-zone-occupancy.json +2 -1
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/examples/22-heatmap.json +2 -1
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/examples/24-detection-log.json +2 -1
- pynode_flow-0.2.9/pynode/static/examples/25-reid-identities.json +134 -0
- pynode_flow-0.2.9/pynode/static/examples/26-reid-cross-camera.json +240 -0
- pynode_flow-0.2.9/pynode/static/examples/27-tracker-comparison.json +142 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/examples/README.md +16 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/examples/manifest.json +21 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/index.html +49 -1
- pynode_flow-0.2.9/pynode/static/js/credentials.js +267 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/js/mqtt-services.js +55 -5
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/js/properties.js +38 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/workflow_engine.py +18 -14
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/workflow_manager.py +58 -3
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode_flow.egg-info/PKG-INFO +30 -4
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode_flow.egg-info/SOURCES.txt +22 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode_flow.egg-info/requires.txt +8 -1
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode_flow.egg-info/scm_file_list.json +22 -0
- pynode_flow-0.2.9/pynode_flow.egg-info/scm_version.json +8 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pyproject.toml +11 -2
- pynode_flow-0.2.9/ruff.toml +9 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/conftest.py +6 -1
- pynode_flow-0.2.9/tests/test_api_credentials.py +180 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/test_api_security.py +0 -1
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/test_base_node_messaging.py +72 -5
- pynode_flow-0.2.9/tests/test_credential_store.py +308 -0
- pynode_flow-0.2.9/tests/test_credential_wiring.py +167 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/test_debug_workflow_scope.py +0 -1
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/test_image_upload_node.py +1 -2
- pynode_flow-0.2.9/tests/test_image_viewer_node.py +217 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/test_link_nodes.py +0 -1
- pynode_flow-0.2.9/tests/test_mqtt_credentials.py +237 -0
- pynode_flow-0.2.9/tests/test_node_credentials.py +287 -0
- pynode_flow-0.2.9/tests/test_production_hardening.py +256 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/test_send_copy_semantics.py +71 -8
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/test_socket_nodes.py +6 -6
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/test_supervision_phase3_nodes.py +0 -1
- pynode_flow-0.2.9/tests/test_supervision_reid_node.py +468 -0
- pynode_flow-0.2.9/tests/test_supervision_tracker_node.py +385 -0
- pynode_flow-0.2.9/tests/test_webhook_credentials.py +186 -0
- pynode_flow-0.2.8/.github/workflows/ci.yml +0 -44
- pynode_flow-0.2.8/pynode/nodes/ImageViewerNode/image_viewer_node.py +0 -169
- pynode_flow-0.2.8/pynode/nodes/Supervision/requirements.txt +0 -3
- pynode_flow-0.2.8/pynode/nodes/Supervision/supervision_tracker_node.py +0 -229
- pynode_flow-0.2.8/pynode_flow.egg-info/scm_version.json +0 -8
- pynode_flow-0.2.8/tests/test_supervision_tracker_node.py +0 -226
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/.github/workflows/publish.yaml +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/LICENSE +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/MANIFEST.in +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/docs/CUSTOM_NODES.md +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/docs/DOCKER.md +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/docs/UI_COMPONENTS.md +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/examples/README.md +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/install_nodes.bat +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/install_nodes.sh +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/__main__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/_perf.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/api/helpers.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/api/nodes.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/api/sse.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/api/uploads.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/api/workflows.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/install_nodes.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/node_registry.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/BBoxMetricsNode/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/BBoxMetricsNode/requirements.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/BatchNode/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/BatchNode/requirements.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/CameraNode/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/CameraNode/requirements.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/ChangeNode/requirements.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/ClassMapNode/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/ClassMapNode/requirements.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/ConfidenceFilterNode/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/ConfidenceFilterNode/requirements.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/CounterNode/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/CropNode/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/CropNode/requirements.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/DebugNode/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/DebugNode/requirements.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/DelayNode/requirements.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/DrawPredictionsNode/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/DrawPredictionsNode/requirements.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/ErrorNode/requirements.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/FilterNode/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/FilterNode/requirements.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/FrameSourceNode/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/FrameSourceNode/requirements.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/FunctionNode/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/FunctionNode/requirements.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/GateNode/requirements.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/ImageFormatNode/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/ImageFormatNode/requirements.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/ImageViewerNode/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/ImageViewerNode/requirements.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/ImageWriterNode/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/ImageWriterNode/requirements.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/InferenceNode/InferenceEngine/inference_engine_factory.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/InferenceNode/InferenceEngine/readme.md +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/InferenceNode/requirements.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/InjectNode/inject_node.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/InjectNode/requirements.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/JoinNode/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/JoinNode/requirements.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/LabelFilterNode/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/LabelFilterNode/requirements.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/LinkNode/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/LinkNode/link_bus.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/LinkNode/requirements.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/MQTTNode/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/MQTTNode/requirements.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/MessageWriterNode/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/OmronCameraNode/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/OmronCameraNode/requirements.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/OpenCV/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/OpenCV/requirements.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/PointInShapeNode/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/PointInShapeNode/requirements.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/PolygonMetricsNode/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/PolygonMetricsNode/requirements.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/QueueLengthProbeNode/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/Qwen3VLMNode/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/Qwen3VLMNode/requirements.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/RESTEndpointNode/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/RangeNode/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/RangeNode/requirements.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/RateProbeNode/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/RateProbeNode/requirements.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/RoboflowUploadNode/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/RoboflowUploadNode/requirements.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/SliceNode/README.md +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/SliceNode/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/SliceNode/requirements.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/SocketNode/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/SocketNode/interop/README.md +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/SocketNode/interop/nodered-example-flow.json +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/SocketNode/requirements.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/SocketNode/udp_probe.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/SplitNode/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/SplitNode/requirements.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/SwitchNode/requirements.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/SyncNode/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/TemplateNode/requirements.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/UltralyticsNode/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/UltralyticsNode/model_paths.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/UltralyticsNode/requirements.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/UnknownNode/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/VLLMNode/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/VideoReaderNode/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/VideoWriterNode/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/VideoWriterNode/video_writer_node.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/WebhookNode/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/mDNSNode/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/mDNSNode/mdns_discovery_node.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/nodes/mDNSNode/requirements.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/examples/01-hello-world.json +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/examples/02-function-transform.json +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/examples/03-switch-routing.json +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/examples/04-flow-control.json +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/examples/05-webcam-viewer.json +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/examples/06-webcam-yolo.json +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/examples/07-blur-people.json +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/examples/08-video-yolo-recorder.json +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/examples/09-mqtt-pub-sub.json +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/examples/10-person-alert-mqtt.json +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/examples/16-socket-bridge.json +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/examples/17-slider-crop.json +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/examples/21-annotator-gallery.json +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/examples/23-privacy-blur.json +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/images/favicon4.png +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/js/README.md +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/js/auth.js +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/js/clipboard.js +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/js/config.js +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/js/connections.js +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/js/debug.js +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/js/events.js +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/js/geometry-editor.js +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/js/history.js +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/js/main.js +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/js/minimap.js +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/js/nodes.js +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/js/palette.js +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/js/selection.js +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/js/state.js +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/js/ui-utils.js +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/js/viewport.js +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/js/workflow.js +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/js/workflows.js +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode/static/style.css +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode_flow.egg-info/dependency_links.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode_flow.egg-info/entry_points.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/pynode_flow.egg-info/top_level.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/requirements.txt +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/setup.bat +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/setup.cfg +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/setup.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/setup.sh +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/__init__.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/test_api_auth.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/test_api_errors.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/test_api_nodes.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/test_api_workflows.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/test_camera_node_backend.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/test_change_node.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/test_class_map_node.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/test_concurrency.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/test_config.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/test_control_slider_node.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/test_counter_sse.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/test_crop_node.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/test_debug_json_safety.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/test_delay_rate_limit.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/test_detection_utils.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/test_device_detection.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/test_frame_pacing.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/test_function_node.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/test_message_reader_pickle.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/test_models_dir.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/test_mqtt_services.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/test_node_registration.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/test_node_types_api.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/test_persistence.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/test_range_node.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/test_serialization.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/test_supervision_annotate_node.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/test_supervision_utils.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/test_supervision_zone_nodes.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/test_video_reader_node.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/test_workflow_engine.py +0 -0
- {pynode_flow-0.2.8 → pynode_flow-0.2.9}/tests/test_workflow_roundtrip.py +0 -0
|
@@ -90,12 +90,13 @@ Other vision nodes: `DrawPredictionsNode`, `CropNode`,
|
|
|
90
90
|
read their `DEFAULT_CONFIG`/`properties` in `pynode/nodes/<Name>/` before use.
|
|
91
91
|
|
|
92
92
|
### Supervision
|
|
93
|
-
Nodes built on the `supervision` library
|
|
94
|
-
without
|
|
93
|
+
Nodes built on the `supervision` library plus Roboflow's `trackers` (both in the
|
|
94
|
+
`[vision]` extra) and `reid` (`[reid]` extra). They register even without them installed, reporting an error and passing messages through.
|
|
95
95
|
|
|
96
96
|
| Type | Ports | Config |
|
|
97
97
|
|---|---|---|
|
|
98
|
-
| `SupervisionTrackerNode` | 1→1 | `track_thresh`: "0.25"; `track_buffer`: "30" (frames a lost track is kept, scaled by `frame_rate`); `
|
|
98
|
+
| `SupervisionTrackerNode` | 1→1 | `algorithm`: `bytetrack` (default)\|`sort`\|`ocsort`\|`botsort` (from Roboflow's `trackers` package — `sv.ByteTrack` was removed in supervision 0.31); `track_thresh`: "0.25" (confidence to start a track); `high_thresh`: "" (blank = same as `track_thresh`); `track_buffer`: "30" (frames a lost track is kept, scaled by `frame_rate`); `min_iou`: "0.2" (**a real IoU** — the legacy `match_thresh` was an IoU *distance* and is auto-translated as `1 - value`; do not write `match_thresh` in new flows); `min_consecutive_frames`: "1" (raise to 2-3 to kill ghost tracks); `frame_rate`: "30" (**set to the real source fps**); `keep_untracked`: false (true = also pass detections with no track yet, without a `track_id`); `camera_motion`: false (ocsort/botsort, needs `payload.image`); botsort only: `reid_model`: `none`\|a Re-ID alias (needs `[reid]` extra + `payload.image`), `reid_device`: `auto`\|`cpu`\|`cuda`, `reid_appearance_thresh`: "0.25". Adds `track_id` (**0-based**) to each detection plus `payload.tracks`/`track_count`. Has a `reset` action — a new object needs 2 frames before it is reported. Thresholds apply live; changing algorithm/buffer/frame rate/camera motion/Re-ID rebuilds the tracker (ids restart). **Does not draw** — wire an `SupervisionAnnotateNode` after it. |
|
|
99
|
+
| `SupervisionReIDNode` | 1→**2** | Appearance re-identification (`reid` package, `[reid]` extra). `model`: `osnet_x1_0_msmt17_combineall` (default)\|`osnet_x1_0_mot17`\|`osnet_x1_0_sportsmot`\|`osnet_x1_0_dancetrack`\|`osnet_x1_0_soccernet`\|`fastreid_mot17_sbs50`\|`custom` (+ `custom_model`: hf://, https:// or local path); `device`: `auto`\|`cpu`\|`cuda`; `classes`: "person" (comma names/ids, blank = all); `match_distance`: "0.3" (max cosine distance, lower = stricter); `gallery`: "" (blank = private; **same name on several nodes = shared identities / cross-camera**); `use_as_track_id`: false (true = `track_id` becomes the identity so annotate/zone/line nodes follow it; original kept in `sv.data.local_track_id`); `refresh_interval`: "10" (frames between re-embeds of a tracked object); `ttl_seconds`: "0" (0 = never forget); `max_identities`: "1000"; `min_box_size`: "16"; `include_embeddings`: false. **Needs `payload.image`**; place after an SV Tracker (works without one, slower). Output 0 = msg with `reid_id` on each identified detection + `payload.identities`/`identity_count`; **output 1 = events** `{event:'new'\|'reidentified', reid_id, track_id?, bbox, class_name, source, gallery, distance?, absent_seconds?, previous_source?}` (topic `reid/<event>`). Model loads in the background on first frame (frames pass through until ready). `reset` action clears the gallery. SV Annotate labels show `ID<n>` when a `reid_id` is present. |
|
|
99
100
|
| `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
101
|
| `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
102
|
| `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). |
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
# Weekly run against the latest releases of every dependency: upstream
|
|
7
|
+
# removals (e.g. supervision 0.31 dropping sv.ByteTrack) surface here
|
|
8
|
+
# instead of in a user's install.
|
|
9
|
+
schedule:
|
|
10
|
+
- cron: "0 6 * * 1"
|
|
11
|
+
workflow_dispatch:
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
test:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
|
|
17
|
+
strategy:
|
|
18
|
+
fail-fast: false
|
|
19
|
+
matrix:
|
|
20
|
+
python-version:
|
|
21
|
+
- "3.10"
|
|
22
|
+
- 3.11
|
|
23
|
+
- 3.12
|
|
24
|
+
|
|
25
|
+
steps:
|
|
26
|
+
- uses: actions/checkout@v4
|
|
27
|
+
|
|
28
|
+
- name: Install Python ${{ matrix.python-version }}
|
|
29
|
+
uses: actions/setup-python@v5
|
|
30
|
+
with:
|
|
31
|
+
python-version: ${{ matrix.python-version }}
|
|
32
|
+
cache: pip
|
|
33
|
+
cache-dependency-path: pyproject.toml
|
|
34
|
+
|
|
35
|
+
- name: Install system libraries for OpenCV
|
|
36
|
+
run: sudo apt-get update && sudo apt-get install -y libgl1 libglib2.0-0
|
|
37
|
+
|
|
38
|
+
- name: Install package
|
|
39
|
+
run: pip install -e .[dev]
|
|
40
|
+
|
|
41
|
+
- name: Run tests
|
|
42
|
+
run: pytest -q
|
|
43
|
+
|
|
44
|
+
# The core job installs no ML packages, so every Supervision / tracker /
|
|
45
|
+
# Re-ID test is skipped there (pytest.importorskip). This job installs the
|
|
46
|
+
# CPU builds so those nodes are actually exercised.
|
|
47
|
+
test-vision:
|
|
48
|
+
runs-on: ubuntu-latest
|
|
49
|
+
steps:
|
|
50
|
+
- uses: actions/checkout@v4
|
|
51
|
+
|
|
52
|
+
- name: Install Python
|
|
53
|
+
uses: actions/setup-python@v5
|
|
54
|
+
with:
|
|
55
|
+
python-version: "3.12"
|
|
56
|
+
cache: pip
|
|
57
|
+
cache-dependency-path: pyproject.toml
|
|
58
|
+
|
|
59
|
+
- name: Install system libraries for OpenCV
|
|
60
|
+
run: sudo apt-get update && sudo apt-get install -y libgl1 libglib2.0-0
|
|
61
|
+
|
|
62
|
+
- name: Install CPU torch
|
|
63
|
+
run: pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu
|
|
64
|
+
|
|
65
|
+
- name: Install package with vision, reid and mqtt extras
|
|
66
|
+
run: pip install -e ".[dev,reid,mqtt]"
|
|
67
|
+
|
|
68
|
+
- name: Show resolved versions
|
|
69
|
+
run: pip list | grep -i -E "supervision|trackers|reid|torch|ultralytics|numpy|opencv|paho"
|
|
70
|
+
|
|
71
|
+
- name: Run tests (deprecations from our own code are errors)
|
|
72
|
+
run: pytest -q -W "error::DeprecationWarning:pynode"
|
|
73
|
+
|
|
74
|
+
typecheck:
|
|
75
|
+
runs-on: ubuntu-latest
|
|
76
|
+
continue-on-error: true
|
|
77
|
+
steps:
|
|
78
|
+
- uses: actions/checkout@v4
|
|
79
|
+
|
|
80
|
+
- uses: actions/setup-python@v5
|
|
81
|
+
with:
|
|
82
|
+
python-version: "3.11"
|
|
83
|
+
cache: pip
|
|
84
|
+
cache-dependency-path: pyproject.toml
|
|
85
|
+
|
|
86
|
+
- name: Install package and pyright
|
|
87
|
+
run: pip install -e .[dev] pyright
|
|
88
|
+
|
|
89
|
+
- name: Run pyright
|
|
90
|
+
run: pyright
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: "Git Hooks"
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
pull_request: ~
|
|
8
|
+
|
|
9
|
+
permissions: {}
|
|
10
|
+
|
|
11
|
+
concurrency:
|
|
12
|
+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
|
13
|
+
cancel-in-progress: true
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
run-prek:
|
|
17
|
+
name: prek run --all-files
|
|
18
|
+
runs-on: ubuntu-26.04
|
|
19
|
+
timeout-minutes: 1
|
|
20
|
+
|
|
21
|
+
steps:
|
|
22
|
+
- name: Checkout
|
|
23
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
24
|
+
with:
|
|
25
|
+
persist-credentials: false
|
|
26
|
+
|
|
27
|
+
- name: Run prek
|
|
28
|
+
uses: j178/prek-action@1382ad72dea72f9d53832f2bcb65b3e87a947204 # v3.0.1
|
|
@@ -155,6 +155,9 @@ Thumbs.db
|
|
|
155
155
|
# ...and the example Node-RED flow shipped with the SocketNode nodes
|
|
156
156
|
!pynode/nodes/SocketNode/interop/*.json
|
|
157
157
|
|
|
158
|
+
# Credential store key, generated next to credentials.json on first use
|
|
159
|
+
.credential_key
|
|
160
|
+
|
|
158
161
|
# PyTorch model files
|
|
159
162
|
*.pt
|
|
160
163
|
models/
|
|
@@ -60,8 +60,9 @@ deploy:
|
|
|
60
60
|
### Persistent Storage
|
|
61
61
|
|
|
62
62
|
The following directories are mounted for persistence:
|
|
63
|
-
- `./
|
|
64
|
-
- `./logs` -
|
|
63
|
+
- `./models` - Downloaded model weights (YOLO, Re-ID; Hugging Face downloads land in `models/huggingface` via `HF_HOME`). Without this volume every new container downloads them again.
|
|
64
|
+
- `./logs` - `pynode.log`, rotated at 10 MB with 5 backups (`PYNODE_LOG_FILE`).
|
|
65
|
+
- `./workflows` - Saved workflows, plus the credential store (`credentials.json`) and, unless `PYNODE_CREDENTIAL_KEY` or `PYNODE_CREDENTIAL_KEY_FILE` is set, its generated key (`.credential_key`). Back up both files together. Stop PyNode before restoring `credentials.json` from a backup: a running instance keeps the store in memory and would overwrite the restored file on its next change.
|
|
65
66
|
|
|
66
67
|
## Building
|
|
67
68
|
|
|
@@ -76,7 +76,7 @@ EXPOSE 5000
|
|
|
76
76
|
|
|
77
77
|
# Health check (uses the bundled Python interpreter; no extra tools required)
|
|
78
78
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=40s --retries=3 \
|
|
79
|
-
CMD ["/app/appenv/bin/python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:5000/')"]
|
|
79
|
+
CMD ["/app/appenv/bin/python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:5000/healthz')"]
|
|
80
80
|
|
|
81
81
|
ENTRYPOINT ["/app/docker-entrypoint.sh"]
|
|
82
82
|
CMD ["/app/appenv/bin/python", "-m", "pynode", "--production", "--host", "0.0.0.0", "--port", "5000"]
|
|
@@ -56,7 +56,7 @@ EXPOSE 5000
|
|
|
56
56
|
|
|
57
57
|
# Health check (uses the bundled Python interpreter; no extra tools required)
|
|
58
58
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=40s --retries=3 \
|
|
59
|
-
CMD ["/app/appenv/bin/python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:5000/')"]
|
|
59
|
+
CMD ["/app/appenv/bin/python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:5000/healthz')"]
|
|
60
60
|
|
|
61
61
|
ENTRYPOINT ["/app/docker-entrypoint.sh"]
|
|
62
62
|
CMD ["/app/appenv/bin/python", "-m", "pynode", "--production", "--host", "0.0.0.0", "--port", "5000"]
|
|
@@ -32,7 +32,8 @@ 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, DrawPredictionsNode, the Supervision nodes, … |
|
|
35
|
+
| `vision` | ultralytics, torch, torchvision, supervision, trackers | UltralyticsNode, DrawPredictionsNode, the Supervision nodes (tracking, zones, annotators), … |
|
|
36
|
+
| `reid` | reid (+ `vision`) | SV Re-ID node, BoT-SORT appearance association in SV Tracker |
|
|
36
37
|
| `mqtt` | paho-mqtt | MQTTNode |
|
|
37
38
|
| `camera` | framesource[full] | FrameSourceNode |
|
|
38
39
|
| `inference` | onnxruntime (+ `vision`) | InferenceNode |
|
|
@@ -102,6 +103,14 @@ pynode --host 0.0.0.0 --port 5000 --production
|
|
|
102
103
|
- `--host`: Host address to bind to (default: 0.0.0.0)
|
|
103
104
|
- `--port`: Port to bind to (default: 5000)
|
|
104
105
|
- `--production`: Run in production mode using Waitress server
|
|
106
|
+
- `--threads`: Request threads in production mode (default: 32, or `PYNODE_THREADS`)
|
|
107
|
+
- `--api-key`: Require an API key on all `/api/` requests (or `PYNODE_API_KEY`)
|
|
108
|
+
- `--cors-origins`: Comma-separated allowed browser origins (or `PYNODE_CORS_ORIGINS`)
|
|
109
|
+
- `--data-dir` / `--models-dir`: Where workflows and model weights are stored
|
|
110
|
+
- `--log-level`: DEBUG, INFO, WARNING or ERROR (default: INFO)
|
|
111
|
+
- `--log-file`: Also log to a rotating file (or `PYNODE_LOG_FILE`)
|
|
112
|
+
|
|
113
|
+
See "Running in Production" in the README for health checks, shutdown behaviour and reverse-proxy notes.
|
|
105
114
|
|
|
106
115
|
## Installing Node Dependencies
|
|
107
116
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pynode-flow
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.9
|
|
4
4
|
Summary: Python based Node-RED-like visual workflow system
|
|
5
5
|
Author: olkham
|
|
6
6
|
License-Expression: MIT
|
|
@@ -26,11 +26,16 @@ Requires-Dist: flask-cors>=4.0
|
|
|
26
26
|
Requires-Dist: waitress>=2.1
|
|
27
27
|
Requires-Dist: numpy
|
|
28
28
|
Requires-Dist: opencv-python
|
|
29
|
+
Requires-Dist: cryptography>=42
|
|
29
30
|
Provides-Extra: vision
|
|
30
31
|
Requires-Dist: ultralytics; extra == "vision"
|
|
31
32
|
Requires-Dist: torch; extra == "vision"
|
|
32
33
|
Requires-Dist: torchvision; extra == "vision"
|
|
33
|
-
Requires-Dist: supervision; extra == "vision"
|
|
34
|
+
Requires-Dist: supervision>=0.31.0; extra == "vision"
|
|
35
|
+
Requires-Dist: trackers>=2.7.0; extra == "vision"
|
|
36
|
+
Provides-Extra: reid
|
|
37
|
+
Requires-Dist: pynode-flow[vision]; extra == "reid"
|
|
38
|
+
Requires-Dist: reid>=0.1.0; extra == "reid"
|
|
34
39
|
Provides-Extra: mqtt
|
|
35
40
|
Requires-Dist: paho-mqtt; extra == "mqtt"
|
|
36
41
|
Provides-Extra: camera
|
|
@@ -53,6 +58,7 @@ Requires-Dist: pynode-flow[mqtt]; extra == "full"
|
|
|
53
58
|
Requires-Dist: pynode-flow[camera]; extra == "full"
|
|
54
59
|
Requires-Dist: pynode-flow[inference]; extra == "full"
|
|
55
60
|
Requires-Dist: pynode-flow[vlm]; extra == "full"
|
|
61
|
+
Requires-Dist: pynode-flow[reid]; extra == "full"
|
|
56
62
|
Requires-Dist: pynode-flow[upload]; extra == "full"
|
|
57
63
|
Requires-Dist: pynode-flow[discovery]; extra == "full"
|
|
58
64
|
Provides-Extra: all
|
|
@@ -67,7 +73,10 @@ Dynamic: license-file
|
|
|
67
73
|
|
|
68
74
|
A Node-RED-like visual workflow editor with a Python backend. Create workflows by connecting Python nodes that process and route messages.
|
|
69
75
|
|
|
70
|
-
https://github.com/user-attachments/assets/0b53085a-2cc6-4c26-bd43-e0de1e0716a2
|
|
76
|
+
<!-- https://github.com/user-attachments/assets/0b53085a-2cc6-4c26-bd43-e0de1e0716a2 -->
|
|
77
|
+
[](https://www.youtube.com/watch?v=d-sQIWd4X9Q)
|
|
78
|
+
|
|
79
|
+
<p align="center"><a href="https://www.youtube.com/watch?v=d-sQIWd4X9Q">▶ Watch the promo video on YouTube</a></p>
|
|
71
80
|
|
|
72
81
|
## Features
|
|
73
82
|
|
|
@@ -95,7 +104,7 @@ carries its own documentation in the editor's **Information** panel (ℹ️ tab)
|
|
|
95
104
|
| **Logic** | Switch (route on conditions), Gate, Sync, Auto Sync |
|
|
96
105
|
| **Network** | MQTT In / Out, UDP In / Out, TCP In / Out, REST Endpoint, Webhook, mDNS Broadcast / Discovery |
|
|
97
106
|
| **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) |
|
|
107
|
+
| **Supervision** | SV Tracker (ByteTrack / SORT / OC-SORT / BoT-SORT via Roboflow `trackers`), SV Re-ID (appearance identities that survive occlusion and carry across cameras), 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) |
|
|
99
108
|
| **Analysis** | BBox Metrics, Polygon Metrics, Point in Shape |
|
|
100
109
|
| **OpenCV** | ~28 image-processing nodes: Blur, Threshold, Morphology, Edge Detector, Find Contours, Perspective, Resize, Rotate, Colormap, Histogram, FFT, Template Match, RealSense Depth, and more |
|
|
101
110
|
| **Node Probes** | Rate Probe, Queue Length Probe, Counter |
|
|
@@ -256,8 +265,25 @@ order:
|
|
|
256
265
|
|
|
257
266
|
- **API key**: start with `pynode --api-key <secret>` (or set the `PYNODE_API_KEY` env var). All `/api/` requests then require the key via the `X-API-Key` header or an `api_key` query parameter; the web UI prompts for it on first load and remembers it in the browser. Unset/empty = no authentication (the default).
|
|
258
267
|
- **CORS**: restrict allowed browser origins with `pynode --cors-origins http://localhost:5000,https://myhost` (or the `PYNODE_CORS_ORIGINS` env var). Default is `*` (all origins).
|
|
268
|
+
- **Credentials**: nodes that need a secret (Webhook and REST Endpoint authentication, the Roboflow Upload API key, the Qwen3-VL HuggingFace token) and MQTT brokers (the password) reference a stored credential by ID instead of keeping the secret in the workflow or the broker settings. Secrets saved in plain text by earlier versions keep working until you select a credential in their place. Secrets are encrypted in `<data dir>/workflows/credentials.json` and are never sent back to the browser. The encryption key comes from `PYNODE_CREDENTIAL_KEY` (a Fernet key, e.g. from `python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"`) or from the file named by `PYNODE_CREDENTIAL_KEY_FILE`. With neither set, PyNode generates `<data dir>/workflows/.credential_key` on first use and logs its location. Back up the key together with `credentials.json`, because the secrets cannot be recovered without it. If the data directory may be copied elsewhere, supply the key from outside it. Credentials prevent accidental exposure, not a malicious flow editor: anyone who can edit and deploy flows (e.g. with a Function node) can use any stored credential.
|
|
259
269
|
- **Bind locally**: when you don't need network access, run `pynode --host 127.0.0.1` so the server is only reachable from the local machine.
|
|
260
270
|
|
|
271
|
+
PyNode logs a warning at startup when it is listening on a network interface without an API key.
|
|
272
|
+
|
|
273
|
+
## Running in Production
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
pynode --production --host 0.0.0.0 --api-key "$PYNODE_API_KEY" --log-file /var/log/pynode/pynode.log
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
- **Server**: `--production` serves through Waitress. `--threads N` (or `PYNODE_THREADS`, default 32) sets the request threads. Every open editor tab holds one for its event stream and every live MJPEG viewer holds another, so raise it if many people watch at once.
|
|
280
|
+
- **Health checks**: `GET /healthz` is an unauthenticated liveness probe for Docker, Kubernetes or a load balancer. `GET /api/health` (API key required) returns the version, uptime and the run state of every workflow, for monitoring and alerting.
|
|
281
|
+
- **Graceful shutdown**: on `SIGTERM` (`docker stop`, systemd, Kubernetes) and Ctrl+C, PyNode stops every workflow before exiting, so video writers finalise their files, detection logs are flushed and cameras are released. Give the supervisor a stop timeout of 10 seconds or more.
|
|
282
|
+
- **Logs**: `--log-file <path>` (or `PYNODE_LOG_FILE`) adds a rotating log file (10 MB, 5 backups) next to console output.
|
|
283
|
+
- **Upload limit**: request bodies are capped at 4096 MB; change it with `PYNODE_MAX_UPLOAD_MB` (`0` = unlimited).
|
|
284
|
+
- **TLS**: PyNode speaks plain HTTP. Terminate TLS in a reverse proxy (nginx, Caddy, Traefik) and disable response buffering for `/api/debug/stream` and the MJPEG routes, which are long-lived streams.
|
|
285
|
+
- **Persistent data**: keep the data directory (`--data-dir`, workflows and credentials) and the models directory (`--models-dir`, downloaded weights) on persistent storage. See [DOCKER.md](DOCKER.md) for the container setup.
|
|
286
|
+
|
|
261
287
|
## Docker Setup
|
|
262
288
|
|
|
263
289
|
PyNode can be run in a Docker container with GPU support (CUDA 12.6).
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
A Node-RED-like visual workflow editor with a Python backend. Create workflows by connecting Python nodes that process and route messages.
|
|
4
4
|
|
|
5
|
-
https://github.com/user-attachments/assets/0b53085a-2cc6-4c26-bd43-e0de1e0716a2
|
|
5
|
+
<!-- https://github.com/user-attachments/assets/0b53085a-2cc6-4c26-bd43-e0de1e0716a2 -->
|
|
6
|
+
[](https://www.youtube.com/watch?v=d-sQIWd4X9Q)
|
|
7
|
+
|
|
8
|
+
<p align="center"><a href="https://www.youtube.com/watch?v=d-sQIWd4X9Q">▶ Watch the promo video on YouTube</a></p>
|
|
6
9
|
|
|
7
10
|
## Features
|
|
8
11
|
|
|
@@ -30,7 +33,7 @@ carries its own documentation in the editor's **Information** panel (ℹ️ tab)
|
|
|
30
33
|
| **Logic** | Switch (route on conditions), Gate, Sync, Auto Sync |
|
|
31
34
|
| **Network** | MQTT In / Out, UDP In / Out, TCP In / Out, REST Endpoint, Webhook, mDNS Broadcast / Discovery |
|
|
32
35
|
| **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) |
|
|
36
|
+
| **Supervision** | SV Tracker (ByteTrack / SORT / OC-SORT / BoT-SORT via Roboflow `trackers`), SV Re-ID (appearance identities that survive occlusion and carry across cameras), 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) |
|
|
34
37
|
| **Analysis** | BBox Metrics, Polygon Metrics, Point in Shape |
|
|
35
38
|
| **OpenCV** | ~28 image-processing nodes: Blur, Threshold, Morphology, Edge Detector, Find Contours, Perspective, Resize, Rotate, Colormap, Histogram, FFT, Template Match, RealSense Depth, and more |
|
|
36
39
|
| **Node Probes** | Rate Probe, Queue Length Probe, Counter |
|
|
@@ -191,8 +194,25 @@ order:
|
|
|
191
194
|
|
|
192
195
|
- **API key**: start with `pynode --api-key <secret>` (or set the `PYNODE_API_KEY` env var). All `/api/` requests then require the key via the `X-API-Key` header or an `api_key` query parameter; the web UI prompts for it on first load and remembers it in the browser. Unset/empty = no authentication (the default).
|
|
193
196
|
- **CORS**: restrict allowed browser origins with `pynode --cors-origins http://localhost:5000,https://myhost` (or the `PYNODE_CORS_ORIGINS` env var). Default is `*` (all origins).
|
|
197
|
+
- **Credentials**: nodes that need a secret (Webhook and REST Endpoint authentication, the Roboflow Upload API key, the Qwen3-VL HuggingFace token) and MQTT brokers (the password) reference a stored credential by ID instead of keeping the secret in the workflow or the broker settings. Secrets saved in plain text by earlier versions keep working until you select a credential in their place. Secrets are encrypted in `<data dir>/workflows/credentials.json` and are never sent back to the browser. The encryption key comes from `PYNODE_CREDENTIAL_KEY` (a Fernet key, e.g. from `python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"`) or from the file named by `PYNODE_CREDENTIAL_KEY_FILE`. With neither set, PyNode generates `<data dir>/workflows/.credential_key` on first use and logs its location. Back up the key together with `credentials.json`, because the secrets cannot be recovered without it. If the data directory may be copied elsewhere, supply the key from outside it. Credentials prevent accidental exposure, not a malicious flow editor: anyone who can edit and deploy flows (e.g. with a Function node) can use any stored credential.
|
|
194
198
|
- **Bind locally**: when you don't need network access, run `pynode --host 127.0.0.1` so the server is only reachable from the local machine.
|
|
195
199
|
|
|
200
|
+
PyNode logs a warning at startup when it is listening on a network interface without an API key.
|
|
201
|
+
|
|
202
|
+
## Running in Production
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
pynode --production --host 0.0.0.0 --api-key "$PYNODE_API_KEY" --log-file /var/log/pynode/pynode.log
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
- **Server**: `--production` serves through Waitress. `--threads N` (or `PYNODE_THREADS`, default 32) sets the request threads. Every open editor tab holds one for its event stream and every live MJPEG viewer holds another, so raise it if many people watch at once.
|
|
209
|
+
- **Health checks**: `GET /healthz` is an unauthenticated liveness probe for Docker, Kubernetes or a load balancer. `GET /api/health` (API key required) returns the version, uptime and the run state of every workflow, for monitoring and alerting.
|
|
210
|
+
- **Graceful shutdown**: on `SIGTERM` (`docker stop`, systemd, Kubernetes) and Ctrl+C, PyNode stops every workflow before exiting, so video writers finalise their files, detection logs are flushed and cameras are released. Give the supervisor a stop timeout of 10 seconds or more.
|
|
211
|
+
- **Logs**: `--log-file <path>` (or `PYNODE_LOG_FILE`) adds a rotating log file (10 MB, 5 backups) next to console output.
|
|
212
|
+
- **Upload limit**: request bodies are capped at 4096 MB; change it with `PYNODE_MAX_UPLOAD_MB` (`0` = unlimited).
|
|
213
|
+
- **TLS**: PyNode speaks plain HTTP. Terminate TLS in a reverse proxy (nginx, Caddy, Traefik) and disable response buffering for `/api/debug/stream` and the MJPEG routes, which are long-lived streams.
|
|
214
|
+
- **Persistent data**: keep the data directory (`--data-dir`, workflows and credentials) and the models directory (`--models-dir`, downloaded weights) on persistent storage. See [DOCKER.md](DOCKER.md) for the container setup.
|
|
215
|
+
|
|
196
216
|
## Docker Setup
|
|
197
217
|
|
|
198
218
|
PyNode can be run in a Docker container with GPU support (CUDA 12.6).
|
|
@@ -14,11 +14,19 @@ services:
|
|
|
14
14
|
volumes:
|
|
15
15
|
# Mount persistent data
|
|
16
16
|
- ./workflows:/app/workflows
|
|
17
|
+
- ./models:/app/models
|
|
17
18
|
- ./logs:/app/logs
|
|
18
19
|
environment:
|
|
19
20
|
- PYTHONUNBUFFERED=1
|
|
21
|
+
- PYNODE_LOG_FILE=/app/logs/pynode.log
|
|
22
|
+
# Keep Hugging Face downloads (Re-ID / VLM weights) on the models volume
|
|
23
|
+
- HF_HOME=/app/models/huggingface
|
|
24
|
+
# Strongly recommended whenever the port is reachable from other machines:
|
|
25
|
+
- PYNODE_API_KEY=${PYNODE_API_KEY:-}
|
|
20
26
|
- HOST_IP=${HOST_IP:-}
|
|
21
27
|
restart: unless-stopped
|
|
28
|
+
# PyNode stops every workflow on SIGTERM so recordings and logs are finalised.
|
|
29
|
+
stop_grace_period: 30s
|
|
22
30
|
# Rotate container logs so they cannot fill the host disk
|
|
23
31
|
logging:
|
|
24
32
|
driver: json-file
|
|
@@ -3,7 +3,7 @@ set -e
|
|
|
3
3
|
|
|
4
4
|
# Ensure bind-mounted directories are writable by appuser (uid 1000).
|
|
5
5
|
# docker-compose mounts host directories that may be owned by root.
|
|
6
|
-
for dir in /app/workflows /app/workflows/services /app/logs; do
|
|
6
|
+
for dir in /app/workflows /app/workflows/services /app/logs /app/models; do
|
|
7
7
|
mkdir -p "$dir"
|
|
8
8
|
chown -R 1000:1000 "$dir" 2>/dev/null || true
|
|
9
9
|
done
|
|
@@ -246,6 +246,45 @@ Properties define the configuration UI shown when a node is selected.
|
|
|
246
246
|
}
|
|
247
247
|
```
|
|
248
248
|
|
|
249
|
+
### Credential (stored secret)
|
|
250
|
+
|
|
251
|
+
Never put a secret (token, password, API key) in a `text` property. Node config is saved as plain text in `workflow.json`, its backups and exports, and is sent to the editor. Use a `credential` property instead: the config then holds only a credential ID, while the secret is stored encrypted in the credential store and never sent back to the browser.
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
{
|
|
255
|
+
'name': 'credential',
|
|
256
|
+
'label': 'Credential',
|
|
257
|
+
'type': 'credential',
|
|
258
|
+
'credentialType': 'secret' # core type: one secret value
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Resolve it when you need the value, and keep the result in memory only (never in `self.config`, a message or a log line):
|
|
263
|
+
|
|
264
|
+
```python
|
|
265
|
+
from pynode.nodes.base_node import CredentialError
|
|
266
|
+
|
|
267
|
+
try:
|
|
268
|
+
token = self.get_credential(self.config.get('credential'))['value']
|
|
269
|
+
except CredentialError as e:
|
|
270
|
+
self.report_error(str(e)) # not found, store locked, or wrong key
|
|
271
|
+
return
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
For the core `secret` type, `self.get_secret()` does the lookup for you and raises `CredentialError` if the credential is of another type. Give it the name of a legacy plain-text property when you are converting an existing node, so flows saved before the change keep working. A selected credential that cannot be resolved raises and never falls back to the legacy value:
|
|
275
|
+
|
|
276
|
+
```python
|
|
277
|
+
try:
|
|
278
|
+
token = self.get_secret('credential', legacy_key='api_key') # '' when neither is set
|
|
279
|
+
except CredentialError as e:
|
|
280
|
+
self.report_error(str(e))
|
|
281
|
+
return
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Libraries often quote a secret in their error messages (for example in a request URL), so remove it from any exception text before you report or log it.
|
|
285
|
+
|
|
286
|
+
Node packages can register their own credential types (for example a username plus a password) with `pynode.credential_store.register_credential_type()`.
|
|
287
|
+
|
|
249
288
|
### Conditional Visibility (`showIf`)
|
|
250
289
|
|
|
251
290
|
Show a property only when another property has a specific value:
|
|
@@ -689,6 +728,8 @@ class InternalNode(BaseNode):
|
|
|
689
728
|
4. **Using hardcoded strings** - Use `MessageKeys.PAYLOAD` not `'payload'`, etc.
|
|
690
729
|
5. **Not handling `None` payloads** - Always provide defaults: `msg.get(MessageKeys.PAYLOAD, default)`.
|
|
691
730
|
6. **Blocking in `on_input`** - Long operations block the worker thread. For CPU-bound work, consider additional threads.
|
|
731
|
+
7. **Doing heavy work in a sink's `on_input_direct`** - It runs on the *sending* node's thread for every message, so slow work there (encoding, disk or network I/O) caps the frame rate of the node feeding it. Keep it to storing a reference and do the work on your own thread or when the result is requested.
|
|
732
|
+
8. **Setting `shares_input = True` on a node that modifies or forwards its input** - That flag lets `send()` skip the fan-out copy for read-only nodes. A node that mutates the payload or sends it on must leave it `False`.
|
|
692
733
|
7. **Putting the class in the wrong folder** - The folder must be directly under `pynode/nodes/`, not nested deeper.
|
|
693
734
|
8. **Class name not ending in `Node`** - Auto-discovery filters by this suffix.
|
|
694
735
|
9. **Circular imports** - Only import from `pynode.nodes.base_node`. Don't import other node types.
|
|
@@ -202,28 +202,31 @@ class BaseNode:
|
|
|
202
202
|
|
|
203
203
|
### Queue Management
|
|
204
204
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
205
|
+
Delivery is decided per recipient inside `BaseNode.send()`:
|
|
206
|
+
|
|
207
|
+
| Recipient | What happens |
|
|
208
|
+
|---|---|
|
|
209
|
+
| Sink with `on_input_direct` (no outputs) | Called synchronously **on the sending node's thread**. |
|
|
210
|
+
| Idle node | Message is put on the node's queue; its worker thread wakes immediately. |
|
|
211
|
+
| Busy node, `drop_messages` on | Message is parked in a one-slot, latest-wins inbox (one slot per input port). A newer message replaces it and `drop_count` is incremented. When the node finishes, it continues straight away with the newest message. |
|
|
212
|
+
| Busy node, `drop_messages` off | Message is queued (up to 1000). |
|
|
213
|
+
|
|
214
|
+
Copies are made only where a branch could otherwise see another branch's changes:
|
|
215
|
+
|
|
216
|
+
- one recipient: no copy, the recipient takes over the sender's objects;
|
|
217
|
+
- several recipients: each gets its own `deepcopy`, except
|
|
218
|
+
- nodes that declare `shares_input = True` (they only read the message and never
|
|
219
|
+
forward its objects, e.g. Image Viewer and Rate Probe), which share the original, and
|
|
220
|
+
- busy nodes, whose copy is made later on their own worker thread and only for the
|
|
221
|
+
messages they actually process.
|
|
222
|
+
|
|
223
|
+
Two consequences for node authors:
|
|
224
|
+
|
|
225
|
+
- A sink's `on_input_direct` runs on the upstream node's thread for every message, so
|
|
226
|
+
anything slow there throttles the source. Store what you need and do the heavy work
|
|
227
|
+
elsewhere (the Image Viewer keeps a reference and encodes when the UI asks for a frame).
|
|
228
|
+
- Set `shares_input = True` only if the node never mutates anything reachable from the
|
|
229
|
+
incoming message and never passes those objects downstream.
|
|
227
230
|
|
|
228
231
|
### Connection Routing
|
|
229
232
|
|
|
Binary file
|
|
@@ -4,13 +4,11 @@ This example runs without the UI - it creates nodes programmatically,
|
|
|
4
4
|
connects them, and displays the output in an OpenCV window.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
import sys
|
|
8
|
-
import os
|
|
9
7
|
import time
|
|
10
8
|
import cv2
|
|
11
9
|
import numpy as np
|
|
12
10
|
import base64
|
|
13
|
-
from typing import
|
|
11
|
+
from typing import Any
|
|
14
12
|
|
|
15
13
|
from pynode.workflow_engine import WorkflowEngine
|
|
16
14
|
from pynode.nodes.base_node import BaseNode, MessageKeys
|
|
@@ -47,7 +45,7 @@ class DisplayNode(BaseNode):
|
|
|
47
45
|
cv2.destroyWindow(self.window_name)
|
|
48
46
|
print(f"[{self.name}] Display window closed")
|
|
49
47
|
|
|
50
|
-
def on_input(self, msg:
|
|
48
|
+
def on_input(self, msg: dict[str, Any], input_index: int = 0):
|
|
51
49
|
"""
|
|
52
50
|
Process incoming image messages and display them.
|
|
53
51
|
"""
|
|
@@ -4,13 +4,11 @@ and displays the annotated results using OpenCV.
|
|
|
4
4
|
This example runs without the UI - it creates nodes programmatically.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
import sys
|
|
8
|
-
import os
|
|
9
7
|
import time
|
|
10
8
|
import cv2
|
|
11
9
|
import numpy as np
|
|
12
10
|
import base64
|
|
13
|
-
from typing import
|
|
11
|
+
from typing import Any
|
|
14
12
|
|
|
15
13
|
from pynode.workflow_engine import WorkflowEngine
|
|
16
14
|
from pynode.nodes.base_node import BaseNode, MessageKeys
|
|
@@ -48,7 +46,7 @@ class DisplayNode(BaseNode):
|
|
|
48
46
|
cv2.destroyWindow(self.window_name)
|
|
49
47
|
print(f"[{self.name}] Display window closed")
|
|
50
48
|
|
|
51
|
-
def on_input(self, msg:
|
|
49
|
+
def on_input(self, msg: dict[str, Any], input_index: int = 0):
|
|
52
50
|
"""
|
|
53
51
|
Process incoming image messages and display them.
|
|
54
52
|
"""
|
|
@@ -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.9'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 2, 9)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'gdd99cab55'
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
"""API blueprints for the PyNode Flask server."""
|
|
2
2
|
|
|
3
|
+
from pynode.api.credentials import credentials_bp
|
|
4
|
+
from pynode.api.health import health_bp
|
|
3
5
|
from pynode.api.nodes import nodes_bp
|
|
4
6
|
from pynode.api.services import services_bp
|
|
5
7
|
from pynode.api.sse import sse_bp
|
|
6
8
|
from pynode.api.uploads import uploads_bp
|
|
7
9
|
from pynode.api.workflows import workflows_bp
|
|
8
10
|
|
|
9
|
-
ALL_BLUEPRINTS = (workflows_bp, nodes_bp, services_bp, uploads_bp, sse_bp
|
|
11
|
+
ALL_BLUEPRINTS = (workflows_bp, nodes_bp, services_bp, credentials_bp, uploads_bp, sse_bp,
|
|
12
|
+
health_bp)
|
|
10
13
|
|
|
11
14
|
|
|
12
15
|
def register_blueprints(app):
|