pynode-flow 0.2.0__py3-none-any.whl
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/__init__.py +37 -0
- pynode/__main__.py +9 -0
- pynode/_perf.py +65 -0
- pynode/_version.py +24 -0
- pynode/api/__init__.py +15 -0
- pynode/api/helpers.py +79 -0
- pynode/api/nodes.py +423 -0
- pynode/api/services.py +124 -0
- pynode/api/sse.py +58 -0
- pynode/api/uploads.py +59 -0
- pynode/api/workflows.py +466 -0
- pynode/config.py +93 -0
- pynode/install_nodes.py +143 -0
- pynode/main.py +118 -0
- pynode/node_registry.py +156 -0
- pynode/nodes/AutoSyncNode/auto_sync_node.py +151 -0
- pynode/nodes/BBoxMetricsNode/__init__.py +3 -0
- pynode/nodes/BBoxMetricsNode/bbox_metrics_node.py +250 -0
- pynode/nodes/BBoxMetricsNode/requirements.txt +1 -0
- pynode/nodes/BatchNode/__init__.py +3 -0
- pynode/nodes/BatchNode/batch_node.py +86 -0
- pynode/nodes/BatchNode/requirements.txt +1 -0
- pynode/nodes/CameraNode/__init__.py +4 -0
- pynode/nodes/CameraNode/camera_node.py +204 -0
- pynode/nodes/CameraNode/requirements.txt +2 -0
- pynode/nodes/ChangeNode/__init__.py +1 -0
- pynode/nodes/ChangeNode/change_node.py +255 -0
- pynode/nodes/ChangeNode/requirements.txt +1 -0
- pynode/nodes/ConfidenceFilterNode/__init__.py +4 -0
- pynode/nodes/ConfidenceFilterNode/confidence_filter_node.py +208 -0
- pynode/nodes/ConfidenceFilterNode/requirements.txt +1 -0
- pynode/nodes/CounterNode/__init__.py +3 -0
- pynode/nodes/CounterNode/counter_node.py +191 -0
- pynode/nodes/CropNode/__init__.py +4 -0
- pynode/nodes/CropNode/crop_node.py +317 -0
- pynode/nodes/CropNode/requirements.txt +2 -0
- pynode/nodes/DebugNode/__init__.py +4 -0
- pynode/nodes/DebugNode/debug_node.py +169 -0
- pynode/nodes/DebugNode/requirements.txt +1 -0
- pynode/nodes/DelayNode/delay_node.py +218 -0
- pynode/nodes/DelayNode/requirements.txt +1 -0
- pynode/nodes/DrawPredictionsNode/__init__.py +3 -0
- pynode/nodes/DrawPredictionsNode/draw_predictions_node.py +365 -0
- pynode/nodes/DrawPredictionsNode/requirements.txt +2 -0
- pynode/nodes/ErrorNode/error_node.py +94 -0
- pynode/nodes/ErrorNode/requirements.txt +1 -0
- pynode/nodes/FilterNode/__init__.py +3 -0
- pynode/nodes/FilterNode/filter_node.py +102 -0
- pynode/nodes/FilterNode/requirements.txt +1 -0
- pynode/nodes/FrameSourceNode/__init__.py +4 -0
- pynode/nodes/FrameSourceNode/frame_source_node.py +319 -0
- pynode/nodes/FrameSourceNode/requirements.txt +1 -0
- pynode/nodes/FunctionNode/__init__.py +3 -0
- pynode/nodes/FunctionNode/function_node.py +168 -0
- pynode/nodes/FunctionNode/requirements.txt +1 -0
- pynode/nodes/GateNode/gate_node.py +60 -0
- pynode/nodes/GateNode/requirements.txt +1 -0
- pynode/nodes/GetiUploadNode/__init__.py +3 -0
- pynode/nodes/GetiUploadNode/geti_upload_node.py +278 -0
- pynode/nodes/GetiUploadNode/requirements.txt +1 -0
- pynode/nodes/ImageFormatNode/__init__.py +4 -0
- pynode/nodes/ImageFormatNode/image_format_node.py +310 -0
- pynode/nodes/ImageFormatNode/requirements.txt +2 -0
- pynode/nodes/ImageUploadNode/image_upload_node.py +96 -0
- pynode/nodes/ImageViewerNode/__init__.py +4 -0
- pynode/nodes/ImageViewerNode/image_viewer_node.py +162 -0
- pynode/nodes/ImageViewerNode/requirements.txt +2 -0
- pynode/nodes/ImageWriterNode/__init__.py +3 -0
- pynode/nodes/ImageWriterNode/imagewriter_node.py +352 -0
- pynode/nodes/ImageWriterNode/requirements.txt +1 -0
- pynode/nodes/InferenceNode/InferenceEngine/__init__.py +15 -0
- pynode/nodes/InferenceNode/InferenceEngine/device_detection.py +149 -0
- pynode/nodes/InferenceNode/InferenceEngine/engines/base_engine.py +155 -0
- pynode/nodes/InferenceNode/InferenceEngine/engines/example_engine_template.py +272 -0
- pynode/nodes/InferenceNode/InferenceEngine/engines/geti_engine.py +309 -0
- pynode/nodes/InferenceNode/InferenceEngine/engines/onnx_engine.py +346 -0
- pynode/nodes/InferenceNode/InferenceEngine/engines/pass_engine.py +86 -0
- pynode/nodes/InferenceNode/InferenceEngine/engines/ultralytics_engine.py +863 -0
- pynode/nodes/InferenceNode/InferenceEngine/inference_engine_factory.py +533 -0
- pynode/nodes/InferenceNode/InferenceEngine/readme.md +107 -0
- pynode/nodes/InferenceNode/InferenceEngine/result_converters.py +121 -0
- pynode/nodes/InferenceNode/inference_node.py +451 -0
- pynode/nodes/InferenceNode/requirements.txt +13 -0
- pynode/nodes/InjectNode/inject_node.py +245 -0
- pynode/nodes/InjectNode/requirements.txt +1 -0
- pynode/nodes/JoinNode/__init__.py +3 -0
- pynode/nodes/JoinNode/join_node.py +156 -0
- pynode/nodes/JoinNode/requirements.txt +1 -0
- pynode/nodes/LabelFilterNode/__init__.py +3 -0
- pynode/nodes/LabelFilterNode/label_filter_node.py +188 -0
- pynode/nodes/LabelFilterNode/requirements.txt +1 -0
- pynode/nodes/MQTTNode/__init__.py +8 -0
- pynode/nodes/MQTTNode/mqtt_in_node.py +148 -0
- pynode/nodes/MQTTNode/mqtt_out_node.py +157 -0
- pynode/nodes/MQTTNode/mqtt_service.py +367 -0
- pynode/nodes/MQTTNode/requirements.txt +1 -0
- pynode/nodes/MessageWriterNode/__init__.py +7 -0
- pynode/nodes/MessageWriterNode/messagereader_node.py +568 -0
- pynode/nodes/MessageWriterNode/messagewriter_node.py +505 -0
- pynode/nodes/OmronCameraNode/__init__.py +3 -0
- pynode/nodes/OmronCameraNode/omron_camera_node.py +652 -0
- pynode/nodes/OmronCameraNode/requirements.txt +4 -0
- pynode/nodes/OpenCV/__init__.py +1 -0
- pynode/nodes/OpenCV/arithmetic_node.py +376 -0
- pynode/nodes/OpenCV/bitwise_node.py +127 -0
- pynode/nodes/OpenCV/blend_node.py +127 -0
- pynode/nodes/OpenCV/blob_detector_node.py +233 -0
- pynode/nodes/OpenCV/blur_node.py +141 -0
- pynode/nodes/OpenCV/color_space_node.py +139 -0
- pynode/nodes/OpenCV/colormap_node.py +262 -0
- pynode/nodes/OpenCV/contours_node.py +258 -0
- pynode/nodes/OpenCV/denormalize_coords_node.py +365 -0
- pynode/nodes/OpenCV/draw_node.py +312 -0
- pynode/nodes/OpenCV/edge_detector_node.py +177 -0
- pynode/nodes/OpenCV/equirectangular_node.py +447 -0
- pynode/nodes/OpenCV/fft_node.py +102 -0
- pynode/nodes/OpenCV/focus_estimation_node.py +189 -0
- pynode/nodes/OpenCV/frequency_filter_node.py +241 -0
- pynode/nodes/OpenCV/histogram_node.py +160 -0
- pynode/nodes/OpenCV/hough_circles_node.py +224 -0
- pynode/nodes/OpenCV/hough_lines_node.py +243 -0
- pynode/nodes/OpenCV/in_range_node.py +186 -0
- pynode/nodes/OpenCV/morphology_node.py +136 -0
- pynode/nodes/OpenCV/normalize_coords_node.py +344 -0
- pynode/nodes/OpenCV/paste_node.py +244 -0
- pynode/nodes/OpenCV/perspective_node.py +174 -0
- pynode/nodes/OpenCV/realsense_depth_node.py +253 -0
- pynode/nodes/OpenCV/requirements.txt +2 -0
- pynode/nodes/OpenCV/resize_node.py +175 -0
- pynode/nodes/OpenCV/rotate_node.py +159 -0
- pynode/nodes/OpenCV/template_match_node.py +227 -0
- pynode/nodes/OpenCV/threshold_node.py +167 -0
- pynode/nodes/PointInShapeNode/__init__.py +3 -0
- pynode/nodes/PointInShapeNode/point_in_shape_node.py +313 -0
- pynode/nodes/PointInShapeNode/requirements.txt +2 -0
- pynode/nodes/PolygonMetricsNode/__init__.py +3 -0
- pynode/nodes/PolygonMetricsNode/polygon_metrics_node.py +331 -0
- pynode/nodes/PolygonMetricsNode/requirements.txt +2 -0
- pynode/nodes/QueueLengthProbeNode/__init__.py +3 -0
- pynode/nodes/QueueLengthProbeNode/queue_length_probe_node.py +116 -0
- pynode/nodes/Qwen3VLMNode/__init__.py +3 -0
- pynode/nodes/Qwen3VLMNode/qwen3_vlm_node.py +519 -0
- pynode/nodes/Qwen3VLMNode/requirements.txt +5 -0
- pynode/nodes/RESTEndpointNode/__init__.py +3 -0
- pynode/nodes/RESTEndpointNode/rest_endpoint_node.py +491 -0
- pynode/nodes/RangeNode/__init__.py +3 -0
- pynode/nodes/RangeNode/range_node.py +115 -0
- pynode/nodes/RangeNode/requirements.txt +1 -0
- pynode/nodes/RateProbeNode/__init__.py +3 -0
- pynode/nodes/RateProbeNode/rate_probe_node.py +183 -0
- pynode/nodes/RateProbeNode/requirements.txt +1 -0
- pynode/nodes/RoboflowUploadNode/__init__.py +3 -0
- pynode/nodes/RoboflowUploadNode/requirements.txt +1 -0
- pynode/nodes/RoboflowUploadNode/roboflow_upload_node.py +229 -0
- pynode/nodes/SliceNode/README.md +149 -0
- pynode/nodes/SliceNode/__init__.py +5 -0
- pynode/nodes/SliceNode/merge_predictions_node.py +444 -0
- pynode/nodes/SliceNode/requirements.txt +3 -0
- pynode/nodes/SliceNode/slice_collector_node.py +370 -0
- pynode/nodes/SliceNode/slice_image_node.py +380 -0
- pynode/nodes/SplitNode/__init__.py +3 -0
- pynode/nodes/SplitNode/requirements.txt +1 -0
- pynode/nodes/SplitNode/split_node.py +155 -0
- pynode/nodes/SwitchNode/requirements.txt +1 -0
- pynode/nodes/SwitchNode/switch_node.py +259 -0
- pynode/nodes/SyncNode/__init__.py +3 -0
- pynode/nodes/SyncNode/sync_node.py +287 -0
- pynode/nodes/TemplateNode/requirements.txt +1 -0
- pynode/nodes/TemplateNode/template_node.py +86 -0
- pynode/nodes/TrackerNode/__init__.py +2 -0
- pynode/nodes/TrackerNode/supervision_tracker_node.py +248 -0
- pynode/nodes/TrackerNode/tracker_node.py +745 -0
- pynode/nodes/UltralyticsNode/__init__.py +4 -0
- pynode/nodes/UltralyticsNode/requirements.txt +2 -0
- pynode/nodes/UltralyticsNode/ultralytics_node.py +364 -0
- pynode/nodes/UnknownNode/__init__.py +93 -0
- pynode/nodes/VLLMNode/__init__.py +3 -0
- pynode/nodes/VLLMNode/vllm_node.py +345 -0
- pynode/nodes/VideoReaderNode/__init__.py +3 -0
- pynode/nodes/VideoReaderNode/video_reader_node.py +425 -0
- pynode/nodes/VideoWriterNode/__init__.py +3 -0
- pynode/nodes/VideoWriterNode/video_writer_node.py +441 -0
- pynode/nodes/WebhookNode/__init__.py +3 -0
- pynode/nodes/WebhookNode/webhook_node.py +591 -0
- pynode/nodes/__init__.py +109 -0
- pynode/nodes/base_node.py +562 -0
- pynode/nodes/image_utils.py +291 -0
- pynode/nodes/info.py +109 -0
- pynode/nodes/mDNSNode/__init__.py +13 -0
- pynode/nodes/mDNSNode/mdns_broadcast_node.py +214 -0
- pynode/nodes/mDNSNode/mdns_discovery_node.py +362 -0
- pynode/nodes/mDNSNode/mdns_manager.py +399 -0
- pynode/nodes/mDNSNode/requirements.txt +1 -0
- pynode/nodes/messages.py +81 -0
- pynode/server.py +187 -0
- pynode/static/images/favicon4.png +0 -0
- pynode/static/index.html +200 -0
- pynode/static/js/README.md +139 -0
- pynode/static/js/auth.js +89 -0
- pynode/static/js/clipboard.js +152 -0
- pynode/static/js/config.js +14 -0
- pynode/static/js/connections.js +844 -0
- pynode/static/js/debug.js +524 -0
- pynode/static/js/events.js +775 -0
- pynode/static/js/history.js +199 -0
- pynode/static/js/main.js +55 -0
- pynode/static/js/minimap.js +242 -0
- pynode/static/js/nodes.js +995 -0
- pynode/static/js/palette.js +255 -0
- pynode/static/js/properties.js +1270 -0
- pynode/static/js/selection.js +241 -0
- pynode/static/js/state.js +211 -0
- pynode/static/js/ui-utils.js +28 -0
- pynode/static/js/viewport.js +204 -0
- pynode/static/js/workflow.js +469 -0
- pynode/static/js/workflows.js +344 -0
- pynode/static/style.css +2751 -0
- pynode/workflow_engine.py +483 -0
- pynode/workflow_manager.py +440 -0
- pynode_flow-0.2.0.dist-info/METADATA +520 -0
- pynode_flow-0.2.0.dist-info/RECORD +227 -0
- pynode_flow-0.2.0.dist-info/WHEEL +5 -0
- pynode_flow-0.2.0.dist-info/entry_points.txt +3 -0
- pynode_flow-0.2.0.dist-info/licenses/LICENSE +21 -0
- pynode_flow-0.2.0.dist-info/scm_file_list.json +270 -0
- pynode_flow-0.2.0.dist-info/scm_version.json +8 -0
- pynode_flow-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pynode-flow
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Python based Node-RED-like visual workflow system
|
|
5
|
+
Author: olkham
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/olkham/pynode
|
|
8
|
+
Project-URL: Repository, https://github.com/olkham/pynode
|
|
9
|
+
Project-URL: Issues, https://github.com/olkham/pynode/issues
|
|
10
|
+
Keywords: node-red,workflow,visual-programming,flask,computer-vision,yolo,no-code
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Framework :: Flask
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Image Recognition
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
20
|
+
Classifier: Operating System :: OS Independent
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: flask>=3.0
|
|
25
|
+
Requires-Dist: flask-cors>=4.0
|
|
26
|
+
Requires-Dist: waitress>=2.1
|
|
27
|
+
Requires-Dist: numpy
|
|
28
|
+
Requires-Dist: opencv-python
|
|
29
|
+
Provides-Extra: vision
|
|
30
|
+
Requires-Dist: ultralytics; extra == "vision"
|
|
31
|
+
Requires-Dist: torch; extra == "vision"
|
|
32
|
+
Requires-Dist: torchvision; extra == "vision"
|
|
33
|
+
Requires-Dist: supervision; extra == "vision"
|
|
34
|
+
Provides-Extra: mqtt
|
|
35
|
+
Requires-Dist: paho-mqtt; extra == "mqtt"
|
|
36
|
+
Provides-Extra: camera
|
|
37
|
+
Requires-Dist: framesource[full]; extra == "camera"
|
|
38
|
+
Provides-Extra: inference
|
|
39
|
+
Requires-Dist: pynode-flow[vision]; extra == "inference"
|
|
40
|
+
Requires-Dist: onnxruntime; extra == "inference"
|
|
41
|
+
Requires-Dist: geti-sdk>=2.0.0; extra == "inference"
|
|
42
|
+
Provides-Extra: vlm
|
|
43
|
+
Requires-Dist: pynode-flow[vision]; extra == "vlm"
|
|
44
|
+
Requires-Dist: transformers>=4.50.0; extra == "vlm"
|
|
45
|
+
Requires-Dist: qwen-vl-utils>=0.0.8; extra == "vlm"
|
|
46
|
+
Requires-Dist: Pillow>=9.0.0; extra == "vlm"
|
|
47
|
+
Provides-Extra: upload
|
|
48
|
+
Requires-Dist: roboflow>=1.0.0; extra == "upload"
|
|
49
|
+
Requires-Dist: geti-sdk>=2.0.0; extra == "upload"
|
|
50
|
+
Provides-Extra: discovery
|
|
51
|
+
Requires-Dist: zeroconf>=0.38.0; extra == "discovery"
|
|
52
|
+
Provides-Extra: full
|
|
53
|
+
Requires-Dist: pynode-flow[vision]; extra == "full"
|
|
54
|
+
Requires-Dist: pynode-flow[mqtt]; extra == "full"
|
|
55
|
+
Requires-Dist: pynode-flow[camera]; extra == "full"
|
|
56
|
+
Requires-Dist: pynode-flow[inference]; extra == "full"
|
|
57
|
+
Requires-Dist: pynode-flow[vlm]; extra == "full"
|
|
58
|
+
Requires-Dist: pynode-flow[upload]; extra == "full"
|
|
59
|
+
Requires-Dist: pynode-flow[discovery]; extra == "full"
|
|
60
|
+
Provides-Extra: all
|
|
61
|
+
Requires-Dist: pynode-flow[full]; extra == "all"
|
|
62
|
+
Provides-Extra: dev
|
|
63
|
+
Requires-Dist: opencv-stubs; extra == "dev"
|
|
64
|
+
Requires-Dist: types-flask; extra == "dev"
|
|
65
|
+
Requires-Dist: pytest; extra == "dev"
|
|
66
|
+
Dynamic: license-file
|
|
67
|
+
|
|
68
|
+
# <img src="pynode/static/images/favicon4.png" alt="PyNode" width="48" height="48" style="vertical-align: middle;" /> PyNode - Visual Workflow System
|
|
69
|
+
|
|
70
|
+
A Node-RED-like visual workflow editor with a Python backend. Create workflows by connecting Python nodes that process and route messages.
|
|
71
|
+
|
|
72
|
+
https://github.com/user-attachments/assets/0b53085a-2cc6-4c26-bd43-e0de1e0716a2
|
|
73
|
+
|
|
74
|
+
## Features
|
|
75
|
+
|
|
76
|
+
- **Visual Node Editor**: Drag-and-drop interface for creating workflows
|
|
77
|
+
- **Python Backend**: All nodes are Python classes that can be easily extended
|
|
78
|
+
- **Fully Extensible**: Third-party nodes can be added without modifying core code
|
|
79
|
+
- **UI Components**: Nodes can define interactive controls (buttons, toggles, displays) in their cards
|
|
80
|
+
- **Node-RED Compatible Messages**: Message structure with `payload` and `topic` fields
|
|
81
|
+
- **Built-in Nodes**:
|
|
82
|
+
- **InjectNode**: Generate messages with configurable payloads
|
|
83
|
+
- **FunctionNode**: Execute custom Python code on messages
|
|
84
|
+
- **DebugNode**: Display messages in the debug panel
|
|
85
|
+
- **ChangeNode**: Modify message properties
|
|
86
|
+
- **SwitchNode**: Route messages based on conditions
|
|
87
|
+
- **DelayNode**: Delay message delivery
|
|
88
|
+
- **GateNode**: Control message flow with real-time toggle
|
|
89
|
+
- **RateProbeNode**: Monitor message throughput
|
|
90
|
+
- **QueueLengthProbeNode**: Monitor queue lengths
|
|
91
|
+
- **CounterNode**: Count messages
|
|
92
|
+
- **MQTTNode**: MQTT communication
|
|
93
|
+
- **MessageWriterNode**: Save message data
|
|
94
|
+
- **VideoWriterNode**: Save video output
|
|
95
|
+
- **Vision Nodes**: Camera input, YOLO detection, tracking, image processing, display, and file output
|
|
96
|
+
- **REST API**: Complete API for programmatic workflow management
|
|
97
|
+
- **Export/Import**: Save and load workflows as JSON
|
|
98
|
+
- **Dynamic Properties**: Node properties and UI components defined in node classes
|
|
99
|
+
|
|
100
|
+
## Quick Start
|
|
101
|
+
|
|
102
|
+
### Install from PyPI (recommended for users)
|
|
103
|
+
|
|
104
|
+
PyNode is published on PyPI as **`pynode-flow`** (the import package stays `pynode`):
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
# Core install
|
|
108
|
+
pip install pynode-flow
|
|
109
|
+
|
|
110
|
+
# ...or everything PyPI-installable (all optional nodes)
|
|
111
|
+
pip install "pynode-flow[full]"
|
|
112
|
+
|
|
113
|
+
# Run it
|
|
114
|
+
pynode
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
See [INSTALL.md](INSTALL.md) for the full list of extras (`vision`, `mqtt`,
|
|
118
|
+
`camera`, `inference`, `vlm`, `upload`, `discovery`, `full`) and how to install
|
|
119
|
+
per-node dependencies with `pynode-install-nodes`.
|
|
120
|
+
|
|
121
|
+
### Install from source (for development)
|
|
122
|
+
|
|
123
|
+
Clone the repository:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
git clone https://github.com/olkham/pynode.git
|
|
127
|
+
cd pynode
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Option 1: Automated Setup (Recommended)
|
|
131
|
+
|
|
132
|
+
The setup scripts will create a virtual environment, detect CUDA if available, install PyTorch with appropriate GPU support, and install all dependencies.
|
|
133
|
+
|
|
134
|
+
**Windows**:
|
|
135
|
+
```bash
|
|
136
|
+
# Use Python from PATH
|
|
137
|
+
setup.bat
|
|
138
|
+
|
|
139
|
+
# Or specify Python path
|
|
140
|
+
setup.bat "C:\Python312\python.exe"
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**Linux/Mac**:
|
|
144
|
+
```bash
|
|
145
|
+
chmod +x setup.sh
|
|
146
|
+
./setup.sh
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
The scripts will:
|
|
150
|
+
- Create a virtual environment in `appenv/`
|
|
151
|
+
- Detect CUDA version and install matching PyTorch build
|
|
152
|
+
- Install CPU-only PyTorch if CUDA is not detected
|
|
153
|
+
- Install all required dependencies
|
|
154
|
+
- Optionally install node-specific dependencies
|
|
155
|
+
|
|
156
|
+
**Activate the environment**:
|
|
157
|
+
- Windows: `appenv\Scripts\activate.bat`
|
|
158
|
+
- Linux/Mac: `source appenv/bin/activate`
|
|
159
|
+
|
|
160
|
+
### Option 2: Manual Installation
|
|
161
|
+
|
|
162
|
+
If you prefer manual installation or have specific requirements:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
# Core install (extras optional — see INSTALL.md)
|
|
166
|
+
pip install -e .
|
|
167
|
+
|
|
168
|
+
# With optional extras: specific node groups...
|
|
169
|
+
pip install -e ".[vision,mqtt]"
|
|
170
|
+
|
|
171
|
+
# ...or everything PyPI-installable
|
|
172
|
+
pip install -e ".[full]"
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
> `pip install` only pulls the dependencies and extras declared in
|
|
176
|
+
> `pyproject.toml`; it does not run each node's `requirements.txt`. The `[full]`
|
|
177
|
+
> extra covers every PyPI-installable node. For the few nodes that need a vendor
|
|
178
|
+
> SDK (e.g. Omron's `stapipy`), run `pynode-install-nodes` after installing.
|
|
179
|
+
|
|
180
|
+
### Run the Server
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
pynode
|
|
184
|
+
# or
|
|
185
|
+
python -m pynode
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Open Your Browser
|
|
189
|
+
|
|
190
|
+
Navigate to `http://localhost:5000`
|
|
191
|
+
|
|
192
|
+
### Data Directory
|
|
193
|
+
|
|
194
|
+
PyNode persists workflows under `<data dir>/workflows/` (`workflow.json` plus timestamped backups in `_backups/`). The data directory is resolved in this order:
|
|
195
|
+
|
|
196
|
+
1. `pynode --data-dir <path>` CLI flag,
|
|
197
|
+
2. `PYNODE_DATA_DIR` environment variable,
|
|
198
|
+
3. the source checkout root when running from a git clone / editable install (i.e. `pyproject.toml` sits next to the `pynode` package — this keeps the familiar `workflows/` folder in the repo),
|
|
199
|
+
4. `~/.pynode` otherwise (e.g. a regular `pip install`).
|
|
200
|
+
|
|
201
|
+
The resolved location is logged at startup (`Workflow data directory: ...`).
|
|
202
|
+
|
|
203
|
+
## Securing PyNode
|
|
204
|
+
|
|
205
|
+
**PyNode executes arbitrary Python by design** (e.g. FunctionNode runs whatever code is in the workflow), so anyone who can reach the API can run code on the host. Authentication is the trust boundary — secure the server before exposing it beyond your own machine:
|
|
206
|
+
|
|
207
|
+
- **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).
|
|
208
|
+
- **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).
|
|
209
|
+
- **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.
|
|
210
|
+
|
|
211
|
+
## Docker Setup
|
|
212
|
+
|
|
213
|
+
PyNode can be run in a Docker container with GPU support (CUDA 12.6).
|
|
214
|
+
|
|
215
|
+
### Running with Docker Compose
|
|
216
|
+
|
|
217
|
+
For mDNS service discovery to work correctly inside Docker, set the `HOST_IP` environment variable to your host machine's IP address.
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
# Set the host IP address
|
|
221
|
+
export HOST_IP=$(hostname -I | awk '{print $1}')
|
|
222
|
+
|
|
223
|
+
# Start the container
|
|
224
|
+
docker compose up -d
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
The container will:
|
|
228
|
+
- Use NVIDIA CUDA 12.6 runtime (requires nvidia-docker)
|
|
229
|
+
- Install PyTorch with CUDA 12.6 support
|
|
230
|
+
- Install all dependencies including node-specific packages
|
|
231
|
+
- Expose port 5000 for web interface
|
|
232
|
+
- Support mDNS broadcasting with the correct host IP
|
|
233
|
+
|
|
234
|
+
**Why set HOST_IP?**
|
|
235
|
+
When using the mDNS Broadcast Node inside Docker, it needs to advertise the host machine's IP address rather than the container's internal IP so other devices on your network can discover and connect to the service.
|
|
236
|
+
|
|
237
|
+
**Access the application:**
|
|
238
|
+
- Web UI: `http://localhost:5000`
|
|
239
|
+
- From other devices: `http://<your-host-ip>:5000`
|
|
240
|
+
|
|
241
|
+
**GPU Access:**
|
|
242
|
+
The Docker setup requires [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html) to be installed on the host system.
|
|
243
|
+
|
|
244
|
+
For more details, see [DOCKER.md](DOCKER.md).
|
|
245
|
+
|
|
246
|
+
## Extending PyNode
|
|
247
|
+
|
|
248
|
+
PyNode is designed to be easily extended with custom nodes:
|
|
249
|
+
|
|
250
|
+
- **[Creating Custom Nodes](docs/CUSTOM_NODES.md)** - Complete guide to creating your own nodes
|
|
251
|
+
- **[UI Components Guide](docs/UI_COMPONENTS.md)** - Add interactive controls to your nodes
|
|
252
|
+
- **[Extensibility Overview](docs/EXTENSIBILITY.md)** - Architecture and design principles
|
|
253
|
+
|
|
254
|
+
## Project Structure
|
|
255
|
+
|
|
256
|
+
```text
|
|
257
|
+
pynode/ # Project root
|
|
258
|
+
├── pynode/ # Main package
|
|
259
|
+
│ ├── __init__.py
|
|
260
|
+
│ ├── __main__.py # Entry point for 'python -m pynode'
|
|
261
|
+
│ ├── _version.py # Version (generated by setuptools_scm at build)
|
|
262
|
+
│ ├── main.py # CLI application
|
|
263
|
+
│ ├── server.py # Flask REST API with SSE support
|
|
264
|
+
│ ├── workflow_engine.py # Workflow management
|
|
265
|
+
│ ├── models/ # ML model storage
|
|
266
|
+
│ ├── nodes/ # Node implementations (each in its own folder)
|
|
267
|
+
│ │ ├── __init__.py
|
|
268
|
+
│ │ ├── base_node.py # BaseNode class
|
|
269
|
+
│ │ ├── InjectNode/ # Generate messages
|
|
270
|
+
│ │ ├── FunctionNode/ # Custom Python code
|
|
271
|
+
│ │ ├── DebugNode/ # Debug output
|
|
272
|
+
│ │ ├── ChangeNode/ # Modify messages
|
|
273
|
+
│ │ ├── SwitchNode/ # Route based on conditions
|
|
274
|
+
│ │ ├── DelayNode/ # Delay messages
|
|
275
|
+
│ │ ├── GateNode/ # Control message flow
|
|
276
|
+
│ │ ├── RateProbeNode/ # Monitor throughput
|
|
277
|
+
│ │ ├── QueueLengthProbeNode/ # Monitor queue lengths
|
|
278
|
+
│ │ ├── CounterNode/ # Count messages
|
|
279
|
+
│ │ ├── CameraNode/ # Camera input
|
|
280
|
+
│ │ ├── UltralyticsNode/ # YOLO detection
|
|
281
|
+
│ │ ├── ImageViewerNode/ # Display images
|
|
282
|
+
│ │ ├── ImageWriterNode/ # Save images
|
|
283
|
+
│ │ ├── TrackerNode/ # Object tracking
|
|
284
|
+
│ │ ├── MQTTNode/ # MQTT communication
|
|
285
|
+
│ │ ├── MessageWriterNode/ # Save message data
|
|
286
|
+
│ │ ├── VideoWriterNode/ # Save video
|
|
287
|
+
│ │ ├── OpenCV/ # OpenCV operations
|
|
288
|
+
│ │ └── ... # 30+ other node types
|
|
289
|
+
│ └── static/ # Web UI
|
|
290
|
+
│ ├── index.html
|
|
291
|
+
│ ├── style.css
|
|
292
|
+
│ ├── js/ # JavaScript modules
|
|
293
|
+
│ │ ├── nodes.js
|
|
294
|
+
│ │ ├── events.js
|
|
295
|
+
│ │ ├── connections.js
|
|
296
|
+
│ │ ├── debug.js
|
|
297
|
+
│ │ └── ...
|
|
298
|
+
│ └── images/ # UI assets
|
|
299
|
+
├── examples/ # Example workflows and tutorials
|
|
300
|
+
│ ├── camera_workflow.py
|
|
301
|
+
│ ├── camera_yolo_workflow.py
|
|
302
|
+
│ └── README.md
|
|
303
|
+
├── readmes/ # Extended documentation
|
|
304
|
+
├── models/ # ML models (YOLO, etc.)
|
|
305
|
+
├── docs/ # Documentation files
|
|
306
|
+
│ ├── CUSTOM_NODES.md # Guide to creating custom nodes
|
|
307
|
+
│ ├── UI_COMPONENTS.md # Guide to node UI components
|
|
308
|
+
│ └── EXTENSIBILITY.md # Extensibility overview
|
|
309
|
+
├── _backup/ # Workflow backups
|
|
310
|
+
├── setup.py # Package installation
|
|
311
|
+
├── setup.bat / setup.sh # Setup scripts
|
|
312
|
+
├── requirements.txt # Convenience installer (deps live in pyproject.toml)
|
|
313
|
+
├── pyproject.toml # Package metadata, dependencies and build config
|
|
314
|
+
├── INSTALL.md # Installation guide
|
|
315
|
+
├── DOCKER.md # Docker setup
|
|
316
|
+
├── docker-compose.yml # Docker compose config
|
|
317
|
+
├── Dockerfile # Docker build (CUDA)
|
|
318
|
+
├── Dockerfile.cpu # Docker build (CPU only)
|
|
319
|
+
├── README.md
|
|
320
|
+
└── workflow.json # Current workflow
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
## Creating Custom Nodes
|
|
324
|
+
|
|
325
|
+
PyNode is fully extensible. All node information (visual properties, property schemas, and behavior) is contained within the node class itself. The main application has no hardcoded knowledge of specific node types.
|
|
326
|
+
|
|
327
|
+
For a complete guide, see [docs/CUSTOM_NODES.md](docs/CUSTOM_NODES.md)
|
|
328
|
+
|
|
329
|
+
Here is a simple example:
|
|
330
|
+
|
|
331
|
+
```python
|
|
332
|
+
from pynode.nodes.base_node import BaseNode
|
|
333
|
+
|
|
334
|
+
class MyCustomNode(BaseNode):
|
|
335
|
+
"""Example custom node."""
|
|
336
|
+
|
|
337
|
+
category = 'custom'
|
|
338
|
+
color = '#FFA07A'
|
|
339
|
+
border_color = '#FF7F50'
|
|
340
|
+
text_color = '#000000'
|
|
341
|
+
|
|
342
|
+
properties = [
|
|
343
|
+
{
|
|
344
|
+
'name': 'multiplier',
|
|
345
|
+
'label': 'Multiplier',
|
|
346
|
+
'type': 'text'
|
|
347
|
+
}
|
|
348
|
+
]
|
|
349
|
+
|
|
350
|
+
def __init__(self, node_id=None, name="custom"):
|
|
351
|
+
super().__init__(node_id, name)
|
|
352
|
+
self.configure({
|
|
353
|
+
'multiplier': 2
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
def on_input(self, msg, input_index=0):
|
|
357
|
+
payload = msg.get('payload')
|
|
358
|
+
multiplier = float(self.config.get('multiplier', 2))
|
|
359
|
+
new_payload = payload * multiplier
|
|
360
|
+
|
|
361
|
+
new_msg = self.create_message(
|
|
362
|
+
payload=new_payload,
|
|
363
|
+
topic=msg.get('topic', '')
|
|
364
|
+
)
|
|
365
|
+
self.send(new_msg)
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
Register the node with the workflow engine used by your application:
|
|
369
|
+
|
|
370
|
+
```python
|
|
371
|
+
from pynode.workflow_engine import WorkflowEngine
|
|
372
|
+
from my_custom_node import MyCustomNode
|
|
373
|
+
|
|
374
|
+
engine = WorkflowEngine()
|
|
375
|
+
engine.register_node_type(MyCustomNode)
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
## Message Structure
|
|
379
|
+
|
|
380
|
+
Messages follow the Node-RED format:
|
|
381
|
+
|
|
382
|
+
```python
|
|
383
|
+
{
|
|
384
|
+
'payload': 'any data type',
|
|
385
|
+
'topic': 'string',
|
|
386
|
+
'_msgid': 'unique-id',
|
|
387
|
+
# ... any additional properties
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
## API Endpoints
|
|
392
|
+
|
|
393
|
+
### Nodes
|
|
394
|
+
- `GET /api/nodes` - List all nodes
|
|
395
|
+
- `POST /api/nodes` - Create a node
|
|
396
|
+
- `GET /api/nodes/<id>` - Get node details
|
|
397
|
+
- `PUT /api/nodes/<id>` - Update node
|
|
398
|
+
- `DELETE /api/nodes/<id>` - Delete node
|
|
399
|
+
- `POST /api/nodes/<id>/<action>` - Trigger node action
|
|
400
|
+
|
|
401
|
+
### Connections
|
|
402
|
+
- `POST /api/connections` - Create connection
|
|
403
|
+
- `DELETE /api/connections` - Delete connection
|
|
404
|
+
|
|
405
|
+
### Workflow
|
|
406
|
+
- `GET /api/workflow` - Export workflow
|
|
407
|
+
- `POST /api/workflow` - Import workflow
|
|
408
|
+
- `POST /api/workflow/start` - Start workflow
|
|
409
|
+
- `POST /api/workflow/stop` - Stop workflow
|
|
410
|
+
- `GET /api/workflow/stats` - Get statistics
|
|
411
|
+
|
|
412
|
+
### Debug
|
|
413
|
+
- `GET /api/nodes/<id>/debug` - Get debug messages
|
|
414
|
+
- `DELETE /api/nodes/<id>/debug` - Clear debug messages
|
|
415
|
+
|
|
416
|
+
## Example Programmatic Usage
|
|
417
|
+
|
|
418
|
+
```python
|
|
419
|
+
from pynode.workflow_engine import WorkflowEngine
|
|
420
|
+
from pynode.nodes import InjectNode, FunctionNode, DebugNode
|
|
421
|
+
|
|
422
|
+
engine = WorkflowEngine()
|
|
423
|
+
engine.register_node_type(InjectNode)
|
|
424
|
+
engine.register_node_type(FunctionNode)
|
|
425
|
+
engine.register_node_type(DebugNode)
|
|
426
|
+
|
|
427
|
+
inject = engine.create_node('InjectNode', name='source')
|
|
428
|
+
inject.configure({'payload': 10, 'payloadType': 'num'})
|
|
429
|
+
|
|
430
|
+
func = engine.create_node('FunctionNode', name='multiply')
|
|
431
|
+
func.configure({'func': 'msg["payload"] = msg["payload"] * 2\nreturn msg'})
|
|
432
|
+
|
|
433
|
+
debug = engine.create_node('DebugNode', name='output')
|
|
434
|
+
|
|
435
|
+
engine.connect_nodes(inject.id, func.id)
|
|
436
|
+
engine.connect_nodes(func.id, debug.id)
|
|
437
|
+
|
|
438
|
+
engine.start()
|
|
439
|
+
engine.trigger_inject_node(inject.id)
|
|
440
|
+
|
|
441
|
+
messages = engine.get_debug_messages(debug.id)
|
|
442
|
+
print(messages)
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
## Web UI Usage
|
|
446
|
+
|
|
447
|
+
1. **Add Nodes**: Drag nodes from the palette onto the canvas
|
|
448
|
+
2. **Connect Nodes**: Click an output port and drag to an input port
|
|
449
|
+
3. **Configure Nodes**: Click a node to show its properties panel
|
|
450
|
+
4. **Test Workflow**:
|
|
451
|
+
- Click **Start** to activate the workflow
|
|
452
|
+
- Use the **Inject** button on inject nodes to send messages
|
|
453
|
+
- View output in the debug panel at the bottom
|
|
454
|
+
5. **Save/Load**: Use Export/Import buttons to save workflows
|
|
455
|
+
|
|
456
|
+
## Extending the System
|
|
457
|
+
|
|
458
|
+
### Adding New Node Types
|
|
459
|
+
|
|
460
|
+
1. Create a new Python class in `pynode/nodes/`
|
|
461
|
+
2. Inherit from `BaseNode`
|
|
462
|
+
3. Override `on_input()` for message processing
|
|
463
|
+
4. Define `properties` for UI configuration
|
|
464
|
+
5. Create `requirements.txt` in the node's directory if needed
|
|
465
|
+
6. Reload the server to detect the new node
|
|
466
|
+
|
|
467
|
+
### Custom Message Processing
|
|
468
|
+
|
|
469
|
+
Nodes can:
|
|
470
|
+
- Modify message payload
|
|
471
|
+
- Add or remove message properties
|
|
472
|
+
- Send to multiple outputs
|
|
473
|
+
- Send multiple messages
|
|
474
|
+
- Filter messages
|
|
475
|
+
- Store state between messages
|
|
476
|
+
|
|
477
|
+
### Advanced Features
|
|
478
|
+
|
|
479
|
+
- **Background Processing**: Use threading for long-running operations
|
|
480
|
+
- **External APIs**: Make HTTP requests from function nodes
|
|
481
|
+
- **Database Integration**: Store and retrieve data from databases
|
|
482
|
+
- **File I/O**: Read and write files in custom nodes
|
|
483
|
+
- **Scheduling**: Implement timed node execution
|
|
484
|
+
|
|
485
|
+
## Development TODOs
|
|
486
|
+
|
|
487
|
+
### Ongoing
|
|
488
|
+
- ⬜ Centralize more strings / constants
|
|
489
|
+
- ⬜ Test all nodes
|
|
490
|
+
- ✅ Add multiple workspaces / canvases
|
|
491
|
+
|
|
492
|
+
### Planned Nodes
|
|
493
|
+
- ⬜ OCR (PaddlePaddle) Node
|
|
494
|
+
- ✅ Qwen VLM Node
|
|
495
|
+
- ⬜ SAM3 Node
|
|
496
|
+
- ✅ REST Endpoint Node
|
|
497
|
+
- ✅ Webhook Node
|
|
498
|
+
- ⬜ UDP/TCP Node
|
|
499
|
+
|
|
500
|
+
### Example Flow Documentation Needed
|
|
501
|
+
- ⬜ Bird seed level monitor
|
|
502
|
+
- ⬜ Capture data and send to Roboflow / Geti
|
|
503
|
+
- ⬜ Track objects time in zone
|
|
504
|
+
- ⬜ Live VLMs
|
|
505
|
+
- ⬜ ANPR (Detect, Crop, OCR, MQTT)
|
|
506
|
+
|
|
507
|
+
### Node-Specific
|
|
508
|
+
- ✅ YOLO: Add custom model support
|
|
509
|
+
- ✅ YOLO: Add custom target HW string
|
|
510
|
+
- ⬜ Roboflow: RF-DETR
|
|
511
|
+
- ⬜ Roboflow: Upload images
|
|
512
|
+
- ⬜ DeepSort: Add option to use a different feature extractor model
|
|
513
|
+
|
|
514
|
+
## License
|
|
515
|
+
|
|
516
|
+
MIT License - Feel free to use and modify.
|
|
517
|
+
|
|
518
|
+
## Contributing
|
|
519
|
+
|
|
520
|
+
Contributions are welcome. Add new node types, improve the UI, or enhance the engine.
|