pyobs-aravis 2.0.0.dev6__tar.gz → 2.0.0.dev8__tar.gz
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.
- {pyobs_aravis-2.0.0.dev6 → pyobs_aravis-2.0.0.dev8}/PKG-INFO +1 -1
- {pyobs_aravis-2.0.0.dev6 → pyobs_aravis-2.0.0.dev8}/pyobs_aravis/araviscamera.py +52 -5
- {pyobs_aravis-2.0.0.dev6 → pyobs_aravis-2.0.0.dev8}/pyproject.toml +1 -1
- {pyobs_aravis-2.0.0.dev6 → pyobs_aravis-2.0.0.dev8}/uv.lock +1 -1
- {pyobs_aravis-2.0.0.dev6 → pyobs_aravis-2.0.0.dev8}/.github/workflows/pypi.yml +0 -0
- {pyobs_aravis-2.0.0.dev6 → pyobs_aravis-2.0.0.dev8}/.github/workflows/ruff.yml +0 -0
- {pyobs_aravis-2.0.0.dev6 → pyobs_aravis-2.0.0.dev8}/.pre-commit-config.yaml +0 -0
- {pyobs_aravis-2.0.0.dev6 → pyobs_aravis-2.0.0.dev8}/.readthedocs.yml +0 -0
- {pyobs_aravis-2.0.0.dev6 → pyobs_aravis-2.0.0.dev8}/CHANGELOG.rst +0 -0
- {pyobs_aravis-2.0.0.dev6 → pyobs_aravis-2.0.0.dev8}/LICENSE +0 -0
- {pyobs_aravis-2.0.0.dev6 → pyobs_aravis-2.0.0.dev8}/README.md +0 -0
- {pyobs_aravis-2.0.0.dev6 → pyobs_aravis-2.0.0.dev8}/docs/Makefile +0 -0
- {pyobs_aravis-2.0.0.dev6 → pyobs_aravis-2.0.0.dev8}/docs/make.bat +0 -0
- {pyobs_aravis-2.0.0.dev6 → pyobs_aravis-2.0.0.dev8}/docs/requirements.txt +0 -0
- {pyobs_aravis-2.0.0.dev6 → pyobs_aravis-2.0.0.dev8}/docs/source/_static/pyobs.gif +0 -0
- {pyobs_aravis-2.0.0.dev6 → pyobs_aravis-2.0.0.dev8}/docs/source/conf.py +0 -0
- {pyobs_aravis-2.0.0.dev6 → pyobs_aravis-2.0.0.dev8}/docs/source/index.rst +0 -0
- {pyobs_aravis-2.0.0.dev6 → pyobs_aravis-2.0.0.dev8}/pyobs_aravis/__init__.py +0 -0
- {pyobs_aravis-2.0.0.dev6 → pyobs_aravis-2.0.0.dev8}/pyobs_aravis/aravis.py +0 -0
- {pyobs_aravis-2.0.0.dev6 → pyobs_aravis-2.0.0.dev8}/pyobs_aravis/gui.py +0 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import logging
|
|
3
|
+
import threading
|
|
3
4
|
import time
|
|
5
|
+
from collections.abc import Callable
|
|
4
6
|
from typing import Any
|
|
5
7
|
|
|
6
8
|
import numpy.typing as npt
|
|
@@ -9,6 +11,11 @@ from pyobs.modules.camera import BaseVideo
|
|
|
9
11
|
|
|
10
12
|
log = logging.getLogger(__name__)
|
|
11
13
|
|
|
14
|
+
# aravis/GLib calls are blocking and are made directly on the event loop thread (see _run_blocking).
|
|
15
|
+
# If the camera has gone unresponsive, they can hang indefinitely, so we bound them with a timeout
|
|
16
|
+
# rather than let a single dead camera freeze the whole module.
|
|
17
|
+
_SDK_CALL_TIMEOUT = 5.0
|
|
18
|
+
|
|
12
19
|
|
|
13
20
|
class AravisCamera(BaseVideo, IExposureTime):
|
|
14
21
|
"""A pyobs module for Aravis cameras."""
|
|
@@ -50,7 +57,18 @@ class AravisCamera(BaseVideo, IExposureTime):
|
|
|
50
57
|
|
|
51
58
|
await BaseVideo.open(self)
|
|
52
59
|
|
|
53
|
-
|
|
60
|
+
# device discovery is a blocking, network-based scan (GigE Vision/USB3 Vision devices
|
|
61
|
+
# reply to a broadcast query) that can take multiple seconds -- run it like the other
|
|
62
|
+
# aravis/GLib calls (see _run_blocking) instead of freezing the whole module's event
|
|
63
|
+
# loop, and with it, the ability to respond to any other module, for that long
|
|
64
|
+
ids: list[str] = []
|
|
65
|
+
|
|
66
|
+
def _list_device_ids() -> None:
|
|
67
|
+
ids.extend(aravis.get_device_ids()) # type: ignore[arg-type]
|
|
68
|
+
|
|
69
|
+
if not await self._run_blocking(_list_device_ids):
|
|
70
|
+
raise TimeoutError(f"Timed out listing available cameras after {_SDK_CALL_TIMEOUT}s.")
|
|
71
|
+
|
|
54
72
|
if self._camera_device_name not in ids:
|
|
55
73
|
raise ValueError("Could not find given device name in list of available cameras.")
|
|
56
74
|
|
|
@@ -59,8 +77,7 @@ class AravisCamera(BaseVideo, IExposureTime):
|
|
|
59
77
|
async def close(self) -> None:
|
|
60
78
|
"""Close the module."""
|
|
61
79
|
await BaseVideo.close(self)
|
|
62
|
-
|
|
63
|
-
self._close_camera()
|
|
80
|
+
await self._deactivate_camera()
|
|
64
81
|
|
|
65
82
|
def _open_camera(self) -> None:
|
|
66
83
|
"""Open camera."""
|
|
@@ -87,15 +104,45 @@ class AravisCamera(BaseVideo, IExposureTime):
|
|
|
87
104
|
log.exception("Error closing camera.")
|
|
88
105
|
self._camera = None
|
|
89
106
|
|
|
107
|
+
@staticmethod
|
|
108
|
+
async def _run_blocking(func: Callable[[], None], timeout: float = _SDK_CALL_TIMEOUT) -> bool:
|
|
109
|
+
"""Run a blocking aravis/GLib call in a daemon thread, so a hung call can't freeze the module.
|
|
110
|
+
|
|
111
|
+
A plain executor isn't used here, since its worker threads are non-daemon and Python joins
|
|
112
|
+
them on interpreter shutdown -- a hung call would then just move the freeze to process exit.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
True if func completed within timeout, False if it's still running in the background.
|
|
116
|
+
"""
|
|
117
|
+
loop = asyncio.get_running_loop()
|
|
118
|
+
future: asyncio.Future[None] = loop.create_future()
|
|
119
|
+
|
|
120
|
+
def _wrapper() -> None:
|
|
121
|
+
try:
|
|
122
|
+
func()
|
|
123
|
+
finally:
|
|
124
|
+
loop.call_soon_threadsafe(future.set_result, None)
|
|
125
|
+
|
|
126
|
+
threading.Thread(target=_wrapper, daemon=True).start()
|
|
127
|
+
try:
|
|
128
|
+
await asyncio.wait_for(future, timeout=timeout)
|
|
129
|
+
return True
|
|
130
|
+
except TimeoutError:
|
|
131
|
+
return False
|
|
132
|
+
|
|
90
133
|
async def _activate_camera(self) -> None:
|
|
91
134
|
"""Open camera on activation."""
|
|
92
135
|
async with self._camera_lock:
|
|
93
|
-
self._open_camera
|
|
136
|
+
if not await self._run_blocking(self._open_camera):
|
|
137
|
+
log.error("Timed out connecting to camera after %.1fs.", _SDK_CALL_TIMEOUT)
|
|
138
|
+
self._camera = None
|
|
94
139
|
|
|
95
140
|
async def _deactivate_camera(self) -> None:
|
|
96
141
|
"""Close camera on deactivation."""
|
|
97
142
|
async with self._camera_lock:
|
|
98
|
-
self._close_camera
|
|
143
|
+
if not await self._run_blocking(self._close_camera):
|
|
144
|
+
log.error("Timed out closing camera after %.1fs, abandoning cleanup.", _SDK_CALL_TIMEOUT)
|
|
145
|
+
self._camera = None
|
|
99
146
|
|
|
100
147
|
async def _capture(self) -> None:
|
|
101
148
|
"""Take new images in loop."""
|
|
@@ -1770,7 +1770,7 @@ wheels = [
|
|
|
1770
1770
|
|
|
1771
1771
|
[[package]]
|
|
1772
1772
|
name = "pyobs-aravis"
|
|
1773
|
-
version = "2.0.0.
|
|
1773
|
+
version = "2.0.0.dev8"
|
|
1774
1774
|
source = { editable = "." }
|
|
1775
1775
|
dependencies = [
|
|
1776
1776
|
{ name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" },
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|