Skip to content

Commit 2752a57

Browse files
committed
refactor: migrate video_objectdetection
1 parent a4f07a5 commit 2752a57

File tree

1 file changed

+63
-27
lines changed
  • src/arduino/app_bricks/video_objectdetection

1 file changed

+63
-27
lines changed

src/arduino/app_bricks/video_objectdetection/__init__.py

Lines changed: 63 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,21 @@
22
#
33
# SPDX-License-Identifier: MPL-2.0
44

5-
from arduino.app_utils import brick, Logger
6-
from arduino.app_internal.core import load_brick_compose_file, resolve_address
7-
from arduino.app_internal.core import EdgeImpulseRunnerFacade
85
import time
6+
import json
7+
import inspect
98
import threading
9+
import socket
1010
from typing import Callable
11+
1112
from websockets.sync.client import connect, ClientConnection
1213
from websockets.exceptions import ConnectionClosedOK, ConnectionClosedError
13-
import json
14-
import inspect
14+
15+
from arduino.app_peripherals.camera import Camera
16+
from arduino.app_internal.core import load_brick_compose_file, resolve_address
17+
from arduino.app_internal.core import EdgeImpulseRunnerFacade
18+
from arduino.app_utils.image.adjustments import compress_to_jpeg
19+
from arduino.app_utils import brick, Logger
1520

1621
logger = Logger("VideoObjectDetection")
1722

@@ -30,16 +35,19 @@ class VideoObjectDetection:
3035

3136
ALL_HANDLERS_KEY = "__ALL"
3237

33-
def __init__(self, confidence: float = 0.3, debounce_sec: float = 0.0):
38+
def __init__(self, camera: Camera = None, confidence: float = 0.3, debounce_sec: float = 0.0):
3439
"""Initialize the VideoObjectDetection class.
3540
3641
Args:
42+
camera (Camera): The camera instance to use for capturing video. If None, a default camera will be initialized.
3743
confidence (float): Confidence level for detection. Default is 0.3 (30%).
3844
debounce_sec (float): Minimum seconds between repeated detections of the same object. Default is 0 seconds.
3945
4046
Raises:
4147
RuntimeError: If the host address could not be resolved.
4248
"""
49+
self._camera = camera if camera else Camera()
50+
4351
self._confidence = confidence
4452
self._debounce_sec = debounce_sec
4553
self._last_detected: dict[str, float] = {}
@@ -107,32 +115,25 @@ def on_detect_all(self, callback: Callable[[dict], None]):
107115

108116
def start(self):
109117
"""Start the video object detection process."""
118+
self._camera.start()
110119
self._is_running.set()
111120

112121
def stop(self):
113-
"""Stop the video object detection process."""
122+
"""Stop the video object detection process and release resources."""
114123
self._is_running.clear()
124+
self._camera.stop()
125+
126+
@brick.execute
127+
def object_detection_loop(self):
128+
"""Object detection main loop.
115129
116-
def execute(self):
117-
"""Connect to the model runner and process messages until `stop` is called.
118-
119-
Behavior:
120-
- Establishes a WebSocket connection to the runner.
121-
- Parses ``"hello"`` messages to capture model metadata and optionally
122-
performs a threshold override to align the runner with the local setting.
123-
- Parses ``"classification"`` messages, filters detections by confidence,
124-
applies debounce, then invokes registered callbacks.
125-
- Retries on transient WebSocket errors while running.
126-
127-
Exceptions:
128-
ConnectionClosedOK:
129-
Propagated to exit cleanly when the server closes the connection.
130-
ConnectionClosedError, TimeoutError, ConnectionRefusedError:
131-
Logged and retried with a short backoff while running.
130+
Maintains WebSocket connection to the model runner and processes object detection messages.
131+
Retries on connection errors until stopped.
132132
"""
133133
while self._is_running.is_set():
134134
try:
135135
with connect(self._uri) as ws:
136+
logger.info("WebSocket connection established")
136137
while self._is_running.is_set():
137138
try:
138139
message = ws.recv()
@@ -142,21 +143,56 @@ def execute(self):
142143
except ConnectionClosedOK:
143144
raise
144145
except (TimeoutError, ConnectionRefusedError, ConnectionClosedError):
145-
logger.warning(f"Connection lost. Retrying...")
146+
logger.warning(f"WebSocket connection lost. Retrying...")
146147
raise
147148
except Exception as e:
148149
logger.exception(f"Failed to process detection: {e}")
149150
except ConnectionClosedOK:
150-
logger.debug(f"Disconnected cleanly, exiting WebSocket read loop.")
151+
logger.debug(f"WebSocket disconnected cleanly, exiting loop.")
151152
return
152153
except (TimeoutError, ConnectionRefusedError, ConnectionClosedError):
153154
logger.debug(f"Waiting for model runner. Retrying...")
154-
import time
155-
156155
time.sleep(2)
157156
continue
158157
except Exception as e:
159158
logger.exception(f"Failed to establish WebSocket connection to {self._host}: {e}")
159+
time.sleep(2)
160+
161+
@brick.execute
162+
def camera_loop(self):
163+
"""Camera main loop.
164+
165+
Captures images from the camera and forwards them over the TCP connection.
166+
Retries on connection errors until stopped.
167+
"""
168+
while self._is_running.is_set():
169+
try:
170+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as tcp_socket:
171+
tcp_socket.connect((self._host, "5050"))
172+
logger.info(f"TCP connection established to {self._host}:5050")
173+
174+
while self._is_running.is_set():
175+
try:
176+
frame = self._camera.capture()
177+
if frame is None:
178+
time.sleep(0.01) # Brief sleep if no image available
179+
continue
180+
181+
jpeg_frame = compress_to_jpeg(frame)
182+
tcp_socket.sendall(jpeg_frame.tobytes())
183+
184+
except (BrokenPipeError, ConnectionResetError, OSError) as e:
185+
logger.warning(f"TCP connection lost: {e}. Retrying...")
186+
break
187+
except Exception as e:
188+
logger.exception(f"Error capturing/sending image: {e}")
189+
190+
except (ConnectionRefusedError, OSError) as e:
191+
logger.debug(f"TCP connection failed: {e}. Retrying in 2 seconds...")
192+
time.sleep(2)
193+
except Exception as e:
194+
logger.exception(f"Unexpected error in TCP loop: {e}")
195+
time.sleep(2)
160196

161197
def _process_message(self, ws: ClientConnection, message: str):
162198
jmsg = json.loads(message)

0 commit comments

Comments
 (0)