|
2 | 2 | import cv2 |
3 | 3 | import base64 |
4 | 4 | import threading |
5 | | -import time |
6 | | -import numpy as np |
| 5 | + |
| 6 | +import rclpy |
| 7 | +from rclpy.node import Node |
| 8 | +from rclpy.qos import QoSProfile, DurabilityPolicy |
| 9 | +from sensor_msgs.msg import Image |
| 10 | +from cv_bridge import CvBridge |
| 11 | +from rclpy.executors import SingleThreadedExecutor |
| 12 | + |
7 | 13 | from gui_interfaces.general.measuring_threading_gui_harmonic import ( |
8 | 14 | MeasuringThreadingGUI, |
9 | 15 | ) |
10 | 16 | from console_interfaces.general.console import start_console |
11 | 17 |
|
12 | 18 |
|
| 19 | +class WebGUINode(Node): |
| 20 | + def __init__(self, gui_instance): |
| 21 | + super().__init__("webgui_car_junction") |
| 22 | + self.gui = gui_instance |
| 23 | + self.bridge = CvBridge() |
| 24 | + |
| 25 | + qos_profile = QoSProfile(depth=1, durability=DurabilityPolicy.TRANSIENT_LOCAL) |
| 26 | + self.create_subscription(Image, "/webgui/image", self.img_cb, qos_profile) |
| 27 | + |
| 28 | + def img_cb(self, msg): |
| 29 | + try: |
| 30 | + cv_image = self.bridge.imgmsg_to_cv2(msg, desired_encoding="passthrough") |
| 31 | + self.gui.set_incoming_image(cv_image) |
| 32 | + except Exception: |
| 33 | + pass |
| 34 | + |
| 35 | + |
13 | 36 | class WebGUI(MeasuringThreadingGUI): |
14 | 37 | def __init__(self, host="ws://127.0.0.1:2303"): |
15 | 38 | super().__init__(host) |
16 | 39 |
|
17 | | - self.image_to_be_shown = None |
18 | | - self.image_to_be_shown_updated = False |
19 | | - self.image_show_lock = threading.Lock() |
| 40 | + if not rclpy.ok(): |
| 41 | + rclpy.init() |
20 | 42 |
|
| 43 | + self.incoming_image = None |
| 44 | + self.incoming_lock = threading.Lock() |
| 45 | + |
| 46 | + self.encoded_payload = "" |
| 47 | + self.payload_lock = threading.Lock() |
21 | 48 | self.payload = {"image": ""} |
22 | 49 |
|
23 | | - self.start() |
| 50 | + self.ros_node = WebGUINode(self) |
| 51 | + self.executor = SingleThreadedExecutor() |
| 52 | + self.executor.add_node(self.ros_node) |
| 53 | + |
| 54 | + self.executor_thread = threading.Thread(target=self.executor.spin, daemon=True) |
| 55 | + self.executor_thread.start() |
24 | 56 |
|
25 | | - def payloadImage(self): |
26 | | - with self.image_show_lock: |
27 | | - image_to_be_shown_updated = self.image_to_be_shown_updated |
28 | | - image_to_be_shown = self.image_to_be_shown |
| 57 | + self.unified_thread = threading.Thread( |
| 58 | + target=self._unified_image_loop, daemon=True |
| 59 | + ) |
| 60 | + self.unified_thread.start() |
29 | 61 |
|
30 | | - payload = {"image": "", "shape": ""} |
| 62 | + self.start() |
31 | 63 |
|
32 | | - if not image_to_be_shown_updated or image_to_be_shown is None: |
33 | | - return payload |
| 64 | + def set_incoming_image(self, image): |
| 65 | + with self.incoming_lock: |
| 66 | + self.incoming_image = image |
34 | 67 |
|
35 | | - shape = image_to_be_shown.shape |
36 | | - frame = cv2.imencode(".JPEG", image_to_be_shown)[1] |
37 | | - encoded_image = base64.b64encode(frame) |
| 68 | + def _unified_image_loop(self): |
| 69 | + while True: |
| 70 | + try: |
| 71 | + with self.incoming_lock: |
| 72 | + image = self.incoming_image |
| 73 | + self.incoming_image = None |
38 | 74 |
|
39 | | - payload["image"] = encoded_image.decode("utf-8") |
40 | | - payload["shape"] = shape |
| 75 | + if image is not None: |
| 76 | + h, w = image.shape[:2] |
| 77 | + if w > 640 or h > 480: |
| 78 | + image = cv2.resize(image, (640, 480)) |
41 | 79 |
|
42 | | - with self.image_show_lock: |
43 | | - self.image_to_be_shown_updated = False |
| 80 | + _, frame = cv2.imencode( |
| 81 | + ".JPEG", image, [int(cv2.IMWRITE_JPEG_QUALITY), 60] |
| 82 | + ) |
| 83 | + encoded = base64.b64encode(frame).decode("utf-8") |
44 | 84 |
|
45 | | - return payload |
| 85 | + with self.payload_lock: |
| 86 | + self.encoded_payload = encoded |
46 | 87 |
|
47 | | - def update_gui(self): |
48 | | - payload = self.payloadImage() |
49 | | - self.payload["image"] = json.dumps(payload) |
| 88 | + threading.Event().wait(0.033) |
| 89 | + except Exception: |
| 90 | + threading.Event().wait(1.0) |
50 | 91 |
|
| 92 | + def update_gui(self): |
| 93 | + with self.payload_lock: |
| 94 | + if not self.encoded_payload: |
| 95 | + return |
| 96 | + current_payload = self.encoded_payload |
| 97 | + self.encoded_payload = "" |
| 98 | + |
| 99 | + self.payload["image"] = json.dumps( |
| 100 | + {"image": current_payload, "shape": [480, 640, 3]} |
| 101 | + ) |
51 | 102 | message = json.dumps(self.payload) |
52 | 103 | self.send_to_client(message) |
53 | 104 |
|
54 | 105 | def setImage(self, image): |
55 | | - """Single point of entry for all images""" |
56 | | - with self.image_show_lock: |
57 | | - self.image_to_be_shown = image |
58 | | - self.image_to_be_shown_updated = True |
| 106 | + self.set_incoming_image(image) |
59 | 107 |
|
60 | 108 |
|
61 | 109 | host = "ws://127.0.0.1:2303" |
|
0 commit comments