oscura 0.0.1__py3-none-any.whl → 0.1.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 (465) hide show
  1. oscura/__init__.py +813 -8
  2. oscura/__main__.py +392 -0
  3. oscura/analyzers/__init__.py +37 -0
  4. oscura/analyzers/digital/__init__.py +177 -0
  5. oscura/analyzers/digital/bus.py +691 -0
  6. oscura/analyzers/digital/clock.py +805 -0
  7. oscura/analyzers/digital/correlation.py +720 -0
  8. oscura/analyzers/digital/edges.py +632 -0
  9. oscura/analyzers/digital/extraction.py +413 -0
  10. oscura/analyzers/digital/quality.py +878 -0
  11. oscura/analyzers/digital/signal_quality.py +877 -0
  12. oscura/analyzers/digital/thresholds.py +708 -0
  13. oscura/analyzers/digital/timing.py +1104 -0
  14. oscura/analyzers/eye/__init__.py +46 -0
  15. oscura/analyzers/eye/diagram.py +434 -0
  16. oscura/analyzers/eye/metrics.py +555 -0
  17. oscura/analyzers/jitter/__init__.py +83 -0
  18. oscura/analyzers/jitter/ber.py +333 -0
  19. oscura/analyzers/jitter/decomposition.py +759 -0
  20. oscura/analyzers/jitter/measurements.py +413 -0
  21. oscura/analyzers/jitter/spectrum.py +220 -0
  22. oscura/analyzers/measurements.py +40 -0
  23. oscura/analyzers/packet/__init__.py +171 -0
  24. oscura/analyzers/packet/daq.py +1077 -0
  25. oscura/analyzers/packet/metrics.py +437 -0
  26. oscura/analyzers/packet/parser.py +327 -0
  27. oscura/analyzers/packet/payload.py +2156 -0
  28. oscura/analyzers/packet/payload_analysis.py +1312 -0
  29. oscura/analyzers/packet/payload_extraction.py +236 -0
  30. oscura/analyzers/packet/payload_patterns.py +670 -0
  31. oscura/analyzers/packet/stream.py +359 -0
  32. oscura/analyzers/patterns/__init__.py +266 -0
  33. oscura/analyzers/patterns/clustering.py +1036 -0
  34. oscura/analyzers/patterns/discovery.py +539 -0
  35. oscura/analyzers/patterns/learning.py +797 -0
  36. oscura/analyzers/patterns/matching.py +1091 -0
  37. oscura/analyzers/patterns/periodic.py +650 -0
  38. oscura/analyzers/patterns/sequences.py +767 -0
  39. oscura/analyzers/power/__init__.py +116 -0
  40. oscura/analyzers/power/ac_power.py +391 -0
  41. oscura/analyzers/power/basic.py +383 -0
  42. oscura/analyzers/power/conduction.py +314 -0
  43. oscura/analyzers/power/efficiency.py +297 -0
  44. oscura/analyzers/power/ripple.py +356 -0
  45. oscura/analyzers/power/soa.py +372 -0
  46. oscura/analyzers/power/switching.py +479 -0
  47. oscura/analyzers/protocol/__init__.py +150 -0
  48. oscura/analyzers/protocols/__init__.py +150 -0
  49. oscura/analyzers/protocols/base.py +500 -0
  50. oscura/analyzers/protocols/can.py +620 -0
  51. oscura/analyzers/protocols/can_fd.py +448 -0
  52. oscura/analyzers/protocols/flexray.py +405 -0
  53. oscura/analyzers/protocols/hdlc.py +399 -0
  54. oscura/analyzers/protocols/i2c.py +368 -0
  55. oscura/analyzers/protocols/i2s.py +296 -0
  56. oscura/analyzers/protocols/jtag.py +393 -0
  57. oscura/analyzers/protocols/lin.py +445 -0
  58. oscura/analyzers/protocols/manchester.py +333 -0
  59. oscura/analyzers/protocols/onewire.py +501 -0
  60. oscura/analyzers/protocols/spi.py +334 -0
  61. oscura/analyzers/protocols/swd.py +325 -0
  62. oscura/analyzers/protocols/uart.py +393 -0
  63. oscura/analyzers/protocols/usb.py +495 -0
  64. oscura/analyzers/signal_integrity/__init__.py +63 -0
  65. oscura/analyzers/signal_integrity/embedding.py +294 -0
  66. oscura/analyzers/signal_integrity/equalization.py +370 -0
  67. oscura/analyzers/signal_integrity/sparams.py +484 -0
  68. oscura/analyzers/spectral/__init__.py +53 -0
  69. oscura/analyzers/spectral/chunked.py +273 -0
  70. oscura/analyzers/spectral/chunked_fft.py +571 -0
  71. oscura/analyzers/spectral/chunked_wavelet.py +391 -0
  72. oscura/analyzers/spectral/fft.py +92 -0
  73. oscura/analyzers/statistical/__init__.py +250 -0
  74. oscura/analyzers/statistical/checksum.py +923 -0
  75. oscura/analyzers/statistical/chunked_corr.py +228 -0
  76. oscura/analyzers/statistical/classification.py +778 -0
  77. oscura/analyzers/statistical/entropy.py +1113 -0
  78. oscura/analyzers/statistical/ngrams.py +614 -0
  79. oscura/analyzers/statistics/__init__.py +119 -0
  80. oscura/analyzers/statistics/advanced.py +885 -0
  81. oscura/analyzers/statistics/basic.py +263 -0
  82. oscura/analyzers/statistics/correlation.py +630 -0
  83. oscura/analyzers/statistics/distribution.py +298 -0
  84. oscura/analyzers/statistics/outliers.py +463 -0
  85. oscura/analyzers/statistics/streaming.py +93 -0
  86. oscura/analyzers/statistics/trend.py +520 -0
  87. oscura/analyzers/validation.py +598 -0
  88. oscura/analyzers/waveform/__init__.py +36 -0
  89. oscura/analyzers/waveform/measurements.py +943 -0
  90. oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
  91. oscura/analyzers/waveform/spectral.py +1689 -0
  92. oscura/analyzers/waveform/wavelets.py +298 -0
  93. oscura/api/__init__.py +62 -0
  94. oscura/api/dsl.py +538 -0
  95. oscura/api/fluent.py +571 -0
  96. oscura/api/operators.py +498 -0
  97. oscura/api/optimization.py +392 -0
  98. oscura/api/profiling.py +396 -0
  99. oscura/automotive/__init__.py +73 -0
  100. oscura/automotive/can/__init__.py +52 -0
  101. oscura/automotive/can/analysis.py +356 -0
  102. oscura/automotive/can/checksum.py +250 -0
  103. oscura/automotive/can/correlation.py +212 -0
  104. oscura/automotive/can/discovery.py +355 -0
  105. oscura/automotive/can/message_wrapper.py +375 -0
  106. oscura/automotive/can/models.py +385 -0
  107. oscura/automotive/can/patterns.py +381 -0
  108. oscura/automotive/can/session.py +452 -0
  109. oscura/automotive/can/state_machine.py +300 -0
  110. oscura/automotive/can/stimulus_response.py +461 -0
  111. oscura/automotive/dbc/__init__.py +15 -0
  112. oscura/automotive/dbc/generator.py +156 -0
  113. oscura/automotive/dbc/parser.py +146 -0
  114. oscura/automotive/dtc/__init__.py +30 -0
  115. oscura/automotive/dtc/database.py +3036 -0
  116. oscura/automotive/j1939/__init__.py +14 -0
  117. oscura/automotive/j1939/decoder.py +745 -0
  118. oscura/automotive/loaders/__init__.py +35 -0
  119. oscura/automotive/loaders/asc.py +98 -0
  120. oscura/automotive/loaders/blf.py +77 -0
  121. oscura/automotive/loaders/csv_can.py +136 -0
  122. oscura/automotive/loaders/dispatcher.py +136 -0
  123. oscura/automotive/loaders/mdf.py +331 -0
  124. oscura/automotive/loaders/pcap.py +132 -0
  125. oscura/automotive/obd/__init__.py +14 -0
  126. oscura/automotive/obd/decoder.py +707 -0
  127. oscura/automotive/uds/__init__.py +48 -0
  128. oscura/automotive/uds/decoder.py +265 -0
  129. oscura/automotive/uds/models.py +64 -0
  130. oscura/automotive/visualization.py +369 -0
  131. oscura/batch/__init__.py +55 -0
  132. oscura/batch/advanced.py +627 -0
  133. oscura/batch/aggregate.py +300 -0
  134. oscura/batch/analyze.py +139 -0
  135. oscura/batch/logging.py +487 -0
  136. oscura/batch/metrics.py +556 -0
  137. oscura/builders/__init__.py +41 -0
  138. oscura/builders/signal_builder.py +1131 -0
  139. oscura/cli/__init__.py +14 -0
  140. oscura/cli/batch.py +339 -0
  141. oscura/cli/characterize.py +273 -0
  142. oscura/cli/compare.py +775 -0
  143. oscura/cli/decode.py +551 -0
  144. oscura/cli/main.py +247 -0
  145. oscura/cli/shell.py +350 -0
  146. oscura/comparison/__init__.py +66 -0
  147. oscura/comparison/compare.py +397 -0
  148. oscura/comparison/golden.py +487 -0
  149. oscura/comparison/limits.py +391 -0
  150. oscura/comparison/mask.py +434 -0
  151. oscura/comparison/trace_diff.py +30 -0
  152. oscura/comparison/visualization.py +481 -0
  153. oscura/compliance/__init__.py +70 -0
  154. oscura/compliance/advanced.py +756 -0
  155. oscura/compliance/masks.py +363 -0
  156. oscura/compliance/reporting.py +483 -0
  157. oscura/compliance/testing.py +298 -0
  158. oscura/component/__init__.py +38 -0
  159. oscura/component/impedance.py +365 -0
  160. oscura/component/reactive.py +598 -0
  161. oscura/component/transmission_line.py +312 -0
  162. oscura/config/__init__.py +191 -0
  163. oscura/config/defaults.py +254 -0
  164. oscura/config/loader.py +348 -0
  165. oscura/config/memory.py +271 -0
  166. oscura/config/migration.py +458 -0
  167. oscura/config/pipeline.py +1077 -0
  168. oscura/config/preferences.py +530 -0
  169. oscura/config/protocol.py +875 -0
  170. oscura/config/schema.py +713 -0
  171. oscura/config/settings.py +420 -0
  172. oscura/config/thresholds.py +599 -0
  173. oscura/convenience.py +457 -0
  174. oscura/core/__init__.py +299 -0
  175. oscura/core/audit.py +457 -0
  176. oscura/core/backend_selector.py +405 -0
  177. oscura/core/cache.py +590 -0
  178. oscura/core/cancellation.py +439 -0
  179. oscura/core/confidence.py +225 -0
  180. oscura/core/config.py +506 -0
  181. oscura/core/correlation.py +216 -0
  182. oscura/core/cross_domain.py +422 -0
  183. oscura/core/debug.py +301 -0
  184. oscura/core/edge_cases.py +541 -0
  185. oscura/core/exceptions.py +535 -0
  186. oscura/core/gpu_backend.py +523 -0
  187. oscura/core/lazy.py +832 -0
  188. oscura/core/log_query.py +540 -0
  189. oscura/core/logging.py +931 -0
  190. oscura/core/logging_advanced.py +952 -0
  191. oscura/core/memoize.py +171 -0
  192. oscura/core/memory_check.py +274 -0
  193. oscura/core/memory_guard.py +290 -0
  194. oscura/core/memory_limits.py +336 -0
  195. oscura/core/memory_monitor.py +453 -0
  196. oscura/core/memory_progress.py +465 -0
  197. oscura/core/memory_warnings.py +315 -0
  198. oscura/core/numba_backend.py +362 -0
  199. oscura/core/performance.py +352 -0
  200. oscura/core/progress.py +524 -0
  201. oscura/core/provenance.py +358 -0
  202. oscura/core/results.py +331 -0
  203. oscura/core/types.py +504 -0
  204. oscura/core/uncertainty.py +383 -0
  205. oscura/discovery/__init__.py +52 -0
  206. oscura/discovery/anomaly_detector.py +672 -0
  207. oscura/discovery/auto_decoder.py +415 -0
  208. oscura/discovery/comparison.py +497 -0
  209. oscura/discovery/quality_validator.py +528 -0
  210. oscura/discovery/signal_detector.py +769 -0
  211. oscura/dsl/__init__.py +73 -0
  212. oscura/dsl/commands.py +246 -0
  213. oscura/dsl/interpreter.py +455 -0
  214. oscura/dsl/parser.py +689 -0
  215. oscura/dsl/repl.py +172 -0
  216. oscura/exceptions.py +59 -0
  217. oscura/exploratory/__init__.py +111 -0
  218. oscura/exploratory/error_recovery.py +642 -0
  219. oscura/exploratory/fuzzy.py +513 -0
  220. oscura/exploratory/fuzzy_advanced.py +786 -0
  221. oscura/exploratory/legacy.py +831 -0
  222. oscura/exploratory/parse.py +358 -0
  223. oscura/exploratory/recovery.py +275 -0
  224. oscura/exploratory/sync.py +382 -0
  225. oscura/exploratory/unknown.py +707 -0
  226. oscura/export/__init__.py +25 -0
  227. oscura/export/wireshark/README.md +265 -0
  228. oscura/export/wireshark/__init__.py +47 -0
  229. oscura/export/wireshark/generator.py +312 -0
  230. oscura/export/wireshark/lua_builder.py +159 -0
  231. oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
  232. oscura/export/wireshark/type_mapping.py +165 -0
  233. oscura/export/wireshark/validator.py +105 -0
  234. oscura/exporters/__init__.py +94 -0
  235. oscura/exporters/csv.py +303 -0
  236. oscura/exporters/exporters.py +44 -0
  237. oscura/exporters/hdf5.py +219 -0
  238. oscura/exporters/html_export.py +701 -0
  239. oscura/exporters/json_export.py +291 -0
  240. oscura/exporters/markdown_export.py +367 -0
  241. oscura/exporters/matlab_export.py +354 -0
  242. oscura/exporters/npz_export.py +219 -0
  243. oscura/exporters/spice_export.py +210 -0
  244. oscura/extensibility/__init__.py +131 -0
  245. oscura/extensibility/docs.py +752 -0
  246. oscura/extensibility/extensions.py +1125 -0
  247. oscura/extensibility/logging.py +259 -0
  248. oscura/extensibility/measurements.py +485 -0
  249. oscura/extensibility/plugins.py +414 -0
  250. oscura/extensibility/registry.py +346 -0
  251. oscura/extensibility/templates.py +913 -0
  252. oscura/extensibility/validation.py +651 -0
  253. oscura/filtering/__init__.py +89 -0
  254. oscura/filtering/base.py +563 -0
  255. oscura/filtering/convenience.py +564 -0
  256. oscura/filtering/design.py +725 -0
  257. oscura/filtering/filters.py +32 -0
  258. oscura/filtering/introspection.py +605 -0
  259. oscura/guidance/__init__.py +24 -0
  260. oscura/guidance/recommender.py +429 -0
  261. oscura/guidance/wizard.py +518 -0
  262. oscura/inference/__init__.py +251 -0
  263. oscura/inference/active_learning/README.md +153 -0
  264. oscura/inference/active_learning/__init__.py +38 -0
  265. oscura/inference/active_learning/lstar.py +257 -0
  266. oscura/inference/active_learning/observation_table.py +230 -0
  267. oscura/inference/active_learning/oracle.py +78 -0
  268. oscura/inference/active_learning/teachers/__init__.py +15 -0
  269. oscura/inference/active_learning/teachers/simulator.py +192 -0
  270. oscura/inference/adaptive_tuning.py +453 -0
  271. oscura/inference/alignment.py +653 -0
  272. oscura/inference/bayesian.py +943 -0
  273. oscura/inference/binary.py +1016 -0
  274. oscura/inference/crc_reverse.py +711 -0
  275. oscura/inference/logic.py +288 -0
  276. oscura/inference/message_format.py +1305 -0
  277. oscura/inference/protocol.py +417 -0
  278. oscura/inference/protocol_dsl.py +1084 -0
  279. oscura/inference/protocol_library.py +1230 -0
  280. oscura/inference/sequences.py +809 -0
  281. oscura/inference/signal_intelligence.py +1509 -0
  282. oscura/inference/spectral.py +215 -0
  283. oscura/inference/state_machine.py +634 -0
  284. oscura/inference/stream.py +918 -0
  285. oscura/integrations/__init__.py +59 -0
  286. oscura/integrations/llm.py +1827 -0
  287. oscura/jupyter/__init__.py +32 -0
  288. oscura/jupyter/display.py +268 -0
  289. oscura/jupyter/magic.py +334 -0
  290. oscura/loaders/__init__.py +526 -0
  291. oscura/loaders/binary.py +69 -0
  292. oscura/loaders/configurable.py +1255 -0
  293. oscura/loaders/csv.py +26 -0
  294. oscura/loaders/csv_loader.py +473 -0
  295. oscura/loaders/hdf5.py +9 -0
  296. oscura/loaders/hdf5_loader.py +510 -0
  297. oscura/loaders/lazy.py +370 -0
  298. oscura/loaders/mmap_loader.py +583 -0
  299. oscura/loaders/numpy_loader.py +436 -0
  300. oscura/loaders/pcap.py +432 -0
  301. oscura/loaders/preprocessing.py +368 -0
  302. oscura/loaders/rigol.py +287 -0
  303. oscura/loaders/sigrok.py +321 -0
  304. oscura/loaders/tdms.py +367 -0
  305. oscura/loaders/tektronix.py +711 -0
  306. oscura/loaders/validation.py +584 -0
  307. oscura/loaders/vcd.py +464 -0
  308. oscura/loaders/wav.py +233 -0
  309. oscura/math/__init__.py +45 -0
  310. oscura/math/arithmetic.py +824 -0
  311. oscura/math/interpolation.py +413 -0
  312. oscura/onboarding/__init__.py +39 -0
  313. oscura/onboarding/help.py +498 -0
  314. oscura/onboarding/tutorials.py +405 -0
  315. oscura/onboarding/wizard.py +466 -0
  316. oscura/optimization/__init__.py +19 -0
  317. oscura/optimization/parallel.py +440 -0
  318. oscura/optimization/search.py +532 -0
  319. oscura/pipeline/__init__.py +43 -0
  320. oscura/pipeline/base.py +338 -0
  321. oscura/pipeline/composition.py +242 -0
  322. oscura/pipeline/parallel.py +448 -0
  323. oscura/pipeline/pipeline.py +375 -0
  324. oscura/pipeline/reverse_engineering.py +1119 -0
  325. oscura/plugins/__init__.py +122 -0
  326. oscura/plugins/base.py +272 -0
  327. oscura/plugins/cli.py +497 -0
  328. oscura/plugins/discovery.py +411 -0
  329. oscura/plugins/isolation.py +418 -0
  330. oscura/plugins/lifecycle.py +959 -0
  331. oscura/plugins/manager.py +493 -0
  332. oscura/plugins/registry.py +421 -0
  333. oscura/plugins/versioning.py +372 -0
  334. oscura/py.typed +0 -0
  335. oscura/quality/__init__.py +65 -0
  336. oscura/quality/ensemble.py +740 -0
  337. oscura/quality/explainer.py +338 -0
  338. oscura/quality/scoring.py +616 -0
  339. oscura/quality/warnings.py +456 -0
  340. oscura/reporting/__init__.py +248 -0
  341. oscura/reporting/advanced.py +1234 -0
  342. oscura/reporting/analyze.py +448 -0
  343. oscura/reporting/argument_preparer.py +596 -0
  344. oscura/reporting/auto_report.py +507 -0
  345. oscura/reporting/batch.py +615 -0
  346. oscura/reporting/chart_selection.py +223 -0
  347. oscura/reporting/comparison.py +330 -0
  348. oscura/reporting/config.py +615 -0
  349. oscura/reporting/content/__init__.py +39 -0
  350. oscura/reporting/content/executive.py +127 -0
  351. oscura/reporting/content/filtering.py +191 -0
  352. oscura/reporting/content/minimal.py +257 -0
  353. oscura/reporting/content/verbosity.py +162 -0
  354. oscura/reporting/core.py +508 -0
  355. oscura/reporting/core_formats/__init__.py +17 -0
  356. oscura/reporting/core_formats/multi_format.py +210 -0
  357. oscura/reporting/engine.py +836 -0
  358. oscura/reporting/export.py +366 -0
  359. oscura/reporting/formatting/__init__.py +129 -0
  360. oscura/reporting/formatting/emphasis.py +81 -0
  361. oscura/reporting/formatting/numbers.py +403 -0
  362. oscura/reporting/formatting/standards.py +55 -0
  363. oscura/reporting/formatting.py +466 -0
  364. oscura/reporting/html.py +578 -0
  365. oscura/reporting/index.py +590 -0
  366. oscura/reporting/multichannel.py +296 -0
  367. oscura/reporting/output.py +379 -0
  368. oscura/reporting/pdf.py +373 -0
  369. oscura/reporting/plots.py +731 -0
  370. oscura/reporting/pptx_export.py +360 -0
  371. oscura/reporting/renderers/__init__.py +11 -0
  372. oscura/reporting/renderers/pdf.py +94 -0
  373. oscura/reporting/sections.py +471 -0
  374. oscura/reporting/standards.py +680 -0
  375. oscura/reporting/summary_generator.py +368 -0
  376. oscura/reporting/tables.py +397 -0
  377. oscura/reporting/template_system.py +724 -0
  378. oscura/reporting/templates/__init__.py +15 -0
  379. oscura/reporting/templates/definition.py +205 -0
  380. oscura/reporting/templates/index.html +649 -0
  381. oscura/reporting/templates/index.md +173 -0
  382. oscura/schemas/__init__.py +158 -0
  383. oscura/schemas/bus_configuration.json +322 -0
  384. oscura/schemas/device_mapping.json +182 -0
  385. oscura/schemas/packet_format.json +418 -0
  386. oscura/schemas/protocol_definition.json +363 -0
  387. oscura/search/__init__.py +16 -0
  388. oscura/search/anomaly.py +292 -0
  389. oscura/search/context.py +149 -0
  390. oscura/search/pattern.py +160 -0
  391. oscura/session/__init__.py +34 -0
  392. oscura/session/annotations.py +289 -0
  393. oscura/session/history.py +313 -0
  394. oscura/session/session.py +445 -0
  395. oscura/streaming/__init__.py +43 -0
  396. oscura/streaming/chunked.py +611 -0
  397. oscura/streaming/progressive.py +393 -0
  398. oscura/streaming/realtime.py +622 -0
  399. oscura/testing/__init__.py +54 -0
  400. oscura/testing/synthetic.py +808 -0
  401. oscura/triggering/__init__.py +68 -0
  402. oscura/triggering/base.py +229 -0
  403. oscura/triggering/edge.py +353 -0
  404. oscura/triggering/pattern.py +344 -0
  405. oscura/triggering/pulse.py +581 -0
  406. oscura/triggering/window.py +453 -0
  407. oscura/ui/__init__.py +48 -0
  408. oscura/ui/formatters.py +526 -0
  409. oscura/ui/progressive_display.py +340 -0
  410. oscura/utils/__init__.py +99 -0
  411. oscura/utils/autodetect.py +338 -0
  412. oscura/utils/buffer.py +389 -0
  413. oscura/utils/lazy.py +407 -0
  414. oscura/utils/lazy_imports.py +147 -0
  415. oscura/utils/memory.py +836 -0
  416. oscura/utils/memory_advanced.py +1326 -0
  417. oscura/utils/memory_extensions.py +465 -0
  418. oscura/utils/progressive.py +352 -0
  419. oscura/utils/windowing.py +362 -0
  420. oscura/visualization/__init__.py +321 -0
  421. oscura/visualization/accessibility.py +526 -0
  422. oscura/visualization/annotations.py +374 -0
  423. oscura/visualization/axis_scaling.py +305 -0
  424. oscura/visualization/colors.py +453 -0
  425. oscura/visualization/digital.py +337 -0
  426. oscura/visualization/eye.py +420 -0
  427. oscura/visualization/histogram.py +281 -0
  428. oscura/visualization/interactive.py +858 -0
  429. oscura/visualization/jitter.py +702 -0
  430. oscura/visualization/keyboard.py +394 -0
  431. oscura/visualization/layout.py +365 -0
  432. oscura/visualization/optimization.py +1028 -0
  433. oscura/visualization/palettes.py +446 -0
  434. oscura/visualization/plot.py +92 -0
  435. oscura/visualization/power.py +290 -0
  436. oscura/visualization/power_extended.py +626 -0
  437. oscura/visualization/presets.py +467 -0
  438. oscura/visualization/protocols.py +932 -0
  439. oscura/visualization/render.py +207 -0
  440. oscura/visualization/rendering.py +444 -0
  441. oscura/visualization/reverse_engineering.py +791 -0
  442. oscura/visualization/signal_integrity.py +808 -0
  443. oscura/visualization/specialized.py +553 -0
  444. oscura/visualization/spectral.py +811 -0
  445. oscura/visualization/styles.py +381 -0
  446. oscura/visualization/thumbnails.py +311 -0
  447. oscura/visualization/time_axis.py +351 -0
  448. oscura/visualization/waveform.py +367 -0
  449. oscura/workflow/__init__.py +13 -0
  450. oscura/workflow/dag.py +377 -0
  451. oscura/workflows/__init__.py +58 -0
  452. oscura/workflows/compliance.py +280 -0
  453. oscura/workflows/digital.py +272 -0
  454. oscura/workflows/multi_trace.py +502 -0
  455. oscura/workflows/power.py +178 -0
  456. oscura/workflows/protocol.py +492 -0
  457. oscura/workflows/reverse_engineering.py +639 -0
  458. oscura/workflows/signal_integrity.py +227 -0
  459. oscura-0.1.0.dist-info/METADATA +300 -0
  460. oscura-0.1.0.dist-info/RECORD +463 -0
  461. oscura-0.1.0.dist-info/entry_points.txt +2 -0
  462. {oscura-0.0.1.dist-info → oscura-0.1.0.dist-info}/licenses/LICENSE +1 -1
  463. oscura-0.0.1.dist-info/METADATA +0 -63
  464. oscura-0.0.1.dist-info/RECORD +0 -5
  465. {oscura-0.0.1.dist-info → oscura-0.1.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,1125 @@
1
+ """Extension point registry and management system.
2
+
3
+ This module implements a central registry for extension points that allows
4
+ plugins and custom code to extend TraceKit functionality at well-defined
5
+ integration points.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import logging
11
+ from dataclasses import dataclass, field
12
+ from enum import Enum, auto
13
+ from typing import TYPE_CHECKING, Any, TypeVar
14
+
15
+ if TYPE_CHECKING:
16
+ from collections.abc import Callable
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+ T = TypeVar("T")
21
+
22
+
23
+ class HookErrorPolicy(Enum):
24
+ """Policy for handling hook errors.
25
+
26
+ Attributes:
27
+ CONTINUE: Continue executing remaining hooks after error
28
+ ABORT: Stop execution immediately on error
29
+ IGNORE: Ignore error silently
30
+ """
31
+
32
+ CONTINUE = auto()
33
+ ABORT = auto()
34
+ IGNORE = auto()
35
+
36
+
37
+ @dataclass
38
+ class ExtensionPointSpec:
39
+ """Specification for an extension point.
40
+
41
+ Defines the contract that implementations must follow including
42
+ required and optional methods, version info, and documentation.
43
+
44
+ Attributes:
45
+ name: Unique name for the extension point
46
+ version: API version (semver format)
47
+ description: Human-readable description
48
+ required_methods: List of method names that must be implemented
49
+ optional_methods: List of optional method names
50
+ interface: Optional interface class that implementations should inherit from
51
+
52
+ Example:
53
+ >>> spec = ExtensionPointSpec(
54
+ ... name="protocol_decoder",
55
+ ... version="1.0.0",
56
+ ... description="Decode protocol from waveform",
57
+ ... required_methods=["decode", "get_metadata"],
58
+ ... optional_methods=["configure", "reset"]
59
+ ... )
60
+
61
+ References:
62
+ EXT-001: Extension Point Registry
63
+ """
64
+
65
+ name: str
66
+ version: str = "1.0.0"
67
+ description: str = ""
68
+ required_methods: list[str] = field(default_factory=list)
69
+ optional_methods: list[str] = field(default_factory=list)
70
+ interface: type | None = None
71
+
72
+ def validate_implementation(self, impl: Any) -> tuple[bool, list[str]]:
73
+ """Validate that implementation matches interface.
74
+
75
+ Args:
76
+ impl: Implementation to validate
77
+
78
+ Returns:
79
+ Tuple of (is_valid, list of missing methods)
80
+
81
+ Example:
82
+ >>> is_valid, missing = spec.validate_implementation(MyDecoder())
83
+ >>> if not is_valid:
84
+ ... print(f"Missing methods: {missing}")
85
+ """
86
+ missing = []
87
+ for method in self.required_methods:
88
+ if not hasattr(impl, method) or not callable(getattr(impl, method)):
89
+ missing.append(method)
90
+ return len(missing) == 0, missing
91
+
92
+
93
+ @dataclass
94
+ class RegisteredAlgorithm:
95
+ """Metadata for a registered algorithm.
96
+
97
+ Attributes:
98
+ name: Algorithm name
99
+ category: Algorithm category
100
+ func: Algorithm implementation
101
+ priority: Execution priority (higher = first)
102
+ performance: Performance characteristics
103
+ supports: Supported data types
104
+ description: Human-readable description
105
+ complexity: Time complexity string
106
+ capabilities: Algorithm capabilities
107
+ memory_usage: Memory usage characteristics
108
+ registration_order: Order in which algorithm was registered
109
+
110
+ References:
111
+ EXT-002: Algorithm Registration (capability queries, performance metadata)
112
+ EXT-004: Priority System (registration order for tie-breaking)
113
+ """
114
+
115
+ name: str
116
+ category: str
117
+ func: Callable[..., Any]
118
+ priority: int = 50
119
+ performance: dict[str, str] = field(default_factory=dict)
120
+ supports: list[str] = field(default_factory=list)
121
+ description: str = ""
122
+ complexity: str = "O(n)"
123
+ capabilities: dict[str, Any] = field(default_factory=dict)
124
+ memory_usage: str = "unknown"
125
+ registration_order: int = 0
126
+
127
+ def can(self, capability: str) -> bool:
128
+ """Check if algorithm has a specific capability.
129
+
130
+ Args:
131
+ capability: Capability name to check
132
+
133
+ Returns:
134
+ True if algorithm supports the capability
135
+
136
+ Example:
137
+ >>> algo.can("multi_channel")
138
+ True
139
+
140
+ References:
141
+ EXT-002: Algorithm Registration (capability queries)
142
+ """
143
+ return self.capabilities.get(capability, False) # type: ignore[no-any-return]
144
+
145
+ def get_capabilities(self) -> dict[str, Any]:
146
+ """Get all capabilities of this algorithm.
147
+
148
+ Returns:
149
+ Dictionary of capability names to values
150
+
151
+ Example:
152
+ >>> caps = algo.get_capabilities()
153
+ >>> print(caps)
154
+ {'multi_channel': True, 'real_time': False, 'max_sample_rate': 1000000}
155
+
156
+ References:
157
+ EXT-002: Algorithm Registration (capability queries)
158
+ """
159
+ return self.capabilities.copy()
160
+
161
+
162
+ @dataclass
163
+ class HookContext:
164
+ """Context passed to hook functions.
165
+
166
+ Attributes:
167
+ data: Primary data being processed
168
+ metadata: Additional context metadata
169
+ abort: Set to True to abort operation
170
+ abort_reason: Reason for abort
171
+
172
+ Example:
173
+ >>> @tk.hooks.register("pre_decode")
174
+ >>> def validate_waveform(context):
175
+ ... if context.data.sample_rate < 1000:
176
+ ... context.abort = True
177
+ ... context.abort_reason = "Sample rate too low"
178
+ ... return context
179
+
180
+ References:
181
+ EXT-005: Hook System
182
+ """
183
+
184
+ data: Any = None
185
+ metadata: dict[str, Any] = field(default_factory=dict)
186
+ abort: bool = False
187
+ abort_reason: str = ""
188
+
189
+ def __post_init__(self): # type: ignore[no-untyped-def]
190
+ """Initialize metadata if None."""
191
+ if self.metadata is None:
192
+ self.metadata = {} # type: ignore[unreachable]
193
+
194
+
195
+ @dataclass
196
+ class RegisteredHook:
197
+ """Registered hook function.
198
+
199
+ Attributes:
200
+ hook_point: Name of hook point
201
+ func: Hook function
202
+ priority: Execution priority (higher = first)
203
+ name: Optional hook name
204
+ """
205
+
206
+ hook_point: str
207
+ func: Callable[[HookContext], HookContext]
208
+ priority: int = 50
209
+ name: str = ""
210
+
211
+
212
+ class ExtensionPointRegistry:
213
+ """Central registry of all extension points in TraceKit.
214
+
215
+ Manages registration and lookup of extension points, algorithms,
216
+ and hooks throughout the system.
217
+
218
+ Example:
219
+ >>> # List all extension points
220
+ >>> extension_points = tk.extensions.list()
221
+ >>> for ep in extension_points:
222
+ ... print(f"{ep.name} v{ep.version}")
223
+
224
+ >>> # Get specific extension point
225
+ >>> decoder_ep = tk.extensions.get("protocol_decoder")
226
+ >>> print(f"Required methods: {decoder_ep.required_methods}")
227
+
228
+ References:
229
+ EXT-001: Extension Point Registry
230
+ EXT-002: Algorithm Registration
231
+ EXT-003: Algorithm Selection
232
+ EXT-004: Priority System
233
+ EXT-005: Hook System
234
+ EXT-006: Custom Decoder Registration
235
+ """
236
+
237
+ _instance: ExtensionPointRegistry | None = None
238
+
239
+ def __new__(cls) -> ExtensionPointRegistry:
240
+ """Ensure singleton instance.
241
+
242
+ Returns:
243
+ Singleton ExtensionPointRegistry instance.
244
+ """
245
+ if cls._instance is None:
246
+ cls._instance = super().__new__(cls)
247
+ cls._instance._extension_points: dict[str, ExtensionPointSpec] = {} # type: ignore[misc, attr-defined]
248
+ cls._instance._algorithms: dict[str, dict[str, RegisteredAlgorithm]] = {} # type: ignore[misc, attr-defined]
249
+ cls._instance._hooks: dict[str, list[RegisteredHook]] = {} # type: ignore[misc, attr-defined]
250
+ cls._instance._hook_error_policy = HookErrorPolicy.CONTINUE
251
+ cls._instance._log_hook_errors = True
252
+ cls._instance._initialized = False # type: ignore[has-type]
253
+ cls._instance._registration_counter = 0 # type: ignore[misc, attr-defined]
254
+ return cls._instance
255
+
256
+ def initialize(self) -> None:
257
+ """Initialize built-in extension points.
258
+
259
+ Registers the standard extension points that come with TraceKit.
260
+ """
261
+ if self._initialized: # type: ignore[has-type]
262
+ return
263
+
264
+ # Register standard extension points
265
+ self.register_point(
266
+ ExtensionPointSpec(
267
+ name="protocol_decoder",
268
+ version="1.0.0",
269
+ description="Decode protocol from waveform or digital trace",
270
+ required_methods=["decode", "get_metadata"],
271
+ optional_methods=["configure", "reset", "validate_config"],
272
+ )
273
+ )
274
+
275
+ self.register_point(
276
+ ExtensionPointSpec(
277
+ name="file_loader",
278
+ version="1.0.0",
279
+ description="Load trace data from file format",
280
+ required_methods=["load", "can_load"],
281
+ optional_methods=["get_metadata", "get_channels"],
282
+ )
283
+ )
284
+
285
+ self.register_point(
286
+ ExtensionPointSpec(
287
+ name="measurement",
288
+ version="1.0.0",
289
+ description="Compute measurement from trace",
290
+ required_methods=["measure"],
291
+ optional_methods=["validate_input", "get_units"],
292
+ )
293
+ )
294
+
295
+ self.register_point(
296
+ ExtensionPointSpec(
297
+ name="exporter",
298
+ version="1.0.0",
299
+ description="Export trace data to file format",
300
+ required_methods=["export"],
301
+ optional_methods=["get_supported_formats"],
302
+ )
303
+ )
304
+
305
+ self.register_point(
306
+ ExtensionPointSpec(
307
+ name="algorithm",
308
+ version="1.0.0",
309
+ description="Signal processing algorithm",
310
+ required_methods=["process"],
311
+ optional_methods=["configure", "get_parameters"],
312
+ )
313
+ )
314
+
315
+ self._initialized = True
316
+ logger.debug("Extension point registry initialized with built-in points")
317
+
318
+ # =========================================================================
319
+ # Extension Point Management (EXT-001)
320
+ # =========================================================================
321
+
322
+ def register_point(self, spec: ExtensionPointSpec) -> None:
323
+ """Register an extension point.
324
+
325
+ Args:
326
+ spec: Extension point specification
327
+
328
+ Raises:
329
+ ValueError: If extension point already exists
330
+
331
+ Example:
332
+ >>> spec = ExtensionPointSpec(
333
+ ... name="my_extension",
334
+ ... version="1.0.0",
335
+ ... required_methods=["process"]
336
+ ... )
337
+ >>> registry.register_point(spec)
338
+ """
339
+ if spec.name in self._extension_points: # type: ignore[attr-defined]
340
+ raise ValueError(f"Extension point '{spec.name}' already registered")
341
+ self._extension_points[spec.name] = spec # type: ignore[attr-defined]
342
+ logger.debug(f"Registered extension point: {spec.name} v{spec.version}")
343
+
344
+ def get_point(self, name: str) -> ExtensionPointSpec:
345
+ """Get extension point specification.
346
+
347
+ Args:
348
+ name: Extension point name
349
+
350
+ Returns:
351
+ Extension point specification
352
+
353
+ Raises:
354
+ KeyError: If extension point not found
355
+ """
356
+ if name not in self._extension_points: # type: ignore[attr-defined]
357
+ raise KeyError(
358
+ f"Extension point '{name}' not found. "
359
+ f"Available: {list(self._extension_points.keys())}" # type: ignore[attr-defined]
360
+ )
361
+ return self._extension_points[name] # type: ignore[no-any-return, attr-defined]
362
+
363
+ def list_points(self) -> list[ExtensionPointSpec]:
364
+ """List all registered extension points.
365
+
366
+ Returns:
367
+ List of extension point specifications
368
+ """
369
+ return list(self._extension_points.values()) # type: ignore[attr-defined]
370
+
371
+ def exists(self, name: str) -> bool:
372
+ """Check if extension point exists.
373
+
374
+ Args:
375
+ name: Extension point name
376
+
377
+ Returns:
378
+ True if exists
379
+ """
380
+ return name in self._extension_points # type: ignore[attr-defined]
381
+
382
+ # =========================================================================
383
+ # Algorithm Management (EXT-002, EXT-003, EXT-004)
384
+ # =========================================================================
385
+
386
+ def register_algorithm(
387
+ self,
388
+ name: str,
389
+ func: Callable[..., Any],
390
+ category: str,
391
+ priority: int = 50,
392
+ performance: dict[str, str] | None = None,
393
+ supports: list[str] | None = None,
394
+ description: str = "",
395
+ complexity: str = "O(n)",
396
+ capabilities: dict[str, Any] | None = None,
397
+ memory_usage: str = "unknown",
398
+ ) -> None:
399
+ """Register a custom algorithm implementation.
400
+
401
+ Args:
402
+ name: Algorithm name
403
+ func: Algorithm function
404
+ category: Algorithm category
405
+ priority: Execution priority (0-100, higher = first)
406
+ performance: Performance characteristics dict (speed/accuracy/memory)
407
+ supports: List of supported data types
408
+ description: Human-readable description
409
+ complexity: Time complexity string (e.g., "O(n)", "O(n log n)")
410
+ capabilities: Algorithm capabilities dict (e.g., {'multi_channel': True})
411
+ memory_usage: Memory usage characteristics (low/medium/high/unknown)
412
+
413
+ Raises:
414
+ ValueError: If algorithm already registered
415
+ TypeError: If func is not callable
416
+
417
+ Example:
418
+ >>> def my_edge_detector(data, threshold=0.5):
419
+ ... return find_edges(data, threshold)
420
+ >>> registry.register_algorithm(
421
+ ... name="my_detector",
422
+ ... func=my_edge_detector,
423
+ ... category="edge_detection",
424
+ ... priority=75,
425
+ ... performance={"speed": "fast", "accuracy": "medium", "memory": "low"},
426
+ ... capabilities={"multi_channel": True, "max_sample_rate": 1000000},
427
+ ... memory_usage="low"
428
+ ... )
429
+
430
+ References:
431
+ EXT-002: Algorithm Registration (capability queries, performance metadata)
432
+ """
433
+ if not callable(func):
434
+ raise TypeError(f"Algorithm must be callable, got {type(func).__name__}")
435
+
436
+ if category not in self._algorithms: # type: ignore[attr-defined]
437
+ self._algorithms[category] = {} # type: ignore[attr-defined]
438
+
439
+ if name in self._algorithms[category]: # type: ignore[attr-defined]
440
+ raise ValueError(f"Algorithm '{name}' already registered in category '{category}'")
441
+
442
+ # Increment registration counter
443
+ self._registration_counter += 1 # type: ignore[attr-defined]
444
+
445
+ algo = RegisteredAlgorithm(
446
+ name=name,
447
+ category=category,
448
+ func=func,
449
+ priority=priority,
450
+ performance=performance or {},
451
+ supports=supports or [],
452
+ description=description,
453
+ complexity=complexity,
454
+ capabilities=capabilities or {},
455
+ memory_usage=memory_usage,
456
+ registration_order=self._registration_counter, # type: ignore[attr-defined]
457
+ )
458
+
459
+ self._algorithms[category][name] = algo # type: ignore[attr-defined]
460
+ logger.debug(f"Registered algorithm: {name} in category {category}")
461
+
462
+ def get_algorithm(self, category: str, name: str) -> RegisteredAlgorithm:
463
+ """Get algorithm by category and name.
464
+
465
+ Args:
466
+ category: Algorithm category
467
+ name: Algorithm name
468
+
469
+ Returns:
470
+ Registered algorithm metadata
471
+
472
+ Raises:
473
+ KeyError: If not found
474
+ """
475
+ if category not in self._algorithms: # type: ignore[attr-defined]
476
+ raise KeyError(f"Category '{category}' not found")
477
+ if name not in self._algorithms[category]: # type: ignore[attr-defined]
478
+ raise KeyError(f"Algorithm '{name}' not found in category '{category}'")
479
+ return self._algorithms[category][name] # type: ignore[no-any-return, attr-defined]
480
+
481
+ def select_algorithm(
482
+ self,
483
+ category: str,
484
+ name: str | None = None,
485
+ *,
486
+ optimize_for: str = "speed",
487
+ constraints: dict[str, Any] | None = None,
488
+ required_capabilities: list[str] | None = None,
489
+ ) -> RegisteredAlgorithm:
490
+ """Select algorithm implementation at runtime.
491
+
492
+ Selects by name if provided, otherwise auto-selects based on
493
+ optimization criteria and capability matching.
494
+
495
+ Args:
496
+ category: Algorithm category
497
+ name: Specific algorithm name (optional)
498
+ optimize_for: Optimization target: "speed", "accuracy", "memory"
499
+ constraints: Filter constraints on performance/supports
500
+ required_capabilities: List of required capabilities for auto-selection
501
+
502
+ Returns:
503
+ Selected algorithm
504
+
505
+ Raises:
506
+ KeyError: If category not found or no matching algorithm
507
+
508
+ Example:
509
+ >>> # Select by name
510
+ >>> algo = registry.select_algorithm("edge_detection", "fast_detector")
511
+
512
+ >>> # Auto-select for speed
513
+ >>> algo = registry.select_algorithm(
514
+ ... "edge_detection",
515
+ ... optimize_for="speed"
516
+ ... )
517
+
518
+ >>> # Auto-select by capability matching
519
+ >>> algo = registry.select_algorithm(
520
+ ... "edge_detection",
521
+ ... required_capabilities=["multi_channel", "real_time"]
522
+ ... )
523
+
524
+ References:
525
+ EXT-003: Algorithm Selection (auto-selection by capability matching)
526
+ """
527
+ if category not in self._algorithms: # type: ignore[attr-defined]
528
+ raise KeyError(f"Category '{category}' not found")
529
+
530
+ if name:
531
+ return self.get_algorithm(category, name)
532
+
533
+ # Auto-select based on criteria
534
+ candidates = list(self._algorithms[category].values()) # type: ignore[attr-defined]
535
+
536
+ if not candidates:
537
+ raise KeyError(f"No algorithms registered in category '{category}'")
538
+
539
+ # Filter by required capabilities (EXT-003)
540
+ if required_capabilities:
541
+ filtered = []
542
+ for algo in candidates:
543
+ if all(algo.can(cap) for cap in required_capabilities):
544
+ filtered.append(algo)
545
+ candidates = filtered
546
+
547
+ if not candidates:
548
+ raise KeyError(f"No algorithms match required capabilities in category '{category}'")
549
+
550
+ # Apply constraints
551
+ if constraints:
552
+ filtered = []
553
+ for algo in candidates:
554
+ match = True
555
+ for key, value in constraints.items():
556
+ if key.startswith("performance."):
557
+ perf_key = key.split(".", 1)[1]
558
+ if algo.performance.get(perf_key) != value:
559
+ match = False
560
+ break
561
+ elif key.startswith("capabilities."):
562
+ cap_key = key.split(".", 1)[1]
563
+ if algo.capabilities.get(cap_key) != value:
564
+ match = False
565
+ break
566
+ elif key == "supports":
567
+ if isinstance(value, list):
568
+ if not any(s in algo.supports for s in value):
569
+ match = False
570
+ break
571
+ elif value not in algo.supports:
572
+ match = False
573
+ break
574
+ elif key == "memory_usage":
575
+ if algo.memory_usage != value:
576
+ match = False
577
+ break
578
+ if match:
579
+ filtered.append(algo)
580
+ candidates = filtered
581
+
582
+ if not candidates:
583
+ raise KeyError(f"No algorithms match constraints in category '{category}'")
584
+
585
+ # Sort by optimization criteria
586
+ if optimize_for == "speed":
587
+
588
+ def sort_key(a): # type: ignore[no-untyped-def]
589
+ return (
590
+ 0
591
+ if a.performance.get("speed") == "fast"
592
+ else 1
593
+ if a.performance.get("speed") == "medium"
594
+ else 2,
595
+ -a.priority,
596
+ )
597
+ elif optimize_for == "accuracy":
598
+
599
+ def sort_key(a): # type: ignore[no-untyped-def]
600
+ return (
601
+ 0
602
+ if a.performance.get("accuracy") == "high"
603
+ else 1
604
+ if a.performance.get("accuracy") == "medium"
605
+ else 2,
606
+ -a.priority,
607
+ )
608
+ elif optimize_for == "memory":
609
+
610
+ def sort_key(a): # type: ignore[no-untyped-def]
611
+ return (
612
+ 0
613
+ if a.performance.get("memory") == "low"
614
+ else 1
615
+ if a.performance.get("memory") == "medium"
616
+ else 2,
617
+ -a.priority,
618
+ )
619
+ else:
620
+ # Default to priority only
621
+ def sort_key(a): # type: ignore[no-untyped-def]
622
+ return -a.priority
623
+
624
+ candidates.sort(key=sort_key)
625
+ return candidates[0] # type: ignore[no-any-return]
626
+
627
+ def list_algorithms(
628
+ self,
629
+ category: str,
630
+ ordered: bool = False,
631
+ tie_break: str = "name",
632
+ ) -> list[RegisteredAlgorithm]:
633
+ """List all algorithms in a category.
634
+
635
+ Args:
636
+ category: Algorithm category
637
+ ordered: If True, sort by priority (highest first)
638
+ tie_break: Tie-breaking rule: "name" (alphabetical) or "registration" (order registered)
639
+
640
+ Returns:
641
+ List of registered algorithms
642
+
643
+ Raises:
644
+ KeyError: If category not found
645
+
646
+ Example:
647
+ >>> # Get algorithms sorted by priority, ties broken by name
648
+ >>> algos = registry.list_algorithms("edge_detection", ordered=True, tie_break="name")
649
+
650
+ >>> # Get algorithms sorted by priority, ties broken by registration order
651
+ >>> algos = registry.list_algorithms("edge_detection", ordered=True, tie_break="registration")
652
+
653
+ References:
654
+ EXT-004: Priority System (tie-breaking rules by name or registration order)
655
+ """
656
+ if category not in self._algorithms: # type: ignore[attr-defined]
657
+ raise KeyError(f"Category '{category}' not found")
658
+
659
+ algos = list(self._algorithms[category].values()) # type: ignore[attr-defined]
660
+
661
+ if ordered:
662
+ if tie_break == "registration":
663
+ # Sort by priority (highest first), then by registration order for ties
664
+ algos.sort(key=lambda a: (-a.priority, a.registration_order))
665
+ else:
666
+ # Sort by priority (highest first), then by name for ties (default)
667
+ algos.sort(key=lambda a: (-a.priority, a.name))
668
+
669
+ return algos
670
+
671
+ def list_categories(self) -> list[str]:
672
+ """List all algorithm categories.
673
+
674
+ Returns:
675
+ List of category names
676
+ """
677
+ return list(self._algorithms.keys()) # type: ignore[attr-defined]
678
+
679
+ def benchmark_algorithms(
680
+ self,
681
+ category: str,
682
+ test_data: Any,
683
+ *,
684
+ metrics: list[str] | None = None,
685
+ iterations: int = 10,
686
+ ) -> dict[str, dict[str, float]]:
687
+ """Benchmark all algorithms in a category.
688
+
689
+ Runs performance tests on all registered algorithms and measures
690
+ execution time, memory usage, and optionally custom metrics.
691
+
692
+ Args:
693
+ category: Algorithm category to benchmark
694
+ test_data: Test data to pass to algorithms
695
+ metrics: List of metrics to measure (defaults to ["execution_time"])
696
+ iterations: Number of iterations to average over
697
+
698
+ Returns:
699
+ Dict mapping algorithm names to metric results
700
+
701
+ Raises:
702
+ KeyError: If category is not found.
703
+
704
+ Example:
705
+ >>> import numpy as np
706
+ >>> test_signal = np.random.randn(1000)
707
+ >>> results = registry.benchmark_algorithms(
708
+ ... "edge_detection",
709
+ ... test_signal,
710
+ ... metrics=["execution_time", "memory_usage"],
711
+ ... iterations=100
712
+ ... )
713
+ >>> for name, metrics in results.items():
714
+ ... print(f"{name}: {metrics['execution_time']:.3f}s")
715
+
716
+ References:
717
+ EXT-003: Algorithm Selection (benchmarking support)
718
+ """
719
+ import time
720
+ import tracemalloc
721
+
722
+ if category not in self._algorithms: # type: ignore[attr-defined]
723
+ raise KeyError(f"Category '{category}' not found")
724
+
725
+ if metrics is None:
726
+ metrics = ["execution_time"]
727
+
728
+ results = {}
729
+
730
+ for name, algo in self._algorithms[category].items(): # type: ignore[attr-defined]
731
+ algo_results = {}
732
+
733
+ if "execution_time" in metrics:
734
+ times = []
735
+ for _ in range(iterations):
736
+ start = time.perf_counter()
737
+ try:
738
+ algo.func(test_data)
739
+ except Exception as e:
740
+ logger.warning(f"Algorithm {name} failed during benchmark: {e}")
741
+ times.append(float("inf"))
742
+ continue
743
+ end = time.perf_counter()
744
+ times.append(end - start)
745
+
746
+ algo_results["execution_time"] = sum(times) / len(times)
747
+ algo_results["min_time"] = min(times)
748
+ algo_results["max_time"] = max(times)
749
+
750
+ if "memory_usage" in metrics:
751
+ tracemalloc.start()
752
+ try:
753
+ algo.func(test_data)
754
+ current, peak = tracemalloc.get_traced_memory()
755
+ algo_results["memory_current"] = current / 1024 / 1024 # MB
756
+ algo_results["memory_peak"] = peak / 1024 / 1024 # MB
757
+ except Exception as e:
758
+ logger.warning(f"Algorithm {name} failed during benchmark: {e}")
759
+ algo_results["memory_current"] = float("inf")
760
+ algo_results["memory_peak"] = float("inf")
761
+ finally:
762
+ tracemalloc.stop()
763
+
764
+ results[name] = algo_results
765
+
766
+ return results
767
+
768
+ def configure_priorities(self, config: dict[str, dict[str, int]]) -> None:
769
+ """Override algorithm priorities via configuration.
770
+
771
+ Args:
772
+ config: Dict mapping category -> {algorithm_name: new_priority}
773
+
774
+ Example:
775
+ >>> registry.configure_priorities({
776
+ ... "edge_detection": {
777
+ ... "fast_detector": 100,
778
+ ... "accurate_detector": 50
779
+ ... }
780
+ ... })
781
+
782
+ References:
783
+ EXT-004: Priority System
784
+ """
785
+ for category, priorities in config.items():
786
+ if category not in self._algorithms: # type: ignore[attr-defined]
787
+ continue
788
+ for name, priority in priorities.items():
789
+ if name in self._algorithms[category]: # type: ignore[attr-defined]
790
+ self._algorithms[category][name].priority = priority # type: ignore[attr-defined]
791
+ logger.debug(f"Set priority for {category}/{name} to {priority}")
792
+
793
+ # =========================================================================
794
+ # Hook System (EXT-005)
795
+ # =========================================================================
796
+
797
+ def register_hook(
798
+ self,
799
+ hook_point: str,
800
+ func: Callable[[HookContext], HookContext],
801
+ priority: int = 50,
802
+ name: str = "",
803
+ ) -> None:
804
+ """Register a hook function.
805
+
806
+ Args:
807
+ hook_point: Name of hook point (e.g., "pre_decode", "post_decode")
808
+ func: Hook function accepting and returning HookContext
809
+ priority: Execution priority (higher = first)
810
+ name: Optional hook name for identification
811
+
812
+ Example:
813
+ >>> @tk.hooks.register("pre_decode")
814
+ >>> def validate_waveform(context):
815
+ ... if context.data.sample_rate < 1000:
816
+ ... raise ValueError("Sample rate too low")
817
+ ... return context
818
+
819
+ References:
820
+ EXT-005: Hook System
821
+ """
822
+ if hook_point not in self._hooks: # type: ignore[attr-defined]
823
+ self._hooks[hook_point] = [] # type: ignore[attr-defined]
824
+
825
+ hook = RegisteredHook(
826
+ hook_point=hook_point,
827
+ func=func,
828
+ priority=priority,
829
+ name=name or func.__name__,
830
+ )
831
+
832
+ self._hooks[hook_point].append(hook) # type: ignore[attr-defined]
833
+ # Sort by priority (highest first)
834
+ self._hooks[hook_point].sort(key=lambda h: -h.priority) # type: ignore[attr-defined]
835
+
836
+ logger.debug(f"Registered hook '{hook.name}' at point '{hook_point}'")
837
+
838
+ def execute_hooks(self, hook_point: str, context: HookContext) -> HookContext:
839
+ """Execute all hooks at a hook point with chaining and error isolation.
840
+
841
+ Hooks are executed in priority order (highest first). Each hook receives
842
+ the context from the previous hook (chaining). If a hook fails, the error
843
+ is isolated based on the configured error policy, preventing one hook's
844
+ failure from stopping other hooks.
845
+
846
+ Args:
847
+ hook_point: Hook point name
848
+ context: Hook context to pass through
849
+
850
+ Returns:
851
+ Modified context after all hooks
852
+
853
+ Raises:
854
+ Exception: If error policy is ABORT and a hook fails.
855
+
856
+ Example:
857
+ >>> context = HookContext(data=trace)
858
+ >>> context = registry.execute_hooks("pre_decode", context)
859
+ >>> if context.abort:
860
+ ... raise ValueError(context.abort_reason)
861
+
862
+ References:
863
+ EXT-005: Hook System (hook chaining, error isolation)
864
+ """
865
+ if hook_point not in self._hooks: # type: ignore[attr-defined]
866
+ return context
867
+
868
+ # Execute hooks in priority order (hook chaining - EXT-005)
869
+ for hook in self._hooks[hook_point]: # type: ignore[attr-defined]
870
+ try:
871
+ context = hook.func(context)
872
+ if context.abort:
873
+ logger.info(f"Hook '{hook.name}' requested abort: {context.abort_reason}")
874
+ break
875
+ except Exception as e:
876
+ # Error isolation - EXT-005: one hook failure doesn't stop others
877
+ if self._log_hook_errors:
878
+ logger.error(f"Hook '{hook.name}' at '{hook_point}' failed: {e}")
879
+
880
+ if self._hook_error_policy == HookErrorPolicy.ABORT:
881
+ raise
882
+ elif self._hook_error_policy == HookErrorPolicy.CONTINUE:
883
+ continue # Continue to next hook despite error
884
+ # IGNORE falls through
885
+
886
+ return context
887
+
888
+ def configure_hooks(self, on_error: str = "continue", log_errors: bool = True) -> None:
889
+ """Configure hook error handling behavior.
890
+
891
+ Args:
892
+ on_error: Error policy: "continue", "abort", "ignore"
893
+ log_errors: Whether to log hook errors
894
+
895
+ References:
896
+ EXT-005: Hook System
897
+ """
898
+ policy_map = {
899
+ "continue": HookErrorPolicy.CONTINUE,
900
+ "abort": HookErrorPolicy.ABORT,
901
+ "ignore": HookErrorPolicy.IGNORE,
902
+ }
903
+ self._hook_error_policy = policy_map.get(on_error, HookErrorPolicy.CONTINUE)
904
+ self._log_hook_errors = log_errors
905
+
906
+ def list_hooks(self, hook_point: str | None = None) -> dict[str, list[str]]:
907
+ """List registered hooks.
908
+
909
+ Args:
910
+ hook_point: Specific hook point, or None for all
911
+
912
+ Returns:
913
+ Dict mapping hook points to list of hook names
914
+ """
915
+ if hook_point:
916
+ if hook_point not in self._hooks: # type: ignore[attr-defined]
917
+ return {hook_point: []}
918
+ return {hook_point: [h.name for h in self._hooks[hook_point]]} # type: ignore[attr-defined]
919
+
920
+ return {point: [h.name for h in hooks] for point, hooks in self._hooks.items()} # type: ignore[attr-defined]
921
+
922
+ def clear_hooks(self, hook_point: str | None = None) -> None:
923
+ """Clear registered hooks.
924
+
925
+ Args:
926
+ hook_point: Specific hook point to clear, or None for all
927
+ """
928
+ if hook_point:
929
+ self._hooks.pop(hook_point, None) # type: ignore[attr-defined]
930
+ else:
931
+ self._hooks.clear() # type: ignore[attr-defined]
932
+
933
+ # =========================================================================
934
+ # Custom Decoder Registration (EXT-006)
935
+ # =========================================================================
936
+
937
+ def register_decoder(self, protocol: str, decoder_class: type, priority: int = 50) -> None:
938
+ """Register a custom protocol decoder.
939
+
940
+ Args:
941
+ protocol: Protocol name (e.g., "uart", "spi", "my_custom")
942
+ decoder_class: Decoder class implementing ProtocolDecoder interface
943
+ priority: Registration priority
944
+
945
+ Raises:
946
+ ValueError: If decoder doesn't implement required interface or lacks documentation
947
+
948
+ Example:
949
+ >>> class MyDecoder:
950
+ ... '''Custom decoder for my protocol.'''
951
+ ... def decode(self, trace):
952
+ ... return []
953
+ ... def get_metadata(self):
954
+ ... return {"name": "my_decoder"}
955
+ >>> registry.register_decoder("my_protocol", MyDecoder)
956
+
957
+ References:
958
+ EXT-006: Custom Decoder Registration (validation of decoder interface, documentation requirements)
959
+ """
960
+ # Validate decoder implements required interface
961
+ spec = self.get_point("protocol_decoder")
962
+ instance = decoder_class()
963
+ is_valid, missing = spec.validate_implementation(instance)
964
+
965
+ if not is_valid:
966
+ raise ValueError(f"Decoder '{protocol}' missing required methods: {missing}")
967
+
968
+ # Check documentation requirements (EXT-006)
969
+ if not decoder_class.__doc__ or not decoder_class.__doc__.strip():
970
+ raise ValueError(
971
+ f"Decoder '{protocol}' must have a docstring documenting its purpose and usage"
972
+ )
973
+
974
+ # Register as algorithm in protocol_decoder category
975
+ self.register_algorithm(
976
+ name=protocol,
977
+ func=decoder_class,
978
+ category="protocol_decoder",
979
+ priority=priority,
980
+ description=decoder_class.__doc__.strip().split("\n")[0]
981
+ if decoder_class.__doc__
982
+ else f"Protocol decoder for {protocol}",
983
+ )
984
+
985
+ logger.info(f"Registered custom decoder for protocol: {protocol}")
986
+
987
+ def get_decoder(self, protocol: str) -> type:
988
+ """Get decoder class for a protocol.
989
+
990
+ Args:
991
+ protocol: Protocol name
992
+
993
+ Returns:
994
+ Decoder class
995
+ """
996
+ algo = self.get_algorithm("protocol_decoder", protocol)
997
+ return algo.func # type: ignore[return-value]
998
+
999
+ def list_decoders(self) -> list[str]:
1000
+ """List all registered protocol decoders.
1001
+
1002
+ Returns:
1003
+ List of protocol names
1004
+ """
1005
+ if "protocol_decoder" not in self._algorithms: # type: ignore[attr-defined]
1006
+ return []
1007
+ return list(self._algorithms["protocol_decoder"].keys()) # type: ignore[attr-defined]
1008
+
1009
+
1010
+ # Global registry instance
1011
+ _registry = ExtensionPointRegistry()
1012
+
1013
+
1014
+ # =========================================================================
1015
+ # Module-Level Convenience Functions
1016
+ # =========================================================================
1017
+
1018
+
1019
+ def get_registry() -> ExtensionPointRegistry:
1020
+ """Get the global extension point registry.
1021
+
1022
+ Returns:
1023
+ Global ExtensionPointRegistry instance
1024
+ """
1025
+ _registry.initialize()
1026
+ return _registry
1027
+
1028
+
1029
+ def list_extension_points() -> list[ExtensionPointSpec]:
1030
+ """List all registered extension points.
1031
+
1032
+ Returns:
1033
+ List of extension point specifications
1034
+
1035
+ References:
1036
+ EXT-001: Extension Point Registry
1037
+ """
1038
+ return get_registry().list_points()
1039
+
1040
+
1041
+ def get_extension_point(name: str) -> ExtensionPointSpec:
1042
+ """Get extension point by name.
1043
+
1044
+ Args:
1045
+ name: Extension point name
1046
+
1047
+ Returns:
1048
+ Extension point specification
1049
+
1050
+ References:
1051
+ EXT-001: Extension Point Registry
1052
+ """
1053
+ return get_registry().get_point(name)
1054
+
1055
+
1056
+ def extension_point_exists(name: str) -> bool:
1057
+ """Check if extension point exists.
1058
+
1059
+ Args:
1060
+ name: Extension point name
1061
+
1062
+ Returns:
1063
+ True if exists
1064
+
1065
+ References:
1066
+ EXT-001: Extension Point Registry
1067
+ """
1068
+ return get_registry().exists(name)
1069
+
1070
+
1071
+ def register_extension_point(spec: ExtensionPointSpec) -> None:
1072
+ """Register a new extension point.
1073
+
1074
+ Args:
1075
+ spec: Extension point specification
1076
+
1077
+ References:
1078
+ EXT-001: Extension Point Registry
1079
+ """
1080
+ get_registry().register_point(spec)
1081
+
1082
+
1083
+ # Hook decorator
1084
+ def hook(hook_point: str, priority: int = 50, name: str = ""): # type: ignore[no-untyped-def]
1085
+ """Decorator for registering hook functions.
1086
+
1087
+ Args:
1088
+ hook_point: Hook point name
1089
+ priority: Execution priority
1090
+ name: Optional hook name
1091
+
1092
+ Returns:
1093
+ Decorator function that registers the hook.
1094
+
1095
+ Example:
1096
+ >>> @hook("pre_decode", priority=100)
1097
+ >>> def validate_input(context):
1098
+ ... # validation logic
1099
+ ... return context
1100
+
1101
+ References:
1102
+ EXT-005: Hook System
1103
+ """
1104
+
1105
+ def decorator(func: Callable[[HookContext], HookContext]): # type: ignore[no-untyped-def]
1106
+ get_registry().register_hook(hook_point, func, priority, name or func.__name__)
1107
+ return func
1108
+
1109
+ return decorator
1110
+
1111
+
1112
+ __all__ = [
1113
+ "ExtensionPointRegistry",
1114
+ "ExtensionPointSpec",
1115
+ "HookContext",
1116
+ "HookErrorPolicy",
1117
+ "RegisteredAlgorithm",
1118
+ "RegisteredHook",
1119
+ "extension_point_exists",
1120
+ "get_extension_point",
1121
+ "get_registry",
1122
+ "hook",
1123
+ "list_extension_points",
1124
+ "register_extension_point",
1125
+ ]