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,376 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OpenCV Arithmetic Operations Node - performs pixel-wise arithmetic operations on images.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import cv2
|
|
6
|
+
import numpy as np
|
|
7
|
+
from typing import Any, Dict, Optional
|
|
8
|
+
from pynode.nodes.base_node import BaseNode, Info, MessageKeys
|
|
9
|
+
|
|
10
|
+
_info = Info()
|
|
11
|
+
_info.add_text("Performs pixel-wise arithmetic operations between two images or with a constant value.")
|
|
12
|
+
_info.add_header("Inputs")
|
|
13
|
+
_info.add_bullets(
|
|
14
|
+
("Input 0:", "First image (primary)"),
|
|
15
|
+
("Input 1:", "Second image (optional, for image-to-image operations)")
|
|
16
|
+
)
|
|
17
|
+
_info.add_header("Operations")
|
|
18
|
+
_info.add_bullets(
|
|
19
|
+
("Add:", "Adds pixel values (saturated to max value)"),
|
|
20
|
+
("Subtract:", "Subtracts pixel values (saturated to 0)"),
|
|
21
|
+
("Multiply:", "Multiplies pixel values"),
|
|
22
|
+
("Divide:", "Divides pixel values"),
|
|
23
|
+
("Weighted Add:", "Blends two images with alpha weights"),
|
|
24
|
+
("Absolute Difference:", "Absolute difference between images"),
|
|
25
|
+
("Min:", "Minimum of two images pixel-wise"),
|
|
26
|
+
("Max:", "Maximum of two images pixel-wise")
|
|
27
|
+
)
|
|
28
|
+
_info.add_header("Modes")
|
|
29
|
+
_info.add_bullets(
|
|
30
|
+
("Image + Image:", "Operate between two input images"),
|
|
31
|
+
("Image + Constant:", "Operate with a constant scalar value")
|
|
32
|
+
)
|
|
33
|
+
_info.add_header("Output")
|
|
34
|
+
_info.add_text("Outputs the resulting image from the arithmetic operation.")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ArithmeticNode(BaseNode):
|
|
38
|
+
"""
|
|
39
|
+
Arithmetic node - performs pixel-wise arithmetic operations on images.
|
|
40
|
+
Supports operations between two images or with a constant value.
|
|
41
|
+
"""
|
|
42
|
+
info = str(_info)
|
|
43
|
+
display_name = 'Arithmetic'
|
|
44
|
+
icon = '±'
|
|
45
|
+
category = 'opencv'
|
|
46
|
+
color = '#4A90D9'
|
|
47
|
+
border_color = '#2E6BB0'
|
|
48
|
+
text_color = '#FFFFFF'
|
|
49
|
+
input_count = 2 # Input 0: image1, Input 1: image2 (optional)
|
|
50
|
+
output_count = 1
|
|
51
|
+
|
|
52
|
+
DEFAULT_CONFIG = {
|
|
53
|
+
'operation': 'add',
|
|
54
|
+
'mode': 'image',
|
|
55
|
+
'constant': '50',
|
|
56
|
+
'alpha': '0.5',
|
|
57
|
+
'beta': '0.5',
|
|
58
|
+
'gamma': '0',
|
|
59
|
+
'auto_resize': 'true',
|
|
60
|
+
'resize_method': 'resize_second',
|
|
61
|
+
'clip_output': 'true'
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
properties = [
|
|
65
|
+
{
|
|
66
|
+
'name': 'operation',
|
|
67
|
+
'label': 'Operation',
|
|
68
|
+
'type': 'select',
|
|
69
|
+
'options': [
|
|
70
|
+
{'value': 'add', 'label': 'Add'},
|
|
71
|
+
{'value': 'subtract', 'label': 'Subtract'},
|
|
72
|
+
{'value': 'multiply', 'label': 'Multiply'},
|
|
73
|
+
{'value': 'divide', 'label': 'Divide'},
|
|
74
|
+
{'value': 'weighted_add', 'label': 'Weighted Add (Blend)'},
|
|
75
|
+
{'value': 'absdiff', 'label': 'Absolute Difference'},
|
|
76
|
+
{'value': 'min', 'label': 'Minimum'},
|
|
77
|
+
{'value': 'max', 'label': 'Maximum'},
|
|
78
|
+
{'value': 'power', 'label': 'Power'},
|
|
79
|
+
{'value': 'sqrt', 'label': 'Square Root (single image)'}
|
|
80
|
+
],
|
|
81
|
+
'default': DEFAULT_CONFIG['operation'],
|
|
82
|
+
'help': 'Arithmetic operation to perform'
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
'name': 'mode',
|
|
86
|
+
'label': 'Mode',
|
|
87
|
+
'type': 'select',
|
|
88
|
+
'options': [
|
|
89
|
+
{'value': 'image', 'label': 'Image + Image'},
|
|
90
|
+
{'value': 'constant', 'label': 'Image + Constant'}
|
|
91
|
+
],
|
|
92
|
+
'default': DEFAULT_CONFIG['mode'],
|
|
93
|
+
'help': 'Operate between images or with a constant value'
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
'name': 'constant',
|
|
97
|
+
'label': 'Constant Value',
|
|
98
|
+
'type': 'text',
|
|
99
|
+
'default': DEFAULT_CONFIG['constant'],
|
|
100
|
+
'help': 'Scalar value for constant mode (can be negative for subtract)'
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
'name': 'alpha',
|
|
104
|
+
'label': 'Alpha (Weight 1)',
|
|
105
|
+
'type': 'text',
|
|
106
|
+
'default': DEFAULT_CONFIG['alpha'],
|
|
107
|
+
'help': 'Weight for first image in weighted add (0.0-1.0)'
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
'name': 'beta',
|
|
111
|
+
'label': 'Beta (Weight 2)',
|
|
112
|
+
'type': 'text',
|
|
113
|
+
'default': DEFAULT_CONFIG['beta'],
|
|
114
|
+
'help': 'Weight for second image in weighted add (0.0-1.0)'
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
'name': 'gamma',
|
|
118
|
+
'label': 'Gamma (Offset)',
|
|
119
|
+
'type': 'text',
|
|
120
|
+
'default': DEFAULT_CONFIG['gamma'],
|
|
121
|
+
'help': 'Scalar added to weighted sum'
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
'name': 'auto_resize',
|
|
125
|
+
'label': 'Auto Resize',
|
|
126
|
+
'type': 'select',
|
|
127
|
+
'options': [
|
|
128
|
+
{'value': 'true', 'label': 'Yes'},
|
|
129
|
+
{'value': 'false', 'label': 'No'}
|
|
130
|
+
],
|
|
131
|
+
'default': DEFAULT_CONFIG['auto_resize'],
|
|
132
|
+
'help': 'Automatically resize images to match dimensions'
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
'name': 'resize_method',
|
|
136
|
+
'label': 'Resize Method',
|
|
137
|
+
'type': 'select',
|
|
138
|
+
'options': [
|
|
139
|
+
{'value': 'resize_second', 'label': 'Resize second to match first'},
|
|
140
|
+
{'value': 'resize_first', 'label': 'Resize first to match second'},
|
|
141
|
+
{'value': 'resize_larger', 'label': 'Resize to larger dimensions'},
|
|
142
|
+
{'value': 'resize_smaller', 'label': 'Resize to smaller dimensions'}
|
|
143
|
+
],
|
|
144
|
+
'default': DEFAULT_CONFIG['resize_method'],
|
|
145
|
+
'help': 'How to handle size mismatch between images'
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
'name': 'clip_output',
|
|
149
|
+
'label': 'Clip Output',
|
|
150
|
+
'type': 'select',
|
|
151
|
+
'options': [
|
|
152
|
+
{'value': 'true', 'label': 'Yes (0-255)'},
|
|
153
|
+
{'value': 'false', 'label': 'No'}
|
|
154
|
+
],
|
|
155
|
+
'default': DEFAULT_CONFIG['clip_output'],
|
|
156
|
+
'help': 'Clip output values to valid range (0-255)'
|
|
157
|
+
}
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
def __init__(self, node_id=None, name="arithmetic"):
|
|
161
|
+
super().__init__(node_id, name)
|
|
162
|
+
self._image1: Optional[np.ndarray] = None
|
|
163
|
+
self._image2: Optional[np.ndarray] = None
|
|
164
|
+
self._format_type: Optional[str] = None
|
|
165
|
+
self._last_msg: Optional[Dict] = None
|
|
166
|
+
|
|
167
|
+
def _resize_images(self, img1: np.ndarray, img2: np.ndarray) -> tuple:
|
|
168
|
+
"""Resize images based on configuration."""
|
|
169
|
+
if img1.shape[:2] == img2.shape[:2]:
|
|
170
|
+
return img1, img2
|
|
171
|
+
|
|
172
|
+
auto_resize = self.config.get('auto_resize', 'true') == 'true'
|
|
173
|
+
if not auto_resize:
|
|
174
|
+
# Just resize second to first as fallback
|
|
175
|
+
img2 = cv2.resize(img2, (img1.shape[1], img1.shape[0]))
|
|
176
|
+
return img1, img2
|
|
177
|
+
|
|
178
|
+
resize_method = self.config.get('resize_method', 'resize_second')
|
|
179
|
+
|
|
180
|
+
h1, w1 = img1.shape[:2]
|
|
181
|
+
h2, w2 = img2.shape[:2]
|
|
182
|
+
|
|
183
|
+
if resize_method == 'resize_second':
|
|
184
|
+
img2 = cv2.resize(img2, (w1, h1))
|
|
185
|
+
elif resize_method == 'resize_first':
|
|
186
|
+
img1 = cv2.resize(img1, (w2, h2))
|
|
187
|
+
elif resize_method == 'resize_larger':
|
|
188
|
+
target_w = max(w1, w2)
|
|
189
|
+
target_h = max(h1, h2)
|
|
190
|
+
if (h1, w1) != (target_h, target_w):
|
|
191
|
+
img1 = cv2.resize(img1, (target_w, target_h))
|
|
192
|
+
if (h2, w2) != (target_h, target_w):
|
|
193
|
+
img2 = cv2.resize(img2, (target_w, target_h))
|
|
194
|
+
elif resize_method == 'resize_smaller':
|
|
195
|
+
target_w = min(w1, w2)
|
|
196
|
+
target_h = min(h1, h2)
|
|
197
|
+
if (h1, w1) != (target_h, target_w):
|
|
198
|
+
img1 = cv2.resize(img1, (target_w, target_h))
|
|
199
|
+
if (h2, w2) != (target_h, target_w):
|
|
200
|
+
img2 = cv2.resize(img2, (target_w, target_h))
|
|
201
|
+
|
|
202
|
+
return img1, img2
|
|
203
|
+
|
|
204
|
+
def _match_channels(self, img1: np.ndarray, img2: np.ndarray) -> tuple:
|
|
205
|
+
"""Ensure both images have the same number of channels."""
|
|
206
|
+
c1 = img1.shape[2] if len(img1.shape) == 3 else 1
|
|
207
|
+
c2 = img2.shape[2] if len(img2.shape) == 3 else 1
|
|
208
|
+
|
|
209
|
+
if c1 == c2:
|
|
210
|
+
return img1, img2
|
|
211
|
+
|
|
212
|
+
# Convert grayscale to BGR if needed
|
|
213
|
+
if c1 == 1 and c2 == 3:
|
|
214
|
+
img1 = cv2.cvtColor(img1, cv2.COLOR_GRAY2BGR)
|
|
215
|
+
elif c1 == 3 and c2 == 1:
|
|
216
|
+
img2 = cv2.cvtColor(img2, cv2.COLOR_GRAY2BGR)
|
|
217
|
+
|
|
218
|
+
return img1, img2
|
|
219
|
+
|
|
220
|
+
def _create_constant_image(self, img: np.ndarray, value: float) -> np.ndarray:
|
|
221
|
+
"""Create a constant image matching the input dimensions."""
|
|
222
|
+
const_img = np.full_like(img, abs(value), dtype=np.float32)
|
|
223
|
+
return const_img
|
|
224
|
+
|
|
225
|
+
def _perform_operation(self, img1: np.ndarray, img2: Optional[np.ndarray]) -> np.ndarray:
|
|
226
|
+
"""Perform the configured arithmetic operation."""
|
|
227
|
+
operation = self.config.get('operation', 'add')
|
|
228
|
+
mode = self.config.get('mode', 'image')
|
|
229
|
+
clip_output = self.config.get('clip_output', 'true') == 'true'
|
|
230
|
+
|
|
231
|
+
# Convert to float for precision
|
|
232
|
+
img1_f = img1.astype(np.float32)
|
|
233
|
+
|
|
234
|
+
# Handle single-image operations
|
|
235
|
+
if operation == 'sqrt':
|
|
236
|
+
result = np.sqrt(img1_f)
|
|
237
|
+
if clip_output:
|
|
238
|
+
result = np.clip(result, 0, 255)
|
|
239
|
+
return result.astype(np.uint8)
|
|
240
|
+
|
|
241
|
+
# Get second operand (image or constant)
|
|
242
|
+
if mode == 'constant':
|
|
243
|
+
try:
|
|
244
|
+
const_value = float(self.config.get('constant', '50'))
|
|
245
|
+
except ValueError:
|
|
246
|
+
const_value = 50.0
|
|
247
|
+
img2_f = self._create_constant_image(img1, const_value)
|
|
248
|
+
else:
|
|
249
|
+
if img2 is None:
|
|
250
|
+
return img1
|
|
251
|
+
img1, img2 = self._resize_images(img1, img2)
|
|
252
|
+
img1, img2 = self._match_channels(img1, img2)
|
|
253
|
+
img1_f = img1.astype(np.float32)
|
|
254
|
+
img2_f = img2.astype(np.float32)
|
|
255
|
+
|
|
256
|
+
# Perform operation
|
|
257
|
+
if operation == 'add':
|
|
258
|
+
result = cv2.add(img1, img2 if mode == 'image' else img2_f.astype(np.uint8))
|
|
259
|
+
elif operation == 'subtract':
|
|
260
|
+
if mode == 'constant':
|
|
261
|
+
const_value = float(self.config.get('constant', '50'))
|
|
262
|
+
if const_value >= 0:
|
|
263
|
+
result = cv2.subtract(img1, img2_f.astype(np.uint8))
|
|
264
|
+
else:
|
|
265
|
+
# Negative constant means add
|
|
266
|
+
result = cv2.add(img1, np.full_like(img1, abs(const_value), dtype=np.uint8))
|
|
267
|
+
else:
|
|
268
|
+
result = cv2.subtract(img1, img2)
|
|
269
|
+
elif operation == 'multiply':
|
|
270
|
+
# Scale factor for multiply
|
|
271
|
+
result = img1_f * (img2_f / 255.0 if mode == 'image' else img2_f / 255.0)
|
|
272
|
+
if clip_output:
|
|
273
|
+
result = np.clip(result, 0, 255)
|
|
274
|
+
result = result.astype(np.uint8)
|
|
275
|
+
elif operation == 'divide':
|
|
276
|
+
# Avoid division by zero
|
|
277
|
+
img2_safe = np.where(img2_f == 0, 1, img2_f)
|
|
278
|
+
if mode == 'constant':
|
|
279
|
+
result = img1_f / (img2_safe / 255.0) if img2_f.mean() != 0 else img1_f
|
|
280
|
+
else:
|
|
281
|
+
result = (img1_f / img2_safe) * 255.0
|
|
282
|
+
if clip_output:
|
|
283
|
+
result = np.clip(result, 0, 255)
|
|
284
|
+
result = result.astype(np.uint8)
|
|
285
|
+
elif operation == 'weighted_add':
|
|
286
|
+
try:
|
|
287
|
+
alpha = float(self.config.get('alpha', '0.5'))
|
|
288
|
+
beta = float(self.config.get('beta', '0.5'))
|
|
289
|
+
gamma = float(self.config.get('gamma', '0'))
|
|
290
|
+
except ValueError:
|
|
291
|
+
alpha, beta, gamma = 0.5, 0.5, 0.0
|
|
292
|
+
|
|
293
|
+
if mode == 'constant':
|
|
294
|
+
result = cv2.addWeighted(img1, alpha, img2_f.astype(np.uint8), beta, gamma)
|
|
295
|
+
else:
|
|
296
|
+
result = cv2.addWeighted(img1, alpha, img2, beta, gamma)
|
|
297
|
+
elif operation == 'absdiff':
|
|
298
|
+
if mode == 'constant':
|
|
299
|
+
result = cv2.absdiff(img1, img2_f.astype(np.uint8))
|
|
300
|
+
else:
|
|
301
|
+
result = cv2.absdiff(img1, img2)
|
|
302
|
+
elif operation == 'min':
|
|
303
|
+
if mode == 'constant':
|
|
304
|
+
result = np.minimum(img1_f, img2_f)
|
|
305
|
+
else:
|
|
306
|
+
result = np.minimum(img1_f, img2_f)
|
|
307
|
+
result = result.astype(np.uint8)
|
|
308
|
+
elif operation == 'max':
|
|
309
|
+
if mode == 'constant':
|
|
310
|
+
result = np.maximum(img1_f, img2_f)
|
|
311
|
+
else:
|
|
312
|
+
result = np.maximum(img1_f, img2_f)
|
|
313
|
+
result = result.astype(np.uint8)
|
|
314
|
+
elif operation == 'power':
|
|
315
|
+
try:
|
|
316
|
+
const_value = float(self.config.get('constant', '2'))
|
|
317
|
+
except ValueError:
|
|
318
|
+
const_value = 2.0
|
|
319
|
+
# Normalize, apply power, denormalize
|
|
320
|
+
result = np.power(img1_f / 255.0, const_value) * 255.0
|
|
321
|
+
if clip_output:
|
|
322
|
+
result = np.clip(result, 0, 255)
|
|
323
|
+
result = result.astype(np.uint8)
|
|
324
|
+
else:
|
|
325
|
+
result = img1
|
|
326
|
+
|
|
327
|
+
return result
|
|
328
|
+
|
|
329
|
+
def on_input(self, msg: Dict[str, Any], input_index: int = 0):
|
|
330
|
+
"""Process incoming image and perform arithmetic operation."""
|
|
331
|
+
if MessageKeys.PAYLOAD not in msg:
|
|
332
|
+
self.send(msg)
|
|
333
|
+
return
|
|
334
|
+
|
|
335
|
+
img, format_type = self.decode_image(msg[MessageKeys.PAYLOAD])
|
|
336
|
+
if img is None:
|
|
337
|
+
self.send(msg)
|
|
338
|
+
return
|
|
339
|
+
|
|
340
|
+
# Store image based on input index
|
|
341
|
+
if input_index == 0:
|
|
342
|
+
self._image1 = img
|
|
343
|
+
self._format_type = format_type
|
|
344
|
+
self._last_msg = msg.copy()
|
|
345
|
+
else:
|
|
346
|
+
self._image2 = img
|
|
347
|
+
|
|
348
|
+
operation = self.config.get('operation', 'add')
|
|
349
|
+
mode = self.config.get('mode', 'image')
|
|
350
|
+
|
|
351
|
+
# Single-image operations (sqrt) only need image1
|
|
352
|
+
if operation == 'sqrt' and self._image1 is not None:
|
|
353
|
+
result = self._perform_operation(self._image1, None)
|
|
354
|
+
self._send_result(result, msg)
|
|
355
|
+
return
|
|
356
|
+
|
|
357
|
+
# Constant mode only needs image1
|
|
358
|
+
if mode == 'constant' and self._image1 is not None:
|
|
359
|
+
result = self._perform_operation(self._image1, None)
|
|
360
|
+
self._send_result(result, self._last_msg or msg)
|
|
361
|
+
return
|
|
362
|
+
|
|
363
|
+
# Image mode needs both images
|
|
364
|
+
if mode == 'image' and self._image1 is not None and self._image2 is not None:
|
|
365
|
+
result = self._perform_operation(self._image1, self._image2)
|
|
366
|
+
self._send_result(result, self._last_msg or msg)
|
|
367
|
+
# Reset for next pair
|
|
368
|
+
self._image1 = None
|
|
369
|
+
self._image2 = None
|
|
370
|
+
|
|
371
|
+
def _send_result(self, result: np.ndarray, msg: Dict[str, Any]):
|
|
372
|
+
"""Send the result image."""
|
|
373
|
+
if MessageKeys.PAYLOAD not in msg or not isinstance(msg[MessageKeys.PAYLOAD], dict):
|
|
374
|
+
msg[MessageKeys.PAYLOAD] = {}
|
|
375
|
+
msg[MessageKeys.PAYLOAD][MessageKeys.IMAGE.PATH] = self.encode_image(result, self._format_type)
|
|
376
|
+
self.send(msg)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OpenCV Bitwise Operations Node - performs bitwise operations on images.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import cv2
|
|
6
|
+
import numpy as np
|
|
7
|
+
from typing import Any, Dict
|
|
8
|
+
from pynode.nodes.base_node import BaseNode, Info, MessageKeys
|
|
9
|
+
|
|
10
|
+
_info = Info()
|
|
11
|
+
_info.add_text("Performs bitwise operations (AND, OR, XOR, NOT) between two images or applies a mask.")
|
|
12
|
+
_info.add_header("Inputs")
|
|
13
|
+
_info.add_bullets(
|
|
14
|
+
("Input 0:", "First image (or only image for NOT operation)"),
|
|
15
|
+
("Input 1:", "Second image or mask")
|
|
16
|
+
)
|
|
17
|
+
_info.add_header("Operations")
|
|
18
|
+
_info.add_bullets(
|
|
19
|
+
("AND:", "Combines pixels where both images have values"),
|
|
20
|
+
("OR:", "Combines pixels where either image has values"),
|
|
21
|
+
("XOR:", "Combines pixels where only one image has values"),
|
|
22
|
+
("NOT:", "Inverts the first image (ignores second input)"),
|
|
23
|
+
("Mask:", "Applies second image as a mask to the first")
|
|
24
|
+
)
|
|
25
|
+
_info.add_header("Output")
|
|
26
|
+
_info.add_text("Outputs the resulting image from the bitwise operation.")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class BitwiseNode(BaseNode):
|
|
30
|
+
"""
|
|
31
|
+
Bitwise node - performs bitwise operations between images or with masks.
|
|
32
|
+
"""
|
|
33
|
+
info = str(_info)
|
|
34
|
+
display_name = 'Bitwise'
|
|
35
|
+
icon = '&'
|
|
36
|
+
category = 'opencv'
|
|
37
|
+
color = '#4A90D9'
|
|
38
|
+
border_color = '#2E6BB0'
|
|
39
|
+
text_color = '#FFFFFF'
|
|
40
|
+
input_count = 2 # Input 0: image1, Input 1: image2 or mask
|
|
41
|
+
output_count = 1
|
|
42
|
+
|
|
43
|
+
DEFAULT_CONFIG = {
|
|
44
|
+
'operation': 'and'
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
properties = [
|
|
48
|
+
{
|
|
49
|
+
'name': 'operation',
|
|
50
|
+
'label': 'Operation',
|
|
51
|
+
'type': 'select',
|
|
52
|
+
'options': [
|
|
53
|
+
{'value': 'and', 'label': 'AND'},
|
|
54
|
+
{'value': 'or', 'label': 'OR'},
|
|
55
|
+
{'value': 'xor', 'label': 'XOR'},
|
|
56
|
+
{'value': 'not', 'label': 'NOT (invert)'},
|
|
57
|
+
{'value': 'mask', 'label': 'Apply mask'}
|
|
58
|
+
],
|
|
59
|
+
'default': DEFAULT_CONFIG['operation'],
|
|
60
|
+
'help': 'Bitwise operation to perform'
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
def __init__(self, node_id=None, name="bitwise"):
|
|
65
|
+
super().__init__(node_id, name)
|
|
66
|
+
self._image1 = None
|
|
67
|
+
self._image2 = None
|
|
68
|
+
self._format_type = None
|
|
69
|
+
|
|
70
|
+
def on_input(self, msg: Dict[str, Any], input_index: int = 0):
|
|
71
|
+
"""Perform bitwise operation."""
|
|
72
|
+
if MessageKeys.PAYLOAD not in msg:
|
|
73
|
+
self.send(msg)
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
img, format_type = self.decode_image(msg[MessageKeys.PAYLOAD])
|
|
77
|
+
if img is None:
|
|
78
|
+
self.send(msg)
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
if input_index == 0:
|
|
82
|
+
self._image1 = img
|
|
83
|
+
self._format_type = format_type
|
|
84
|
+
else:
|
|
85
|
+
self._image2 = img
|
|
86
|
+
|
|
87
|
+
operation = self.config.get('operation', 'and')
|
|
88
|
+
|
|
89
|
+
# NOT operation only needs one image
|
|
90
|
+
if operation == 'not' and self._image1 is not None:
|
|
91
|
+
result = cv2.bitwise_not(self._image1)
|
|
92
|
+
if MessageKeys.PAYLOAD not in msg or not isinstance(msg[MessageKeys.PAYLOAD], dict):
|
|
93
|
+
msg[MessageKeys.PAYLOAD] = {}
|
|
94
|
+
msg[MessageKeys.PAYLOAD][MessageKeys.IMAGE.PATH] = self.encode_image(result, self._format_type)
|
|
95
|
+
self.send(msg)
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
# Other operations need both images
|
|
99
|
+
if self._image1 is None or self._image2 is None:
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
# Ensure images are same size
|
|
103
|
+
if self._image1.shape[:2] != self._image2.shape[:2]:
|
|
104
|
+
# Resize image2 to match image1
|
|
105
|
+
self._image2 = cv2.resize(self._image2,
|
|
106
|
+
(self._image1.shape[1], self._image1.shape[0]))
|
|
107
|
+
|
|
108
|
+
if operation == 'and':
|
|
109
|
+
result = cv2.bitwise_and(self._image1, self._image2)
|
|
110
|
+
elif operation == 'or':
|
|
111
|
+
result = cv2.bitwise_or(self._image1, self._image2)
|
|
112
|
+
elif operation == 'xor':
|
|
113
|
+
result = cv2.bitwise_xor(self._image1, self._image2)
|
|
114
|
+
elif operation == 'mask':
|
|
115
|
+
# Use image2 as mask
|
|
116
|
+
if len(self._image2.shape) == 3:
|
|
117
|
+
mask = cv2.cvtColor(self._image2, cv2.COLOR_BGR2GRAY)
|
|
118
|
+
else:
|
|
119
|
+
mask = self._image2
|
|
120
|
+
result = cv2.bitwise_and(self._image1, self._image1, mask=mask)
|
|
121
|
+
else:
|
|
122
|
+
result = self._image1
|
|
123
|
+
|
|
124
|
+
if MessageKeys.PAYLOAD not in msg or not isinstance(msg[MessageKeys.PAYLOAD], dict):
|
|
125
|
+
msg[MessageKeys.PAYLOAD] = {}
|
|
126
|
+
msg[MessageKeys.PAYLOAD][MessageKeys.IMAGE.PATH] = self.encode_image(result, self._format_type)
|
|
127
|
+
self.send(msg)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OpenCV Blend Node - blends two images together.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import cv2
|
|
6
|
+
import numpy as np
|
|
7
|
+
from typing import Any, Dict
|
|
8
|
+
from pynode.nodes.base_node import BaseNode, Info, MessageKeys
|
|
9
|
+
|
|
10
|
+
_info = Info()
|
|
11
|
+
_info.add_text("Blends two images together using weighted addition: result = alpha×image1 + beta×image2 + gamma.")
|
|
12
|
+
_info.add_header("Inputs")
|
|
13
|
+
_info.add_bullets(
|
|
14
|
+
("Input 0:", "First image"),
|
|
15
|
+
("Input 1:", "Second image (will be resized to match first if different size)")
|
|
16
|
+
)
|
|
17
|
+
_info.add_header("Parameters")
|
|
18
|
+
_info.add_bullets(
|
|
19
|
+
("Alpha:", "Weight for first image (0-1)"),
|
|
20
|
+
("Beta:", "Weight for second image (0-1)"),
|
|
21
|
+
("Gamma:", "Brightness adjustment added to result")
|
|
22
|
+
)
|
|
23
|
+
_info.add_header("Output")
|
|
24
|
+
_info.add_text("Outputs the blended image.")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class BlendNode(BaseNode):
|
|
28
|
+
"""
|
|
29
|
+
Blend node - blends two images together using weighted addition.
|
|
30
|
+
"""
|
|
31
|
+
info = str(_info)
|
|
32
|
+
display_name = 'Blend'
|
|
33
|
+
icon = '⧉'
|
|
34
|
+
category = 'opencv'
|
|
35
|
+
color = '#4A90D9'
|
|
36
|
+
border_color = '#2E6BB0'
|
|
37
|
+
text_color = '#FFFFFF'
|
|
38
|
+
input_count = 2 # Input 0: image1, Input 1: image2
|
|
39
|
+
output_count = 1
|
|
40
|
+
|
|
41
|
+
DEFAULT_CONFIG = {
|
|
42
|
+
'alpha': 0.5,
|
|
43
|
+
'beta': 0.5,
|
|
44
|
+
'gamma': 0
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
properties = [
|
|
48
|
+
{
|
|
49
|
+
'name': 'alpha',
|
|
50
|
+
'label': 'Alpha (image 1 weight)',
|
|
51
|
+
'type': 'number',
|
|
52
|
+
'default': DEFAULT_CONFIG['alpha'],
|
|
53
|
+
'min': 0,
|
|
54
|
+
'max': 1,
|
|
55
|
+
'step': 0.1,
|
|
56
|
+
'help': 'Weight for first image (0-1)'
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
'name': 'beta',
|
|
60
|
+
'label': 'Beta (image 2 weight)',
|
|
61
|
+
'type': 'number',
|
|
62
|
+
'default': DEFAULT_CONFIG['beta'],
|
|
63
|
+
'min': 0,
|
|
64
|
+
'max': 1,
|
|
65
|
+
'step': 0.1,
|
|
66
|
+
'help': 'Weight for second image (0-1)'
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
'name': 'gamma',
|
|
70
|
+
'label': 'Gamma (brightness)',
|
|
71
|
+
'type': 'number',
|
|
72
|
+
'default': DEFAULT_CONFIG['gamma'],
|
|
73
|
+
'min': -100,
|
|
74
|
+
'max': 100,
|
|
75
|
+
'help': 'Added to final result'
|
|
76
|
+
}
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
def __init__(self, node_id=None, name="blend"):
|
|
80
|
+
super().__init__(node_id, name)
|
|
81
|
+
self._image1 = None
|
|
82
|
+
self._image2 = None
|
|
83
|
+
self._format_type = None
|
|
84
|
+
|
|
85
|
+
def on_input(self, msg: Dict[str, Any], input_index: int = 0):
|
|
86
|
+
"""Blend two images."""
|
|
87
|
+
if MessageKeys.PAYLOAD not in msg:
|
|
88
|
+
self.send(msg)
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
img, format_type = self.decode_image(msg[MessageKeys.PAYLOAD])
|
|
92
|
+
if img is None:
|
|
93
|
+
self.send(msg)
|
|
94
|
+
return
|
|
95
|
+
|
|
96
|
+
if input_index == 0:
|
|
97
|
+
self._image1 = img
|
|
98
|
+
self._format_type = format_type
|
|
99
|
+
else:
|
|
100
|
+
self._image2 = img
|
|
101
|
+
|
|
102
|
+
# Need both images
|
|
103
|
+
if self._image1 is None or self._image2 is None:
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
alpha = self.get_config_float('alpha', 0.5)
|
|
107
|
+
beta = self.get_config_float('beta', 0.5)
|
|
108
|
+
gamma = self.get_config_float('gamma', 0)
|
|
109
|
+
|
|
110
|
+
# Ensure images are same size
|
|
111
|
+
if self._image1.shape[:2] != self._image2.shape[:2]:
|
|
112
|
+
self._image2 = cv2.resize(self._image2,
|
|
113
|
+
(self._image1.shape[1], self._image1.shape[0]))
|
|
114
|
+
|
|
115
|
+
# Ensure same number of channels
|
|
116
|
+
if len(self._image1.shape) != len(self._image2.shape):
|
|
117
|
+
if len(self._image1.shape) == 2:
|
|
118
|
+
self._image1 = cv2.cvtColor(self._image1, cv2.COLOR_GRAY2BGR)
|
|
119
|
+
if len(self._image2.shape) == 2:
|
|
120
|
+
self._image2 = cv2.cvtColor(self._image2, cv2.COLOR_GRAY2BGR)
|
|
121
|
+
|
|
122
|
+
result = cv2.addWeighted(self._image1, alpha, self._image2, beta, gamma)
|
|
123
|
+
|
|
124
|
+
if MessageKeys.PAYLOAD not in msg or not isinstance(msg[MessageKeys.PAYLOAD], dict):
|
|
125
|
+
msg[MessageKeys.PAYLOAD] = {}
|
|
126
|
+
msg[MessageKeys.PAYLOAD][MessageKeys.IMAGE.PATH] = self.encode_image(result, self._format_type)
|
|
127
|
+
self.send(msg)
|