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.
Files changed (37) hide show
  1. frame_processors/__init__.py +37 -0
  2. frame_source/__init__.py +50 -0
  3. framesource/__init__.py +93 -0
  4. framesource/_msmf_config.py +26 -0
  5. framesource/_version.py +24 -0
  6. framesource/discovery.py +109 -0
  7. framesource/errors.py +82 -0
  8. framesource/factory.py +290 -0
  9. framesource/processors/__init__.py +34 -0
  10. framesource/processors/equirectangular360_processor.py +577 -0
  11. framesource/processors/fisheye2equirectangular_processor.py +328 -0
  12. framesource/processors/frame_processor.py +30 -0
  13. framesource/processors/hyperspectral_processor.py +23 -0
  14. framesource/processors/realsense_depth_processor.py +90 -0
  15. framesource/py.typed +0 -0
  16. framesource/sources/__init__.py +40 -0
  17. framesource/sources/audiospectrogram_capture.py +1068 -0
  18. framesource/sources/basler_capture.py +477 -0
  19. framesource/sources/folder_capture.py +920 -0
  20. framesource/sources/genicam_capture.py +681 -0
  21. framesource/sources/huateng_capture.py +245 -0
  22. framesource/sources/ipcamera_capture.py +254 -0
  23. framesource/sources/mvsdk.py +2454 -0
  24. framesource/sources/realsense_capture.py +565 -0
  25. framesource/sources/screen_capture.py +800 -0
  26. framesource/sources/video_capture_base.py +560 -0
  27. framesource/sources/video_file_capture.py +259 -0
  28. framesource/sources/webcam_capture.py +511 -0
  29. framesource/sources/ximea_capture.py +299 -0
  30. framesource/threading_utils.py +790 -0
  31. framesource-0.3.0.dist-info/METADATA +787 -0
  32. framesource-0.3.0.dist-info/RECORD +37 -0
  33. framesource-0.3.0.dist-info/WHEEL +5 -0
  34. framesource-0.3.0.dist-info/licenses/LICENSE +21 -0
  35. framesource-0.3.0.dist-info/scm_file_list.json +276 -0
  36. framesource-0.3.0.dist-info/scm_version.json +8 -0
  37. 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.")