pynode-flow 0.2.3__tar.gz → 0.2.4__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.3 → pynode_flow-0.2.4}/.dockerignore +0 -1
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/.gitignore +2 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/DOCKER.md +7 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/Dockerfile +22 -8
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/Dockerfile.cpu +22 -9
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/PKG-INFO +1 -1
- pynode_flow-0.2.4/docker-entrypoint.sh +12 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/__init__.py +5 -1
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/_version.py +3 -3
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/api/nodes.py +5 -1
- pynode_flow-0.2.4/pynode/nodes/ControlSliderNode/control_slider_node.py +208 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/CropNode/crop_node.py +141 -12
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/DelayNode/delay_node.py +2 -2
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/FrameSourceNode/frame_source_node.py +1 -1
- pynode_flow-0.2.4/pynode/nodes/FunctionNode/function_node.py +309 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/ImageFormatNode/image_format_node.py +2 -2
- pynode_flow-0.2.4/pynode/nodes/ImageUploadNode/image_upload_node.py +246 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/InferenceNode/InferenceEngine/engines/onnx_engine.py +1 -1
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/MQTTNode/mqtt_in_node.py +4 -3
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/MQTTNode/mqtt_out_node.py +1 -1
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/MQTTNode/mqtt_service.py +1 -1
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/arithmetic_node.py +5 -5
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/blob_detector_node.py +1 -1
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/colormap_node.py +1 -1
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/fft_node.py +1 -1
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/focus_estimation_node.py +1 -1
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/histogram_node.py +2 -2
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/hough_circles_node.py +2 -2
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/in_range_node.py +1 -1
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/perspective_node.py +6 -6
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/realsense_depth_node.py +2 -2
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/Qwen3VLMNode/qwen3_vlm_node.py +1 -1
- pynode_flow-0.2.4/pynode/nodes/RangeNode/range_node.py +220 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/RoboflowUploadNode/roboflow_upload_node.py +1 -1
- pynode_flow-0.2.4/pynode/nodes/SocketNode/__init__.py +28 -0
- pynode_flow-0.2.4/pynode/nodes/SocketNode/interop/README.md +239 -0
- pynode_flow-0.2.4/pynode/nodes/SocketNode/interop/nodered-example-flow.json +318 -0
- pynode_flow-0.2.4/pynode/nodes/SocketNode/ndjson_protocol.py +176 -0
- pynode_flow-0.2.4/pynode/nodes/SocketNode/nodered_snippets.py +147 -0
- pynode_flow-0.2.4/pynode/nodes/SocketNode/requirements.txt +4 -0
- pynode_flow-0.2.4/pynode/nodes/SocketNode/tcp_in_node.py +269 -0
- pynode_flow-0.2.4/pynode/nodes/SocketNode/tcp_out_node.py +270 -0
- pynode_flow-0.2.4/pynode/nodes/SocketNode/udp_in_node.py +257 -0
- pynode_flow-0.2.4/pynode/nodes/SocketNode/udp_out_node.py +253 -0
- pynode_flow-0.2.4/pynode/nodes/SocketNode/udp_probe.py +105 -0
- pynode_flow-0.2.4/pynode/nodes/SocketNode/udp_protocol.py +479 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/SplitNode/split_node.py +2 -2
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/UltralyticsNode/ultralytics_node.py +5 -5
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/WebhookNode/webhook_node.py +1 -1
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/base_node.py +1 -1
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/image_utils.py +1 -1
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/info.py +36 -2
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/mDNSNode/mdns_discovery_node.py +1 -1
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/mDNSNode/mdns_manager.py +3 -3
- pynode_flow-0.2.4/pynode/static/examples/16-socket-bridge.json +75 -0
- pynode_flow-0.2.4/pynode/static/examples/17-slider-crop.json +105 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/js/events.js +35 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/js/main.js +5 -1
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/js/nodes.js +56 -2
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/js/properties.js +182 -1
- pynode_flow-0.2.4/pynode/static/js/ui-utils.js +82 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/style.css +226 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode_flow.egg-info/PKG-INFO +1 -1
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode_flow.egg-info/SOURCES.txt +22 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode_flow.egg-info/scm_file_list.json +266 -244
- pynode_flow-0.2.4/pynode_flow.egg-info/scm_version.json +8 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pyproject.toml +3 -1
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/setup.sh +4 -5
- pynode_flow-0.2.4/tests/test_control_slider_node.py +199 -0
- pynode_flow-0.2.4/tests/test_crop_node.py +138 -0
- pynode_flow-0.2.4/tests/test_function_node.py +200 -0
- pynode_flow-0.2.4/tests/test_image_upload_node.py +176 -0
- pynode_flow-0.2.4/tests/test_range_node.py +180 -0
- pynode_flow-0.2.4/tests/test_socket_nodes.py +1292 -0
- pynode_flow-0.2.3/pynode/nodes/FunctionNode/function_node.py +0 -168
- pynode_flow-0.2.3/pynode/nodes/ImageUploadNode/image_upload_node.py +0 -96
- pynode_flow-0.2.3/pynode/nodes/RangeNode/range_node.py +0 -115
- pynode_flow-0.2.3/pynode/static/js/ui-utils.js +0 -28
- pynode_flow-0.2.3/pynode_flow.egg-info/scm_version.json +0 -8
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/.claude/skills/pynode-workflows/SKILL.md +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/.github/workflows/ci.yml +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/.github/workflows/publish.yaml +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/INSTALL.md +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/LICENSE +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/MANIFEST.in +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/README.md +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/docker-compose.yml +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/docs/CREATING_NODES.md +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/docs/CUSTOM_NODES.md +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/docs/DOCKER.md +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/docs/EXTENSIBILITY.md +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/docs/UI_COMPONENTS.md +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/examples/README.md +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/examples/camera_workflow.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/examples/camera_yolo_workflow.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/install_nodes.bat +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/install_nodes.sh +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/plan-pynodeRepoReview.prompt.md +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/plan-pynodeReview-2026-07-18.prompt.md +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/__main__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/_perf.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/api/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/api/helpers.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/api/services.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/api/sse.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/api/uploads.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/api/workflows.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/config.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/install_nodes.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/main.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/node_registry.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/AutoSyncNode/auto_sync_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/BBoxMetricsNode/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/BBoxMetricsNode/bbox_metrics_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/BBoxMetricsNode/requirements.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/BatchNode/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/BatchNode/batch_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/BatchNode/requirements.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/CameraNode/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/CameraNode/camera_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/CameraNode/requirements.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/ChangeNode/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/ChangeNode/change_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/ChangeNode/requirements.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/ConfidenceFilterNode/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/ConfidenceFilterNode/confidence_filter_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/ConfidenceFilterNode/requirements.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/CounterNode/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/CounterNode/counter_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/CropNode/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/CropNode/requirements.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/DebugNode/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/DebugNode/debug_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/DebugNode/requirements.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/DelayNode/requirements.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/DrawPredictionsNode/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/DrawPredictionsNode/draw_predictions_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/DrawPredictionsNode/requirements.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/ErrorNode/error_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/ErrorNode/requirements.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/FilterNode/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/FilterNode/filter_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/FilterNode/requirements.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/FrameSourceNode/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/FrameSourceNode/requirements.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/FunctionNode/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/FunctionNode/requirements.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/GateNode/gate_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/GateNode/requirements.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/ImageFormatNode/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/ImageFormatNode/requirements.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/ImageViewerNode/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/ImageViewerNode/image_viewer_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/ImageViewerNode/requirements.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/ImageWriterNode/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/ImageWriterNode/imagewriter_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/ImageWriterNode/requirements.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/InferenceNode/InferenceEngine/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/InferenceNode/InferenceEngine/device_detection.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/InferenceNode/InferenceEngine/engines/base_engine.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/InferenceNode/InferenceEngine/engines/example_engine_template.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/InferenceNode/InferenceEngine/engines/pass_engine.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/InferenceNode/InferenceEngine/engines/ultralytics_engine.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/InferenceNode/InferenceEngine/inference_engine_factory.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/InferenceNode/InferenceEngine/readme.md +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/InferenceNode/InferenceEngine/result_converters.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/InferenceNode/inference_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/InferenceNode/requirements.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/InjectNode/inject_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/InjectNode/requirements.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/JoinNode/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/JoinNode/join_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/JoinNode/requirements.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/LabelFilterNode/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/LabelFilterNode/label_filter_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/LabelFilterNode/requirements.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/LinkNode/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/LinkNode/link_bus.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/LinkNode/link_in_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/LinkNode/link_out_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/LinkNode/requirements.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/MQTTNode/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/MQTTNode/requirements.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/MessageWriterNode/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/MessageWriterNode/messagereader_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/MessageWriterNode/messagewriter_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OmronCameraNode/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OmronCameraNode/omron_camera_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OmronCameraNode/requirements.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/bitwise_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/blend_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/blur_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/color_space_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/contours_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/denormalize_coords_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/draw_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/edge_detector_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/equirectangular_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/frequency_filter_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/hough_lines_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/morphology_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/normalize_coords_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/paste_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/requirements.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/resize_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/rotate_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/template_match_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/OpenCV/threshold_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/PointInShapeNode/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/PointInShapeNode/point_in_shape_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/PointInShapeNode/requirements.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/PolygonMetricsNode/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/PolygonMetricsNode/polygon_metrics_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/PolygonMetricsNode/requirements.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/QueueLengthProbeNode/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/QueueLengthProbeNode/queue_length_probe_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/Qwen3VLMNode/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/Qwen3VLMNode/requirements.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/RESTEndpointNode/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/RESTEndpointNode/rest_endpoint_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/RangeNode/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/RangeNode/requirements.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/RateProbeNode/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/RateProbeNode/rate_probe_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/RateProbeNode/requirements.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/RoboflowUploadNode/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/RoboflowUploadNode/requirements.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/SliceNode/README.md +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/SliceNode/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/SliceNode/merge_predictions_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/SliceNode/requirements.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/SliceNode/slice_collector_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/SliceNode/slice_image_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/SplitNode/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/SplitNode/requirements.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/SwitchNode/requirements.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/SwitchNode/switch_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/SyncNode/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/SyncNode/sync_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/TemplateNode/requirements.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/TemplateNode/template_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/TrackerNode/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/TrackerNode/supervision_tracker_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/TrackerNode/tracker_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/UltralyticsNode/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/UltralyticsNode/model_paths.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/UltralyticsNode/requirements.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/UnknownNode/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/VLLMNode/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/VLLMNode/vllm_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/VideoReaderNode/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/VideoReaderNode/video_reader_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/VideoWriterNode/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/VideoWriterNode/video_writer_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/WebhookNode/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/mDNSNode/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/mDNSNode/mdns_broadcast_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/mDNSNode/requirements.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/nodes/messages.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/server.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/examples/01-hello-world.json +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/examples/02-function-transform.json +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/examples/03-switch-routing.json +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/examples/04-flow-control.json +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/examples/05-webcam-viewer.json +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/examples/06-webcam-yolo.json +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/examples/07-blur-people.json +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/examples/08-video-yolo-recorder.json +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/examples/09-mqtt-pub-sub.json +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/examples/10-person-alert-mqtt.json +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/examples/README.md +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/examples/manifest.json +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/images/favicon4.png +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/index.html +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/js/README.md +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/js/auth.js +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/js/clipboard.js +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/js/config.js +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/js/connections.js +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/js/debug.js +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/js/history.js +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/js/minimap.js +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/js/mqtt-services.js +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/js/palette.js +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/js/selection.js +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/js/state.js +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/js/viewport.js +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/js/workflow.js +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/static/js/workflows.js +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/workflow_engine.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode/workflow_manager.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode_flow.egg-info/dependency_links.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode_flow.egg-info/entry_points.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode_flow.egg-info/requires.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/pynode_flow.egg-info/top_level.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/requirements.txt +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/setup.bat +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/setup.cfg +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/setup.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/__init__.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/conftest.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_api_auth.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_api_errors.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_api_nodes.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_api_security.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_api_workflows.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_base_node_messaging.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_camera_node_backend.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_concurrency.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_config.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_counter_sse.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_debug_workflow_scope.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_delay_rate_limit.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_device_detection.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_link_nodes.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_message_reader_pickle.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_models_dir.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_mqtt_services.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_node_registration.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_node_types_api.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_persistence.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_send_copy_semantics.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_serialization.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_video_reader_node.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_workflow_engine.py +0 -0
- {pynode_flow-0.2.3 → pynode_flow-0.2.4}/tests/test_workflow_roundtrip.py +0 -0
|
@@ -152,6 +152,8 @@ Thumbs.db
|
|
|
152
152
|
*.json
|
|
153
153
|
# ...but keep the bundled, GUI-loadable example workflows
|
|
154
154
|
!pynode/static/examples/*.json
|
|
155
|
+
# ...and the example Node-RED flow shipped with the SocketNode nodes
|
|
156
|
+
!pynode/nodes/SocketNode/interop/*.json
|
|
155
157
|
|
|
156
158
|
# PyTorch model files
|
|
157
159
|
*.pt
|
|
@@ -65,6 +65,13 @@ The following directories are mounted for persistence:
|
|
|
65
65
|
|
|
66
66
|
## Building
|
|
67
67
|
|
|
68
|
+
### Version
|
|
69
|
+
|
|
70
|
+
The version shown in the UI is derived automatically from git history during
|
|
71
|
+
the Docker build — no extra flags or environment variables are needed. Tagged
|
|
72
|
+
commits display a clean version (e.g. `v0.2.3`); untagged commits include the
|
|
73
|
+
distance and short SHA (e.g. `v0.2.3-1-gcd272cf`).
|
|
74
|
+
|
|
68
75
|
### Custom CUDA Version
|
|
69
76
|
|
|
70
77
|
To use a different CUDA version, edit the `Dockerfile`:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
FROM docker.io/nvidia/cuda:12.6.0-
|
|
1
|
+
FROM docker.io/nvidia/cuda:12.6.0-runtime-ubuntu22.04
|
|
2
2
|
|
|
3
3
|
# Set environment variables
|
|
4
4
|
ENV DEBIAN_FRONTEND=noninteractive
|
|
@@ -12,6 +12,7 @@ RUN apt-get update && apt-get install -y \
|
|
|
12
12
|
python3-venv \
|
|
13
13
|
git \
|
|
14
14
|
wget \
|
|
15
|
+
gosu \
|
|
15
16
|
gfortran \
|
|
16
17
|
libgl1-mesa-glx \
|
|
17
18
|
libgtk-3-0 \
|
|
@@ -44,16 +45,29 @@ WORKDIR /app
|
|
|
44
45
|
# Copy application files
|
|
45
46
|
COPY . /app/
|
|
46
47
|
|
|
47
|
-
#
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
# Derive the display version from git history, then remove .git to save
|
|
49
|
+
# space. SETUPTOOLS_SCM_PRETEND_VERSION keeps pip install happy (PEP 440);
|
|
50
|
+
# the real version string is written to _version.py afterwards.
|
|
51
|
+
RUN chmod +x /app/setup.sh /app/install_nodes.sh && \
|
|
52
|
+
RAW=$(git -C /app describe --tags --always 2>/dev/null || true) && \
|
|
53
|
+
if echo "$RAW" | grep -qE '^v?[0-9]+\.[0-9]+'; then \
|
|
54
|
+
VERSION=$(echo "$RAW" | sed 's/^v//'); \
|
|
55
|
+
elif [ -n "$RAW" ]; then \
|
|
56
|
+
VERSION="0.0.0+g${RAW}"; \
|
|
57
|
+
else \
|
|
58
|
+
VERSION="0.0.0"; \
|
|
59
|
+
fi && \
|
|
60
|
+
rm -rf /app/.git && \
|
|
61
|
+
SETUPTOOLS_SCM_PRETEND_VERSION=0.0.0 bash -c "source /app/setup.sh < /dev/null" && \
|
|
62
|
+
printf '__version__ = "%s"\n' "$VERSION" > /app/pynode/_version.py
|
|
52
63
|
|
|
53
64
|
# Create a non-root user and give it ownership of the app directory
|
|
54
65
|
RUN useradd --create-home --uid 1000 appuser \
|
|
55
66
|
&& chown -R appuser:appuser /app
|
|
56
|
-
|
|
67
|
+
|
|
68
|
+
# Entrypoint ensures bind-mounted directories are writable by appuser
|
|
69
|
+
COPY docker-entrypoint.sh /app/docker-entrypoint.sh
|
|
70
|
+
RUN chmod +x /app/docker-entrypoint.sh
|
|
57
71
|
|
|
58
72
|
# Expose port
|
|
59
73
|
EXPOSE 5000
|
|
@@ -62,5 +76,5 @@ EXPOSE 5000
|
|
|
62
76
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=40s --retries=3 \
|
|
63
77
|
CMD ["/app/appenv/bin/python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:5000/')"]
|
|
64
78
|
|
|
65
|
-
|
|
79
|
+
ENTRYPOINT ["/app/docker-entrypoint.sh"]
|
|
66
80
|
CMD ["/app/appenv/bin/python", "-m", "pynode", "--production", "--host", "0.0.0.0", "--port", "5000"]
|
|
@@ -6,6 +6,7 @@ ENV PYTHONUNBUFFERED=1
|
|
|
6
6
|
|
|
7
7
|
# Install system dependencies
|
|
8
8
|
RUN apt-get update && apt-get install -y \
|
|
9
|
+
gosu \
|
|
9
10
|
libgl1-mesa-glx \
|
|
10
11
|
libglib2.0-0 \
|
|
11
12
|
libsm6 \
|
|
@@ -19,15 +20,24 @@ WORKDIR /app
|
|
|
19
20
|
# Copy application files
|
|
20
21
|
COPY . /app/
|
|
21
22
|
|
|
22
|
-
#
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
# Derive the display version from git history, then remove .git to save
|
|
24
|
+
# space. SETUPTOOLS_SCM_PRETEND_VERSION keeps pip install happy (PEP 440);
|
|
25
|
+
# the real version string is written to _version.py afterwards.
|
|
26
|
+
RUN RAW=$(git describe --tags --always 2>/dev/null || true) && \
|
|
27
|
+
if echo "$RAW" | grep -qE '^v?[0-9]+\.[0-9]+'; then \
|
|
28
|
+
VERSION=$(echo "$RAW" | sed 's/^v//'); \
|
|
29
|
+
elif [ -n "$RAW" ]; then \
|
|
30
|
+
VERSION="0.0.0+g${RAW}"; \
|
|
31
|
+
else \
|
|
32
|
+
VERSION="0.0.0"; \
|
|
33
|
+
fi && \
|
|
34
|
+
rm -rf /app/.git && \
|
|
35
|
+
python3 -m venv appenv && \
|
|
36
|
+
. appenv/bin/activate && \
|
|
28
37
|
pip install --upgrade pip && \
|
|
29
38
|
pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu && \
|
|
30
|
-
pip install -e ".[vision,mqtt]"
|
|
39
|
+
SETUPTOOLS_SCM_PRETEND_VERSION=0.0.0 pip install -e ".[vision,mqtt]" && \
|
|
40
|
+
printf '__version__ = "%s"\n' "$VERSION" > /app/pynode/_version.py
|
|
31
41
|
|
|
32
42
|
# Install per-node dependencies (best-effort: a node whose vendor SDK is not
|
|
33
43
|
# on PyPI, e.g. OmronCameraNode's stapipy, is skipped without failing the build)
|
|
@@ -36,7 +46,10 @@ RUN . appenv/bin/activate && pynode-install-nodes || true
|
|
|
36
46
|
# Create a non-root user and give it ownership of the app directory
|
|
37
47
|
RUN useradd --create-home --uid 1000 appuser \
|
|
38
48
|
&& chown -R appuser:appuser /app
|
|
39
|
-
|
|
49
|
+
|
|
50
|
+
# Entrypoint ensures bind-mounted directories are writable by appuser
|
|
51
|
+
COPY docker-entrypoint.sh /app/docker-entrypoint.sh
|
|
52
|
+
RUN chmod +x /app/docker-entrypoint.sh
|
|
40
53
|
|
|
41
54
|
# Expose port
|
|
42
55
|
EXPOSE 5000
|
|
@@ -45,5 +58,5 @@ EXPOSE 5000
|
|
|
45
58
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=40s --retries=3 \
|
|
46
59
|
CMD ["/app/appenv/bin/python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:5000/')"]
|
|
47
60
|
|
|
48
|
-
|
|
61
|
+
ENTRYPOINT ["/app/docker-entrypoint.sh"]
|
|
49
62
|
CMD ["/app/appenv/bin/python", "-m", "pynode", "--production", "--host", "0.0.0.0", "--port", "5000"]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
# Ensure bind-mounted directories are writable by appuser (uid 1000).
|
|
5
|
+
# docker-compose mounts host directories that may be owned by root.
|
|
6
|
+
for dir in /app/workflows /app/workflows/services /app/logs; do
|
|
7
|
+
mkdir -p "$dir"
|
|
8
|
+
chown -R 1000:1000 "$dir" 2>/dev/null || true
|
|
9
|
+
done
|
|
10
|
+
|
|
11
|
+
# Drop privileges and exec the CMD
|
|
12
|
+
exec gosu appuser "$@"
|
|
@@ -21,7 +21,11 @@ def _resolve_version() -> str:
|
|
|
21
21
|
try:
|
|
22
22
|
from importlib.metadata import PackageNotFoundError, version
|
|
23
23
|
try:
|
|
24
|
-
|
|
24
|
+
v = version("pynode-flow")
|
|
25
|
+
# 0.0.0 is the placeholder used during Docker builds; skip it so
|
|
26
|
+
# the real version written to _version.py is preferred.
|
|
27
|
+
if v != "0.0.0":
|
|
28
|
+
return v
|
|
25
29
|
except PackageNotFoundError:
|
|
26
30
|
pass
|
|
27
31
|
except Exception:
|
|
@@ -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.4'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 2, 4)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'ga1c7a9d87'
|
|
@@ -349,6 +349,7 @@ def _register_json_route(route, methods, handler_name, endpoint_name):
|
|
|
349
349
|
_, method, error = _resolve_node_handler(node_id, handler_name)
|
|
350
350
|
if error:
|
|
351
351
|
return error
|
|
352
|
+
assert method is not None
|
|
352
353
|
try:
|
|
353
354
|
result = method()
|
|
354
355
|
if result is None:
|
|
@@ -369,6 +370,8 @@ def _register_file_upload_route(route, methods, handler_name, endpoint_name, all
|
|
|
369
370
|
payload, status = error
|
|
370
371
|
return jsonify({'success': False, 'error': payload.get_json()['error']}), status
|
|
371
372
|
|
|
373
|
+
assert method is not None
|
|
374
|
+
|
|
372
375
|
if 'file' not in request.files:
|
|
373
376
|
return jsonify({'success': False, 'error': 'No file provided'}), 400
|
|
374
377
|
|
|
@@ -397,9 +400,10 @@ def _register_stream_route(route, handler_name, endpoint_name):
|
|
|
397
400
|
_, method, error = _resolve_node_handler(node_id, handler_name)
|
|
398
401
|
if error:
|
|
399
402
|
return error
|
|
403
|
+
assert method is not None
|
|
400
404
|
|
|
401
405
|
def generate():
|
|
402
|
-
for img_data in method():
|
|
406
|
+
for img_data in method(): # type: ignore[reportGeneralTypeIssues]
|
|
403
407
|
yield (b'--frame\r\n'
|
|
404
408
|
b'Content-Type: image/jpeg\r\n\r\n' + img_data + b'\r\n')
|
|
405
409
|
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Slider control node - an interactive slider on the node card that stamps its
|
|
3
|
+
current value onto a configurable path of every message flowing through.
|
|
4
|
+
|
|
5
|
+
Drag the slider to tune a value live and watch the effect ripple through the
|
|
6
|
+
downstream pipeline in real time (no redeploy). Because the node has an input
|
|
7
|
+
and an output and only sets one path, several can be daisy-chained to control
|
|
8
|
+
a compound value - e.g. four sliders writing bbox[0..3] to drive a Crop node's
|
|
9
|
+
detection box.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from typing import Any, Dict
|
|
13
|
+
|
|
14
|
+
from pynode.nodes.base_node import BaseNode, Info, MessageKeys
|
|
15
|
+
|
|
16
|
+
_info = Info()
|
|
17
|
+
_info.add_text("Interactive slider. Drag it on the node card to set a value; "
|
|
18
|
+
"each message passing through has that value written to the "
|
|
19
|
+
"configured path and is then forwarded. Changes take effect "
|
|
20
|
+
"live on the next message - no redeploy needed.")
|
|
21
|
+
_info.add_header("Inputs")
|
|
22
|
+
_info.add_bullets(
|
|
23
|
+
("Input 0:", "Any message. The slider value is written to Target Path, "
|
|
24
|
+
"then the message is forwarded."),
|
|
25
|
+
)
|
|
26
|
+
_info.add_header("Outputs")
|
|
27
|
+
_info.add_bullets(
|
|
28
|
+
("Output 0:", "The same message with Target Path set to the slider value."),
|
|
29
|
+
)
|
|
30
|
+
_info.add_header("Configuration")
|
|
31
|
+
_info.add_bullets(
|
|
32
|
+
("Target Path:", "Dot/bracket path to set, e.g. 'payload.threshold', "
|
|
33
|
+
"'payload.crop.x', or 'payload.detections[0].bbox[2]'. "
|
|
34
|
+
"Intermediate objects/lists are created as needed."),
|
|
35
|
+
("Min / Max:", "Slider range (e.g. 0 and 1)."),
|
|
36
|
+
("Step:", "Slider granularity (e.g. 0.01). A whole-number step emits "
|
|
37
|
+
"integers, otherwise floats."),
|
|
38
|
+
("Value:", "Current/initial value; persisted with the workflow."),
|
|
39
|
+
("Label:", "Optional caption shown on the node card."),
|
|
40
|
+
("Send on Change:", "When on, moving the slider emits a message (value "
|
|
41
|
+
"at Target Path) immediately - even with no input "
|
|
42
|
+
"wired - so the slider can act as a live source."),
|
|
43
|
+
)
|
|
44
|
+
_info.add_header("Tips")
|
|
45
|
+
_info.add_bullets(
|
|
46
|
+
("Daisy-chain:", "Wire several sliders in series to control a compound "
|
|
47
|
+
"value - 4 sliders -> a crop box, 2 -> an x/y point. "
|
|
48
|
+
"Leave Send on Change OFF for this - the frames drive it."),
|
|
49
|
+
("As a source:", "Turn Send on Change ON and leave the input unwired to "
|
|
50
|
+
"drive a downstream value purely by dragging."),
|
|
51
|
+
("Live tuning:", "Feed a steady message stream (e.g. a camera) and the "
|
|
52
|
+
"downstream result updates as you drag."),
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class ControlSliderNode(BaseNode):
|
|
57
|
+
"""Stamps an interactive slider's value onto a message path, then forwards."""
|
|
58
|
+
|
|
59
|
+
display_name = 'Slider'
|
|
60
|
+
icon = '🎚'
|
|
61
|
+
category = 'function'
|
|
62
|
+
color = '#D8C7ED'
|
|
63
|
+
border_color = '#A98FC9'
|
|
64
|
+
text_color = '#000000'
|
|
65
|
+
input_count = 1
|
|
66
|
+
output_count = 1
|
|
67
|
+
info = str(_info)
|
|
68
|
+
|
|
69
|
+
ui_component = 'slider'
|
|
70
|
+
|
|
71
|
+
# UI-triggerable action: the on-card slider POSTs its live value here.
|
|
72
|
+
actions = ['set_value']
|
|
73
|
+
|
|
74
|
+
DEFAULT_CONFIG = {
|
|
75
|
+
'path': 'msg.payload',
|
|
76
|
+
'min': 0,
|
|
77
|
+
'max': 1,
|
|
78
|
+
'step': 0.01,
|
|
79
|
+
'value': 0.5,
|
|
80
|
+
'label': '',
|
|
81
|
+
'send_on_change': False,
|
|
82
|
+
# A control shouldn't silently drop frames while tuning a pipeline.
|
|
83
|
+
MessageKeys.DROP_MESSAGES: False,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
properties = [
|
|
87
|
+
{
|
|
88
|
+
'name': 'path',
|
|
89
|
+
'label': 'Target Path',
|
|
90
|
+
'type': 'text',
|
|
91
|
+
'default': DEFAULT_CONFIG['path'],
|
|
92
|
+
'help': "Message path to set, e.g. payload.crop.x or "
|
|
93
|
+
"payload.detections[0].bbox[2]",
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
'name': 'min',
|
|
97
|
+
'label': 'Min',
|
|
98
|
+
'type': 'number',
|
|
99
|
+
'default': DEFAULT_CONFIG['min'],
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
'name': 'max',
|
|
103
|
+
'label': 'Max',
|
|
104
|
+
'type': 'number',
|
|
105
|
+
'default': DEFAULT_CONFIG['max'],
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
'name': 'step',
|
|
109
|
+
'label': 'Step',
|
|
110
|
+
'type': 'number',
|
|
111
|
+
'default': DEFAULT_CONFIG['step'],
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
'name': 'value',
|
|
115
|
+
'label': 'Value',
|
|
116
|
+
'type': 'number',
|
|
117
|
+
'default': DEFAULT_CONFIG['value'],
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
'name': 'label',
|
|
121
|
+
'label': 'Label',
|
|
122
|
+
'type': 'text',
|
|
123
|
+
'default': DEFAULT_CONFIG['label'],
|
|
124
|
+
'help': 'Optional caption shown on the node card',
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
'name': 'send_on_change',
|
|
128
|
+
'label': 'Send on Change',
|
|
129
|
+
'type': 'checkbox',
|
|
130
|
+
'default': DEFAULT_CONFIG['send_on_change'],
|
|
131
|
+
'help': 'Emit a message every time the slider moves, even with no '
|
|
132
|
+
'input wired (use as a live source). Redeploy to apply.',
|
|
133
|
+
},
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
def __init__(self, node_id=None, name="slider"):
|
|
137
|
+
super().__init__(node_id, name)
|
|
138
|
+
# Live value set from the UI; None until set, then falls back to config.
|
|
139
|
+
self._live_value = None
|
|
140
|
+
|
|
141
|
+
def _coerce(self, raw: Any) -> Any:
|
|
142
|
+
"""Parse to a number, clamp to [min, max], and pick int vs float.
|
|
143
|
+
|
|
144
|
+
A whole-number result is returned as ``int`` so paths like a bbox
|
|
145
|
+
pixel come out as ``200`` rather than ``200.0``; fractional values
|
|
146
|
+
stay ``float``.
|
|
147
|
+
"""
|
|
148
|
+
try:
|
|
149
|
+
val = float(raw)
|
|
150
|
+
except (TypeError, ValueError):
|
|
151
|
+
val = self.get_config_float('value', float(self.DEFAULT_CONFIG['value']))
|
|
152
|
+
|
|
153
|
+
lo = self.get_config_float('min', float(self.DEFAULT_CONFIG['min']))
|
|
154
|
+
hi = self.get_config_float('max', float(self.DEFAULT_CONFIG['max']))
|
|
155
|
+
if lo > hi:
|
|
156
|
+
lo, hi = hi, lo
|
|
157
|
+
val = max(lo, min(hi, val))
|
|
158
|
+
|
|
159
|
+
return int(val) if val.is_integer() else val
|
|
160
|
+
|
|
161
|
+
def _active_value(self) -> Any:
|
|
162
|
+
"""The value to stamp: the live UI value if set, else the config value."""
|
|
163
|
+
raw = self._live_value if self._live_value is not None else self.config.get('value', 0)
|
|
164
|
+
return self._coerce(raw)
|
|
165
|
+
|
|
166
|
+
def set_value(self, value: Any):
|
|
167
|
+
"""UI action: update the live slider value.
|
|
168
|
+
|
|
169
|
+
The new value is applied to the next incoming message. When
|
|
170
|
+
``send_on_change`` is enabled it is ALSO emitted immediately as a
|
|
171
|
+
fresh message, so dragging drives downstream nodes with no input
|
|
172
|
+
wired. Called on the deployed node from the Flask action thread -
|
|
173
|
+
the same place InjectNode.inject() sends from, so ``send()`` here is
|
|
174
|
+
safe.
|
|
175
|
+
"""
|
|
176
|
+
self._live_value = self._coerce(value)
|
|
177
|
+
if self.get_config_bool('send_on_change', False):
|
|
178
|
+
self._emit_value()
|
|
179
|
+
|
|
180
|
+
def _emit_value(self):
|
|
181
|
+
"""Emit a fresh message carrying the current value at the target path."""
|
|
182
|
+
msg = self.create_message()
|
|
183
|
+
path = str(self.config.get('path', '')).strip()
|
|
184
|
+
try:
|
|
185
|
+
if path:
|
|
186
|
+
self._set_nested_value(msg, path, self._active_value())
|
|
187
|
+
else:
|
|
188
|
+
# No path: the value is the payload.
|
|
189
|
+
msg[MessageKeys.PAYLOAD] = self._active_value()
|
|
190
|
+
except Exception as e:
|
|
191
|
+
self.report_error(f"Slider: could not set '{path}': {e}")
|
|
192
|
+
return
|
|
193
|
+
self.send(msg)
|
|
194
|
+
|
|
195
|
+
def on_start(self):
|
|
196
|
+
"""Seed the live value from config so it is active right after deploy."""
|
|
197
|
+
super().on_start()
|
|
198
|
+
self._live_value = self._coerce(self.config.get('value', 0))
|
|
199
|
+
|
|
200
|
+
def on_input(self, msg: Dict[str, Any], input_index: int = 0):
|
|
201
|
+
"""Write the current value to the target path and forward the message."""
|
|
202
|
+
path = str(self.config.get('path', '')).strip()
|
|
203
|
+
if path:
|
|
204
|
+
try:
|
|
205
|
+
self._set_nested_value(msg, path, self._active_value())
|
|
206
|
+
except Exception as e:
|
|
207
|
+
self.report_error(f"Slider: could not set '{path}': {e}")
|
|
208
|
+
self.send(msg)
|