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,477 @@
1
+ import logging
2
+ import warnings
3
+ from typing import Any, Optional
4
+
5
+ import numpy as np
6
+
7
+ from ..errors import MissingDependencyError
8
+ from .video_capture_base import VideoCaptureBase
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class BaslerCapture(VideoCaptureBase):
14
+ has_discovery = True
15
+ supports_exposure = True
16
+ supports_gain = True
17
+
18
+ """Basler camera capture using pypylon."""
19
+
20
+ def __init__(
21
+ self,
22
+ source: Any = None,
23
+ *,
24
+ is_mono: Optional[bool] = None,
25
+ exposure: Optional[float] = None,
26
+ gain: Optional[float] = None,
27
+ width: Optional[int] = None,
28
+ height: Optional[int] = None,
29
+ **kwargs,
30
+ ):
31
+ """Initialize the Basler capture.
32
+
33
+ Args:
34
+ source: Camera serial number (str) or device index (int,
35
+ default: 0).
36
+ is_mono: Configure the camera for monochrome (``Mono8``) output
37
+ instead of BGR8 (default: False). Applied on :meth:`connect`.
38
+ exposure: Initial exposure time in microseconds. Applied on
39
+ :meth:`connect` when provided.
40
+ gain: Initial gain in dB. Applied on :meth:`connect` when
41
+ provided.
42
+ width: Desired frame width in pixels. Applied on :meth:`connect`
43
+ (together with ``height``) when provided.
44
+ height: Desired frame height in pixels. Applied on
45
+ :meth:`connect` (together with ``width``) when provided.
46
+ **kwargs: Additional passthrough options stored on ``self.config``.
47
+
48
+ Raises:
49
+ MissingDependencyError: If the ``pypylon`` package is not
50
+ installed.
51
+ """
52
+ # Preserve the historical ``self.config`` contents: only forward
53
+ # explicitly-provided values so the ``'width' in self.config`` style
54
+ # presence checks in connect() keep their old meaning.
55
+ for _key, _val in (
56
+ ("is_mono", is_mono),
57
+ ("exposure", exposure),
58
+ ("gain", gain),
59
+ ("width", width),
60
+ ("height", height),
61
+ ):
62
+ if _val is not None:
63
+ kwargs[_key] = _val
64
+ super().__init__(source, **kwargs)
65
+ self.camera = None
66
+ self.converter = None
67
+ try:
68
+ from pypylon import pylon
69
+
70
+ self.pylon = pylon
71
+ except ImportError as e:
72
+ raise MissingDependencyError("pypylon", extra="basler", details=str(e)) from e
73
+
74
+ self.is_mono = self.config.get("is_mono", False)
75
+ self.serial_number = source if isinstance(source, str) else None
76
+ self.device_index = source if isinstance(source, int) else 0
77
+
78
+ def connect(self) -> bool:
79
+ """Connect to Basler camera."""
80
+ if self.pylon is None:
81
+ logger.error("pypylon not available")
82
+ return False
83
+
84
+ try:
85
+ # Get the transport layer factory
86
+ tlFactory = self.pylon.TlFactory.GetInstance()
87
+
88
+ # Get all attached devices and exit application if no device is found
89
+ devices = tlFactory.EnumerateDevices()
90
+ if len(devices) == 0:
91
+ logger.error("No Basler cameras found")
92
+ return False
93
+
94
+ # Create camera object
95
+ if self.serial_number:
96
+ # Connect by serial number
97
+ device_info = None
98
+ for device in devices:
99
+ if device.GetSerialNumber() == self.serial_number:
100
+ device_info = device
101
+ break
102
+ if device_info is None:
103
+ logger.error(f"Basler camera with serial number {self.serial_number} not found")
104
+ return False
105
+ self.camera = self.pylon.InstantCamera(tlFactory.CreateDevice(device_info))
106
+ else:
107
+ # Connect by index
108
+ if self.device_index >= len(devices):
109
+ logger.error(f"Basler camera index {self.device_index} out of range")
110
+ return False
111
+ self.camera = self.pylon.InstantCamera(
112
+ tlFactory.CreateDevice(devices[self.device_index])
113
+ )
114
+
115
+ # Open camera
116
+ self.camera.Open()
117
+
118
+ # Create image converter for color images
119
+ self.converter = self.pylon.ImageFormatConverter()
120
+ if self.is_mono:
121
+ self.converter.OutputPixelFormat = self.pylon.PixelType_Mono8
122
+ else:
123
+ self.converter.OutputPixelFormat = self.pylon.PixelType_BGR8packed
124
+ self.converter.OutputBitAlignment = self.pylon.OutputBitAlignment_MsbAligned
125
+
126
+ # Apply config parameters
127
+ if "exposure" in self.config:
128
+ self.set_exposure(self.config["exposure"])
129
+ if "gain" in self.config:
130
+ self.set_gain(self.config["gain"])
131
+ if "width" in self.config and "height" in self.config:
132
+ self.set_frame_size(self.config["width"], self.config["height"])
133
+
134
+ # Start grabbing
135
+ self.camera.StartGrabbing(self.pylon.GrabStrategy_LatestImageOnly)
136
+
137
+ self.is_connected = True
138
+ logger.info(f"Connected to Basler camera {self.camera.GetDeviceInfo().GetModelName()}")
139
+ return True
140
+
141
+ except Exception as e:
142
+ logger.error(f"Error connecting to Basler camera: {e}")
143
+ return False
144
+
145
+ def disconnect(self) -> bool:
146
+ """Disconnect from Basler camera."""
147
+ try:
148
+ if self.camera is not None:
149
+ if self.camera.IsGrabbing():
150
+ self.camera.StopGrabbing()
151
+ self.camera.Close()
152
+ self.camera = None
153
+ self.converter = None
154
+ self.is_connected = False
155
+ logger.info("Disconnected from Basler camera")
156
+ return True
157
+ except Exception as e:
158
+ logger.error(f"Error disconnecting from Basler camera: {e}")
159
+ return False
160
+
161
+ def _read_implementation(self) -> tuple[bool, Optional[np.ndarray]]:
162
+ """
163
+ Read a single frame from the Basler camera.
164
+ Returns:
165
+ Tuple[bool, Optional[np.ndarray]]: (success, frame)
166
+ """
167
+ if not self.is_connected or self.camera is None:
168
+ return False, None
169
+ try:
170
+ grabResult = self.camera.RetrieveResult(5000, self.pylon.TimeoutHandling_ThrowException) # type: ignore
171
+ if grabResult.GrabSucceeded():
172
+ if self.converter:
173
+ image = self.converter.Convert(grabResult)
174
+ img_array = image.GetArray()
175
+ else:
176
+ img_array = grabResult.Array
177
+ grabResult.Release()
178
+ return True, img_array
179
+ else:
180
+ grabResult.Release()
181
+ return False, None
182
+ except Exception as e:
183
+ logger.error(f"Error reading from Basler camera: {e}")
184
+ return False, None
185
+
186
+ def get_exposure_range(self) -> tuple[float, float]:
187
+ """Get exposure range in microseconds."""
188
+ if not self.is_connected or self.camera is None:
189
+ return (0.0, 0.0)
190
+
191
+ try:
192
+ min_exposure = self.camera.ExposureTime.GetMin()
193
+ max_exposure = self.camera.ExposureTime.GetMax()
194
+ return (min_exposure, max_exposure)
195
+ except Exception as e:
196
+ logger.error(f"Error getting exposure range: {e}")
197
+ return (0.0, 0.0)
198
+
199
+ def get_gain_range(self) -> tuple[float, float]:
200
+ """Get gain range in dB."""
201
+ if not self.is_connected or self.camera is None:
202
+ return (0.0, 0.0)
203
+
204
+ try:
205
+ min_gain = self.camera.Gain.GetMin()
206
+ max_gain = self.camera.Gain.GetMax()
207
+ return (min_gain, max_gain)
208
+ except Exception as e:
209
+ logger.error(f"Error getting gain range: {e}")
210
+ return (0.0, 0.0)
211
+
212
+ def set_exposure(self, value: float) -> bool:
213
+ """Set exposure in microseconds."""
214
+ if not self.is_connected or self.camera is None:
215
+ return False
216
+
217
+ try:
218
+ self.camera.ExposureTime.SetValue(value)
219
+ self._exposure = value
220
+ return True
221
+ except Exception as e:
222
+ logger.error(f"Error setting exposure: {e}")
223
+ return False
224
+
225
+ def get_exposure(self) -> Optional[float]:
226
+ """Get exposure in microseconds."""
227
+ if not self.is_connected or self.camera is None:
228
+ return self._exposure
229
+
230
+ try:
231
+ return self.camera.ExposureTime()
232
+ except Exception:
233
+ return self._exposure
234
+
235
+ def set_gain(self, value: float) -> bool:
236
+ """Set gain in dB."""
237
+ if not self.is_connected or self.camera is None:
238
+ return False
239
+ try:
240
+ self.camera.Gain.SetValue(value)
241
+ self._gain = value
242
+ return True
243
+ except Exception as e:
244
+ logger.error(f"Error setting gain: {e}")
245
+ return False
246
+
247
+ def get_gain(self) -> Optional[float]:
248
+ """Get gain in dB."""
249
+ if not self.is_connected or self.camera is None:
250
+ return self._gain
251
+
252
+ try:
253
+ return self.camera.Gain.GetValue()
254
+ except Exception:
255
+ return self._gain
256
+
257
+ def enable_auto_exposure(self, enable: bool = True) -> bool:
258
+ """Enable or disable auto exposure for Basler camera."""
259
+ if not self.is_connected or self.camera is None:
260
+ return False
261
+ try:
262
+ if enable:
263
+ self.camera.ExposureAuto.SetValue("Continuous")
264
+ else:
265
+ self.camera.ExposureAuto.SetValue("Off")
266
+ logger.info(f"Set Basler auto exposure to {enable}")
267
+ return True
268
+ except Exception as e:
269
+ logger.error(f"Error setting Basler auto exposure: {e}")
270
+ return False
271
+
272
+ def set_frame_size(self, width: int, height: int) -> bool:
273
+ """Set frame size for Basler camera."""
274
+ if not self.is_connected or self.camera is None:
275
+ return False
276
+ try:
277
+ was_grabbing = self.camera.IsGrabbing()
278
+ if was_grabbing:
279
+ self.camera.StopGrabbing()
280
+ self.camera.Width.SetValue(width)
281
+ self.camera.Height.SetValue(height)
282
+ if was_grabbing:
283
+ self.camera.StartGrabbing(self.pylon.GrabStrategy_LatestImageOnly)
284
+ logger.info(f"Set Basler camera resolution to {width}x{height}")
285
+ return True
286
+ except Exception as e:
287
+ logger.error(f"Error setting Basler camera resolution: {e}")
288
+ return False
289
+
290
+ def get_frame_size(self) -> Optional[tuple[int, int]]:
291
+ """Get frame size."""
292
+ if not self.is_connected or self.camera is None:
293
+ return None
294
+
295
+ try:
296
+ width = self.camera.Width.GetValue()
297
+ height = self.camera.Height.GetValue()
298
+ return (width, height)
299
+ except Exception:
300
+ return None
301
+
302
+ def set_fps(self, fps: float) -> bool:
303
+ """Set FPS for Basler camera."""
304
+ if not self.is_connected or self.camera is None:
305
+ return False
306
+ try:
307
+ self.camera.AcquisitionFrameRateEnable.SetValue(True)
308
+ self.camera.AcquisitionFrameRate.SetValue(fps)
309
+ logger.info(f"Set Basler camera FPS to {fps}")
310
+ return True
311
+ except Exception as e:
312
+ logger.error(f"Error setting Basler camera FPS: {e}")
313
+ return False
314
+
315
+ def get_fps(self) -> Optional[float]:
316
+ """Get FPS."""
317
+ if not self.is_connected or self.camera is None:
318
+ return None
319
+
320
+ try:
321
+ return self.camera.AcquisitionFrameRate.GetValue()
322
+ except Exception:
323
+ return None
324
+
325
+ @classmethod
326
+ def discover(cls) -> list:
327
+ """
328
+ Discover available Basler cameras.
329
+
330
+ Returns:
331
+ list: List of dictionaries containing Basler camera information.
332
+ Each dict contains: {'index': int, 'serial_number': str, 'name': str,
333
+ 'device_class': str}
334
+ """
335
+ devices = []
336
+
337
+ try:
338
+ from pypylon import pylon
339
+ except ImportError:
340
+ logger.warning("pypylon module not available. Cannot discover Basler cameras.")
341
+ return []
342
+
343
+ try:
344
+ # Get the transport layer factory
345
+ tlFactory = pylon.TlFactory.GetInstance()
346
+
347
+ # Get all attached devices
348
+ device_list = tlFactory.EnumerateDevices()
349
+
350
+ for index, device_info in enumerate(device_list):
351
+ try:
352
+ device_data = {
353
+ "index": index,
354
+ "id": index,
355
+ "serial_number": device_info.GetSerialNumber(),
356
+ "name": device_info.GetFriendlyName(),
357
+ "device_class": device_info.GetDeviceClass(),
358
+ }
359
+ devices.append(device_data)
360
+ logger.info(f"Found Basler camera: {device_data}")
361
+
362
+ except Exception as e:
363
+ logger.warning(f"Could not get info for Basler device {index}: {e}")
364
+ continue
365
+
366
+ except Exception as e:
367
+ logger.error(f"Error discovering Basler cameras: {e}")
368
+
369
+ return devices
370
+
371
+ @classmethod
372
+ def get_config_schema(cls) -> dict[str, Any]:
373
+ """Get configuration schema for Basler camera capture"""
374
+ warnings.warn(
375
+ "get_config_schema() is deprecated and will be removed in a future release; "
376
+ "UI form schemas belong in the consuming application.",
377
+ DeprecationWarning,
378
+ stacklevel=2,
379
+ )
380
+ return {
381
+ "title": "Basler Camera Configuration",
382
+ "description": "Configure Basler industrial camera settings",
383
+ "fields": [
384
+ {
385
+ "name": "source",
386
+ "label": "Camera Source",
387
+ "type": "text",
388
+ "placeholder": "Serial number or device index (0, 1, 2...)",
389
+ "description": "Camera serial number or device index",
390
+ "required": False,
391
+ "default": "0",
392
+ },
393
+ {
394
+ "name": "exposure",
395
+ "label": "Exposure Time (µs)",
396
+ "type": "number",
397
+ "min": 1,
398
+ "max": 1000000,
399
+ "placeholder": "10000",
400
+ "description": "Exposure time in microseconds",
401
+ "required": False,
402
+ },
403
+ {
404
+ "name": "gain",
405
+ "label": "Gain (dB)",
406
+ "type": "number",
407
+ "min": 0,
408
+ "max": 40,
409
+ "step": 0.1,
410
+ "placeholder": "0.0",
411
+ "description": "Camera gain in decibels",
412
+ "required": False,
413
+ },
414
+ {
415
+ "name": "width",
416
+ "label": "Width",
417
+ "type": "number",
418
+ "min": 1,
419
+ "max": 10000,
420
+ "placeholder": "1920",
421
+ "description": "Frame width in pixels",
422
+ "required": False,
423
+ },
424
+ {
425
+ "name": "height",
426
+ "label": "Height",
427
+ "type": "number",
428
+ "min": 1,
429
+ "max": 10000,
430
+ "placeholder": "1080",
431
+ "description": "Frame height in pixels",
432
+ "required": False,
433
+ },
434
+ {
435
+ "name": "fps",
436
+ "label": "Frame Rate (FPS)",
437
+ "type": "number",
438
+ "min": 1,
439
+ "max": 1000,
440
+ "placeholder": "30",
441
+ "description": "Frames per second",
442
+ "required": False,
443
+ "default": 30,
444
+ },
445
+ {
446
+ "name": "is_mono",
447
+ "label": "Monochrome",
448
+ "type": "checkbox",
449
+ "description": "Camera outputs monochrome (grayscale) images",
450
+ "required": False,
451
+ "default": False,
452
+ },
453
+ ],
454
+ }
455
+
456
+
457
+ if __name__ == "__main__":
458
+ # Example usage
459
+ import cv2
460
+
461
+ camera = BaslerCapture() # Replace with actual serial number or index
462
+ if camera.connect():
463
+ print("Webcam connected successfully.")
464
+ print(f"Exposure: {camera.get_exposure()}")
465
+ print(f"Gain: {camera.get_gain()}")
466
+ print(f"Frame size: {camera.get_frame_size()}")
467
+
468
+ # Read a few frames
469
+ while camera.is_connected:
470
+ ret, frame = camera.read()
471
+ if ret and frame is not None:
472
+ cv2.imshow("Webcam", frame)
473
+ if cv2.waitKey(1) & 0xFF == ord("q"):
474
+ break
475
+ camera.disconnect()
476
+ else:
477
+ print("Failed to connect to webcam.")