wip unify the live mode on cameras
This commit is contained in:
@@ -6,7 +6,7 @@ from enum import IntEnum
|
||||
|
||||
import numpy as np
|
||||
from bec_lib.logger import bec_logger
|
||||
from ophyd import Component as Cpt
|
||||
from ophyd import Component as Cpt, Kind, Signal
|
||||
from ophyd.areadetector import ADComponent as ADCpt
|
||||
from ophyd.areadetector import DetectorBase
|
||||
from ophyd_devices import PreviewSignal
|
||||
@@ -44,6 +44,14 @@ class AlliedVisionCamera(PSIDeviceBase, DetectorBase):
|
||||
doc="Preview signal of the AlliedVision camera.",
|
||||
)
|
||||
|
||||
live_mode_enabled = Cpt(
|
||||
Signal,
|
||||
name="live_mode_enabled",
|
||||
value=False,
|
||||
doc="Enable or disable live mode.",
|
||||
kind=Kind.config,
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@@ -68,24 +76,36 @@ class AlliedVisionCamera(PSIDeviceBase, DetectorBase):
|
||||
self._poll_rate = poll_rate
|
||||
self._unique_array_id = 0
|
||||
self._pv_timeout = 2.0
|
||||
self.r_lock = threading.RLock()
|
||||
self.image: ImagePlugin
|
||||
self._live_mode_lock = threading.RLock()
|
||||
self.live_mode_enabled.subscribe(self._on_live_mode_enabled_changed, run=False)
|
||||
|
||||
def start_live_mode(self) -> None:
|
||||
"""Start live mode."""
|
||||
if not self._poll_start_event.is_set():
|
||||
self._poll_start_event.set()
|
||||
self.cam.acquire.put(ACQUIRE_MODES.ACQUIRING.value) # Start acquisition
|
||||
else:
|
||||
logger.info(f"Live mode already started for {self.name}.")
|
||||
self.live_mode_enabled.put(True)
|
||||
|
||||
def stop_live_mode(self) -> None:
|
||||
"""Stop live mode."""
|
||||
if self._poll_start_event.is_set():
|
||||
self._poll_start_event.clear()
|
||||
self.cam.acquire.put(ACQUIRE_MODES.DONE.value) # Stop acquisition
|
||||
else:
|
||||
logger.info(f"Live mode already stopped for {self.name}.")
|
||||
self.live_mode_enabled.put(False)
|
||||
|
||||
def _on_live_mode_enabled_changed(self, *args, value, **kwargs) -> None:
|
||||
self._apply_live_mode(bool(value))
|
||||
|
||||
def _apply_live_mode(self, enabled: bool) -> None:
|
||||
with self._live_mode_lock:
|
||||
if enabled:
|
||||
if not self._poll_start_event.is_set():
|
||||
self._poll_start_event.set()
|
||||
self.cam.acquire.put(ACQUIRE_MODES.ACQUIRING.value) # Start acquisition
|
||||
else:
|
||||
logger.info(f"Live mode already started for {self.name}.")
|
||||
return
|
||||
|
||||
if self._poll_start_event.is_set():
|
||||
self._poll_start_event.clear()
|
||||
self.cam.acquire.put(ACQUIRE_MODES.DONE.value) # Stop acquisition
|
||||
else:
|
||||
logger.info(f"Live mode already stopped for {self.name}.")
|
||||
|
||||
def on_connected(self):
|
||||
"""Reset the unique array ID on connection."""
|
||||
|
||||
@@ -156,7 +156,6 @@ class Camera:
|
||||
camera_id (int): The ID of the camera device.
|
||||
m_n_colormode (Literal[0, 1, 2, 3]): Color mode for the camera.
|
||||
bits_per_pixel (Literal[8, 24]): Number of bits per pixel for the camera.
|
||||
live_mode (bool): Whether to enable live mode for the camera.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
|
||||
@@ -37,9 +37,15 @@ class IDSCamera(PSIDeviceBase):
|
||||
doc="Signal for the region of interest (ROI).",
|
||||
async_update={"type": "add", "max_shape": [None]},
|
||||
)
|
||||
live_mode_enabled = Cpt(Signal, name="live_mode_enabled", value=False ,doc="Enable or disable live mode.",kind=Kind.config)
|
||||
live_mode_enabled = Cpt(
|
||||
Signal,
|
||||
name="live_mode_enabled",
|
||||
value=False,
|
||||
doc="Enable or disable live mode.",
|
||||
kind=Kind.config,
|
||||
)
|
||||
|
||||
USER_ACCESS = ["live_mode", "mask", "set_rect_roi", "get_last_image"]
|
||||
USER_ACCESS = ["start_live_mode", "stop_live_mode", "mask", "set_rect_roi", "get_last_image"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -81,11 +87,17 @@ class IDSCamera(PSIDeviceBase):
|
||||
self.image.num_rotation_90 = num_rotation_90
|
||||
self.image.transpose = transpose
|
||||
self._force_monochrome = force_monochrome
|
||||
self.live_mode_enabled.subscribe(self.on_live_mode_changed,run=False)
|
||||
self.live_mode_enabled.put(live_mode)
|
||||
self.live_mode_enabled.subscribe(self._on_live_mode_enabled_changed, run=False)
|
||||
self.live_mode_enabled.put(bool(live_mode))
|
||||
|
||||
############## Live Mode Methods ##############
|
||||
|
||||
def start_live_mode(self) -> None:
|
||||
self.live_mode_enabled.put(True)
|
||||
|
||||
def stop_live_mode(self) -> None:
|
||||
self.live_mode_enabled.put(False)
|
||||
|
||||
@property
|
||||
def mask(self) -> np.ndarray:
|
||||
"""Return the current region of interest (ROI) for the camera."""
|
||||
@@ -108,23 +120,12 @@ class IDSCamera(PSIDeviceBase):
|
||||
)
|
||||
self._mask = value
|
||||
|
||||
@property
|
||||
def live_mode(self) -> bool:
|
||||
"""Return whether the camera is in live mode."""
|
||||
return bool(self.live_mode_enabled.get())
|
||||
|
||||
@live_mode.setter
|
||||
def live_mode(self, value: bool):
|
||||
"""Set the live mode for the camera."""
|
||||
if value != bool(self.live_mode_enabled.get()):
|
||||
self.live_mode_enabled.put(bool(value))
|
||||
|
||||
def on_live_mode_changed(self, *args, value, **kwargs):
|
||||
def _on_live_mode_enabled_changed(self, *args, value, **kwargs):
|
||||
"""Callback for when live mode is changed."""
|
||||
if self.cam._connected is False: # $ pylint: disable=protected-access
|
||||
enabled = bool(value)
|
||||
if enabled and self.cam._connected is False: # pylint: disable=protected-access
|
||||
self.cam.on_connect()
|
||||
self.live_mode_enabled.put(bool(value))
|
||||
if value:
|
||||
if enabled:
|
||||
self._start_live()
|
||||
else:
|
||||
self._stop_live()
|
||||
@@ -192,7 +193,7 @@ class IDSCamera(PSIDeviceBase):
|
||||
"""Connect to the camera."""
|
||||
self.cam.force_monochrome = self._force_monochrome
|
||||
self.cam.on_connect()
|
||||
self.live_mode = self._inputs.get("live_mode", False)
|
||||
self.live_mode_enabled.put(bool(self._inputs.get("live_mode", False)))
|
||||
self.set_rect_roi(0, 0, self.cam.cam.width.value, self.cam.cam.height.value)
|
||||
|
||||
def on_destroy(self):
|
||||
@@ -202,7 +203,7 @@ class IDSCamera(PSIDeviceBase):
|
||||
|
||||
def on_trigger(self):
|
||||
"""Handle the trigger event."""
|
||||
if not self.live_mode:
|
||||
if not bool(self.live_mode_enabled.get()):
|
||||
return
|
||||
image = self.image.get()
|
||||
if image is not None:
|
||||
|
||||
@@ -2,7 +2,7 @@ import requests
|
||||
import threading
|
||||
import cv2
|
||||
import numpy as np
|
||||
from ophyd import Device, Component as Cpt
|
||||
from ophyd import Device, Component as Cpt, Kind, Signal
|
||||
from ophyd_devices import PreviewSignal
|
||||
import traceback
|
||||
|
||||
@@ -13,6 +13,13 @@ logger = bec_logger.logger
|
||||
class WebcamViewer(Device):
|
||||
USER_ACCESS = ["start_live_mode", "stop_live_mode"]
|
||||
preview = Cpt(PreviewSignal, ndim=2, num_rotation_90=0, transpose=False)
|
||||
live_mode_enabled = Cpt(
|
||||
Signal,
|
||||
name="live_mode_enabled",
|
||||
value=False,
|
||||
doc="Enable or disable live mode.",
|
||||
kind=Kind.config,
|
||||
)
|
||||
|
||||
def __init__(self, url:str, name:str, num_rotation_90=0, transpose=False, **kwargs) -> None:
|
||||
super().__init__(name=name, **kwargs)
|
||||
@@ -21,20 +28,54 @@ class WebcamViewer(Device):
|
||||
self._update_thread = None
|
||||
self._buffer = b""
|
||||
self._shutdown_event = threading.Event()
|
||||
self._live_mode_lock = threading.RLock()
|
||||
self.preview.num_rotation_90 = num_rotation_90
|
||||
self.preview.transpose = transpose
|
||||
self.live_mode_enabled.subscribe(self._on_live_mode_enabled_changed, run=False)
|
||||
|
||||
def start_live_mode(self) -> None:
|
||||
if self._connection is not None:
|
||||
return
|
||||
self._update_thread = threading.Thread(target=self._update_loop, daemon=True)
|
||||
self._update_thread.start()
|
||||
self.live_mode_enabled.put(True)
|
||||
|
||||
def stop_live_mode(self) -> None:
|
||||
self.live_mode_enabled.put(False)
|
||||
|
||||
def _on_live_mode_enabled_changed(self, *args, value, **kwargs) -> None:
|
||||
self._apply_live_mode(bool(value))
|
||||
|
||||
def _apply_live_mode(self, enabled: bool) -> None:
|
||||
with self._live_mode_lock:
|
||||
if enabled:
|
||||
if self._update_thread is not None and self._update_thread.is_alive():
|
||||
return
|
||||
self._shutdown_event.clear()
|
||||
self._update_thread = threading.Thread(target=self._update_loop, daemon=True)
|
||||
self._update_thread.start()
|
||||
return
|
||||
|
||||
if self._update_thread is None:
|
||||
return
|
||||
self._shutdown_event.set()
|
||||
if self._connection is not None:
|
||||
try:
|
||||
self._connection.close()
|
||||
except Exception: # pylint: disable=broad-except
|
||||
pass
|
||||
self._connection = None
|
||||
self._update_thread.join(timeout=2)
|
||||
if self._update_thread.is_alive():
|
||||
logger.warning("Webcam live mode thread did not stop within timeout.")
|
||||
return
|
||||
self._update_thread = None
|
||||
self._buffer = b""
|
||||
self._shutdown_event.clear()
|
||||
|
||||
def _update_loop(self) -> None:
|
||||
while not self._shutdown_event.is_set():
|
||||
try:
|
||||
self._connection = requests.get(self.url, stream=True)
|
||||
self._connection = requests.get(self.url, stream=True, timeout=5)
|
||||
for chunk in self._connection.iter_content(chunk_size=1024):
|
||||
if self._shutdown_event.is_set():
|
||||
break
|
||||
self._buffer += chunk
|
||||
start = self._buffer.find(b'\xff\xd8') # JPEG start
|
||||
end = self._buffer.find(b'\xff\xd9') # JPEG end
|
||||
@@ -50,16 +91,3 @@ class WebcamViewer(Device):
|
||||
except Exception as exc:
|
||||
content = traceback.format_exc()
|
||||
logger.error(f"Image update loop failed: {content}")
|
||||
|
||||
def stop_live_mode(self) -> None:
|
||||
if self._connection is None:
|
||||
return
|
||||
self._shutdown_event.set()
|
||||
if self._connection is not None:
|
||||
self._connection.close()
|
||||
self._connection = None
|
||||
if self._update_thread is not None:
|
||||
self._update_thread.join()
|
||||
self._update_thread = None
|
||||
|
||||
self._shutdown_event.clear()
|
||||
Reference in New Issue
Block a user