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,245 @@
1
+ import logging
2
+ import platform
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
+ # The Huateng/MindVision SDK loads a vendor driver DLL at import time, which is
13
+ # only present when the camera drivers are installed. Guard the import so the
14
+ # module can still be imported (and the class referenced) without the SDK.
15
+ _MVSDK_IMPORT_ERROR: Optional[str] = None
16
+ try:
17
+ from . import mvsdk
18
+ except (ImportError, OSError) as e:
19
+ mvsdk = None
20
+ _MVSDK_IMPORT_ERROR = str(e)
21
+ logger.warning(f"Huateng mvsdk SDK unavailable: {e}")
22
+
23
+
24
+ class HuatengCapture(VideoCaptureBase):
25
+ supports_exposure = True
26
+ supports_gain = True
27
+
28
+ """Huateng camera capture using mvsdk."""
29
+
30
+ def __init__(self, source: Any = None, *, is_mono: Optional[bool] = None, **kwargs):
31
+ """Initialize the Huateng (MindVision) capture.
32
+
33
+ Args:
34
+ source: Reserved for future multi-device selection; the first
35
+ enumerated camera is currently always used regardless of
36
+ this value.
37
+ is_mono: Stored for parity with other vendor sources (default:
38
+ False). Whether the stream is mono or color is auto-detected
39
+ from the device capability in :meth:`connect`, so this flag
40
+ currently has no effect.
41
+ **kwargs: Additional passthrough options stored on ``self.config``.
42
+ """
43
+ # Preserve the historical ``self.config`` contents: only forward an
44
+ # explicitly-provided value.
45
+ if is_mono is not None:
46
+ kwargs["is_mono"] = is_mono
47
+ super().__init__(source, **kwargs)
48
+ self.hCamera = -1
49
+ self.nDev = 0
50
+ self.capability = None
51
+ self.pFrameBuffer = None
52
+ self.frame = None
53
+ self.DevInfo = None
54
+ self.is_mono = self.config.get("is_mono", False)
55
+ self.current_exp = 0
56
+ self.current_gain = 0
57
+ self.prop_frame_height = None
58
+ self.prop_frame_width = None
59
+ self.is_new_frame = False
60
+ self.read_count = 0
61
+
62
+ def connect(self) -> bool:
63
+ if mvsdk is None:
64
+ raise MissingDependencyError(
65
+ "mvsdk (Huateng/MindVision SDK)",
66
+ extra=None,
67
+ details=_MVSDK_IMPORT_ERROR,
68
+ )
69
+ try:
70
+ self.DevList = mvsdk.CameraEnumerateDevice()
71
+ self.nDev = len(self.DevList)
72
+ if self.nDev < 1:
73
+ logger.error("No Huateng cameras found!")
74
+ return False
75
+ i = 0 # Always pick the first camera for now
76
+ self.DevInfo = self.DevList[i]
77
+ self.hCamera = mvsdk.CameraInit(self.DevInfo, -1, -1)
78
+ self.capability = mvsdk.CameraGetCapability(self.hCamera)
79
+ monoCamera = self.capability.sIspCapacity.bMonoSensor != 0
80
+ if monoCamera:
81
+ mvsdk.CameraSetIspOutFormat(self.hCamera, mvsdk.CAMERA_MEDIA_TYPE_MONO8)
82
+ else:
83
+ mvsdk.CameraSetIspOutFormat(self.hCamera, mvsdk.CAMERA_MEDIA_TYPE_BGR8)
84
+ mvsdk.CameraSetTriggerMode(self.hCamera, 0)
85
+ mvsdk.CameraSetAeState(self.hCamera, 1)
86
+ mvsdk.CameraPlay(self.hCamera)
87
+ FrameBufferSize = (
88
+ self.capability.sResolutionRange.iWidthMax
89
+ * self.capability.sResolutionRange.iHeightMax
90
+ * (1 if monoCamera else 3)
91
+ )
92
+ self.pFrameBuffer = mvsdk.CameraAlignMalloc(FrameBufferSize, 16)
93
+ self.prop_frame_height = self.capability.sResolutionRange.iHeightMax
94
+ self.prop_frame_width = self.capability.sResolutionRange.iWidthMax
95
+ self.is_connected = True
96
+ logger.info("Connected to Huateng camera")
97
+ return True
98
+ except Exception as e:
99
+ logger.error(f"Error connecting to Huateng camera: {e}")
100
+ return False
101
+
102
+ def disconnect(self) -> bool:
103
+ try:
104
+ if self.hCamera != -1:
105
+ mvsdk.CameraUnInit(self.hCamera)
106
+ self.hCamera = -1
107
+ if self.pFrameBuffer is not None:
108
+ mvsdk.CameraAlignFree(self.pFrameBuffer)
109
+ self.pFrameBuffer = None
110
+ self.is_connected = False
111
+ logger.info("Disconnected from Huateng camera")
112
+ return True
113
+ except Exception as e:
114
+ logger.error(f"Error disconnecting from Huateng camera: {e}")
115
+ return False
116
+
117
+ def _read_implementation(self) -> tuple[bool, Optional[np.ndarray]]:
118
+ if not self.is_connected or self.hCamera == -1:
119
+ return False, None
120
+ try:
121
+ pRawData, FrameHead = mvsdk.CameraGetImageBuffer(self.hCamera, 10)
122
+ mvsdk.CameraImageProcess(self.hCamera, pRawData, self.pFrameBuffer, FrameHead)
123
+ mvsdk.CameraReleaseImageBuffer(self.hCamera, pRawData)
124
+ if platform.system() == "Windows":
125
+ mvsdk.CameraFlipFrameBuffer(self.pFrameBuffer, FrameHead, 1)
126
+ frame_data = (mvsdk.c_ubyte * FrameHead.uBytes).from_address(self.pFrameBuffer)
127
+ frame = np.frombuffer(frame_data, dtype=np.uint8)
128
+ frame = frame.reshape(
129
+ (
130
+ FrameHead.iHeight,
131
+ FrameHead.iWidth,
132
+ 1 if FrameHead.uiMediaType == mvsdk.CAMERA_MEDIA_TYPE_MONO8 else 3,
133
+ )
134
+ )
135
+ return True, frame
136
+ except Exception as e:
137
+ logger.error(f"Error reading from Huateng camera: {e}")
138
+ return False, None
139
+
140
+ def set_exposure(self, value: float) -> bool:
141
+ try:
142
+ mvsdk.CameraSetAeState(self.hCamera, False)
143
+ mvsdk.CameraSetExposureTime(self.hCamera, int(value))
144
+ self.current_exp = int(value)
145
+ return True
146
+ except Exception as e:
147
+ logger.error(f"Error setting exposure: {e}")
148
+ return False
149
+
150
+ def get_exposure(self) -> Optional[float]:
151
+ try:
152
+ return float(mvsdk.CameraGetExposureTime(self.hCamera))
153
+ except Exception:
154
+ return self.current_exp
155
+
156
+ def set_gain(self, value: float) -> bool:
157
+ try:
158
+ mvsdk.CameraSetAeState(self.hCamera, False)
159
+ mvsdk.CameraSetAnalogGainX(self.hCamera, int(value))
160
+ self.current_gain = int(value)
161
+ return True
162
+ except Exception as e:
163
+ logger.error(f"Error setting gain: {e}")
164
+ return False
165
+
166
+ def get_gain(self) -> Optional[float]:
167
+ try:
168
+ return float(mvsdk.CameraGetAnalogGainX(self.hCamera))
169
+ except Exception:
170
+ return self.current_gain
171
+
172
+ def enable_auto_exposure(self, enable: bool = True) -> bool:
173
+ try:
174
+ mvsdk.CameraSetAeState(self.hCamera, bool(enable))
175
+ logger.info(f"Set Huateng auto exposure to {enable}")
176
+ return True
177
+ except Exception as e:
178
+ logger.error(f"Error setting auto exposure: {e}")
179
+ return False
180
+
181
+ def get_exposure_range(self) -> Optional[tuple[float, float]]:
182
+ try:
183
+ min_exp = 5
184
+ max_exp = 31 * 1000
185
+ return (min_exp, max_exp)
186
+ except Exception:
187
+ return None
188
+
189
+ def get_gain_range(self) -> Optional[tuple[float, float]]:
190
+ try:
191
+ min_gain = 2
192
+ max_gain = 6
193
+ return (min_gain, max_gain)
194
+ except Exception:
195
+ return None
196
+
197
+ def get_frame_size(self) -> Optional[tuple[int, int]]:
198
+ if self.prop_frame_width and self.prop_frame_height:
199
+ return (self.prop_frame_width, self.prop_frame_height)
200
+ return None
201
+
202
+ def set_frame_size(self, width: int, height: int) -> bool:
203
+ try:
204
+ imageres = mvsdk.tSdkImageResolution(width, height)
205
+ mvsdk.CameraSetImageResolution(self.hCamera, imageres)
206
+ self.prop_frame_width = width
207
+ self.prop_frame_height = height
208
+ logger.info(f"Set Huateng camera resolution to {width}x{height}")
209
+ return True
210
+ except Exception as e:
211
+ logger.error(f"Error setting Huateng camera resolution: {e}")
212
+ return False
213
+
214
+ def get_fps(self) -> Optional[float]:
215
+ # Not directly supported in mvsdk, return None
216
+ return None
217
+
218
+ def set_fps(self, fps: float) -> bool:
219
+ # Not directly supported in mvsdk, return False
220
+ return False
221
+
222
+
223
+ if __name__ == "__main__":
224
+ # Example usage
225
+ import cv2
226
+
227
+ camera = HuatengCapture(is_mono=False)
228
+ if camera.connect():
229
+ print("Webcam connected successfully.")
230
+ print(f"Exposure: {camera.get_exposure()}")
231
+ print(f"Gain: {camera.get_gain()}")
232
+ print(f"Frame size: {camera.get_frame_size()}")
233
+
234
+ # Read a few frames
235
+ while camera.is_connected:
236
+ ret, frame = camera.read()
237
+ if ret and frame is not None:
238
+ cv2.imshow("camera", frame)
239
+ if cv2.waitKey(1) & 0xFF == ord("q"):
240
+ break
241
+
242
+ camera.stop()
243
+ camera.disconnect()
244
+ else:
245
+ print("Failed to connect to webcam.")
@@ -0,0 +1,254 @@
1
+ import logging
2
+ import warnings
3
+ from typing import Any, Optional
4
+
5
+ import cv2
6
+ import numpy as np
7
+
8
+ from .video_capture_base import VideoCaptureBase
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class IPCameraCapture(VideoCaptureBase):
14
+ has_discovery = False # Requires manual URL/credential configuration
15
+ supports_exposure = False # set_exposure() is a no-op stub (returns False)
16
+ supports_gain = False # set_gain() is a no-op stub (returns False)
17
+
18
+ """IP Camera capture using OpenCV with RTSP/HTTP streams."""
19
+
20
+ def __init__(
21
+ self,
22
+ source: str,
23
+ username: Optional[str] = None,
24
+ password: Optional[str] = None,
25
+ *,
26
+ width: Optional[int] = None,
27
+ height: Optional[int] = None,
28
+ fps: Optional[float] = None,
29
+ **kwargs,
30
+ ):
31
+ """Initialize the IP camera capture.
32
+
33
+ Args:
34
+ source: RTSP/HTTP stream URL.
35
+ username: Optional login username; when given together with
36
+ ``password`` it is injected into the stream URL.
37
+ password: Optional login password (see ``username``).
38
+ width: Optional frame width hint (if supported by the camera),
39
+ stored on ``self.config``.
40
+ height: Optional frame height hint (see ``width``).
41
+ fps: Optional expected frame rate (informational), stored on
42
+ ``self.config``.
43
+ **kwargs: Additional passthrough options stored on ``self.config``.
44
+ """
45
+ # Forward the promoted options into ``self.config`` exactly as before
46
+ # (they were previously accepted via ``**kwargs``); only include
47
+ # explicitly-provided values so config keys are unchanged.
48
+ for _key, _val in (("width", width), ("height", height), ("fps", fps)):
49
+ if _val is not None:
50
+ kwargs[_key] = _val
51
+ super().__init__(source, **kwargs)
52
+ self.username = username
53
+ self.password = password
54
+ self.cap = None
55
+ if username is not None and password is not None:
56
+ self.stream_url = self._build_stream_url()
57
+ else:
58
+ self.stream_url = source
59
+
60
+ def _build_stream_url(self) -> str:
61
+ """Build stream URL with authentication if provided."""
62
+ if self.username and self.password:
63
+ # Insert credentials into URL
64
+ if "://" in self.source:
65
+ protocol, rest = self.source.split("://", 1)
66
+ return f"{protocol}://{self.username}:{self.password}@{rest}"
67
+ return self.source
68
+
69
+ def connect(self) -> bool:
70
+ """Connect to IP camera."""
71
+ try:
72
+ self.cap = cv2.VideoCapture(self.stream_url)
73
+ if not self.cap.isOpened():
74
+ logger.error(f"Failed to open IP camera stream: {self.stream_url}")
75
+ return False
76
+
77
+ # Set buffer size to reduce latency
78
+ self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
79
+
80
+ self.is_connected = True
81
+ logger.info(f"Connected to IP camera: {self.source}")
82
+ return True
83
+ except Exception as e:
84
+ logger.error(f"Error connecting to IP camera: {e}")
85
+ return False
86
+
87
+ def disconnect(self) -> bool:
88
+ """Disconnect from IP camera."""
89
+ try:
90
+ if self.cap is not None:
91
+ self.cap.release()
92
+ self.cap = None
93
+ self.is_connected = False
94
+ logger.info("Disconnected from IP camera")
95
+ return True
96
+ except Exception as e:
97
+ logger.error(f"Error disconnecting from IP camera: {e}")
98
+ return False
99
+
100
+ def _read_implementation(self) -> tuple[bool, Optional[np.ndarray]]:
101
+ """
102
+ Read a single frame from the IP camera.
103
+ Returns:
104
+ Tuple[bool, Optional[np.ndarray]]: (success, frame)
105
+ """
106
+ if not self.is_connected or self.cap is None:
107
+ return False, None
108
+ ret, frame = self.cap.read()
109
+ return ret, frame if ret else None
110
+
111
+ def set_exposure(self, value: float) -> bool:
112
+ """Set exposure (may not be supported by all IP cameras)."""
113
+ logger.warning("Exposure control may not be supported by IP cameras")
114
+ self._exposure = value
115
+ return False
116
+
117
+ def get_exposure(self) -> Optional[float]:
118
+ """Get exposure."""
119
+ return self._exposure
120
+
121
+ def set_gain(self, value: float) -> bool:
122
+ """Set gain (may not be supported by all IP cameras)."""
123
+ logger.warning("Gain control may not be supported by IP cameras")
124
+ self._gain = value
125
+ return False
126
+
127
+ def get_gain(self) -> Optional[float]:
128
+ """Get gain."""
129
+ return self._gain
130
+
131
+ def enable_auto_exposure(self, enable: bool = True) -> bool:
132
+ """
133
+ Enable or disable auto exposure (not generally supported for IP cameras).
134
+ """
135
+ logger.warning("Auto exposure control may not be supported by IP cameras")
136
+ return False
137
+
138
+ def set_frame_size(self, width: int, height: int) -> bool:
139
+ """Set frame size (may not be supported by all IP cameras)."""
140
+ if not self.is_connected or self.cap is None:
141
+ return False
142
+ result1 = self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, width)
143
+ result2 = self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height)
144
+ logger.info(
145
+ f"Set IP camera resolution to {width}x{height} (success: {result1 and result2})"
146
+ )
147
+ return result1 and result2
148
+
149
+ @classmethod
150
+ def discover(cls) -> list:
151
+ """
152
+ Discover method for IP camera capture.
153
+
154
+ Returns:
155
+ list: Empty list, as IP cameras require manual configuration with URLs/credentials.
156
+ Use this class directly with RTSP/HTTP URLs as the source parameter.
157
+ """
158
+ # IP cameras require manual configuration - cannot be auto-discovered easily
159
+ logger.info("IPCameraCapture requires manual configuration with URLs and credentials.")
160
+ return []
161
+
162
+ @classmethod
163
+ def get_config_schema(cls) -> dict[str, Any]:
164
+ """Get configuration schema for IP camera capture"""
165
+ warnings.warn(
166
+ "get_config_schema() is deprecated and will be removed in a future release; "
167
+ "UI form schemas belong in the consuming application.",
168
+ DeprecationWarning,
169
+ stacklevel=2,
170
+ )
171
+ return {
172
+ "title": "IP Camera Configuration",
173
+ "description": "Configure IP camera with RTSP/HTTP stream settings",
174
+ "fields": [
175
+ {
176
+ "name": "source",
177
+ "label": "Stream URL",
178
+ "type": "text",
179
+ "placeholder": "rtsp://192.168.1.100:554/stream1",
180
+ "description": "RTSP or HTTP stream URL",
181
+ "required": True,
182
+ },
183
+ {
184
+ "name": "username",
185
+ "label": "Username",
186
+ "type": "text",
187
+ "placeholder": "admin",
188
+ "description": "Camera login username (optional)",
189
+ "required": False,
190
+ },
191
+ {
192
+ "name": "password",
193
+ "label": "Password",
194
+ "type": "password",
195
+ "placeholder": "password",
196
+ "description": "Camera login password (optional)",
197
+ "required": False,
198
+ },
199
+ {
200
+ "name": "width",
201
+ "label": "Width",
202
+ "type": "number",
203
+ "min": 160,
204
+ "max": 4096,
205
+ "placeholder": "1920",
206
+ "description": "Frame width in pixels (if supported)",
207
+ "required": False,
208
+ },
209
+ {
210
+ "name": "height",
211
+ "label": "Height",
212
+ "type": "number",
213
+ "min": 120,
214
+ "max": 2160,
215
+ "placeholder": "1080",
216
+ "description": "Frame height in pixels (if supported)",
217
+ "required": False,
218
+ },
219
+ {
220
+ "name": "fps",
221
+ "label": "Frame Rate (FPS)",
222
+ "type": "number",
223
+ "min": 1,
224
+ "max": 60,
225
+ "placeholder": "25",
226
+ "description": "Expected frame rate (informational)",
227
+ "required": False,
228
+ "default": 25,
229
+ },
230
+ ],
231
+ }
232
+
233
+
234
+ if __name__ == "__main__":
235
+ # Example usage
236
+ camera = IPCameraCapture(
237
+ source="rtsp://192.168.1.153:554/h264Preview_01_sub", username="admin", password="password"
238
+ )
239
+
240
+ if camera.connect():
241
+ print("IP Camera connected successfully.")
242
+ print(f"Exposure: {camera.get_exposure()}")
243
+ print(f"Gain: {camera.get_gain()}")
244
+ print(f"Frame size: {camera.get_frame_size()}")
245
+
246
+ # Read a few frames
247
+ while camera.is_connected:
248
+ ret, frame = camera.read()
249
+ if ret:
250
+ cv2.imshow("IP Camera", frame) # type: ignore
251
+ if cv2.waitKey(1000) & 0xFF == ord("q"):
252
+ break
253
+ camera.stop()
254
+ camera.disconnect()