framesource 0.3.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.
- frame_processors/__init__.py +37 -0
- frame_source/__init__.py +50 -0
- framesource/__init__.py +93 -0
- framesource/_msmf_config.py +26 -0
- framesource/_version.py +24 -0
- framesource/discovery.py +109 -0
- framesource/errors.py +82 -0
- framesource/factory.py +290 -0
- framesource/processors/__init__.py +34 -0
- framesource/processors/equirectangular360_processor.py +577 -0
- framesource/processors/fisheye2equirectangular_processor.py +328 -0
- framesource/processors/frame_processor.py +30 -0
- framesource/processors/hyperspectral_processor.py +23 -0
- framesource/processors/realsense_depth_processor.py +90 -0
- framesource/py.typed +0 -0
- framesource/sources/__init__.py +40 -0
- framesource/sources/audiospectrogram_capture.py +1068 -0
- framesource/sources/basler_capture.py +477 -0
- framesource/sources/folder_capture.py +920 -0
- framesource/sources/genicam_capture.py +681 -0
- framesource/sources/huateng_capture.py +245 -0
- framesource/sources/ipcamera_capture.py +254 -0
- framesource/sources/mvsdk.py +2454 -0
- framesource/sources/realsense_capture.py +565 -0
- framesource/sources/screen_capture.py +800 -0
- framesource/sources/video_capture_base.py +560 -0
- framesource/sources/video_file_capture.py +259 -0
- framesource/sources/webcam_capture.py +511 -0
- framesource/sources/ximea_capture.py +299 -0
- framesource/threading_utils.py +790 -0
- framesource-0.3.0.dist-info/METADATA +787 -0
- framesource-0.3.0.dist-info/RECORD +37 -0
- framesource-0.3.0.dist-info/WHEEL +5 -0
- framesource-0.3.0.dist-info/licenses/LICENSE +21 -0
- framesource-0.3.0.dist-info/scm_file_list.json +276 -0
- framesource-0.3.0.dist-info/scm_version.json +8 -0
- framesource-0.3.0.dist-info/top_level.txt +3 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
from ..errors import MissingDependencyError
|
|
7
|
+
from .video_capture_base import VideoCaptureBase
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class XimeaCapture(VideoCaptureBase):
|
|
13
|
+
supports_exposure = True
|
|
14
|
+
supports_gain = True
|
|
15
|
+
|
|
16
|
+
"""Ximea camera capture using xiapi."""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
source: int = 0,
|
|
21
|
+
*,
|
|
22
|
+
is_mono: Optional[bool] = None,
|
|
23
|
+
exposure: Optional[float] = None,
|
|
24
|
+
gain: Optional[float] = None,
|
|
25
|
+
**kwargs,
|
|
26
|
+
):
|
|
27
|
+
"""Initialize the Ximea capture.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
source: Camera device index (default: 0).
|
|
31
|
+
is_mono: Configure the camera for monochrome (``XI_MONO8``)
|
|
32
|
+
output instead of ``XI_RGB24`` (default: False). Applied on
|
|
33
|
+
:meth:`connect`.
|
|
34
|
+
exposure: Initial exposure time in microseconds. Applied on
|
|
35
|
+
:meth:`connect` when provided (overrides the 10ms default).
|
|
36
|
+
gain: Initial gain in dB. Applied on :meth:`connect` when
|
|
37
|
+
provided.
|
|
38
|
+
**kwargs: Additional passthrough options stored on ``self.config``.
|
|
39
|
+
|
|
40
|
+
Raises:
|
|
41
|
+
MissingDependencyError: If the ``ximea`` (xiapi) package is not
|
|
42
|
+
installed.
|
|
43
|
+
"""
|
|
44
|
+
# Preserve the historical ``self.config`` contents: only forward
|
|
45
|
+
# explicitly-provided values so the ``'exposure' in self.config``
|
|
46
|
+
# style presence checks in connect() keep their old meaning.
|
|
47
|
+
for _key, _val in (
|
|
48
|
+
("is_mono", is_mono),
|
|
49
|
+
("exposure", exposure),
|
|
50
|
+
("gain", gain),
|
|
51
|
+
):
|
|
52
|
+
if _val is not None:
|
|
53
|
+
kwargs[_key] = _val
|
|
54
|
+
super().__init__(source, **kwargs)
|
|
55
|
+
self.cam = None
|
|
56
|
+
try:
|
|
57
|
+
from ximea import xiapi
|
|
58
|
+
|
|
59
|
+
self.xiapi = xiapi
|
|
60
|
+
except ImportError as e:
|
|
61
|
+
raise MissingDependencyError(
|
|
62
|
+
"ximea",
|
|
63
|
+
extra=None,
|
|
64
|
+
details=(
|
|
65
|
+
f"{e}; no pip extra is available for the Ximea xiapi bindings. "
|
|
66
|
+
"Download and install the XIMEA Software Package for your OS from "
|
|
67
|
+
"https://www.ximea.com/support/wiki/apis/xiapi_manual, which provides "
|
|
68
|
+
"the 'ximea' Python module."
|
|
69
|
+
),
|
|
70
|
+
) from e
|
|
71
|
+
|
|
72
|
+
self.is_mono = self.config.get("is_mono", False)
|
|
73
|
+
|
|
74
|
+
def connect(self) -> bool:
|
|
75
|
+
"""Connect to Ximea camera."""
|
|
76
|
+
if self.xiapi is None:
|
|
77
|
+
logger.error("Ximea xiapi not available")
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
self.cam = self.xiapi.Camera()
|
|
82
|
+
# self.cam.open_device_by_SN(self.source) if isinstance(self.source, str)
|
|
83
|
+
# else self.cam.open_device(self.source)
|
|
84
|
+
self.cam.open_device()
|
|
85
|
+
# self.cam.set_imgdataformat('XI_RGB24')
|
|
86
|
+
# Get the number of channels from the camera
|
|
87
|
+
# try:
|
|
88
|
+
# channel_count = self.cam.get_param('imgdataformat')
|
|
89
|
+
# if channel_count == 'XI_MONO8':
|
|
90
|
+
# self.channel_count = 1
|
|
91
|
+
# self.is_mono = True
|
|
92
|
+
# elif channel_count == 'XI_RGB24':
|
|
93
|
+
# self.channel_count = 3
|
|
94
|
+
# self.is_mono = False
|
|
95
|
+
# else:
|
|
96
|
+
# # Fallback: use get_channel_count if available
|
|
97
|
+
# if hasattr(self.cam, 'get_channel_count'):
|
|
98
|
+
# self.channel_count = self.cam.get_channel_count()
|
|
99
|
+
# else:
|
|
100
|
+
# self.channel_count = None
|
|
101
|
+
# logger.info(f"Ximea camera channel count: {self.channel_count}")
|
|
102
|
+
# except Exception as e:
|
|
103
|
+
# logger.warning(f"Could not determine channel count: {e}")
|
|
104
|
+
# self.channel_count = None
|
|
105
|
+
|
|
106
|
+
# Set default parameters
|
|
107
|
+
if not self.is_mono:
|
|
108
|
+
self.cam.set_imgdataformat("XI_RGB24")
|
|
109
|
+
self.cam.enable_auto_wb()
|
|
110
|
+
# actual channel order = BGR
|
|
111
|
+
# XI_RGB24 RGB data format. [Blue][Green][Red] (see Note5)
|
|
112
|
+
# https://www.ximea.com/support/wiki/apis/xiapi_manual
|
|
113
|
+
|
|
114
|
+
if self.is_mono:
|
|
115
|
+
self.cam.set_imgdataformat("XI_MONO8")
|
|
116
|
+
|
|
117
|
+
self.cam.set_exposure(10000) # 10ms default
|
|
118
|
+
|
|
119
|
+
# Apply config parameters
|
|
120
|
+
if "exposure" in self.config:
|
|
121
|
+
self.cam.set_exposure(self.config["exposure"])
|
|
122
|
+
if "gain" in self.config:
|
|
123
|
+
self.cam.set_gain(self.config["gain"])
|
|
124
|
+
|
|
125
|
+
self.cam.start_acquisition()
|
|
126
|
+
self.is_connected = True
|
|
127
|
+
logger.info(f"Connected to Ximea camera {self.source}")
|
|
128
|
+
return True
|
|
129
|
+
except Exception as e:
|
|
130
|
+
logger.error(f"Error connecting to Ximea camera: {e}")
|
|
131
|
+
return False
|
|
132
|
+
|
|
133
|
+
def disconnect(self) -> bool:
|
|
134
|
+
"""Disconnect from Ximea camera."""
|
|
135
|
+
try:
|
|
136
|
+
if self.cam is not None:
|
|
137
|
+
self.cam.stop_acquisition()
|
|
138
|
+
self.cam.close_device()
|
|
139
|
+
self.cam = None
|
|
140
|
+
self.is_connected = False
|
|
141
|
+
logger.info("Disconnected from Ximea camera")
|
|
142
|
+
return True
|
|
143
|
+
except Exception as e:
|
|
144
|
+
logger.error(f"Error disconnecting from Ximea camera: {e}")
|
|
145
|
+
return False
|
|
146
|
+
|
|
147
|
+
def _read_implementation(self) -> tuple[bool, Optional[np.ndarray]]:
|
|
148
|
+
"""
|
|
149
|
+
Read a single frame from the Ximea camera.
|
|
150
|
+
Returns:
|
|
151
|
+
Tuple[bool, Optional[np.ndarray]]: (success, frame)
|
|
152
|
+
"""
|
|
153
|
+
if not self.is_connected or self.cam is None or self.xiapi is None:
|
|
154
|
+
return False, None
|
|
155
|
+
try:
|
|
156
|
+
img = self.xiapi.Image()
|
|
157
|
+
self.cam.get_image(img)
|
|
158
|
+
data = img.get_image_data_numpy()
|
|
159
|
+
return True, data
|
|
160
|
+
except Exception as e:
|
|
161
|
+
logger.error(f"Error reading from Ximea camera: {e}")
|
|
162
|
+
return False, None
|
|
163
|
+
|
|
164
|
+
def get_exposure_range(self) -> Optional[tuple[float, float]]:
|
|
165
|
+
"""Get exposure range in microseconds."""
|
|
166
|
+
if not self.is_connected or self.cam is None:
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
try:
|
|
170
|
+
min_exposure = self.cam.get_exposure_minimum()
|
|
171
|
+
max_exposure = self.cam.get_exposure_maximum()
|
|
172
|
+
if min_exposure is None or max_exposure is None:
|
|
173
|
+
return None
|
|
174
|
+
return (float(min_exposure), float(max_exposure))
|
|
175
|
+
except Exception as e:
|
|
176
|
+
logger.error(f"Error getting exposure range: {e}")
|
|
177
|
+
return None
|
|
178
|
+
|
|
179
|
+
def get_gain_range(self) -> Optional[tuple[float, float]]:
|
|
180
|
+
"""Get gain range in dB."""
|
|
181
|
+
if not self.is_connected or self.cam is None:
|
|
182
|
+
return None
|
|
183
|
+
|
|
184
|
+
try:
|
|
185
|
+
min_gain = self.cam.get_gain_minimum()
|
|
186
|
+
max_gain = self.cam.get_gain_maximum()
|
|
187
|
+
if min_gain is None or max_gain is None:
|
|
188
|
+
return None
|
|
189
|
+
return (float(min_gain), float(max_gain))
|
|
190
|
+
except Exception as e:
|
|
191
|
+
logger.error(f"Error getting gain range: {e}")
|
|
192
|
+
return None
|
|
193
|
+
|
|
194
|
+
def set_exposure(self, value: float) -> bool:
|
|
195
|
+
"""Set exposure in microseconds."""
|
|
196
|
+
if not self.is_connected or self.cam is None:
|
|
197
|
+
return False
|
|
198
|
+
|
|
199
|
+
try:
|
|
200
|
+
self.cam.set_exposure(int(value))
|
|
201
|
+
self._exposure = value
|
|
202
|
+
return True
|
|
203
|
+
except Exception as e:
|
|
204
|
+
logger.error(f"Error setting exposure: {e}")
|
|
205
|
+
return False
|
|
206
|
+
|
|
207
|
+
def get_exposure(self) -> Optional[float]:
|
|
208
|
+
"""Get exposure in microseconds."""
|
|
209
|
+
if not self.is_connected or self.cam is None:
|
|
210
|
+
return self._exposure
|
|
211
|
+
|
|
212
|
+
try:
|
|
213
|
+
exposure_value = self.cam.get_exposure()
|
|
214
|
+
if exposure_value is not None:
|
|
215
|
+
return float(exposure_value)
|
|
216
|
+
return self._exposure
|
|
217
|
+
except Exception:
|
|
218
|
+
return self._exposure
|
|
219
|
+
|
|
220
|
+
def set_gain(self, value: float) -> bool:
|
|
221
|
+
"""Set gain in dB."""
|
|
222
|
+
if not self.is_connected or self.cam is None:
|
|
223
|
+
return False
|
|
224
|
+
|
|
225
|
+
try:
|
|
226
|
+
self.cam.set_gain(value)
|
|
227
|
+
self._gain = value
|
|
228
|
+
return True
|
|
229
|
+
except Exception as e:
|
|
230
|
+
logger.error(f"Error setting gain: {e}")
|
|
231
|
+
return False
|
|
232
|
+
|
|
233
|
+
def get_gain(self) -> Optional[float]:
|
|
234
|
+
"""Get gain in dB."""
|
|
235
|
+
if not self.is_connected or self.cam is None:
|
|
236
|
+
return self._gain
|
|
237
|
+
|
|
238
|
+
try:
|
|
239
|
+
gain_value = self.cam.get_gain()
|
|
240
|
+
if gain_value is not None:
|
|
241
|
+
return float(gain_value)
|
|
242
|
+
return self._gain
|
|
243
|
+
except Exception:
|
|
244
|
+
return self._gain
|
|
245
|
+
|
|
246
|
+
def enable_auto_exposure(self, enable: bool = True) -> bool:
|
|
247
|
+
"""
|
|
248
|
+
Enable or disable auto exposure for Ximea camera.
|
|
249
|
+
"""
|
|
250
|
+
if not self.is_connected or self.cam is None:
|
|
251
|
+
return False
|
|
252
|
+
try:
|
|
253
|
+
if enable:
|
|
254
|
+
self.cam.enable_aeag()
|
|
255
|
+
else:
|
|
256
|
+
self.cam.disable_aeag()
|
|
257
|
+
logger.info(f"Set Ximea auto exposure to {enable}")
|
|
258
|
+
return True
|
|
259
|
+
except Exception as e:
|
|
260
|
+
logger.error(f"Error setting Ximea auto exposure: {e}")
|
|
261
|
+
return False
|
|
262
|
+
|
|
263
|
+
def set_frame_size(self, width: int, height: int) -> bool:
|
|
264
|
+
"""Set frame size for Ximea camera."""
|
|
265
|
+
if not self.is_connected or self.cam is None:
|
|
266
|
+
return False
|
|
267
|
+
try:
|
|
268
|
+
self.cam.set_width(width)
|
|
269
|
+
self.cam.set_height(height)
|
|
270
|
+
logger.info(f"Set Ximea camera resolution to {width}x{height}")
|
|
271
|
+
return True
|
|
272
|
+
except Exception as e:
|
|
273
|
+
logger.error(f"Error setting Ximea camera resolution: {e}")
|
|
274
|
+
return False
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
if __name__ == "__main__":
|
|
278
|
+
# Example usage
|
|
279
|
+
import cv2
|
|
280
|
+
|
|
281
|
+
camera = XimeaCapture(is_mono=False)
|
|
282
|
+
if camera.connect():
|
|
283
|
+
print("Webcam connected successfully.")
|
|
284
|
+
print(f"Exposure: {camera.get_exposure()}")
|
|
285
|
+
print(f"Gain: {camera.get_gain()}")
|
|
286
|
+
print(f"Frame size: {camera.get_frame_size()}")
|
|
287
|
+
|
|
288
|
+
# Read a few frames
|
|
289
|
+
while camera.is_connected:
|
|
290
|
+
ret, frame = camera.read()
|
|
291
|
+
if ret or frame is not None:
|
|
292
|
+
cv2.imshow("Webcam", frame) # type: ignore
|
|
293
|
+
if cv2.waitKey(1) & 0xFF == ord("q"):
|
|
294
|
+
break
|
|
295
|
+
|
|
296
|
+
camera.stop()
|
|
297
|
+
camera.disconnect()
|
|
298
|
+
else:
|
|
299
|
+
print("Failed to connect to webcam.")
|