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,37 @@
1
+ """Deprecated compatibility shim for the old ``frame_processors`` package.
2
+
3
+ This package moved to :mod:`framesource.processors`. Importing
4
+ ``frame_processors`` still works for now but emits a
5
+ :class:`DeprecationWarning` and will be removed in a future major release.
6
+ Update imports to ``framesource.processors``.
7
+ """
8
+
9
+ import importlib
10
+ import sys
11
+ import warnings
12
+
13
+ warnings.warn(
14
+ "'frame_processors' is deprecated and will be removed in a future major "
15
+ "release; import from 'framesource.processors' instead.",
16
+ DeprecationWarning,
17
+ stacklevel=2,
18
+ )
19
+
20
+ import framesource.processors as _processors
21
+ from framesource.processors import * # noqa: F401,F403
22
+
23
+ __all__ = getattr(_processors, "__all__", [])
24
+
25
+ _SUBMODULE_ALIASES = {
26
+ "frame_processor": "framesource.processors.frame_processor",
27
+ "equirectangular360_processor": "framesource.processors.equirectangular360_processor",
28
+ "fisheye2equirectangular_processor": "framesource.processors.fisheye2equirectangular_processor",
29
+ "realsense_depth_processor": "framesource.processors.realsense_depth_processor",
30
+ "hyperspectral_processor": "framesource.processors.hyperspectral_processor",
31
+ }
32
+
33
+ for _alias, _target in _SUBMODULE_ALIASES.items():
34
+ try:
35
+ sys.modules[f"{__name__}.{_alias}"] = importlib.import_module(_target)
36
+ except Exception:
37
+ pass
@@ -0,0 +1,50 @@
1
+ """Deprecated compatibility shim for the old ``frame_source`` package.
2
+
3
+ This package was renamed to :mod:`framesource`. Importing ``frame_source``
4
+ continues to work for now but emits a :class:`DeprecationWarning` and will be
5
+ removed in a future major release. Update imports to ``framesource``.
6
+ """
7
+
8
+ import importlib
9
+ import sys
10
+ import warnings
11
+
12
+ warnings.warn(
13
+ "'frame_source' is deprecated and will be removed in a future major "
14
+ "release; import from 'framesource' instead.",
15
+ DeprecationWarning,
16
+ stacklevel=2,
17
+ )
18
+
19
+ import framesource as _framesource
20
+ from framesource import * # noqa: F401,F403
21
+ from framesource import FrameSourceFactory, VideoCaptureBase # noqa: F401
22
+
23
+ __all__ = getattr(_framesource, "__all__", [])
24
+ __version__ = getattr(_framesource, "__version__", "0.0.0")
25
+
26
+ # Alias the old flat module paths (e.g. ``frame_source.webcam_capture``) onto
27
+ # the new locations so existing deep imports keep working.
28
+ _SUBMODULE_ALIASES = {
29
+ "factory": "framesource.factory",
30
+ "threading_utils": "framesource.threading_utils",
31
+ "video_capture_base": "framesource.sources.video_capture_base",
32
+ "webcam_capture": "framesource.sources.webcam_capture",
33
+ "ipcamera_capture": "framesource.sources.ipcamera_capture",
34
+ "basler_capture": "framesource.sources.basler_capture",
35
+ "genicam_capture": "framesource.sources.genicam_capture",
36
+ "realsense_capture": "framesource.sources.realsense_capture",
37
+ "ximea_capture": "framesource.sources.ximea_capture",
38
+ "huateng_capture": "framesource.sources.huateng_capture",
39
+ "video_file_capture": "framesource.sources.video_file_capture",
40
+ "folder_capture": "framesource.sources.folder_capture",
41
+ "screen_capture": "framesource.sources.screen_capture",
42
+ "audiospectrogram_capture": "framesource.sources.audiospectrogram_capture",
43
+ }
44
+
45
+ for _alias, _target in _SUBMODULE_ALIASES.items():
46
+ try:
47
+ sys.modules[f"{__name__}.{_alias}"] = importlib.import_module(_target)
48
+ except Exception:
49
+ # Optional/vendor backend unavailable; skip aliasing it.
50
+ pass
@@ -0,0 +1,93 @@
1
+ import importlib
2
+ import logging
3
+
4
+ # Must be imported before any capture module pulls in cv2 (it configures the
5
+ # OpenCV MSMF backend, which reads its env var at cv2-import time). The `_`
6
+ # prefix sorts it first within this first-party import group.
7
+ from . import _msmf_config as _msmf_config # noqa: F401
8
+ from .discovery import DeviceInfo
9
+ from .errors import (
10
+ FrameSourceError,
11
+ MissingDependencyError,
12
+ NotConnectedError,
13
+ UnknownSourceTypeError,
14
+ )
15
+ from .factory import FrameSourceFactory
16
+ from .sources.video_capture_base import Frame, FrameSourceProtocol, VideoCaptureBase
17
+ from .threading_utils import (
18
+ AsyncFrameSource,
19
+ FrameProducer,
20
+ ProducerConsumer,
21
+ SharedProducer,
22
+ create_producer_consumer_pair,
23
+ multiprocess_frame_producer,
24
+ simple_frame_producer,
25
+ )
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+ try:
30
+ from ._version import version as __version__ # generated by setuptools_scm
31
+ except Exception: # pragma: no cover - fallback when not built/installed
32
+ try:
33
+ from importlib.metadata import version as _pkg_version
34
+
35
+ __version__ = _pkg_version("framesource")
36
+ except Exception:
37
+ __version__ = "0.0.0"
38
+
39
+
40
+ # Import capture classes with graceful handling for missing optional
41
+ # dependencies. Unavailable classes are set to None and logged at debug level.
42
+ _OPTIONAL_CLASSES = {
43
+ "WebcamCapture": "webcam_capture",
44
+ "AudioSpectrogramCapture": "audiospectrogram_capture",
45
+ "BaslerCapture": "basler_capture",
46
+ "GenicamCapture": "genicam_capture",
47
+ "IPCameraCapture": "ipcamera_capture",
48
+ "VideoFileCapture": "video_file_capture",
49
+ "FolderCapture": "folder_capture",
50
+ "RealsenseCapture": "realsense_capture",
51
+ "ScreenCapture": "screen_capture",
52
+ "XimeaCapture": "ximea_capture",
53
+ "HuatengCapture": "huateng_capture",
54
+ }
55
+
56
+ for _class_name, _module_name in _OPTIONAL_CLASSES.items():
57
+ try:
58
+ _module = importlib.import_module(f".sources.{_module_name}", __name__)
59
+ globals()[_class_name] = getattr(_module, _class_name)
60
+ except Exception as e: # noqa: BLE001 - vendor SDKs may raise non-ImportError
61
+ globals()[_class_name] = None
62
+ logger.debug("%s unavailable: %s", _class_name, e)
63
+
64
+
65
+ __all__ = [
66
+ "FrameSourceFactory",
67
+ "VideoCaptureBase",
68
+ "Frame",
69
+ "FrameSourceProtocol",
70
+ "DeviceInfo",
71
+ "FrameSourceError",
72
+ "MissingDependencyError",
73
+ "UnknownSourceTypeError",
74
+ "NotConnectedError",
75
+ "simple_frame_producer",
76
+ "FrameProducer",
77
+ "multiprocess_frame_producer",
78
+ "create_producer_consumer_pair",
79
+ "ProducerConsumer",
80
+ "AsyncFrameSource",
81
+ "SharedProducer",
82
+ "WebcamCapture",
83
+ "IPCameraCapture",
84
+ "BaslerCapture",
85
+ "GenicamCapture",
86
+ "RealsenseCapture",
87
+ "XimeaCapture",
88
+ "HuatengCapture",
89
+ "VideoFileCapture",
90
+ "FolderCapture",
91
+ "ScreenCapture",
92
+ "AudioSpectrogramCapture",
93
+ ]
@@ -0,0 +1,26 @@
1
+ """Configure OpenCV's Media Foundation (MSMF) backend before ``cv2`` loads.
2
+
3
+ On Windows, OpenCV's MSMF capture backend reads
4
+ ``OPENCV_VIDEOIO_MSMF_ENABLE_HW_TRANSFORMS`` **once, when cv2 is first
5
+ imported**. With hardware transforms enabled (the default), opening some
6
+ webcams stalls for 20+ seconds while Media Foundation initializes hardware
7
+ transforms — even though frame throughput afterwards is fine. Disabling them
8
+ makes the camera open near-instantly (measured 22.6s -> 0.33s) with no
9
+ measurable throughput cost.
10
+
11
+ Because the value is read at ``import cv2`` time, it must be set *before* cv2
12
+ is imported anywhere in the process. This module carries no cv2 dependency and
13
+ is imported first by :mod:`framesource`, ahead of any capture module, so the
14
+ setting is in place in time. ``setdefault`` is used so a user who configured
15
+ the variable explicitly (to ``"0"`` or ``"1"``) always wins.
16
+
17
+ Caveat: if the host application runs ``import cv2`` and opens an MSMF device
18
+ *before* importing :mod:`framesource`, cv2 has already cached the value and
19
+ this has no effect; set the environment variable yourself in that case.
20
+ """
21
+
22
+ import os
23
+ import platform
24
+
25
+ if platform.system() == "Windows":
26
+ os.environ.setdefault("OPENCV_VIDEOIO_MSMF_ENABLE_HW_TRANSFORMS", "0")
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '0.3.0'
22
+ __version_tuple__ = version_tuple = (0, 3, 0)
23
+
24
+ __commit_id__ = commit_id = None
@@ -0,0 +1,109 @@
1
+ """Structured device descriptor returned by ``discover()`` implementations.
2
+
3
+ Historically each capture type's :meth:`discover` returned a list of plain
4
+ ``dict`` objects with type-specific keys. :class:`DeviceInfo` replaces those
5
+ dicts with a typed :mod:`dataclasses` dataclass while remaining fully
6
+ dict-compatible (indexing, ``get``, ``in``, ``keys``/``items``) so existing
7
+ consumers that treated discovery results as dictionaries keep working
8
+ unchanged.
9
+
10
+ The historical webcam dicts used an ``'id'`` key; that is preserved as an
11
+ alias for :attr:`DeviceInfo.device_id`, and any extra type-specific keys are
12
+ kept flat via :attr:`DeviceInfo.metadata` so ``dev['id']``, ``dev['name']``,
13
+ ``dev['index']`` and ``dev['backend_name']`` all still resolve.
14
+ """
15
+
16
+ from dataclasses import dataclass, field
17
+ from typing import Any, Optional
18
+
19
+
20
+ @dataclass
21
+ class DeviceInfo:
22
+ """A discovered capture device.
23
+
24
+ Instances behave like the plain dicts previously returned by
25
+ ``discover()``: they support ``dev[key]``, ``dev.get(key)``,
26
+ ``key in dev``, ``dev.keys()`` and ``dev.items()``. The ``'id'`` key is an
27
+ alias for :attr:`device_id`, and :attr:`metadata` entries are exposed as
28
+ flat top-level keys.
29
+
30
+ Attributes:
31
+ device_id: String identifier used as the ``source`` when creating a
32
+ capture. Exposed under the alias ``'id'`` for dict-compatibility.
33
+ index: OS/enumeration index, if applicable.
34
+ name: Human-readable device name.
35
+ driver: Backend/driver that produced this entry (e.g. ``'opencv'`` or
36
+ ``'pyaudio'``).
37
+ id_stable: True when ``device_id`` is stable across reboots/replug
38
+ (e.g. a serial number); False for OS index-based identifiers.
39
+ metadata: Free-form extra fields, kept flat for dict-compatibility.
40
+ """
41
+
42
+ device_id: str
43
+ index: Optional[int] = None
44
+ name: str = ""
45
+ driver: str = ""
46
+ id_stable: bool = False
47
+ metadata: dict[str, Any] = field(default_factory=dict)
48
+
49
+ def _flat(self) -> dict[str, Any]:
50
+ """Return the flat, dict-compatible view (``'id'`` alias + metadata)."""
51
+ flat: dict[str, Any] = {
52
+ "id": self.device_id,
53
+ "index": self.index,
54
+ "name": self.name,
55
+ "driver": self.driver,
56
+ "id_stable": self.id_stable,
57
+ }
58
+ flat.update(self.metadata)
59
+ return flat
60
+
61
+ def __getitem__(self, key: str) -> Any:
62
+ if key in ("id", "device_id"):
63
+ return self.device_id
64
+ if key in ("index", "name", "driver", "id_stable"):
65
+ return getattr(self, key)
66
+ if key in self.metadata:
67
+ return self.metadata[key]
68
+ raise KeyError(key)
69
+
70
+ def get(self, key: str, default: Any = None) -> Any:
71
+ """Dict-style ``get`` with alias/metadata fallback."""
72
+ try:
73
+ return self[key]
74
+ except KeyError:
75
+ return default
76
+
77
+ def __contains__(self, key: str) -> bool:
78
+ if key in ("id", "device_id", "index", "name", "driver", "id_stable"):
79
+ return True
80
+ return key in self.metadata
81
+
82
+ def keys(self):
83
+ """Return the flat top-level keys (``'id'`` alias + metadata keys)."""
84
+ return self._flat().keys()
85
+
86
+ def values(self):
87
+ """Return the flat top-level values."""
88
+ return self._flat().values()
89
+
90
+ def items(self):
91
+ """Return the flat ``(key, value)`` pairs."""
92
+ return self._flat().items()
93
+
94
+ def as_dict(self) -> dict[str, Any]:
95
+ """Return a faithful dict of the dataclass fields.
96
+
97
+ Unlike the flat dict-compat view, this uses the canonical
98
+ ``'device_id'`` key and keeps ``metadata`` nested, so the result
99
+ round-trips: ``DeviceInfo(**info.as_dict())`` reconstructs an
100
+ equivalent instance.
101
+ """
102
+ return {
103
+ "device_id": self.device_id,
104
+ "index": self.index,
105
+ "name": self.name,
106
+ "driver": self.driver,
107
+ "id_stable": self.id_stable,
108
+ "metadata": dict(self.metadata),
109
+ }
framesource/errors.py ADDED
@@ -0,0 +1,82 @@
1
+ """Exception hierarchy for FrameSource.
2
+
3
+ All library-specific errors derive from :class:`FrameSourceError` so callers
4
+ that want to catch "anything FrameSource raises" have a single type to
5
+ catch. At the same time, several of these exceptions are *also* subclassed
6
+ from the built-in exception type that FrameSource historically raised at the
7
+ same call site (e.g. ``ImportError`` for missing optional dependencies,
8
+ ``ValueError`` for bad/unknown arguments).
9
+
10
+ This dual inheritance is deliberate: it preserves backwards compatibility.
11
+ Existing user code written against earlier FrameSource versions may contain
12
+ ``except ImportError:`` or ``except ValueError:`` clauses around calls into
13
+ this library. Because ``MissingDependencyError`` is-a ``ImportError`` and
14
+ ``UnknownSourceTypeError`` is-a ``ValueError``, that old code keeps working
15
+ unchanged, while new code can catch the more specific FrameSource types (or
16
+ the common ``FrameSourceError`` base) for finer-grained handling.
17
+ """
18
+
19
+ from typing import Optional
20
+
21
+
22
+ class FrameSourceError(RuntimeError):
23
+ """Base class for all errors raised by FrameSource.
24
+
25
+ Catch this to handle any FrameSource-specific failure without needing
26
+ to know which built-in exception type it also derives from.
27
+ """
28
+
29
+
30
+ class MissingDependencyError(FrameSourceError, ImportError):
31
+ """Raised when an optional third-party dependency is not installed.
32
+
33
+ Inherits from :class:`ImportError` so existing ``except ImportError:``
34
+ handlers written against earlier FrameSource versions continue to catch
35
+ this exception unchanged.
36
+
37
+ Attributes:
38
+ package (str): Name of the missing package (or packages).
39
+ extra (Optional[str]): Name of the FrameSource extra that installs
40
+ the dependency (e.g. ``'audio'`` for ``pip install
41
+ framesource[audio]``), if applicable.
42
+ """
43
+
44
+ def __init__(self, package: str, extra: Optional[str] = None, details: Optional[str] = None):
45
+ """Initialize the error.
46
+
47
+ Args:
48
+ package: Name of the missing package (or packages).
49
+ extra: Name of the FrameSource extra that installs the
50
+ dependency, if any. When provided, the message includes an
51
+ install hint (``pip install framesource[<extra>]``).
52
+ details: Additional details to append to the message, such as
53
+ the original ``ImportError`` text.
54
+ """
55
+ self.package = package
56
+ self.extra = extra
57
+
58
+ message = f"{package} is required but not installed."
59
+ if extra:
60
+ message += f" Install with: pip install framesource[{extra}]"
61
+ if details:
62
+ message += f" ({details})"
63
+
64
+ super().__init__(message)
65
+
66
+
67
+ class UnknownSourceTypeError(FrameSourceError, ValueError):
68
+ """Raised when a requested capture source type is not registered.
69
+
70
+ Inherits from :class:`ValueError` so existing ``except ValueError:``
71
+ handlers written against earlier FrameSource versions continue to catch
72
+ this exception unchanged.
73
+ """
74
+
75
+
76
+ class NotConnectedError(FrameSourceError):
77
+ """Raised when an operation requires a connected source but it isn't.
78
+
79
+ Reserved for use by subclasses and downstream code that wants to signal
80
+ reads/operations attempted on a disconnected capture source. FrameSource
81
+ itself does not currently raise this from its read paths.
82
+ """