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.
Files changed (227) hide show
  1. pynode/__init__.py +37 -0
  2. pynode/__main__.py +9 -0
  3. pynode/_perf.py +65 -0
  4. pynode/_version.py +24 -0
  5. pynode/api/__init__.py +15 -0
  6. pynode/api/helpers.py +79 -0
  7. pynode/api/nodes.py +423 -0
  8. pynode/api/services.py +124 -0
  9. pynode/api/sse.py +58 -0
  10. pynode/api/uploads.py +59 -0
  11. pynode/api/workflows.py +466 -0
  12. pynode/config.py +93 -0
  13. pynode/install_nodes.py +143 -0
  14. pynode/main.py +118 -0
  15. pynode/node_registry.py +156 -0
  16. pynode/nodes/AutoSyncNode/auto_sync_node.py +151 -0
  17. pynode/nodes/BBoxMetricsNode/__init__.py +3 -0
  18. pynode/nodes/BBoxMetricsNode/bbox_metrics_node.py +250 -0
  19. pynode/nodes/BBoxMetricsNode/requirements.txt +1 -0
  20. pynode/nodes/BatchNode/__init__.py +3 -0
  21. pynode/nodes/BatchNode/batch_node.py +86 -0
  22. pynode/nodes/BatchNode/requirements.txt +1 -0
  23. pynode/nodes/CameraNode/__init__.py +4 -0
  24. pynode/nodes/CameraNode/camera_node.py +204 -0
  25. pynode/nodes/CameraNode/requirements.txt +2 -0
  26. pynode/nodes/ChangeNode/__init__.py +1 -0
  27. pynode/nodes/ChangeNode/change_node.py +255 -0
  28. pynode/nodes/ChangeNode/requirements.txt +1 -0
  29. pynode/nodes/ConfidenceFilterNode/__init__.py +4 -0
  30. pynode/nodes/ConfidenceFilterNode/confidence_filter_node.py +208 -0
  31. pynode/nodes/ConfidenceFilterNode/requirements.txt +1 -0
  32. pynode/nodes/CounterNode/__init__.py +3 -0
  33. pynode/nodes/CounterNode/counter_node.py +191 -0
  34. pynode/nodes/CropNode/__init__.py +4 -0
  35. pynode/nodes/CropNode/crop_node.py +317 -0
  36. pynode/nodes/CropNode/requirements.txt +2 -0
  37. pynode/nodes/DebugNode/__init__.py +4 -0
  38. pynode/nodes/DebugNode/debug_node.py +169 -0
  39. pynode/nodes/DebugNode/requirements.txt +1 -0
  40. pynode/nodes/DelayNode/delay_node.py +218 -0
  41. pynode/nodes/DelayNode/requirements.txt +1 -0
  42. pynode/nodes/DrawPredictionsNode/__init__.py +3 -0
  43. pynode/nodes/DrawPredictionsNode/draw_predictions_node.py +365 -0
  44. pynode/nodes/DrawPredictionsNode/requirements.txt +2 -0
  45. pynode/nodes/ErrorNode/error_node.py +94 -0
  46. pynode/nodes/ErrorNode/requirements.txt +1 -0
  47. pynode/nodes/FilterNode/__init__.py +3 -0
  48. pynode/nodes/FilterNode/filter_node.py +102 -0
  49. pynode/nodes/FilterNode/requirements.txt +1 -0
  50. pynode/nodes/FrameSourceNode/__init__.py +4 -0
  51. pynode/nodes/FrameSourceNode/frame_source_node.py +319 -0
  52. pynode/nodes/FrameSourceNode/requirements.txt +1 -0
  53. pynode/nodes/FunctionNode/__init__.py +3 -0
  54. pynode/nodes/FunctionNode/function_node.py +168 -0
  55. pynode/nodes/FunctionNode/requirements.txt +1 -0
  56. pynode/nodes/GateNode/gate_node.py +60 -0
  57. pynode/nodes/GateNode/requirements.txt +1 -0
  58. pynode/nodes/GetiUploadNode/__init__.py +3 -0
  59. pynode/nodes/GetiUploadNode/geti_upload_node.py +278 -0
  60. pynode/nodes/GetiUploadNode/requirements.txt +1 -0
  61. pynode/nodes/ImageFormatNode/__init__.py +4 -0
  62. pynode/nodes/ImageFormatNode/image_format_node.py +310 -0
  63. pynode/nodes/ImageFormatNode/requirements.txt +2 -0
  64. pynode/nodes/ImageUploadNode/image_upload_node.py +96 -0
  65. pynode/nodes/ImageViewerNode/__init__.py +4 -0
  66. pynode/nodes/ImageViewerNode/image_viewer_node.py +162 -0
  67. pynode/nodes/ImageViewerNode/requirements.txt +2 -0
  68. pynode/nodes/ImageWriterNode/__init__.py +3 -0
  69. pynode/nodes/ImageWriterNode/imagewriter_node.py +352 -0
  70. pynode/nodes/ImageWriterNode/requirements.txt +1 -0
  71. pynode/nodes/InferenceNode/InferenceEngine/__init__.py +15 -0
  72. pynode/nodes/InferenceNode/InferenceEngine/device_detection.py +149 -0
  73. pynode/nodes/InferenceNode/InferenceEngine/engines/base_engine.py +155 -0
  74. pynode/nodes/InferenceNode/InferenceEngine/engines/example_engine_template.py +272 -0
  75. pynode/nodes/InferenceNode/InferenceEngine/engines/geti_engine.py +309 -0
  76. pynode/nodes/InferenceNode/InferenceEngine/engines/onnx_engine.py +346 -0
  77. pynode/nodes/InferenceNode/InferenceEngine/engines/pass_engine.py +86 -0
  78. pynode/nodes/InferenceNode/InferenceEngine/engines/ultralytics_engine.py +863 -0
  79. pynode/nodes/InferenceNode/InferenceEngine/inference_engine_factory.py +533 -0
  80. pynode/nodes/InferenceNode/InferenceEngine/readme.md +107 -0
  81. pynode/nodes/InferenceNode/InferenceEngine/result_converters.py +121 -0
  82. pynode/nodes/InferenceNode/inference_node.py +451 -0
  83. pynode/nodes/InferenceNode/requirements.txt +13 -0
  84. pynode/nodes/InjectNode/inject_node.py +245 -0
  85. pynode/nodes/InjectNode/requirements.txt +1 -0
  86. pynode/nodes/JoinNode/__init__.py +3 -0
  87. pynode/nodes/JoinNode/join_node.py +156 -0
  88. pynode/nodes/JoinNode/requirements.txt +1 -0
  89. pynode/nodes/LabelFilterNode/__init__.py +3 -0
  90. pynode/nodes/LabelFilterNode/label_filter_node.py +188 -0
  91. pynode/nodes/LabelFilterNode/requirements.txt +1 -0
  92. pynode/nodes/MQTTNode/__init__.py +8 -0
  93. pynode/nodes/MQTTNode/mqtt_in_node.py +148 -0
  94. pynode/nodes/MQTTNode/mqtt_out_node.py +157 -0
  95. pynode/nodes/MQTTNode/mqtt_service.py +367 -0
  96. pynode/nodes/MQTTNode/requirements.txt +1 -0
  97. pynode/nodes/MessageWriterNode/__init__.py +7 -0
  98. pynode/nodes/MessageWriterNode/messagereader_node.py +568 -0
  99. pynode/nodes/MessageWriterNode/messagewriter_node.py +505 -0
  100. pynode/nodes/OmronCameraNode/__init__.py +3 -0
  101. pynode/nodes/OmronCameraNode/omron_camera_node.py +652 -0
  102. pynode/nodes/OmronCameraNode/requirements.txt +4 -0
  103. pynode/nodes/OpenCV/__init__.py +1 -0
  104. pynode/nodes/OpenCV/arithmetic_node.py +376 -0
  105. pynode/nodes/OpenCV/bitwise_node.py +127 -0
  106. pynode/nodes/OpenCV/blend_node.py +127 -0
  107. pynode/nodes/OpenCV/blob_detector_node.py +233 -0
  108. pynode/nodes/OpenCV/blur_node.py +141 -0
  109. pynode/nodes/OpenCV/color_space_node.py +139 -0
  110. pynode/nodes/OpenCV/colormap_node.py +262 -0
  111. pynode/nodes/OpenCV/contours_node.py +258 -0
  112. pynode/nodes/OpenCV/denormalize_coords_node.py +365 -0
  113. pynode/nodes/OpenCV/draw_node.py +312 -0
  114. pynode/nodes/OpenCV/edge_detector_node.py +177 -0
  115. pynode/nodes/OpenCV/equirectangular_node.py +447 -0
  116. pynode/nodes/OpenCV/fft_node.py +102 -0
  117. pynode/nodes/OpenCV/focus_estimation_node.py +189 -0
  118. pynode/nodes/OpenCV/frequency_filter_node.py +241 -0
  119. pynode/nodes/OpenCV/histogram_node.py +160 -0
  120. pynode/nodes/OpenCV/hough_circles_node.py +224 -0
  121. pynode/nodes/OpenCV/hough_lines_node.py +243 -0
  122. pynode/nodes/OpenCV/in_range_node.py +186 -0
  123. pynode/nodes/OpenCV/morphology_node.py +136 -0
  124. pynode/nodes/OpenCV/normalize_coords_node.py +344 -0
  125. pynode/nodes/OpenCV/paste_node.py +244 -0
  126. pynode/nodes/OpenCV/perspective_node.py +174 -0
  127. pynode/nodes/OpenCV/realsense_depth_node.py +253 -0
  128. pynode/nodes/OpenCV/requirements.txt +2 -0
  129. pynode/nodes/OpenCV/resize_node.py +175 -0
  130. pynode/nodes/OpenCV/rotate_node.py +159 -0
  131. pynode/nodes/OpenCV/template_match_node.py +227 -0
  132. pynode/nodes/OpenCV/threshold_node.py +167 -0
  133. pynode/nodes/PointInShapeNode/__init__.py +3 -0
  134. pynode/nodes/PointInShapeNode/point_in_shape_node.py +313 -0
  135. pynode/nodes/PointInShapeNode/requirements.txt +2 -0
  136. pynode/nodes/PolygonMetricsNode/__init__.py +3 -0
  137. pynode/nodes/PolygonMetricsNode/polygon_metrics_node.py +331 -0
  138. pynode/nodes/PolygonMetricsNode/requirements.txt +2 -0
  139. pynode/nodes/QueueLengthProbeNode/__init__.py +3 -0
  140. pynode/nodes/QueueLengthProbeNode/queue_length_probe_node.py +116 -0
  141. pynode/nodes/Qwen3VLMNode/__init__.py +3 -0
  142. pynode/nodes/Qwen3VLMNode/qwen3_vlm_node.py +519 -0
  143. pynode/nodes/Qwen3VLMNode/requirements.txt +5 -0
  144. pynode/nodes/RESTEndpointNode/__init__.py +3 -0
  145. pynode/nodes/RESTEndpointNode/rest_endpoint_node.py +491 -0
  146. pynode/nodes/RangeNode/__init__.py +3 -0
  147. pynode/nodes/RangeNode/range_node.py +115 -0
  148. pynode/nodes/RangeNode/requirements.txt +1 -0
  149. pynode/nodes/RateProbeNode/__init__.py +3 -0
  150. pynode/nodes/RateProbeNode/rate_probe_node.py +183 -0
  151. pynode/nodes/RateProbeNode/requirements.txt +1 -0
  152. pynode/nodes/RoboflowUploadNode/__init__.py +3 -0
  153. pynode/nodes/RoboflowUploadNode/requirements.txt +1 -0
  154. pynode/nodes/RoboflowUploadNode/roboflow_upload_node.py +229 -0
  155. pynode/nodes/SliceNode/README.md +149 -0
  156. pynode/nodes/SliceNode/__init__.py +5 -0
  157. pynode/nodes/SliceNode/merge_predictions_node.py +444 -0
  158. pynode/nodes/SliceNode/requirements.txt +3 -0
  159. pynode/nodes/SliceNode/slice_collector_node.py +370 -0
  160. pynode/nodes/SliceNode/slice_image_node.py +380 -0
  161. pynode/nodes/SplitNode/__init__.py +3 -0
  162. pynode/nodes/SplitNode/requirements.txt +1 -0
  163. pynode/nodes/SplitNode/split_node.py +155 -0
  164. pynode/nodes/SwitchNode/requirements.txt +1 -0
  165. pynode/nodes/SwitchNode/switch_node.py +259 -0
  166. pynode/nodes/SyncNode/__init__.py +3 -0
  167. pynode/nodes/SyncNode/sync_node.py +287 -0
  168. pynode/nodes/TemplateNode/requirements.txt +1 -0
  169. pynode/nodes/TemplateNode/template_node.py +86 -0
  170. pynode/nodes/TrackerNode/__init__.py +2 -0
  171. pynode/nodes/TrackerNode/supervision_tracker_node.py +248 -0
  172. pynode/nodes/TrackerNode/tracker_node.py +745 -0
  173. pynode/nodes/UltralyticsNode/__init__.py +4 -0
  174. pynode/nodes/UltralyticsNode/requirements.txt +2 -0
  175. pynode/nodes/UltralyticsNode/ultralytics_node.py +364 -0
  176. pynode/nodes/UnknownNode/__init__.py +93 -0
  177. pynode/nodes/VLLMNode/__init__.py +3 -0
  178. pynode/nodes/VLLMNode/vllm_node.py +345 -0
  179. pynode/nodes/VideoReaderNode/__init__.py +3 -0
  180. pynode/nodes/VideoReaderNode/video_reader_node.py +425 -0
  181. pynode/nodes/VideoWriterNode/__init__.py +3 -0
  182. pynode/nodes/VideoWriterNode/video_writer_node.py +441 -0
  183. pynode/nodes/WebhookNode/__init__.py +3 -0
  184. pynode/nodes/WebhookNode/webhook_node.py +591 -0
  185. pynode/nodes/__init__.py +109 -0
  186. pynode/nodes/base_node.py +562 -0
  187. pynode/nodes/image_utils.py +291 -0
  188. pynode/nodes/info.py +109 -0
  189. pynode/nodes/mDNSNode/__init__.py +13 -0
  190. pynode/nodes/mDNSNode/mdns_broadcast_node.py +214 -0
  191. pynode/nodes/mDNSNode/mdns_discovery_node.py +362 -0
  192. pynode/nodes/mDNSNode/mdns_manager.py +399 -0
  193. pynode/nodes/mDNSNode/requirements.txt +1 -0
  194. pynode/nodes/messages.py +81 -0
  195. pynode/server.py +187 -0
  196. pynode/static/images/favicon4.png +0 -0
  197. pynode/static/index.html +200 -0
  198. pynode/static/js/README.md +139 -0
  199. pynode/static/js/auth.js +89 -0
  200. pynode/static/js/clipboard.js +152 -0
  201. pynode/static/js/config.js +14 -0
  202. pynode/static/js/connections.js +844 -0
  203. pynode/static/js/debug.js +524 -0
  204. pynode/static/js/events.js +775 -0
  205. pynode/static/js/history.js +199 -0
  206. pynode/static/js/main.js +55 -0
  207. pynode/static/js/minimap.js +242 -0
  208. pynode/static/js/nodes.js +995 -0
  209. pynode/static/js/palette.js +255 -0
  210. pynode/static/js/properties.js +1270 -0
  211. pynode/static/js/selection.js +241 -0
  212. pynode/static/js/state.js +211 -0
  213. pynode/static/js/ui-utils.js +28 -0
  214. pynode/static/js/viewport.js +204 -0
  215. pynode/static/js/workflow.js +469 -0
  216. pynode/static/js/workflows.js +344 -0
  217. pynode/static/style.css +2751 -0
  218. pynode/workflow_engine.py +483 -0
  219. pynode/workflow_manager.py +440 -0
  220. pynode_flow-0.2.0.dist-info/METADATA +520 -0
  221. pynode_flow-0.2.0.dist-info/RECORD +227 -0
  222. pynode_flow-0.2.0.dist-info/WHEEL +5 -0
  223. pynode_flow-0.2.0.dist-info/entry_points.txt +3 -0
  224. pynode_flow-0.2.0.dist-info/licenses/LICENSE +21 -0
  225. pynode_flow-0.2.0.dist-info/scm_file_list.json +270 -0
  226. pynode_flow-0.2.0.dist-info/scm_version.json +8 -0
  227. 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.