sef 0.2.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 (253) hide show
  1. sef/__init__.py +62 -0
  2. sef/__main__.py +5 -0
  3. sef/api/__init__.py +27 -0
  4. sef/api/config.py +159 -0
  5. sef/api/decorators.py +143 -0
  6. sef/api/function_adapters.py +129 -0
  7. sef/api/orchestrator.py +203 -0
  8. sef/api/pipeline.py +300 -0
  9. sef/api/registry.py +65 -0
  10. sef/api/stage_refs.py +211 -0
  11. sef/builtin/Main.py +83 -0
  12. sef/builtin/__init__.py +28 -0
  13. sef/builtin/_optional_dependencies.py +71 -0
  14. sef/builtin/analyzers/ArUco/ArucoMarkerDisplacementAnalyzer.py +159 -0
  15. sef/builtin/analyzers/ArUco/ArucoMarkerRelativeMotionAnalyzer.py +170 -0
  16. sef/builtin/analyzers/ArUco/__init__.py +0 -0
  17. sef/builtin/analyzers/COCO_pose/COCOPoseStreamAnalyzer.py +152 -0
  18. sef/builtin/analyzers/COCO_pose/__init__.py +0 -0
  19. sef/builtin/analyzers/NoAnalyzer.py +34 -0
  20. sef/builtin/analyzers/multiple_tracker/MultiObjectBarrierCountingAnalyzer.py +93 -0
  21. sef/builtin/analyzers/multiple_tracker/MultipleDistanceAnalyzer.py +61 -0
  22. sef/builtin/analyzers/multiple_tracker/__init__.py +0 -0
  23. sef/builtin/analyzers/optical_flow/DenseOpticalFlowVectorFieldAnalyzer.py +80 -0
  24. sef/builtin/analyzers/optical_flow/SparseOpticalFlowTrajectoryAnalyzer.py +81 -0
  25. sef/builtin/analyzers/optical_flow/__init__.py +0 -0
  26. sef/builtin/analyzers/playback/TrackingPlaybackAnalyzer.py +115 -0
  27. sef/builtin/analyzers/playback/__init__.py +0 -0
  28. sef/builtin/analyzers/single_tracker/HoriziontalPositionAnalyzer.py +41 -0
  29. sef/builtin/analyzers/single_tracker/HorizontalFrequencyAnalyzer.py +53 -0
  30. sef/builtin/analyzers/single_tracker/HorizontalVelocityAnalyzer.py +60 -0
  31. sef/builtin/analyzers/single_tracker/VerticalFrequencyAnalyzer.py +53 -0
  32. sef/builtin/analyzers/single_tracker/VerticalPositionAnalyzer.py +41 -0
  33. sef/builtin/analyzers/single_tracker/VerticalPositionStreamAnalyzer.py +78 -0
  34. sef/builtin/analyzers/single_tracker/VerticalVelocityAnalyzer.py +60 -0
  35. sef/builtin/analyzers/single_tracker/__init__.py +0 -0
  36. sef/builtin/branching_rules/NewTrackBranchingRule.py +57 -0
  37. sef/builtin/exporters/IntermediateFrameArtifactExporter.py +98 -0
  38. sef/builtin/exporters/OpenCVFrameBufferVideoExporter.py +179 -0
  39. sef/builtin/frame_extractors/OpenCVBufferedFrameExtractor.py +104 -0
  40. sef/builtin/frame_extractors/OpenCVWebcamFrameExtractor.py +125 -0
  41. sef/builtin/frame_processors/ColorStabilizationFrameProcessor.py +786 -0
  42. sef/builtin/frame_processors/DynamicObjectRemovalFrameProcessor.py +3 -0
  43. sef/builtin/frame_processors/OpenCV/OpenCVBackgroundReplacementFrameProcessor.py +79 -0
  44. sef/builtin/frame_processors/OpenCV/OpenCVBackgroundSubtractionFrameProcessor.py +93 -0
  45. sef/builtin/frame_processors/OpenCV/OpenCVDynamicBackgroundReplacementFrameProcessor.py +120 -0
  46. sef/builtin/frame_processors/OpenCV/OpenCVDynamicInpaintFrameProcessor.py +134 -0
  47. sef/builtin/frame_processors/OpenCV/OpenCVGrayFrameProcessor.py +24 -0
  48. sef/builtin/frame_processors/OpenCV/OpenCVHistogramEqualizationFrameProcessor.py +34 -0
  49. sef/builtin/frame_processors/OpenCV/OpenCVInpaintFrameProcessor.py +75 -0
  50. sef/builtin/frame_processors/OpenCV/OpenCVResizeFrameProcessor.py +39 -0
  51. sef/builtin/frame_processors/OpenCV/OpenCVRotateFrameProcessor.py +49 -0
  52. sef/builtin/frame_processors/OpenCV/OpenCVZoomFrameProcessor.py +56 -0
  53. sef/builtin/frame_processors/OpenCV/__init__.py +0 -0
  54. sef/builtin/frame_processors/RealtimeFrameTapProcessor.py +88 -0
  55. sef/builtin/frame_processors/SmoothingFrameProcessor.py +61 -0
  56. sef/builtin/frame_processors/__init__.py +0 -0
  57. sef/builtin/frame_processors/dynamic_object_removal/__init__.py +34 -0
  58. sef/builtin/frame_processors/dynamic_object_removal/background_estimator.py +98 -0
  59. sef/builtin/frame_processors/dynamic_object_removal/config.py +116 -0
  60. sef/builtin/frame_processors/dynamic_object_removal/foreground_mask_extractor.py +56 -0
  61. sef/builtin/frame_processors/dynamic_object_removal/mask_refiner.py +93 -0
  62. sef/builtin/frame_processors/dynamic_object_removal/processor.py +374 -0
  63. sef/builtin/frame_processors/dynamic_object_removal/region_reconstructor.py +42 -0
  64. sef/builtin/frame_processors/motion_magnification/PhaseMagnificationFrameProcessor.py +488 -0
  65. sef/builtin/frame_processors/motion_magnification/__init__.py +0 -0
  66. sef/builtin/live_analyzers/LiveVerticalPositionAnalyzer.py +46 -0
  67. sef/builtin/live_analyzers/__init__.py +0 -0
  68. sef/builtin/registry.py +196 -0
  69. sef/builtin/retry_policies/ExponentialBackoffRetryPolicy.py +53 -0
  70. sef/builtin/retry_policies/FixedRetryPolicy.py +30 -0
  71. sef/builtin/retry_policies/NoRetryPolicy.py +3 -0
  72. sef/builtin/retry_policies/__init__.py +9 -0
  73. sef/builtin/signal_cleaners/ArUco/ArucoTemporalStabilizerCleaner.py +242 -0
  74. sef/builtin/signal_cleaners/ArUco/__init__.py +0 -0
  75. sef/builtin/signal_cleaners/COCO_pose/COCOSkeletonNormalizationSignalCleaner.py +109 -0
  76. sef/builtin/signal_cleaners/COCO_pose/__init__.py +0 -0
  77. sef/builtin/signal_cleaners/__init__.py +0 -0
  78. sef/builtin/signal_cleaners/optical_flow/OpticalFlowOutlierCleaner.py +76 -0
  79. sef/builtin/signal_cleaners/optical_flow/__init__.py +0 -0
  80. sef/builtin/signal_cleaners/single_tracker/MovingAverageCleaner.py +46 -0
  81. sef/builtin/signal_cleaners/single_tracker/MovingAverageStreamSignalCleaner.py +72 -0
  82. sef/builtin/signal_cleaners/single_tracker/OutlierRejectionCleaner.py +78 -0
  83. sef/builtin/signal_cleaners/single_tracker/SignalWidenerCleaner.py +43 -0
  84. sef/builtin/signal_cleaners/single_tracker/__init__.py +0 -0
  85. sef/builtin/signal_extractors/ArucoMarkerSignalExtractor.py +721 -0
  86. sef/builtin/signal_extractors/NoSignalExtractor.py +49 -0
  87. sef/builtin/signal_extractors/OpenCVBufferedSignalExtractor.py +147 -0
  88. sef/builtin/signal_extractors/OpenCVDenseOpticalFlowSignalExtractor.py +133 -0
  89. sef/builtin/signal_extractors/OpenCVMultiManualSignalExtractor.py +157 -0
  90. sef/builtin/signal_extractors/OpenCVMultiObjectSignalExtractor.py +456 -0
  91. sef/builtin/signal_extractors/OpenCVSparseOpticalFlowSignalExtractor.py +197 -0
  92. sef/builtin/signal_extractors/OpenCVStreamSignalExtractor.py +162 -0
  93. sef/builtin/signal_extractors/YOLOSkeletonCOCOStreamSignalExtractor.py +170 -0
  94. sef/builtin/visualizers/ArUco/ArucoAnnotatedVideoVisualizer.py +129 -0
  95. sef/builtin/visualizers/ArUco/__init__.py +0 -0
  96. sef/builtin/visualizers/COCO_pose/COCOPoseFrameRenderer.py +167 -0
  97. sef/builtin/visualizers/COCO_pose/OpenCVCOCOPoseRealtimeVisualizer.py +184 -0
  98. sef/builtin/visualizers/COCO_pose/OpenCVCOCOTennisPoseRealtimeVisualizer.py +192 -0
  99. sef/builtin/visualizers/COCO_pose/RealtimeCOCOPoseFrameVisualizer.py +118 -0
  100. sef/builtin/visualizers/COCO_pose/__init__.py +0 -0
  101. sef/builtin/visualizers/Matplotlib/MatplotlibArtifactVisualizer.py +67 -0
  102. sef/builtin/visualizers/Matplotlib/MatplotlibArucoMotionVisualizer.py +178 -0
  103. sef/builtin/visualizers/Matplotlib/MatplotlibFunctionStreamVisualizer.py +111 -0
  104. sef/builtin/visualizers/Matplotlib/MatplotlibFunctionVisualizer.py +68 -0
  105. sef/builtin/visualizers/Matplotlib/MatplotlibHeatmapVisualizer.py +86 -0
  106. sef/builtin/visualizers/Matplotlib/MatplotlibHistogramVisualizer.py +57 -0
  107. sef/builtin/visualizers/Matplotlib/MatplotlibTrajectoryVisualizer.py +83 -0
  108. sef/builtin/visualizers/Matplotlib/MatplotlibVectorFieldVisualizer.py +93 -0
  109. sef/builtin/visualizers/Matplotlib/__init__.py +0 -0
  110. sef/builtin/visualizers/TrackingVideoVisualizer.py +652 -0
  111. sef/builtin/visualizers/__init__.py +0 -0
  112. sef/builtin/visualizers/intermediate_frames/IntermediateFramesGridVisualizer.py +94 -0
  113. sef/builtin/visualizers/intermediate_frames/IntermediateFramesVisualizer.py +141 -0
  114. sef/builtin/visualizers/intermediate_frames/__init__.py +0 -0
  115. sef/cli/__init__.py +5 -0
  116. sef/cli/__main__.py +5 -0
  117. sef/cli/commands.py +1551 -0
  118. sef/cli/output.py +97 -0
  119. sef/core/__init__.py +63 -0
  120. sef/core/_lazy_exports.py +31 -0
  121. sef/core/artifacts/Frame.py +38 -0
  122. sef/core/artifacts/Signal.py +18 -0
  123. sef/core/artifacts/__init__.py +162 -0
  124. sef/core/artifacts/buffer/DataBuffer.py +154 -0
  125. sef/core/artifacts/buffer/FrameBuffer.py +148 -0
  126. sef/core/artifacts/buffer/SignalBuffer.py +172 -0
  127. sef/core/artifacts/buffer/__init__.py +36 -0
  128. sef/core/artifacts/data/ArucoDisplacementData.py +238 -0
  129. sef/core/artifacts/data/ArucoRelativeMotionData.py +38 -0
  130. sef/core/artifacts/data/COCOPoseFrameData.py +31 -0
  131. sef/core/artifacts/data/COCOPoseTennisFrameData.py +32 -0
  132. sef/core/artifacts/data/CategoryData.py +24 -0
  133. sef/core/artifacts/data/NoData.py +10 -0
  134. sef/core/artifacts/data/TrackingPlaybackData.py +66 -0
  135. sef/core/artifacts/data/TrajectoryData.py +23 -0
  136. sef/core/artifacts/data/TwoDimGraphData.py +19 -0
  137. sef/core/artifacts/data/TwoDimPointData.py +19 -0
  138. sef/core/artifacts/data/VectorFieldGraphData.py +26 -0
  139. sef/core/artifacts/data/__init__.py +89 -0
  140. sef/core/artifacts/intermediate_frame/IntermediateFrameArtifacts.py +111 -0
  141. sef/core/artifacts/intermediate_frame/IntermediateFrameComposition.py +336 -0
  142. sef/core/artifacts/intermediate_frame/__init__.py +67 -0
  143. sef/core/artifacts/mask/MaskArtifacts.py +433 -0
  144. sef/core/artifacts/mask/MaskOperations.py +128 -0
  145. sef/core/artifacts/mask/__init__.py +47 -0
  146. sef/core/artifacts/signal_sample/ArucoMarkerSignalSample.py +49 -0
  147. sef/core/artifacts/signal_sample/BoxSignalSample.py +20 -0
  148. sef/core/artifacts/signal_sample/COCOSkeletonSignalSample.py +32 -0
  149. sef/core/artifacts/signal_sample/DenseOpticalFlowSignalSample.py +35 -0
  150. sef/core/artifacts/signal_sample/MultiManualSignalSample.py +15 -0
  151. sef/core/artifacts/signal_sample/MultiObjectSignalSample.py +34 -0
  152. sef/core/artifacts/signal_sample/SparseOpticalFlowSignalSample.py +25 -0
  153. sef/core/artifacts/signal_sample/__init__.py +64 -0
  154. sef/core/enum/FrameRotation.py +7 -0
  155. sef/core/errors.py +276 -0
  156. sef/core/events/Event.py +54 -0
  157. sef/core/events/EventBus.py +85 -0
  158. sef/core/events/PipelineEvent.py +114 -0
  159. sef/core/events/PipelineLifecycleEvent.py +66 -0
  160. sef/core/events/__init__.py +49 -0
  161. sef/core/interfaces/BufferContracts.py +84 -0
  162. sef/core/interfaces/IAnalyzer.py +40 -0
  163. sef/core/interfaces/IData.py +5 -0
  164. sef/core/interfaces/IEventEmitter.py +110 -0
  165. sef/core/interfaces/IFrameBufferProcessor.py +44 -0
  166. sef/core/interfaces/IFrameExporter.py +71 -0
  167. sef/core/interfaces/IFrameExtractor.py +48 -0
  168. sef/core/interfaces/ILiveAnalyzer.py +19 -0
  169. sef/core/interfaces/ISignal.py +48 -0
  170. sef/core/interfaces/ISignalCleaner.py +37 -0
  171. sef/core/interfaces/ISignalExtractor.py +41 -0
  172. sef/core/interfaces/ISignalSample.py +30 -0
  173. sef/core/interfaces/ISingleFrameProcessor.py +35 -0
  174. sef/core/interfaces/IVisualizer.py +48 -0
  175. sef/core/interfaces/StageCapabilities.py +71 -0
  176. sef/core/interfaces/StreamingContracts.py +138 -0
  177. sef/core/interfaces/__init__.py +96 -0
  178. sef/core/interfaces/pipeline/IBranchingRule.py +73 -0
  179. sef/core/interfaces/pipeline/IEventBus.py +55 -0
  180. sef/core/interfaces/pipeline/IPipelineFactory.py +36 -0
  181. sef/core/interfaces/pipeline/IPipelineMonitor.py +55 -0
  182. sef/core/interfaces/pipeline/IPipelineOutputStore.py +29 -0
  183. sef/core/interfaces/pipeline/IPipelineRunner.py +52 -0
  184. sef/core/interfaces/pipeline/IPipelineValidator.py +22 -0
  185. sef/core/interfaces/pipeline/IRetryPolicy.py +55 -0
  186. sef/core/interfaces/pipeline/__init__.py +66 -0
  187. sef/core/pipeline/AnalysisSegmentExecutor.py +359 -0
  188. sef/core/pipeline/BranchingCoordinator.py +110 -0
  189. sef/core/pipeline/ConfigPipelineBuilder.py +403 -0
  190. sef/core/pipeline/DefaultPipelineFactory.py +31 -0
  191. sef/core/pipeline/FluentPipelineBuilder.py +176 -0
  192. sef/core/pipeline/FrameProcessingStage.py +78 -0
  193. sef/core/pipeline/FrameSegmentExecutor.py +303 -0
  194. sef/core/pipeline/InMemoryPipelineMonitor.py +91 -0
  195. sef/core/pipeline/InMemoryPipelineOutputStore.py +33 -0
  196. sef/core/pipeline/IntermediateFrameCapture.py +210 -0
  197. sef/core/pipeline/LatencyPolicy.py +331 -0
  198. sef/core/pipeline/NoRetryPolicy.py +17 -0
  199. sef/core/pipeline/Pipeline.py +152 -0
  200. sef/core/pipeline/PipelineBoundaryMaterializer.py +125 -0
  201. sef/core/pipeline/PipelineBuffers.py +48 -0
  202. sef/core/pipeline/PipelineCodeExporter.py +102 -0
  203. sef/core/pipeline/PipelineComponentCapabilities.py +89 -0
  204. sef/core/pipeline/PipelineConfigExporter.py +533 -0
  205. sef/core/pipeline/PipelineConfigVersioning.py +226 -0
  206. sef/core/pipeline/PipelineContext.py +210 -0
  207. sef/core/pipeline/PipelineErrors.py +49 -0
  208. sef/core/pipeline/PipelineEventInjector.py +57 -0
  209. sef/core/pipeline/PipelineExecutionLookahead.py +64 -0
  210. sef/core/pipeline/PipelineExecutionPlan.py +144 -0
  211. sef/core/pipeline/PipelineExecutionPlanner.py +285 -0
  212. sef/core/pipeline/PipelineExecutionPolicy.py +226 -0
  213. sef/core/pipeline/PipelineExecutionResources.py +45 -0
  214. sef/core/pipeline/PipelineExecutionResult.py +25 -0
  215. sef/core/pipeline/PipelineExportUtils.py +168 -0
  216. sef/core/pipeline/PipelineOrchestrator.py +188 -0
  217. sef/core/pipeline/PipelineOutputAssembler.py +94 -0
  218. sef/core/pipeline/PipelineRunSnapshot.py +33 -0
  219. sef/core/pipeline/PipelineRuntimeState.py +40 -0
  220. sef/core/pipeline/PipelineStageExecutor.py +44 -0
  221. sef/core/pipeline/SegmentedPipelineExecutor.py +147 -0
  222. sef/core/pipeline/SignalSegmentExecutor.py +172 -0
  223. sef/core/pipeline/SingleFrameProcessorAdapter.py +208 -0
  224. sef/core/pipeline/StreamRuntimeConfig.py +122 -0
  225. sef/core/pipeline/ThreadedPipelineRunner.py +322 -0
  226. sef/core/pipeline/VisualizationExecutor.py +178 -0
  227. sef/core/pipeline/VisualizerBinding.py +53 -0
  228. sef/core/pipeline/__init__.py +99 -0
  229. sef/core/plugins/PluginRegistry.py +348 -0
  230. sef/core/plugins/__init__.py +19 -0
  231. sef/core/pose/COCOSkeletonNormalizer.py +164 -0
  232. sef/core/pose/__init__.py +11 -0
  233. sef/core/realtime/IRealtimeFrameSink.py +21 -0
  234. sef/core/realtime/LatestRealtimeFrameStore.py +132 -0
  235. sef/core/realtime/NullRealtimeFrameSink.py +19 -0
  236. sef/core/realtime/RealtimeFrame.py +73 -0
  237. sef/core/realtime/__init__.py +24 -0
  238. sef/core/utils/OpenCVBarrierSelector.py +144 -0
  239. sef/core/utils/OpenCVDisplayUtils.py +64 -0
  240. sef/core/utils/OpenCVMaskSelector.py +206 -0
  241. sef/core/utils/OpenCVMultiStartBoxSelector.py +96 -0
  242. sef/core/utils/OpenCVStartBoxSelector.py +74 -0
  243. sef/core/visualization/PipelineOutputs.py +52 -0
  244. sef/core/visualization/PipelineRunMetadata.py +29 -0
  245. sef/core/visualization/VisualArtifact.py +259 -0
  246. sef/core/visualization/VisualizationContext.py +30 -0
  247. sef/core/visualization/__init__.py +65 -0
  248. sef-0.2.0.dist-info/METADATA +52 -0
  249. sef-0.2.0.dist-info/RECORD +253 -0
  250. sef-0.2.0.dist-info/WHEEL +5 -0
  251. sef-0.2.0.dist-info/entry_points.txt +2 -0
  252. sef-0.2.0.dist-info/licenses/LICENSE +13 -0
  253. sef-0.2.0.dist-info/top_level.txt +1 -0
sef/__init__.py ADDED
@@ -0,0 +1,62 @@
1
+ """Public Pythonic entrypoint for SEF.
2
+
3
+ Use this module for concise workflows:
4
+
5
+ ```python
6
+ import sef
7
+
8
+ outputs = (
9
+ sef.pipeline("quickstart")
10
+ .frames("demo_frames", frame_count=3)
11
+ .signals("demo_signals")
12
+ .analyze("sample_count")
13
+ .visualize("summary_text")
14
+ .run()
15
+ )
16
+ ```
17
+
18
+ Advanced users can still import stable lower-level contracts from
19
+ ``sef.core`` or ``sef.core``.
20
+ """
21
+
22
+ from sef.api import (
23
+ OrchestratorFacade,
24
+ PipelineFacade,
25
+ analyzer,
26
+ cleaner,
27
+ default_registry,
28
+ frame_extractor,
29
+ from_config,
30
+ load_config,
31
+ normalize_config,
32
+ orchestrator,
33
+ pipeline,
34
+ processor,
35
+ register_user_plugin,
36
+ signal_extractor,
37
+ video,
38
+ visualizer,
39
+ webcam,
40
+ )
41
+ from sef.core import PipelineExecutionError
42
+
43
+ __all__ = [
44
+ "OrchestratorFacade",
45
+ "PipelineFacade",
46
+ "PipelineExecutionError",
47
+ "analyzer",
48
+ "cleaner",
49
+ "default_registry",
50
+ "frame_extractor",
51
+ "from_config",
52
+ "load_config",
53
+ "normalize_config",
54
+ "orchestrator",
55
+ "pipeline",
56
+ "processor",
57
+ "register_user_plugin",
58
+ "signal_extractor",
59
+ "video",
60
+ "visualizer",
61
+ "webcam",
62
+ ]
sef/__main__.py ADDED
@@ -0,0 +1,5 @@
1
+ from __future__ import annotations
2
+
3
+ from sef.cli import main
4
+
5
+ raise SystemExit(main())
sef/api/__init__.py ADDED
@@ -0,0 +1,27 @@
1
+ """High-level Pythonic SEF API."""
2
+
3
+ from sef.api.config import load_config, normalize_config
4
+ from sef.api.decorators import analyzer, cleaner, frame_extractor, processor, signal_extractor, visualizer
5
+ from sef.api.orchestrator import OrchestratorFacade, orchestrator
6
+ from sef.api.pipeline import PipelineFacade, from_config, pipeline, video, webcam
7
+ from sef.api.registry import default_registry, register_user_plugin
8
+
9
+ __all__ = [
10
+ "OrchestratorFacade",
11
+ "PipelineFacade",
12
+ "analyzer",
13
+ "cleaner",
14
+ "default_registry",
15
+ "frame_extractor",
16
+ "from_config",
17
+ "load_config",
18
+ "normalize_config",
19
+ "orchestrator",
20
+ "pipeline",
21
+ "processor",
22
+ "register_user_plugin",
23
+ "signal_extractor",
24
+ "video",
25
+ "visualizer",
26
+ "webcam",
27
+ ]
sef/api/config.py ADDED
@@ -0,0 +1,159 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from pathlib import Path
5
+ from typing import Any, Mapping
6
+
7
+ from sef.core.pipeline.PipelineConfigVersioning import normalize_pipeline_config
8
+ from sef.core.pipeline.PipelineErrors import ConfigSchemaError
9
+
10
+ _PIPELINE_STAGE_KEYS = frozenset(
11
+ {
12
+ "frame_extractor",
13
+ "frame_processors",
14
+ "frame_cleaners",
15
+ "signal_extractor",
16
+ "signal_cleaners",
17
+ "analyzers",
18
+ "visualizers",
19
+ "intermediate_frames",
20
+ "runtime",
21
+ }
22
+ )
23
+ _FRAME_SOURCE_CONFIG_KEYS = ("resize", "stride", "max_frames")
24
+ _TRACKING_SIGNAL_EXTRACTORS = frozenset(
25
+ {
26
+ "opencv_tracker",
27
+ "opencv_stream_tracker",
28
+ "opencv_multi_tracker",
29
+ }
30
+ )
31
+
32
+
33
+ def load_config(path: str | Path) -> dict[str, Any]:
34
+ """
35
+ Load a SEF pipeline config from JSON or YAML and return the canonical schema.
36
+
37
+ The returned mapping is safe to pass to ``sef.from_config`` or
38
+ ``ConfigPipelineBuilder``. YAML support intentionally lives here instead of
39
+ in the execution core so the runtime remains independent from file formats.
40
+ """
41
+ config_path = Path(path)
42
+ raw_text = config_path.read_text(encoding="utf-8")
43
+ suffix = config_path.suffix.lower()
44
+ if suffix == ".json":
45
+ loaded = json.loads(raw_text)
46
+ elif suffix in {".yaml", ".yml"}:
47
+ loaded = _load_yaml(raw_text, config_path)
48
+ else:
49
+ raise ConfigSchemaError(
50
+ f"Unsupported config file extension '{config_path.suffix}'. Use .json, .yaml, or .yml.",
51
+ path=str(config_path),
52
+ )
53
+ return normalize_config(loaded)
54
+
55
+
56
+ def normalize_config(config: Mapping[str, Any]) -> dict[str, Any]:
57
+ """
58
+ Normalize user-facing config shapes into the public SEF schema.
59
+
60
+ This is the single compatibility entry point for CLI, UI adapters, and
61
+ facade-based config execution. It accepts both a full top-level config and a
62
+ bare pipeline section, then applies public schema version normalization.
63
+ """
64
+ root = _ensure_root_config(config)
65
+ versioned = normalize_pipeline_config(root).source_config()
66
+ pipeline = dict(versioned["pipeline"])
67
+ _move_frame_source_options(pipeline)
68
+ _inject_frame_source_path(pipeline)
69
+ versioned["pipeline"] = pipeline
70
+ return versioned
71
+
72
+
73
+ def _load_yaml(raw_text: str, config_path: Path) -> Any:
74
+ try:
75
+ import yaml
76
+ except ImportError as exc: # pragma: no cover - exercised only in broken installs
77
+ raise ConfigSchemaError(
78
+ "YAML config loading requires PyYAML. Install SEF with its declared runtime dependencies.",
79
+ path=str(config_path),
80
+ cause=exc,
81
+ ) from exc
82
+
83
+ return yaml.safe_load(raw_text)
84
+
85
+
86
+ def _ensure_root_config(config: Mapping[str, Any]) -> dict[str, Any]:
87
+ if not isinstance(config, Mapping):
88
+ raise ConfigSchemaError("'config' must be a mapping.", path="config")
89
+ root = dict(config)
90
+ if "pipeline" in root:
91
+ return root
92
+ if _PIPELINE_STAGE_KEYS.intersection(root):
93
+ return {"pipeline": root}
94
+ raise ConfigSchemaError("Missing required config section 'pipeline'.", path="pipeline")
95
+
96
+
97
+ def _move_frame_source_options(pipeline: dict[str, Any]) -> None:
98
+ frame_extractor = pipeline.get("frame_extractor")
99
+ if not isinstance(frame_extractor, Mapping):
100
+ return
101
+
102
+ entry = dict(frame_extractor)
103
+ params = entry.get("params", {})
104
+ if not isinstance(params, Mapping):
105
+ return
106
+
107
+ normalized_params = dict(params)
108
+ source_config = normalized_params.get("config", {})
109
+ normalized_source_config = dict(source_config) if isinstance(source_config, Mapping) else {}
110
+
111
+ moved = False
112
+ for key in _FRAME_SOURCE_CONFIG_KEYS:
113
+ if key in normalized_params:
114
+ normalized_source_config[key] = normalized_params.pop(key)
115
+ moved = True
116
+
117
+ if moved:
118
+ normalized_params["config"] = normalized_source_config
119
+ entry["params"] = normalized_params
120
+ pipeline["frame_extractor"] = entry
121
+
122
+
123
+ def _inject_frame_source_path(pipeline: dict[str, Any]) -> None:
124
+ frame_source_path = _frame_source_path(pipeline)
125
+ if frame_source_path is None:
126
+ return
127
+
128
+ signal_extractor = pipeline.get("signal_extractor")
129
+ if not isinstance(signal_extractor, Mapping):
130
+ return
131
+ extractor_name = str(signal_extractor.get("name", ""))
132
+ if extractor_name not in _TRACKING_SIGNAL_EXTRACTORS:
133
+ return
134
+
135
+ entry = dict(signal_extractor)
136
+ params = entry.get("params", {})
137
+ if not isinstance(params, Mapping):
138
+ return
139
+
140
+ normalized_params = dict(params)
141
+ extractor_config = normalized_params.get("config", {})
142
+ normalized_extractor_config = dict(extractor_config) if isinstance(extractor_config, Mapping) else {}
143
+ normalized_extractor_config.setdefault("source_path", frame_source_path)
144
+ normalized_params["config"] = normalized_extractor_config
145
+ entry["params"] = normalized_params
146
+ pipeline["signal_extractor"] = entry
147
+
148
+
149
+ def _frame_source_path(pipeline: Mapping[str, Any]) -> str | None:
150
+ frame_extractor = pipeline.get("frame_extractor")
151
+ if not isinstance(frame_extractor, Mapping):
152
+ return None
153
+ params = frame_extractor.get("params", {})
154
+ if not isinstance(params, Mapping):
155
+ return None
156
+ raw_path = params.get("path")
157
+ if not isinstance(raw_path, str) or not raw_path:
158
+ return None
159
+ return raw_path
sef/api/decorators.py ADDED
@@ -0,0 +1,143 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable
4
+ from typing import Any
5
+
6
+ from sef.api.function_adapters import (
7
+ FunctionAnalyzer,
8
+ FunctionFrameExtractor,
9
+ FunctionFrameProcessor,
10
+ FunctionSignalCleaner,
11
+ FunctionSignalExtractor,
12
+ FunctionVisualizer,
13
+ )
14
+ from sef.api.registry import register_user_plugin
15
+ from sef.core.plugins import PluginCategory, PluginRegistry
16
+
17
+
18
+ def frame_extractor(
19
+ name: str | Callable[..., Any] | None = None,
20
+ *,
21
+ registry: PluginRegistry | None = None,
22
+ ):
23
+ """Register a function as a frame extractor."""
24
+ return _decorator(
25
+ name,
26
+ registry=registry,
27
+ category=PluginCategory.FRAME_EXTRACTOR,
28
+ factory_builder=lambda function: (lambda **params: FunctionFrameExtractor(function, params)),
29
+ )
30
+
31
+
32
+ def signal_extractor(
33
+ name: str | Callable[..., Any] | None = None,
34
+ *,
35
+ registry: PluginRegistry | None = None,
36
+ ):
37
+ """Register a function as a signal extractor."""
38
+ return _decorator(
39
+ name,
40
+ registry=registry,
41
+ category=PluginCategory.SIGNAL_EXTRACTOR,
42
+ factory_builder=lambda function: (lambda **params: FunctionSignalExtractor(function, params)),
43
+ )
44
+
45
+
46
+ def processor(
47
+ name: str | Callable[..., Any] | None = None,
48
+ *,
49
+ registry: PluginRegistry | None = None,
50
+ accepts_frame: bool = False,
51
+ ):
52
+ """Register a function as a single-frame processor."""
53
+ return _decorator(
54
+ name,
55
+ registry=registry,
56
+ category=PluginCategory.SINGLE_FRAME_PROCESSOR,
57
+ factory_builder=lambda function: (
58
+ lambda accepts_frame=accepts_frame, **params: FunctionFrameProcessor(
59
+ function,
60
+ params,
61
+ accepts_frame=bool(accepts_frame),
62
+ )
63
+ ),
64
+ )
65
+
66
+
67
+ def cleaner(
68
+ name: str | Callable[..., Any] | None = None,
69
+ *,
70
+ registry: PluginRegistry | None = None,
71
+ ):
72
+ """Register a function as a signal cleaner."""
73
+ return _decorator(
74
+ name,
75
+ registry=registry,
76
+ category=PluginCategory.SIGNAL_CLEANER,
77
+ factory_builder=lambda function: (lambda **params: FunctionSignalCleaner(function, params)),
78
+ )
79
+
80
+
81
+ def analyzer(
82
+ name: str | Callable[..., Any] | None = None,
83
+ *,
84
+ registry: PluginRegistry | None = None,
85
+ ):
86
+ """Register a function as an analyzer."""
87
+ return _decorator(
88
+ name,
89
+ registry=registry,
90
+ category=PluginCategory.ANALYZER,
91
+ factory_builder=lambda function: (lambda **params: FunctionAnalyzer(function, params)),
92
+ )
93
+
94
+
95
+ def visualizer(
96
+ name: str | Callable[..., Any] | None = None,
97
+ *,
98
+ registry: PluginRegistry | None = None,
99
+ ):
100
+ """Register a function as a visualizer."""
101
+ return _decorator(
102
+ name,
103
+ registry=registry,
104
+ category=PluginCategory.VISUALIZER,
105
+ factory_builder=lambda function: (lambda **params: FunctionVisualizer(function, params)),
106
+ )
107
+
108
+
109
+ def _decorator(
110
+ name: str | Callable[..., Any] | None,
111
+ *,
112
+ registry: PluginRegistry | None,
113
+ category: PluginCategory,
114
+ factory_builder: Callable[[Callable[..., Any]], Callable[..., Any]],
115
+ ):
116
+ if callable(name) and not isinstance(name, str):
117
+ function = name
118
+ _register(function.__name__, function, registry, category, factory_builder)
119
+ return function
120
+
121
+ def _wrap(function: Callable[..., Any]):
122
+ plugin_name = name or function.__name__
123
+ _register(str(plugin_name), function, registry, category, factory_builder)
124
+ return function
125
+
126
+ return _wrap
127
+
128
+
129
+ def _register(
130
+ name: str,
131
+ function: Callable[..., Any],
132
+ registry: PluginRegistry | None,
133
+ category: PluginCategory,
134
+ factory_builder: Callable[[Callable[..., Any]], Callable[..., Any]],
135
+ ) -> None:
136
+ target = registry.register if registry is not None else register_user_plugin
137
+ target(
138
+ category,
139
+ name,
140
+ factory_builder(function),
141
+ f"Function plugin registered through @{category.value}.",
142
+ metadata={"source": "sef.decorator", "function": f"{function.__module__}.{function.__qualname__}"},
143
+ )
@@ -0,0 +1,129 @@
1
+ from __future__ import annotations
2
+
3
+ import inspect
4
+ from collections.abc import Callable
5
+ from typing import Any
6
+
7
+ from sef.core.artifacts.buffer.FrameBuffer import FrameBuffer
8
+ from sef.core.artifacts.Frame import Frame
9
+ from sef.core.interfaces.IAnalyzer import IAnalyzer
10
+ from sef.core.interfaces.IData import IData
11
+ from sef.core.interfaces.IFrameExtractor import IFrameExtractor
12
+ from sef.core.interfaces.ISignal import ISignal
13
+ from sef.core.interfaces.ISignalCleaner import ISignalCleaner
14
+ from sef.core.interfaces.ISignalExtractor import ISignalExtractor
15
+ from sef.core.interfaces.ISingleFrameProcessor import ISingleFrameProcessor
16
+ from sef.core.interfaces.IVisualizer import IVisualizer
17
+ from sef.core.visualization.VisualArtifact import VisualArtifact
18
+ from sef.core.visualization.VisualizationContext import VisualizationContext
19
+
20
+
21
+ class FunctionFrameExtractor(IFrameExtractor):
22
+ """Adapt a plain callable into an ``IFrameExtractor`` plugin."""
23
+
24
+ def __init__(self, function: Callable[..., FrameBuffer], params: dict[str, Any], config: dict[str, Any] | None = None) -> None:
25
+ super().__init__(config)
26
+ self._function = function
27
+ self._params = dict(params)
28
+
29
+ def extract(self) -> FrameBuffer:
30
+ return self._function(**self._params)
31
+
32
+
33
+ class FunctionSignalExtractor(ISignalExtractor):
34
+ """Adapt a plain callable into an ``ISignalExtractor`` plugin."""
35
+
36
+ def __init__(self, function: Callable[..., ISignal], params: dict[str, Any], config: dict[str, Any] | None = None) -> None:
37
+ super().__init__(config)
38
+ self._function = function
39
+ self._params = dict(params)
40
+
41
+ def extract(self, buffer: FrameBuffer) -> ISignal:
42
+ return self._function(buffer, **self._params)
43
+
44
+
45
+ class FunctionSignalCleaner(ISignalCleaner):
46
+ """Adapt a plain callable into an ``ISignalCleaner`` plugin."""
47
+
48
+ def __init__(self, function: Callable[..., ISignal], params: dict[str, Any], config: dict[str, Any] | None = None) -> None:
49
+ super().__init__(config)
50
+ self._function = function
51
+ self._params = dict(params)
52
+
53
+ def clean(self, signal: ISignal) -> ISignal:
54
+ return self._function(signal, **self._params)
55
+
56
+
57
+ class FunctionAnalyzer(IAnalyzer):
58
+ """Adapt a plain callable into an ``IAnalyzer`` plugin."""
59
+
60
+ def __init__(self, function: Callable[..., IData], params: dict[str, Any], config: dict[str, Any] | None = None) -> None:
61
+ super().__init__(config)
62
+ self._function = function
63
+ self._params = dict(params)
64
+
65
+ def analyze(self, signal: ISignal) -> IData:
66
+ return self._function(signal, **self._params)
67
+
68
+
69
+ class FunctionVisualizer(IVisualizer):
70
+ """Adapt a plain callable into an ``IVisualizer`` plugin."""
71
+
72
+ def __init__(
73
+ self,
74
+ function: Callable[..., VisualArtifact | tuple[VisualArtifact, ...] | list[VisualArtifact]],
75
+ params: dict[str, Any],
76
+ config: dict[str, Any] | None = None,
77
+ ) -> None:
78
+ super().__init__(config)
79
+ self._function = function
80
+ self._params = dict(params)
81
+ self._accepts_context = "context" in inspect.signature(function).parameters
82
+
83
+ def render(
84
+ self,
85
+ data: IData,
86
+ context: VisualizationContext | None = None,
87
+ ) -> tuple[VisualArtifact, ...]:
88
+ if self._accepts_context:
89
+ result = self._function(data, context=context, **self._params)
90
+ else:
91
+ result = self._function(data, **self._params)
92
+ if isinstance(result, VisualArtifact):
93
+ return (result,)
94
+ return tuple(result)
95
+
96
+
97
+ class FunctionFrameProcessor(ISingleFrameProcessor):
98
+ """
99
+ Adapt a plain callable into a single-frame processor.
100
+
101
+ By default the callable receives the frame image and may return either a
102
+ new image array or a complete ``Frame``. Set ``accepts_frame=True`` when
103
+ the callable needs access to indexes, timestamps, or metadata.
104
+ """
105
+
106
+ def __init__(
107
+ self,
108
+ function: Callable[..., Any],
109
+ params: dict[str, Any],
110
+ *,
111
+ accepts_frame: bool = False,
112
+ config: dict[str, Any] | None = None,
113
+ ) -> None:
114
+ super().__init__(config)
115
+ self._function = function
116
+ self._params = dict(params)
117
+ self._accepts_frame = accepts_frame
118
+
119
+ def process(self, frame: Frame) -> Frame:
120
+ source = frame if self._accepts_frame else frame.image
121
+ result = self._function(source, **self._params)
122
+ if isinstance(result, Frame):
123
+ return result
124
+ return Frame(
125
+ image=result,
126
+ index=frame.index,
127
+ timestamp_seconds=frame.timestamp_seconds,
128
+ metadata=dict(frame.metadata),
129
+ )