oscura 0.5.0__py3-none-any.whl → 0.6.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 (513) hide show
  1. oscura/__init__.py +169 -167
  2. oscura/analyzers/__init__.py +3 -0
  3. oscura/analyzers/classification.py +659 -0
  4. oscura/analyzers/digital/__init__.py +0 -48
  5. oscura/analyzers/digital/edges.py +325 -65
  6. oscura/analyzers/digital/extraction.py +0 -195
  7. oscura/analyzers/digital/quality.py +293 -166
  8. oscura/analyzers/digital/timing.py +260 -115
  9. oscura/analyzers/digital/timing_numba.py +334 -0
  10. oscura/analyzers/entropy.py +605 -0
  11. oscura/analyzers/eye/diagram.py +176 -109
  12. oscura/analyzers/eye/metrics.py +5 -5
  13. oscura/analyzers/jitter/__init__.py +6 -4
  14. oscura/analyzers/jitter/ber.py +52 -52
  15. oscura/analyzers/jitter/classification.py +156 -0
  16. oscura/analyzers/jitter/decomposition.py +163 -113
  17. oscura/analyzers/jitter/spectrum.py +80 -64
  18. oscura/analyzers/ml/__init__.py +39 -0
  19. oscura/analyzers/ml/features.py +600 -0
  20. oscura/analyzers/ml/signal_classifier.py +604 -0
  21. oscura/analyzers/packet/daq.py +246 -158
  22. oscura/analyzers/packet/parser.py +12 -1
  23. oscura/analyzers/packet/payload.py +50 -2110
  24. oscura/analyzers/packet/payload_analysis.py +361 -181
  25. oscura/analyzers/packet/payload_patterns.py +133 -70
  26. oscura/analyzers/packet/stream.py +84 -23
  27. oscura/analyzers/patterns/__init__.py +26 -5
  28. oscura/analyzers/patterns/anomaly_detection.py +908 -0
  29. oscura/analyzers/patterns/clustering.py +169 -108
  30. oscura/analyzers/patterns/clustering_optimized.py +227 -0
  31. oscura/analyzers/patterns/discovery.py +1 -1
  32. oscura/analyzers/patterns/matching.py +581 -197
  33. oscura/analyzers/patterns/pattern_mining.py +778 -0
  34. oscura/analyzers/patterns/periodic.py +121 -38
  35. oscura/analyzers/patterns/sequences.py +175 -78
  36. oscura/analyzers/power/conduction.py +1 -1
  37. oscura/analyzers/power/soa.py +6 -6
  38. oscura/analyzers/power/switching.py +250 -110
  39. oscura/analyzers/protocol/__init__.py +17 -1
  40. oscura/analyzers/protocols/__init__.py +1 -22
  41. oscura/analyzers/protocols/base.py +6 -6
  42. oscura/analyzers/protocols/ble/__init__.py +38 -0
  43. oscura/analyzers/protocols/ble/analyzer.py +809 -0
  44. oscura/analyzers/protocols/ble/uuids.py +288 -0
  45. oscura/analyzers/protocols/can.py +257 -127
  46. oscura/analyzers/protocols/can_fd.py +107 -80
  47. oscura/analyzers/protocols/flexray.py +139 -80
  48. oscura/analyzers/protocols/hdlc.py +93 -58
  49. oscura/analyzers/protocols/i2c.py +247 -106
  50. oscura/analyzers/protocols/i2s.py +138 -86
  51. oscura/analyzers/protocols/industrial/__init__.py +40 -0
  52. oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
  53. oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
  54. oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
  55. oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
  56. oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
  57. oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
  58. oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
  59. oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
  60. oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
  61. oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
  62. oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
  63. oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
  64. oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
  65. oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
  66. oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
  67. oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
  68. oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
  69. oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
  70. oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
  71. oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
  72. oscura/analyzers/protocols/jtag.py +180 -98
  73. oscura/analyzers/protocols/lin.py +219 -114
  74. oscura/analyzers/protocols/manchester.py +4 -4
  75. oscura/analyzers/protocols/onewire.py +253 -149
  76. oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
  77. oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
  78. oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
  79. oscura/analyzers/protocols/spi.py +192 -95
  80. oscura/analyzers/protocols/swd.py +321 -167
  81. oscura/analyzers/protocols/uart.py +267 -125
  82. oscura/analyzers/protocols/usb.py +235 -131
  83. oscura/analyzers/side_channel/power.py +17 -12
  84. oscura/analyzers/signal/__init__.py +15 -0
  85. oscura/analyzers/signal/timing_analysis.py +1086 -0
  86. oscura/analyzers/signal_integrity/__init__.py +4 -1
  87. oscura/analyzers/signal_integrity/sparams.py +2 -19
  88. oscura/analyzers/spectral/chunked.py +129 -60
  89. oscura/analyzers/spectral/chunked_fft.py +300 -94
  90. oscura/analyzers/spectral/chunked_wavelet.py +100 -80
  91. oscura/analyzers/statistical/checksum.py +376 -217
  92. oscura/analyzers/statistical/classification.py +229 -107
  93. oscura/analyzers/statistical/entropy.py +78 -53
  94. oscura/analyzers/statistics/correlation.py +407 -211
  95. oscura/analyzers/statistics/outliers.py +2 -2
  96. oscura/analyzers/statistics/streaming.py +30 -5
  97. oscura/analyzers/validation.py +216 -101
  98. oscura/analyzers/waveform/measurements.py +9 -0
  99. oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
  100. oscura/analyzers/waveform/spectral.py +500 -228
  101. oscura/api/__init__.py +31 -5
  102. oscura/api/dsl/__init__.py +582 -0
  103. oscura/{dsl → api/dsl}/commands.py +43 -76
  104. oscura/{dsl → api/dsl}/interpreter.py +26 -51
  105. oscura/{dsl → api/dsl}/parser.py +107 -77
  106. oscura/{dsl → api/dsl}/repl.py +2 -2
  107. oscura/api/dsl.py +1 -1
  108. oscura/{integrations → api/integrations}/__init__.py +1 -1
  109. oscura/{integrations → api/integrations}/llm.py +201 -102
  110. oscura/api/operators.py +3 -3
  111. oscura/api/optimization.py +144 -30
  112. oscura/api/rest_server.py +921 -0
  113. oscura/api/server/__init__.py +17 -0
  114. oscura/api/server/dashboard.py +850 -0
  115. oscura/api/server/static/README.md +34 -0
  116. oscura/api/server/templates/base.html +181 -0
  117. oscura/api/server/templates/export.html +120 -0
  118. oscura/api/server/templates/home.html +284 -0
  119. oscura/api/server/templates/protocols.html +58 -0
  120. oscura/api/server/templates/reports.html +43 -0
  121. oscura/api/server/templates/session_detail.html +89 -0
  122. oscura/api/server/templates/sessions.html +83 -0
  123. oscura/api/server/templates/waveforms.html +73 -0
  124. oscura/automotive/__init__.py +8 -1
  125. oscura/automotive/can/__init__.py +10 -0
  126. oscura/automotive/can/checksum.py +3 -1
  127. oscura/automotive/can/dbc_generator.py +590 -0
  128. oscura/automotive/can/message_wrapper.py +121 -74
  129. oscura/automotive/can/patterns.py +98 -21
  130. oscura/automotive/can/session.py +292 -56
  131. oscura/automotive/can/state_machine.py +6 -3
  132. oscura/automotive/can/stimulus_response.py +97 -75
  133. oscura/automotive/dbc/__init__.py +10 -2
  134. oscura/automotive/dbc/generator.py +84 -56
  135. oscura/automotive/dbc/parser.py +6 -6
  136. oscura/automotive/dtc/data.json +2763 -0
  137. oscura/automotive/dtc/database.py +2 -2
  138. oscura/automotive/flexray/__init__.py +31 -0
  139. oscura/automotive/flexray/analyzer.py +504 -0
  140. oscura/automotive/flexray/crc.py +185 -0
  141. oscura/automotive/flexray/fibex.py +449 -0
  142. oscura/automotive/j1939/__init__.py +45 -8
  143. oscura/automotive/j1939/analyzer.py +605 -0
  144. oscura/automotive/j1939/spns.py +326 -0
  145. oscura/automotive/j1939/transport.py +306 -0
  146. oscura/automotive/lin/__init__.py +47 -0
  147. oscura/automotive/lin/analyzer.py +612 -0
  148. oscura/automotive/loaders/blf.py +13 -2
  149. oscura/automotive/loaders/csv_can.py +143 -72
  150. oscura/automotive/loaders/dispatcher.py +50 -2
  151. oscura/automotive/loaders/mdf.py +86 -45
  152. oscura/automotive/loaders/pcap.py +111 -61
  153. oscura/automotive/uds/__init__.py +4 -0
  154. oscura/automotive/uds/analyzer.py +725 -0
  155. oscura/automotive/uds/decoder.py +140 -58
  156. oscura/automotive/uds/models.py +7 -1
  157. oscura/automotive/visualization.py +1 -1
  158. oscura/cli/analyze.py +348 -0
  159. oscura/cli/batch.py +142 -122
  160. oscura/cli/benchmark.py +275 -0
  161. oscura/cli/characterize.py +137 -82
  162. oscura/cli/compare.py +224 -131
  163. oscura/cli/completion.py +250 -0
  164. oscura/cli/config_cmd.py +361 -0
  165. oscura/cli/decode.py +164 -87
  166. oscura/cli/export.py +286 -0
  167. oscura/cli/main.py +115 -31
  168. oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
  169. oscura/{onboarding → cli/onboarding}/help.py +80 -58
  170. oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
  171. oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
  172. oscura/cli/progress.py +147 -0
  173. oscura/cli/shell.py +157 -135
  174. oscura/cli/validate_cmd.py +204 -0
  175. oscura/cli/visualize.py +158 -0
  176. oscura/convenience.py +125 -79
  177. oscura/core/__init__.py +4 -2
  178. oscura/core/backend_selector.py +3 -3
  179. oscura/core/cache.py +126 -15
  180. oscura/core/cancellation.py +1 -1
  181. oscura/{config → core/config}/__init__.py +20 -11
  182. oscura/{config → core/config}/defaults.py +1 -1
  183. oscura/{config → core/config}/loader.py +7 -5
  184. oscura/{config → core/config}/memory.py +5 -5
  185. oscura/{config → core/config}/migration.py +1 -1
  186. oscura/{config → core/config}/pipeline.py +99 -23
  187. oscura/{config → core/config}/preferences.py +1 -1
  188. oscura/{config → core/config}/protocol.py +3 -3
  189. oscura/{config → core/config}/schema.py +426 -272
  190. oscura/{config → core/config}/settings.py +1 -1
  191. oscura/{config → core/config}/thresholds.py +195 -153
  192. oscura/core/correlation.py +5 -6
  193. oscura/core/cross_domain.py +0 -2
  194. oscura/core/debug.py +9 -5
  195. oscura/{extensibility → core/extensibility}/docs.py +158 -70
  196. oscura/{extensibility → core/extensibility}/extensions.py +160 -76
  197. oscura/{extensibility → core/extensibility}/logging.py +1 -1
  198. oscura/{extensibility → core/extensibility}/measurements.py +1 -1
  199. oscura/{extensibility → core/extensibility}/plugins.py +1 -1
  200. oscura/{extensibility → core/extensibility}/templates.py +73 -3
  201. oscura/{extensibility → core/extensibility}/validation.py +1 -1
  202. oscura/core/gpu_backend.py +11 -7
  203. oscura/core/log_query.py +101 -11
  204. oscura/core/logging.py +126 -54
  205. oscura/core/logging_advanced.py +5 -5
  206. oscura/core/memory_limits.py +108 -70
  207. oscura/core/memory_monitor.py +2 -2
  208. oscura/core/memory_progress.py +7 -7
  209. oscura/core/memory_warnings.py +1 -1
  210. oscura/core/numba_backend.py +13 -13
  211. oscura/{plugins → core/plugins}/__init__.py +9 -9
  212. oscura/{plugins → core/plugins}/base.py +7 -7
  213. oscura/{plugins → core/plugins}/cli.py +3 -3
  214. oscura/{plugins → core/plugins}/discovery.py +186 -106
  215. oscura/{plugins → core/plugins}/lifecycle.py +1 -1
  216. oscura/{plugins → core/plugins}/manager.py +7 -7
  217. oscura/{plugins → core/plugins}/registry.py +3 -3
  218. oscura/{plugins → core/plugins}/versioning.py +1 -1
  219. oscura/core/progress.py +16 -1
  220. oscura/core/provenance.py +8 -2
  221. oscura/{schemas → core/schemas}/__init__.py +2 -2
  222. oscura/core/schemas/bus_configuration.json +322 -0
  223. oscura/core/schemas/device_mapping.json +182 -0
  224. oscura/core/schemas/packet_format.json +418 -0
  225. oscura/core/schemas/protocol_definition.json +363 -0
  226. oscura/core/types.py +4 -0
  227. oscura/core/uncertainty.py +3 -3
  228. oscura/correlation/__init__.py +52 -0
  229. oscura/correlation/multi_protocol.py +811 -0
  230. oscura/discovery/auto_decoder.py +117 -35
  231. oscura/discovery/comparison.py +191 -86
  232. oscura/discovery/quality_validator.py +155 -68
  233. oscura/discovery/signal_detector.py +196 -79
  234. oscura/export/__init__.py +18 -20
  235. oscura/export/kaitai_struct.py +513 -0
  236. oscura/export/scapy_layer.py +801 -0
  237. oscura/export/wireshark/README.md +15 -15
  238. oscura/export/wireshark/generator.py +1 -1
  239. oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
  240. oscura/export/wireshark_dissector.py +746 -0
  241. oscura/guidance/wizard.py +207 -111
  242. oscura/hardware/__init__.py +19 -0
  243. oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
  244. oscura/{acquisition → hardware/acquisition}/file.py +2 -2
  245. oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
  246. oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
  247. oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
  248. oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
  249. oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
  250. oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
  251. oscura/hardware/firmware/__init__.py +29 -0
  252. oscura/hardware/firmware/pattern_recognition.py +874 -0
  253. oscura/hardware/hal_detector.py +736 -0
  254. oscura/hardware/security/__init__.py +37 -0
  255. oscura/hardware/security/side_channel_detector.py +1126 -0
  256. oscura/inference/__init__.py +4 -0
  257. oscura/inference/active_learning/README.md +7 -7
  258. oscura/inference/active_learning/observation_table.py +4 -1
  259. oscura/inference/alignment.py +216 -123
  260. oscura/inference/bayesian.py +113 -33
  261. oscura/inference/crc_reverse.py +101 -55
  262. oscura/inference/logic.py +6 -2
  263. oscura/inference/message_format.py +342 -183
  264. oscura/inference/protocol.py +95 -44
  265. oscura/inference/protocol_dsl.py +180 -82
  266. oscura/inference/signal_intelligence.py +1439 -706
  267. oscura/inference/spectral.py +99 -57
  268. oscura/inference/state_machine.py +810 -158
  269. oscura/inference/stream.py +270 -110
  270. oscura/iot/__init__.py +34 -0
  271. oscura/iot/coap/__init__.py +32 -0
  272. oscura/iot/coap/analyzer.py +668 -0
  273. oscura/iot/coap/options.py +212 -0
  274. oscura/iot/lorawan/__init__.py +21 -0
  275. oscura/iot/lorawan/crypto.py +206 -0
  276. oscura/iot/lorawan/decoder.py +801 -0
  277. oscura/iot/lorawan/mac_commands.py +341 -0
  278. oscura/iot/mqtt/__init__.py +27 -0
  279. oscura/iot/mqtt/analyzer.py +999 -0
  280. oscura/iot/mqtt/properties.py +315 -0
  281. oscura/iot/zigbee/__init__.py +31 -0
  282. oscura/iot/zigbee/analyzer.py +615 -0
  283. oscura/iot/zigbee/security.py +153 -0
  284. oscura/iot/zigbee/zcl.py +349 -0
  285. oscura/jupyter/display.py +125 -45
  286. oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
  287. oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
  288. oscura/jupyter/exploratory/fuzzy.py +746 -0
  289. oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
  290. oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
  291. oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
  292. oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
  293. oscura/jupyter/exploratory/sync.py +612 -0
  294. oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
  295. oscura/jupyter/magic.py +4 -4
  296. oscura/{ui → jupyter/ui}/__init__.py +2 -2
  297. oscura/{ui → jupyter/ui}/formatters.py +3 -3
  298. oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
  299. oscura/loaders/__init__.py +171 -63
  300. oscura/loaders/binary.py +88 -1
  301. oscura/loaders/chipwhisperer.py +153 -137
  302. oscura/loaders/configurable.py +208 -86
  303. oscura/loaders/csv_loader.py +458 -215
  304. oscura/loaders/hdf5_loader.py +278 -119
  305. oscura/loaders/lazy.py +87 -54
  306. oscura/loaders/mmap_loader.py +1 -1
  307. oscura/loaders/numpy_loader.py +253 -116
  308. oscura/loaders/pcap.py +226 -151
  309. oscura/loaders/rigol.py +110 -49
  310. oscura/loaders/sigrok.py +201 -78
  311. oscura/loaders/tdms.py +81 -58
  312. oscura/loaders/tektronix.py +291 -174
  313. oscura/loaders/touchstone.py +182 -87
  314. oscura/loaders/vcd.py +215 -117
  315. oscura/loaders/wav.py +155 -68
  316. oscura/reporting/__init__.py +9 -7
  317. oscura/reporting/analyze.py +352 -146
  318. oscura/reporting/argument_preparer.py +69 -14
  319. oscura/reporting/auto_report.py +97 -61
  320. oscura/reporting/batch.py +131 -58
  321. oscura/reporting/chart_selection.py +57 -45
  322. oscura/reporting/comparison.py +63 -17
  323. oscura/reporting/content/executive.py +76 -24
  324. oscura/reporting/core_formats/multi_format.py +11 -8
  325. oscura/reporting/engine.py +312 -158
  326. oscura/reporting/enhanced_reports.py +949 -0
  327. oscura/reporting/export.py +86 -43
  328. oscura/reporting/formatting/numbers.py +69 -42
  329. oscura/reporting/html.py +139 -58
  330. oscura/reporting/index.py +137 -65
  331. oscura/reporting/output.py +158 -67
  332. oscura/reporting/pdf.py +67 -102
  333. oscura/reporting/plots.py +191 -112
  334. oscura/reporting/sections.py +88 -47
  335. oscura/reporting/standards.py +104 -61
  336. oscura/reporting/summary_generator.py +75 -55
  337. oscura/reporting/tables.py +138 -54
  338. oscura/reporting/templates/enhanced/protocol_re.html +525 -0
  339. oscura/reporting/templates/index.md +13 -13
  340. oscura/sessions/__init__.py +14 -23
  341. oscura/sessions/base.py +3 -3
  342. oscura/sessions/blackbox.py +106 -10
  343. oscura/sessions/generic.py +2 -2
  344. oscura/sessions/legacy.py +783 -0
  345. oscura/side_channel/__init__.py +63 -0
  346. oscura/side_channel/dpa.py +1025 -0
  347. oscura/utils/__init__.py +15 -1
  348. oscura/utils/autodetect.py +1 -5
  349. oscura/utils/bitwise.py +118 -0
  350. oscura/{builders → utils/builders}/__init__.py +1 -1
  351. oscura/{comparison → utils/comparison}/__init__.py +6 -6
  352. oscura/{comparison → utils/comparison}/compare.py +202 -101
  353. oscura/{comparison → utils/comparison}/golden.py +83 -63
  354. oscura/{comparison → utils/comparison}/limits.py +313 -89
  355. oscura/{comparison → utils/comparison}/mask.py +151 -45
  356. oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
  357. oscura/{comparison → utils/comparison}/visualization.py +147 -89
  358. oscura/{component → utils/component}/__init__.py +3 -3
  359. oscura/{component → utils/component}/impedance.py +122 -58
  360. oscura/{component → utils/component}/reactive.py +165 -168
  361. oscura/{component → utils/component}/transmission_line.py +3 -3
  362. oscura/{filtering → utils/filtering}/__init__.py +6 -6
  363. oscura/{filtering → utils/filtering}/base.py +1 -1
  364. oscura/{filtering → utils/filtering}/convenience.py +2 -2
  365. oscura/{filtering → utils/filtering}/design.py +169 -93
  366. oscura/{filtering → utils/filtering}/filters.py +2 -2
  367. oscura/{filtering → utils/filtering}/introspection.py +2 -2
  368. oscura/utils/geometry.py +31 -0
  369. oscura/utils/imports.py +184 -0
  370. oscura/utils/lazy.py +1 -1
  371. oscura/{math → utils/math}/__init__.py +2 -2
  372. oscura/{math → utils/math}/arithmetic.py +114 -48
  373. oscura/{math → utils/math}/interpolation.py +139 -106
  374. oscura/utils/memory.py +129 -66
  375. oscura/utils/memory_advanced.py +92 -9
  376. oscura/utils/memory_extensions.py +10 -8
  377. oscura/{optimization → utils/optimization}/__init__.py +1 -1
  378. oscura/{optimization → utils/optimization}/search.py +2 -2
  379. oscura/utils/performance/__init__.py +58 -0
  380. oscura/utils/performance/caching.py +889 -0
  381. oscura/utils/performance/lsh_clustering.py +333 -0
  382. oscura/utils/performance/memory_optimizer.py +699 -0
  383. oscura/utils/performance/optimizations.py +675 -0
  384. oscura/utils/performance/parallel.py +654 -0
  385. oscura/utils/performance/profiling.py +661 -0
  386. oscura/{pipeline → utils/pipeline}/base.py +1 -1
  387. oscura/{pipeline → utils/pipeline}/composition.py +11 -3
  388. oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
  389. oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
  390. oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
  391. oscura/{search → utils/search}/__init__.py +3 -3
  392. oscura/{search → utils/search}/anomaly.py +188 -58
  393. oscura/utils/search/context.py +294 -0
  394. oscura/{search → utils/search}/pattern.py +138 -10
  395. oscura/utils/serial.py +51 -0
  396. oscura/utils/storage/__init__.py +61 -0
  397. oscura/utils/storage/database.py +1166 -0
  398. oscura/{streaming → utils/streaming}/chunked.py +302 -143
  399. oscura/{streaming → utils/streaming}/progressive.py +1 -1
  400. oscura/{streaming → utils/streaming}/realtime.py +3 -2
  401. oscura/{triggering → utils/triggering}/__init__.py +6 -6
  402. oscura/{triggering → utils/triggering}/base.py +6 -6
  403. oscura/{triggering → utils/triggering}/edge.py +2 -2
  404. oscura/{triggering → utils/triggering}/pattern.py +2 -2
  405. oscura/{triggering → utils/triggering}/pulse.py +115 -74
  406. oscura/{triggering → utils/triggering}/window.py +2 -2
  407. oscura/utils/validation.py +32 -0
  408. oscura/validation/__init__.py +121 -0
  409. oscura/{compliance → validation/compliance}/__init__.py +5 -5
  410. oscura/{compliance → validation/compliance}/advanced.py +5 -5
  411. oscura/{compliance → validation/compliance}/masks.py +1 -1
  412. oscura/{compliance → validation/compliance}/reporting.py +127 -53
  413. oscura/{compliance → validation/compliance}/testing.py +114 -52
  414. oscura/validation/compliance_tests.py +915 -0
  415. oscura/validation/fuzzer.py +990 -0
  416. oscura/validation/grammar_tests.py +596 -0
  417. oscura/validation/grammar_validator.py +904 -0
  418. oscura/validation/hil_testing.py +977 -0
  419. oscura/{quality → validation/quality}/__init__.py +4 -4
  420. oscura/{quality → validation/quality}/ensemble.py +251 -171
  421. oscura/{quality → validation/quality}/explainer.py +3 -3
  422. oscura/{quality → validation/quality}/scoring.py +1 -1
  423. oscura/{quality → validation/quality}/warnings.py +4 -4
  424. oscura/validation/regression_suite.py +808 -0
  425. oscura/validation/replay.py +788 -0
  426. oscura/{testing → validation/testing}/__init__.py +2 -2
  427. oscura/{testing → validation/testing}/synthetic.py +5 -5
  428. oscura/visualization/__init__.py +9 -0
  429. oscura/visualization/accessibility.py +1 -1
  430. oscura/visualization/annotations.py +64 -67
  431. oscura/visualization/colors.py +7 -7
  432. oscura/visualization/digital.py +180 -81
  433. oscura/visualization/eye.py +236 -85
  434. oscura/visualization/interactive.py +320 -143
  435. oscura/visualization/jitter.py +587 -247
  436. oscura/visualization/layout.py +169 -134
  437. oscura/visualization/optimization.py +103 -52
  438. oscura/visualization/palettes.py +1 -1
  439. oscura/visualization/power.py +427 -211
  440. oscura/visualization/power_extended.py +626 -297
  441. oscura/visualization/presets.py +2 -0
  442. oscura/visualization/protocols.py +495 -181
  443. oscura/visualization/render.py +79 -63
  444. oscura/visualization/reverse_engineering.py +171 -124
  445. oscura/visualization/signal_integrity.py +460 -279
  446. oscura/visualization/specialized.py +190 -100
  447. oscura/visualization/spectral.py +670 -255
  448. oscura/visualization/thumbnails.py +166 -137
  449. oscura/visualization/waveform.py +150 -63
  450. oscura/workflows/__init__.py +3 -0
  451. oscura/{batch → workflows/batch}/__init__.py +5 -5
  452. oscura/{batch → workflows/batch}/advanced.py +150 -75
  453. oscura/workflows/batch/aggregate.py +531 -0
  454. oscura/workflows/batch/analyze.py +236 -0
  455. oscura/{batch → workflows/batch}/logging.py +2 -2
  456. oscura/{batch → workflows/batch}/metrics.py +1 -1
  457. oscura/workflows/complete_re.py +1144 -0
  458. oscura/workflows/compliance.py +44 -54
  459. oscura/workflows/digital.py +197 -51
  460. oscura/workflows/legacy/__init__.py +12 -0
  461. oscura/{workflow → workflows/legacy}/dag.py +4 -1
  462. oscura/workflows/multi_trace.py +9 -9
  463. oscura/workflows/power.py +42 -62
  464. oscura/workflows/protocol.py +82 -49
  465. oscura/workflows/reverse_engineering.py +351 -150
  466. oscura/workflows/signal_integrity.py +157 -82
  467. oscura-0.6.0.dist-info/METADATA +643 -0
  468. oscura-0.6.0.dist-info/RECORD +590 -0
  469. oscura/analyzers/digital/ic_database.py +0 -498
  470. oscura/analyzers/digital/timing_paths.py +0 -339
  471. oscura/analyzers/digital/vintage.py +0 -377
  472. oscura/analyzers/digital/vintage_result.py +0 -148
  473. oscura/analyzers/protocols/parallel_bus.py +0 -449
  474. oscura/batch/aggregate.py +0 -300
  475. oscura/batch/analyze.py +0 -139
  476. oscura/dsl/__init__.py +0 -73
  477. oscura/exceptions.py +0 -59
  478. oscura/exploratory/fuzzy.py +0 -513
  479. oscura/exploratory/sync.py +0 -384
  480. oscura/export/wavedrom.py +0 -430
  481. oscura/exporters/__init__.py +0 -94
  482. oscura/exporters/csv.py +0 -303
  483. oscura/exporters/exporters.py +0 -44
  484. oscura/exporters/hdf5.py +0 -217
  485. oscura/exporters/html_export.py +0 -701
  486. oscura/exporters/json_export.py +0 -338
  487. oscura/exporters/markdown_export.py +0 -367
  488. oscura/exporters/matlab_export.py +0 -354
  489. oscura/exporters/npz_export.py +0 -219
  490. oscura/exporters/spice_export.py +0 -210
  491. oscura/exporters/vintage_logic_csv.py +0 -247
  492. oscura/reporting/vintage_logic_report.py +0 -523
  493. oscura/search/context.py +0 -149
  494. oscura/session/__init__.py +0 -34
  495. oscura/session/annotations.py +0 -289
  496. oscura/session/history.py +0 -313
  497. oscura/session/session.py +0 -520
  498. oscura/visualization/digital_advanced.py +0 -718
  499. oscura/visualization/figure_manager.py +0 -156
  500. oscura/workflow/__init__.py +0 -13
  501. oscura-0.5.0.dist-info/METADATA +0 -407
  502. oscura-0.5.0.dist-info/RECORD +0 -486
  503. /oscura/core/{config.py → config/legacy.py} +0 -0
  504. /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
  505. /oscura/{extensibility → core/extensibility}/registry.py +0 -0
  506. /oscura/{plugins → core/plugins}/isolation.py +0 -0
  507. /oscura/{builders → utils/builders}/signal_builder.py +0 -0
  508. /oscura/{optimization → utils/optimization}/parallel.py +0 -0
  509. /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
  510. /oscura/{streaming → utils/streaming}/__init__.py +0 -0
  511. {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/WHEEL +0 -0
  512. {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/entry_points.txt +0 -0
  513. {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/licenses/LICENSE +0 -0
oscura/cli/batch.py CHANGED
@@ -24,38 +24,38 @@ from oscura.cli.main import format_output
24
24
  logger = logging.getLogger("oscura.cli.batch")
25
25
 
26
26
 
27
- @click.command() # type: ignore[misc]
28
- @click.argument("pattern") # type: ignore[misc]
29
- @click.option( # type: ignore[misc]
27
+ @click.command()
28
+ @click.argument("pattern")
29
+ @click.option(
30
30
  "--analysis",
31
31
  type=click.Choice(["characterize", "decode", "spectrum"], case_sensitive=False),
32
32
  required=True,
33
33
  help="Type of analysis to perform on each file.",
34
34
  )
35
- @click.option( # type: ignore[misc]
35
+ @click.option(
36
36
  "--parallel",
37
37
  type=int,
38
38
  default=1,
39
39
  help="Number of files to process concurrently (default: 1).",
40
40
  )
41
- @click.option( # type: ignore[misc]
41
+ @click.option(
42
42
  "--output",
43
43
  type=click.Choice(["json", "csv", "html", "table"], case_sensitive=False),
44
44
  default="table",
45
45
  help="Output format (default: table).",
46
46
  )
47
- @click.option( # type: ignore[misc]
47
+ @click.option(
48
48
  "--save-summary",
49
49
  type=click.Path(),
50
50
  default=None,
51
51
  help="Save aggregated results to file (CSV format).",
52
52
  )
53
- @click.option( # type: ignore[misc]
53
+ @click.option(
54
54
  "--continue-on-error",
55
55
  is_flag=True,
56
56
  help="Continue processing even if individual files fail.",
57
57
  )
58
- @click.pass_context # type: ignore[misc]
58
+ @click.pass_context
59
59
  def batch(
60
60
  ctx: click.Context,
61
61
  pattern: str,
@@ -110,7 +110,7 @@ def batch(
110
110
 
111
111
  try:
112
112
  # Expand glob pattern
113
- files = glob.glob(pattern, recursive=True) # noqa: PTH207
113
+ files = glob.glob(pattern, recursive=True)
114
114
 
115
115
  if not files:
116
116
  click.echo(f"No files matched pattern: {pattern}", err=True)
@@ -145,133 +145,91 @@ def batch(
145
145
  ctx.exit(1)
146
146
 
147
147
 
148
- def _perform_batch_analysis(
149
- files: list[str],
150
- analysis_type: str,
151
- parallel: int,
152
- continue_on_error: bool,
153
- verbose: int,
154
- ) -> list[dict[str, Any]]:
155
- """Perform batch analysis on multiple files.
156
-
157
- Uses concurrent.futures for parallel processing when parallel > 1.
148
+ def _analyze_single_file(file_path: str, analysis_type: str) -> dict[str, Any]:
149
+ """Analyze a single file and return results.
158
150
 
159
151
  Args:
160
- files: List of file paths to process.
152
+ file_path: Path to waveform file to analyze.
161
153
  analysis_type: Type of analysis to perform.
162
- parallel: Number of parallel workers.
163
- continue_on_error: Whether to continue on errors.
164
- verbose: Verbosity level.
165
154
 
166
155
  Returns:
167
- List of result dictionaries, one per file.
168
-
169
- Raises:
170
- Exception: If analysis fails and continue_on_error is False.
156
+ Dictionary containing analysis results.
171
157
  """
172
- from concurrent.futures import ThreadPoolExecutor, as_completed
158
+ import numpy as np
159
+
160
+ from oscura.analyzers.waveform.measurements import fall_time, rise_time
161
+ from oscura.analyzers.waveform.spectral import fft, thd
162
+ from oscura.inference import detect_protocol
163
+ from oscura.loaders import load
164
+
165
+ trace = load(file_path)
166
+ sample_rate = trace.metadata.sample_rate
167
+
168
+ result: dict[str, Any] = {
169
+ "file": str(Path(file_path).name),
170
+ "status": "success",
171
+ "analysis_type": analysis_type,
172
+ "samples": len(trace.data), # type: ignore[union-attr]
173
+ "sample_rate": f"{sample_rate / 1e6:.1f} MHz",
174
+ }
173
175
 
174
- def analyze_single_file(file_path: str) -> dict[str, Any]:
175
- """Analyze a single file and return results.
176
-
177
- Args:
178
- file_path: Path to waveform file to analyze.
179
-
180
- Returns:
181
- Dictionary containing analysis results.
182
- """
183
- import numpy as np
184
-
185
- from oscura.analyzers.waveform.measurements import fall_time, rise_time
186
- from oscura.analyzers.waveform.spectral import fft, thd
187
- from oscura.inference import detect_protocol
188
- from oscura.loaders import load
189
-
190
- # Load trace
191
- trace = load(file_path)
192
- sample_rate = trace.metadata.sample_rate
193
-
194
- # Base result
195
- result: dict[str, Any] = {
196
- "file": str(Path(file_path).name),
197
- "status": "success",
198
- "analysis_type": analysis_type,
199
- "samples": len(trace.data), # type: ignore[union-attr]
200
- "sample_rate": f"{sample_rate / 1e6:.1f} MHz",
201
- }
202
-
203
- # Add analysis-specific results
204
- if analysis_type == "characterize":
205
- # Pass WaveformTrace directly to functions (they expect WaveformTrace)
206
- rt = rise_time(trace) # type: ignore[arg-type]
207
- ft = fall_time(trace) # type: ignore[arg-type]
208
- result.update(
209
- {
210
- "rise_time": f"{rt * 1e9:.2f} ns" if not np.isnan(rt) else "N/A",
211
- "fall_time": f"{ft * 1e9:.2f} ns" if not np.isnan(ft) else "N/A",
212
- }
213
- )
214
- elif analysis_type == "decode":
215
- detected = detect_protocol(trace) # type: ignore[arg-type]
216
- result.update(
217
- {
218
- "protocol": detected.get("protocol", "unknown"),
219
- "confidence": f"{detected.get('confidence', 0) * 100:.0f}%",
220
- }
221
- )
222
- elif analysis_type == "spectrum":
223
- # Pass WaveformTrace directly to FFT functions
224
- freqs, mags = fft(trace) # type: ignore[misc, arg-type]
225
- if len(mags) > 0:
226
- peak_idx = int(np.argmax(mags))
227
- peak_freq = freqs[peak_idx]
228
- else:
229
- peak_freq = 0.0
230
- thd_val = thd(trace) # type: ignore[arg-type]
231
- result.update(
232
- {
233
- "peak_frequency": f"{peak_freq / 1e6:.3f} MHz",
234
- "thd": f"{thd_val:.1f} dB" if not np.isnan(thd_val) else "N/A",
235
- }
236
- )
176
+ if analysis_type == "characterize":
177
+ rt = rise_time(trace) # type: ignore[arg-type]
178
+ ft = fall_time(trace) # type: ignore[arg-type]
179
+ result.update(
180
+ {
181
+ "rise_time": f"{rt * 1e9:.2f} ns" if not np.isnan(rt) else "N/A",
182
+ "fall_time": f"{ft * 1e9:.2f} ns" if not np.isnan(ft) else "N/A",
183
+ }
184
+ )
185
+ elif analysis_type == "decode":
186
+ detected = detect_protocol(trace) # type: ignore[arg-type]
187
+ result.update(
188
+ {
189
+ "protocol": detected.get("protocol", "unknown"),
190
+ "confidence": f"{detected.get('confidence', 0) * 100:.0f}%",
191
+ }
192
+ )
193
+ elif analysis_type == "spectrum":
194
+ freqs, mags = fft(trace) # type: ignore[misc, arg-type]
195
+ if len(mags) > 0:
196
+ peak_idx = int(np.argmax(mags))
197
+ peak_freq = freqs[peak_idx]
198
+ else:
199
+ peak_freq = 0.0
200
+ thd_val = thd(trace) # type: ignore[arg-type]
201
+ result.update(
202
+ {
203
+ "peak_frequency": f"{peak_freq / 1e6:.3f} MHz",
204
+ "thd": f"{thd_val:.1f} dB" if not np.isnan(thd_val) else "N/A",
205
+ }
206
+ )
237
207
 
238
- return result
208
+ return result
209
+
210
+
211
+ def _process_parallel(
212
+ files: list[str],
213
+ analysis_type: str,
214
+ parallel: int,
215
+ continue_on_error: bool,
216
+ verbose: int,
217
+ ) -> list[dict[str, Any]]:
218
+ """Process files in parallel using ThreadPoolExecutor."""
219
+ from concurrent.futures import ThreadPoolExecutor, as_completed
239
220
 
240
221
  results: list[dict[str, Any]] = []
241
222
 
242
- if parallel > 1:
243
- # Parallel processing using ThreadPoolExecutor
244
- with ThreadPoolExecutor(max_workers=parallel) as executor:
245
- future_to_file = {executor.submit(analyze_single_file, f): f for f in files}
246
-
247
- for i, future in enumerate(as_completed(future_to_file), 1):
248
- file_path = future_to_file[future]
249
- if verbose:
250
- logger.info(f"[{i}/{len(files)}] Completed {Path(file_path).name}")
251
-
252
- try:
253
- result = future.result()
254
- results.append(result)
255
- except Exception as e:
256
- logger.error(f"Failed to process {file_path}: {e}")
257
- if continue_on_error:
258
- results.append(
259
- {
260
- "file": str(Path(file_path).name),
261
- "status": "error",
262
- "error": str(e),
263
- }
264
- )
265
- else:
266
- raise
267
- else:
268
- # Sequential processing
269
- for i, file_path in enumerate(files, 1):
223
+ with ThreadPoolExecutor(max_workers=parallel) as executor:
224
+ future_to_file = {executor.submit(_analyze_single_file, f, analysis_type): f for f in files}
225
+
226
+ for i, future in enumerate(as_completed(future_to_file), 1):
227
+ file_path = future_to_file[future]
270
228
  if verbose:
271
- logger.info(f"[{i}/{len(files)}] Processing {file_path}")
229
+ logger.info(f"[{i}/{len(files)}] Completed {Path(file_path).name}")
272
230
 
273
231
  try:
274
- result = analyze_single_file(file_path)
232
+ result = future.result()
275
233
  results.append(result)
276
234
  except Exception as e:
277
235
  logger.error(f"Failed to process {file_path}: {e}")
@@ -289,6 +247,68 @@ def _perform_batch_analysis(
289
247
  return results
290
248
 
291
249
 
250
+ def _process_sequential(
251
+ files: list[str],
252
+ analysis_type: str,
253
+ continue_on_error: bool,
254
+ verbose: int,
255
+ ) -> list[dict[str, Any]]:
256
+ """Process files sequentially."""
257
+ results: list[dict[str, Any]] = []
258
+
259
+ for i, file_path in enumerate(files, 1):
260
+ if verbose:
261
+ logger.info(f"[{i}/{len(files)}] Processing {file_path}")
262
+
263
+ try:
264
+ result = _analyze_single_file(file_path, analysis_type)
265
+ results.append(result)
266
+ except Exception as e:
267
+ logger.error(f"Failed to process {file_path}: {e}")
268
+ if continue_on_error:
269
+ results.append(
270
+ {
271
+ "file": str(Path(file_path).name),
272
+ "status": "error",
273
+ "error": str(e),
274
+ }
275
+ )
276
+ else:
277
+ raise
278
+
279
+ return results
280
+
281
+
282
+ def _perform_batch_analysis(
283
+ files: list[str],
284
+ analysis_type: str,
285
+ parallel: int,
286
+ continue_on_error: bool,
287
+ verbose: int,
288
+ ) -> list[dict[str, Any]]:
289
+ """Perform batch analysis on multiple files.
290
+
291
+ Uses concurrent.futures for parallel processing when parallel > 1.
292
+
293
+ Args:
294
+ files: List of file paths to process.
295
+ analysis_type: Type of analysis to perform.
296
+ parallel: Number of parallel workers.
297
+ continue_on_error: Whether to continue on errors.
298
+ verbose: Verbosity level.
299
+
300
+ Returns:
301
+ List of result dictionaries, one per file.
302
+
303
+ Raises:
304
+ Exception: If analysis fails and continue_on_error is False.
305
+ """
306
+ if parallel > 1:
307
+ return _process_parallel(files, analysis_type, parallel, continue_on_error, verbose)
308
+ else:
309
+ return _process_sequential(files, analysis_type, continue_on_error, verbose)
310
+
311
+
292
312
  def _generate_summary(results: list[dict[str, Any]]) -> dict[str, Any]:
293
313
  """Generate summary statistics from batch results.
294
314
 
@@ -0,0 +1,275 @@
1
+ """Oscura Benchmark Command - Performance Benchmarking.
2
+
3
+ Provides CLI for performance benchmarking of analysis operations.
4
+
5
+
6
+ Example:
7
+ $ oscura benchmark --operations all
8
+ $ oscura benchmark --operations decode --protocol uart
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import logging
14
+ import time
15
+ from typing import Any
16
+
17
+ import click
18
+
19
+ logger = logging.getLogger("oscura.cli.benchmark")
20
+
21
+
22
+ @click.command()
23
+ @click.option(
24
+ "--operations",
25
+ type=click.Choice(["all", "load", "decode", "fft", "measurements"], case_sensitive=False),
26
+ default="all",
27
+ help="Operations to benchmark.",
28
+ )
29
+ @click.option(
30
+ "--protocol",
31
+ type=str,
32
+ default="uart",
33
+ help="Protocol for decode benchmark.",
34
+ )
35
+ @click.option(
36
+ "--iterations",
37
+ type=int,
38
+ default=10,
39
+ help="Number of iterations per benchmark.",
40
+ )
41
+ @click.option(
42
+ "--output",
43
+ type=click.Choice(["json", "table"], case_sensitive=False),
44
+ default="table",
45
+ help="Output format.",
46
+ )
47
+ @click.pass_context
48
+ def benchmark(
49
+ ctx: click.Context,
50
+ operations: str,
51
+ protocol: str,
52
+ iterations: int,
53
+ output: str,
54
+ ) -> None:
55
+ """Run performance benchmarks.
56
+
57
+ Measures performance of core operations like loading, decoding,
58
+ FFT, and measurements.
59
+
60
+ Args:
61
+ ctx: Click context object.
62
+ operations: Operations to benchmark.
63
+ protocol: Protocol for decode benchmark.
64
+ iterations: Number of iterations.
65
+ output: Output format.
66
+
67
+ Examples:
68
+
69
+ \b
70
+ # Benchmark all operations
71
+ $ oscura benchmark --operations all
72
+
73
+ \b
74
+ # Benchmark specific operation
75
+ $ oscura benchmark --operations decode --protocol uart --iterations 100
76
+ """
77
+ verbose = ctx.obj.get("verbose", 0)
78
+
79
+ if verbose:
80
+ logger.info(f"Running benchmark: {operations}")
81
+
82
+ try:
83
+ results: dict[str, Any] = {"iterations": iterations, "benchmarks": {}}
84
+
85
+ # Generate test data
86
+ test_data = _generate_test_data()
87
+
88
+ # Run benchmarks
89
+ if operations in ["all", "load"]:
90
+ results["benchmarks"]["load"] = _benchmark_load(test_data, iterations)
91
+
92
+ if operations in ["all", "decode"]:
93
+ results["benchmarks"]["decode"] = _benchmark_decode(test_data, protocol, iterations)
94
+
95
+ if operations in ["all", "fft"]:
96
+ results["benchmarks"]["fft"] = _benchmark_fft(test_data, iterations)
97
+
98
+ if operations in ["all", "measurements"]:
99
+ results["benchmarks"]["measurements"] = _benchmark_measurements(test_data, iterations)
100
+
101
+ # Output results
102
+ if output == "json":
103
+ import json
104
+
105
+ click.echo(json.dumps(results, indent=2))
106
+ else:
107
+ _print_table(results)
108
+
109
+ except Exception as e:
110
+ logger.error(f"Benchmark failed: {e}")
111
+ if verbose > 1:
112
+ raise
113
+ click.echo(f"Error: {e}", err=True)
114
+ ctx.exit(1)
115
+
116
+
117
+ def _generate_test_data() -> Any:
118
+ """Generate test data for benchmarking.
119
+
120
+ Returns:
121
+ Test waveform trace.
122
+ """
123
+ import numpy as np
124
+
125
+ from oscura.core.types import TraceMetadata, WaveformTrace
126
+
127
+ # Generate 100k sample waveform
128
+ samples = 100000
129
+ sample_rate = 1e6
130
+ t = np.arange(samples) / sample_rate
131
+
132
+ # Mix of sine waves
133
+ data = np.sin(2 * np.pi * 1000 * t) + 0.5 * np.sin(2 * np.pi * 5000 * t)
134
+
135
+ return WaveformTrace(data=data, metadata=TraceMetadata(sample_rate=sample_rate))
136
+
137
+
138
+ def _benchmark_load(test_data: Any, iterations: int) -> dict[str, Any]:
139
+ """Benchmark data loading.
140
+
141
+ Args:
142
+ test_data: Test data.
143
+ iterations: Number of iterations.
144
+
145
+ Returns:
146
+ Benchmark results.
147
+ """
148
+ import tempfile
149
+ from pathlib import Path
150
+
151
+ import numpy as np
152
+
153
+ from oscura.loaders import load
154
+
155
+ # Save test data to temp file (use .npz which is supported)
156
+ with tempfile.NamedTemporaryFile(suffix=".npz", delete=False) as f:
157
+ temp_path = Path(f.name)
158
+ np.savez(temp_path, data=test_data.data)
159
+
160
+ try:
161
+ start = time.time()
162
+ for _ in range(iterations):
163
+ _ = load(str(temp_path))
164
+ elapsed = time.time() - start
165
+
166
+ return {
167
+ "total_time": f"{elapsed:.3f}s",
168
+ "avg_time": f"{elapsed / iterations * 1000:.2f}ms",
169
+ "throughput": f"{iterations / elapsed:.1f} ops/sec",
170
+ }
171
+ finally:
172
+ temp_path.unlink()
173
+
174
+
175
+ def _benchmark_decode(test_data: Any, protocol: str, iterations: int) -> dict[str, Any]:
176
+ """Benchmark protocol decoding.
177
+
178
+ Args:
179
+ test_data: Test data.
180
+ protocol: Protocol name.
181
+ iterations: Number of iterations.
182
+
183
+ Returns:
184
+ Benchmark results.
185
+ """
186
+ import numpy as np
187
+
188
+ from oscura.core.types import DigitalTrace
189
+
190
+ # Convert to digital
191
+ threshold = np.mean(test_data.data)
192
+ digital = test_data.data > threshold
193
+ digital_trace = DigitalTrace(data=digital, metadata=test_data.metadata)
194
+
195
+ start = time.time()
196
+ for _ in range(iterations):
197
+ if protocol.lower() == "uart":
198
+ from oscura.analyzers.protocols.uart import UARTDecoder
199
+
200
+ decoder = UARTDecoder(baudrate=9600)
201
+ _ = list(decoder.decode(digital_trace))
202
+
203
+ elapsed = time.time() - start
204
+
205
+ return {
206
+ "protocol": protocol,
207
+ "total_time": f"{elapsed:.3f}s",
208
+ "avg_time": f"{elapsed / iterations * 1000:.2f}ms",
209
+ "throughput": f"{iterations / elapsed:.1f} ops/sec",
210
+ }
211
+
212
+
213
+ def _benchmark_fft(test_data: Any, iterations: int) -> dict[str, Any]:
214
+ """Benchmark FFT computation.
215
+
216
+ Args:
217
+ test_data: Test data.
218
+ iterations: Number of iterations.
219
+
220
+ Returns:
221
+ Benchmark results.
222
+ """
223
+ from oscura.analyzers.waveform.spectral import fft
224
+
225
+ start = time.time()
226
+ for _ in range(iterations):
227
+ _ = fft(test_data)
228
+ elapsed = time.time() - start
229
+
230
+ return {
231
+ "total_time": f"{elapsed:.3f}s",
232
+ "avg_time": f"{elapsed / iterations * 1000:.2f}ms",
233
+ "throughput": f"{iterations / elapsed:.1f} ops/sec",
234
+ }
235
+
236
+
237
+ def _benchmark_measurements(test_data: Any, iterations: int) -> dict[str, Any]:
238
+ """Benchmark waveform measurements.
239
+
240
+ Args:
241
+ test_data: Test data.
242
+ iterations: Number of iterations.
243
+
244
+ Returns:
245
+ Benchmark results.
246
+ """
247
+ from oscura.analyzers.waveform.measurements import fall_time, rise_time
248
+
249
+ start = time.time()
250
+ for _ in range(iterations):
251
+ _ = rise_time(test_data)
252
+ _ = fall_time(test_data)
253
+ elapsed = time.time() - start
254
+
255
+ return {
256
+ "total_time": f"{elapsed:.3f}s",
257
+ "avg_time": f"{elapsed / iterations * 1000:.2f}ms",
258
+ "throughput": f"{iterations / elapsed:.1f} ops/sec",
259
+ }
260
+
261
+
262
+ def _print_table(results: dict[str, Any]) -> None:
263
+ """Print results as table.
264
+
265
+ Args:
266
+ results: Benchmark results.
267
+ """
268
+ click.echo("\n=== Benchmark Results ===\n")
269
+ click.echo(f"Iterations: {results['iterations']}\n")
270
+
271
+ for name, bench in results["benchmarks"].items():
272
+ click.echo(f"{name.upper()}:")
273
+ for key, value in bench.items():
274
+ click.echo(f" {key}: {value}")
275
+ click.echo()