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
@@ -0,0 +1,204 @@
1
+ """Oscura Validate Command - Protocol Specification Validation.
2
+
3
+ Provides CLI for validating protocol specifications and message structures.
4
+
5
+
6
+ Example:
7
+ $ oscura validate protocol_spec.yaml
8
+ $ oscura validate --spec uart --test-data capture.wfm
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import logging
14
+ from pathlib import Path
15
+ from typing import Any
16
+
17
+ import click
18
+
19
+ logger = logging.getLogger("oscura.cli.validate")
20
+
21
+
22
+ @click.command()
23
+ @click.argument("spec", type=click.Path(exists=True))
24
+ @click.option(
25
+ "--test-data",
26
+ type=click.Path(exists=True),
27
+ default=None,
28
+ help="Test data file for validation.",
29
+ )
30
+ @click.option(
31
+ "--output",
32
+ type=click.Choice(["json", "table"], case_sensitive=False),
33
+ default="table",
34
+ help="Output format.",
35
+ )
36
+ @click.pass_context
37
+ def validate(
38
+ ctx: click.Context,
39
+ spec: str,
40
+ test_data: str | None,
41
+ output: str,
42
+ ) -> None:
43
+ """Validate protocol specification.
44
+
45
+ Validates protocol specification files (YAML/JSON) and optionally
46
+ tests them against real data.
47
+
48
+ Args:
49
+ ctx: Click context object.
50
+ spec: Path to specification file.
51
+ test_data: Optional test data file.
52
+ output: Output format.
53
+
54
+ Examples:
55
+
56
+ \b
57
+ # Validate spec only
58
+ $ oscura validate protocol_spec.yaml
59
+
60
+ \b
61
+ # Validate against test data
62
+ $ oscura validate uart_spec.yaml --test-data capture.wfm
63
+ """
64
+ verbose = ctx.obj.get("verbose", 0)
65
+
66
+ if verbose:
67
+ logger.info(f"Validating specification: {spec}")
68
+
69
+ try:
70
+ import yaml
71
+
72
+ # Load specification
73
+ spec_path = Path(spec)
74
+ with open(spec_path) as f:
75
+ if spec_path.suffix in [".yaml", ".yml"]:
76
+ spec_data = yaml.safe_load(f)
77
+ else:
78
+ import json
79
+
80
+ spec_data = json.load(f)
81
+
82
+ # Validate spec structure
83
+ validation_results: dict[str, Any] = {
84
+ "spec_file": str(spec_path.name),
85
+ "valid": True,
86
+ "errors": [],
87
+ "warnings": [],
88
+ }
89
+
90
+ _validate_spec_structure(spec_data, validation_results)
91
+
92
+ # Validate against test data if provided
93
+ if test_data:
94
+ from oscura.loaders import load
95
+
96
+ trace = load(test_data)
97
+ _validate_against_data(spec_data, trace, validation_results)
98
+
99
+ # Output results
100
+ if output == "json":
101
+ import json
102
+
103
+ click.echo(json.dumps(validation_results, indent=2))
104
+ else:
105
+ _print_validation_results(validation_results)
106
+
107
+ # Exit with error if validation failed
108
+ if not validation_results["valid"]:
109
+ ctx.exit(1)
110
+
111
+ except Exception as e:
112
+ logger.error(f"Validation failed: {e}")
113
+ if verbose > 1:
114
+ raise
115
+ click.echo(f"Error: {e}", err=True)
116
+ ctx.exit(1)
117
+
118
+
119
+ def _validate_spec_structure(spec: dict[str, Any], results: dict[str, Any]) -> None:
120
+ """Validate specification structure.
121
+
122
+ Args:
123
+ spec: Specification dictionary.
124
+ results: Results dictionary to update.
125
+ """
126
+ required_fields = ["name", "version"]
127
+
128
+ for field in required_fields:
129
+ if field not in spec:
130
+ results["errors"].append(f"Missing required field: {field}")
131
+ results["valid"] = False
132
+
133
+ # Check optional but recommended fields
134
+ recommended_fields = ["description", "fields", "constraints"]
135
+ for field in recommended_fields:
136
+ if field not in spec:
137
+ results["warnings"].append(f"Missing recommended field: {field}")
138
+
139
+ # Validate fields if present
140
+ if "fields" in spec and isinstance(spec["fields"], list):
141
+ for i, field in enumerate(spec["fields"]):
142
+ if not isinstance(field, dict):
143
+ results["errors"].append(f"Field {i} is not a dictionary")
144
+ results["valid"] = False
145
+ elif "name" not in field:
146
+ results["errors"].append(f"Field {i} missing 'name' attribute")
147
+ results["valid"] = False
148
+
149
+
150
+ def _validate_against_data(spec: dict[str, Any], trace: Any, results: dict[str, Any]) -> None:
151
+ """Validate specification against test data.
152
+
153
+ Args:
154
+ spec: Specification dictionary.
155
+ trace: Test data trace.
156
+ results: Results dictionary to update.
157
+ """
158
+ # Check sample rate requirements
159
+ if "sample_rate_min" in spec:
160
+ min_rate = spec["sample_rate_min"]
161
+ if trace.metadata.sample_rate < min_rate:
162
+ results["warnings"].append(
163
+ f"Sample rate {trace.metadata.sample_rate} below minimum {min_rate}"
164
+ )
165
+
166
+ # Check data length
167
+ if "min_samples" in spec:
168
+ min_samples = spec["min_samples"]
169
+ if len(trace.data) < min_samples:
170
+ results["errors"].append(
171
+ f"Data has {len(trace.data)} samples, need at least {min_samples}"
172
+ )
173
+ results["valid"] = False
174
+
175
+ results["test_data_samples"] = len(trace.data)
176
+ results["test_data_sample_rate"] = trace.metadata.sample_rate
177
+
178
+
179
+ def _print_validation_results(results: dict[str, Any]) -> None:
180
+ """Print validation results.
181
+
182
+ Args:
183
+ results: Validation results dictionary.
184
+ """
185
+ click.echo("\n=== Validation Results ===\n")
186
+ click.echo(f"Specification: {results['spec_file']}")
187
+ click.echo(f"Status: {'PASS' if results['valid'] else 'FAIL'}\n")
188
+
189
+ if results["errors"]:
190
+ click.echo("Errors:")
191
+ for err in results["errors"]:
192
+ click.echo(f" - {err}")
193
+ click.echo()
194
+
195
+ if results["warnings"]:
196
+ click.echo("Warnings:")
197
+ for warn in results["warnings"]:
198
+ click.echo(f" - {warn}")
199
+ click.echo()
200
+
201
+ if "test_data_samples" in results:
202
+ click.echo("Test Data:")
203
+ click.echo(f" Samples: {results['test_data_samples']}")
204
+ click.echo(f" Sample Rate: {results['test_data_sample_rate']} Hz")
@@ -0,0 +1,158 @@
1
+ """Oscura Visualize Command - Interactive Waveform Viewer.
2
+
3
+ Provides CLI for launching interactive waveform visualization.
4
+
5
+
6
+ Example:
7
+ $ oscura visualize signal.wfm
8
+ $ oscura visualize capture.wfm --protocol uart
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import logging
14
+ from pathlib import Path
15
+ from typing import TYPE_CHECKING
16
+
17
+ import click
18
+
19
+ if TYPE_CHECKING:
20
+ from matplotlib.axes import Axes
21
+
22
+ from oscura.core.types import WaveformTrace
23
+
24
+ logger = logging.getLogger("oscura.cli.visualize")
25
+
26
+
27
+ @click.command()
28
+ @click.argument("file", type=click.Path(exists=True))
29
+ @click.option(
30
+ "--protocol",
31
+ type=str,
32
+ default=None,
33
+ help="Protocol for overlay annotations.",
34
+ )
35
+ @click.option(
36
+ "--save",
37
+ type=click.Path(),
38
+ default=None,
39
+ help="Save plot to file instead of showing.",
40
+ )
41
+ @click.pass_context
42
+ def visualize(
43
+ ctx: click.Context,
44
+ file: str,
45
+ protocol: str | None,
46
+ save: str | None,
47
+ ) -> None:
48
+ """Launch interactive waveform viewer.
49
+
50
+ Opens an interactive plot window for waveform analysis with zoom,
51
+ pan, and measurement tools.
52
+
53
+ Args:
54
+ ctx: Click context object.
55
+ file: Path to waveform file.
56
+ protocol: Optional protocol for annotations.
57
+ save: Save plot to file instead of displaying.
58
+
59
+ Examples:
60
+
61
+ \b
62
+ # Interactive viewer
63
+ $ oscura visualize signal.wfm
64
+
65
+ \b
66
+ # With protocol overlay
67
+ $ oscura visualize capture.wfm --protocol uart
68
+
69
+ \b
70
+ # Save to file
71
+ $ oscura visualize data.wfm --save plot.png
72
+ """
73
+ verbose = ctx.obj.get("verbose", 0)
74
+
75
+ if verbose:
76
+ logger.info(f"Visualizing: {file}")
77
+
78
+ try:
79
+ import matplotlib.pyplot as plt
80
+ import numpy as np
81
+
82
+ from oscura.core.types import IQTrace, WaveformTrace
83
+ from oscura.loaders import load
84
+
85
+ # Load file
86
+ trace = load(file)
87
+
88
+ # Create plot
89
+ fig, ax = plt.subplots(figsize=(12, 6))
90
+
91
+ # Plot waveform (handle IQTrace separately)
92
+ if isinstance(trace, IQTrace):
93
+ # Plot I/Q components
94
+ time_axis = np.arange(len(trace.i_data)) / trace.metadata.sample_rate
95
+ ax.plot(time_axis * 1e3, trace.i_data, linewidth=0.5, label="I")
96
+ ax.plot(time_axis * 1e3, trace.q_data, linewidth=0.5, label="Q")
97
+ ax.legend()
98
+ else:
99
+ # Plot regular waveform or digital trace
100
+ time_axis = np.arange(len(trace.data)) / trace.metadata.sample_rate
101
+ ax.plot(time_axis * 1e3, trace.data, linewidth=0.5)
102
+
103
+ ax.set_xlabel("Time (ms)")
104
+ ax.set_ylabel("Voltage (V)")
105
+ ax.set_title(f"Waveform: {Path(file).name}")
106
+ ax.grid(True, alpha=0.3)
107
+
108
+ # Add protocol overlay if requested (only for non-IQ traces)
109
+ if protocol and isinstance(trace, WaveformTrace):
110
+ _add_protocol_overlay(ax, trace, protocol)
111
+
112
+ # Save or show
113
+ if save:
114
+ plt.savefig(save, dpi=300, bbox_inches="tight")
115
+ click.echo(f"Saved to: {save}")
116
+ else:
117
+ plt.tight_layout()
118
+ plt.show()
119
+
120
+ except Exception as e:
121
+ logger.error(f"Visualization failed: {e}")
122
+ if verbose > 1:
123
+ raise
124
+ click.echo(f"Error: {e}", err=True)
125
+ ctx.exit(1)
126
+
127
+
128
+ def _add_protocol_overlay(ax: Axes, trace: WaveformTrace, protocol: str) -> None:
129
+ """Add protocol annotations to plot.
130
+
131
+ Args:
132
+ ax: Matplotlib axis.
133
+ trace: Waveform trace.
134
+ protocol: Protocol name.
135
+ """
136
+ import numpy as np
137
+
138
+ # Convert to digital
139
+ threshold = (np.max(trace.data) + np.min(trace.data)) / 2
140
+ digital = trace.data > threshold
141
+
142
+ # Find edges
143
+ edges = np.where(np.diff(digital.astype(int)) != 0)[0]
144
+
145
+ # Mark edges
146
+ time_axis = np.arange(len(trace.data)) / trace.metadata.sample_rate
147
+ for edge in edges[:100]: # Limit to first 100 edges
148
+ ax.axvline(time_axis[edge] * 1e3, color="red", alpha=0.3, linewidth=0.5)
149
+
150
+ ax.text(
151
+ 0.98,
152
+ 0.98,
153
+ f"Protocol: {protocol.upper()}",
154
+ transform=ax.transAxes,
155
+ ha="right",
156
+ va="top",
157
+ bbox={"boxstyle": "round", "facecolor": "wheat", "alpha": 0.5},
158
+ )
oscura/convenience.py CHANGED
@@ -195,109 +195,142 @@ def auto_decode(
195
195
  References:
196
196
  sigrok Protocol Decoder API
197
197
  """
198
+
199
+ # Prepare digital trace
200
+ digital_trace = _prepare_digital_trace(trace)
201
+
202
+ # Detect or use specified protocol
203
+ protocol, config, confidence = _detect_or_select_protocol(
204
+ trace, protocol, min_confidence, digital_trace
205
+ )
206
+
207
+ # Decode frames
208
+ frames, errors = _decode_protocol_frames(protocol, config, digital_trace)
209
+
210
+ # Calculate statistics
211
+ error_frames = sum(1 for f in frames if hasattr(f, "errors") and f.errors)
212
+ statistics = {
213
+ "total_frames": len(frames),
214
+ "error_frames": error_frames,
215
+ "error_rate": error_frames / len(frames) if frames else 0,
216
+ }
217
+
218
+ return DecodeResult(
219
+ protocol=protocol,
220
+ frames=frames,
221
+ confidence=confidence,
222
+ baud_rate=config.get("baud_rate"),
223
+ config=config,
224
+ errors=errors,
225
+ statistics=statistics,
226
+ )
227
+
228
+
229
+ def _prepare_digital_trace(trace: WaveformTrace | DigitalTrace) -> DigitalTrace:
230
+ """Convert to digital trace if needed."""
198
231
  from oscura.core.types import WaveformTrace
199
- from oscura.inference.protocol import detect_protocol
200
232
 
201
- # Convert to digital if needed
202
233
  if isinstance(trace, WaveformTrace):
203
234
  from oscura.analyzers.digital.extraction import to_digital
204
235
 
205
- digital_trace = to_digital(trace, threshold="auto")
206
- else:
207
- digital_trace = trace
236
+ return to_digital(trace, threshold="auto")
237
+ return trace
238
+
239
+
240
+ def _detect_or_select_protocol(
241
+ trace: WaveformTrace | DigitalTrace,
242
+ protocol: str | None,
243
+ min_confidence: float,
244
+ digital_trace: DigitalTrace,
245
+ ) -> tuple[str, dict[str, Any], float]:
246
+ """Detect protocol or use specified one."""
247
+ from oscura.core.types import WaveformTrace
208
248
 
209
- # Auto-detect protocol if not specified
210
249
  if protocol is None or protocol.lower() == "auto":
211
- # detect_protocol only accepts WaveformTrace, use original trace if it's waveform
212
250
  if isinstance(trace, WaveformTrace):
251
+ from oscura.inference.protocol import detect_protocol
252
+
213
253
  detection = detect_protocol(
214
254
  trace, min_confidence=min_confidence, return_candidates=True
215
255
  )
216
256
  else:
217
- # For DigitalTrace, we can't auto-detect protocol, use default UART
218
257
  detection = {"protocol": "UART", "config": {}, "confidence": 0.5}
219
- protocol = detection.get("protocol", "unknown")
220
- config = detection.get("config", {})
221
- confidence = detection.get("confidence", 0.0)
258
+ return (
259
+ detection.get("protocol", "unknown"),
260
+ detection.get("config", {}),
261
+ detection.get("confidence", 0.0),
262
+ )
222
263
  else:
223
- protocol = protocol.upper()
224
- confidence = 1.0
225
- config = _get_default_protocol_config(protocol)
264
+ return protocol.upper(), _get_default_protocol_config(protocol.upper()), 1.0
265
+
226
266
 
227
- # Decode based on protocol
267
+ def _decode_protocol_frames(
268
+ protocol: str, config: dict[str, Any], digital_trace: DigitalTrace
269
+ ) -> tuple[list[Any], list[str]]:
270
+ """Decode frames based on detected protocol."""
228
271
  frames: list[Any] = []
229
272
  errors: list[str] = []
230
273
 
231
274
  try:
232
275
  if protocol == "UART":
233
- from oscura.analyzers.protocols.uart import UARTDecoder
234
-
235
- baud_rate = config.get("baud_rate", 115200)
236
- decoder = UARTDecoder(
237
- baudrate=baud_rate,
238
- data_bits=config.get("data_bits", 8),
239
- parity=config.get("parity", "none"),
240
- stop_bits=config.get("stop_bits", 1),
241
- )
242
- frames = list(decoder.decode(digital_trace))
243
-
276
+ frames = _decode_uart(digital_trace, config)
244
277
  elif protocol == "SPI":
245
- from oscura.analyzers.protocols.spi import SPIDecoder
246
-
247
- spi_decoder = SPIDecoder(
248
- cpol=config.get("clock_polarity", 0),
249
- cpha=config.get("clock_phase", 0),
250
- )
251
- # Single channel decode - pass trace with channel mapping
252
- frames = list(
253
- spi_decoder.decode(digital_trace, clk=digital_trace.data, mosi=digital_trace.data)
254
- )
255
-
278
+ frames = _decode_spi(digital_trace, config)
256
279
  elif protocol == "I2C":
257
- from oscura.analyzers.protocols.i2c import I2CDecoder
258
-
259
- i2c_decoder = I2CDecoder()
260
- # Single channel - use SDA and create synthetic SCL
261
- sda = digital_trace.data
262
- edges = np.where(np.diff(sda.astype(int)) != 0)[0]
263
- scl = np.ones_like(sda, dtype=bool)
264
- for i, edge in enumerate(edges):
265
- if i % 2 == 0 and edge + 10 < len(scl):
266
- scl[edge : edge + 10] = False
267
- frames = list(i2c_decoder.decode(digital_trace, scl=scl, sda=sda))
268
-
280
+ frames = _decode_i2c(digital_trace, config)
269
281
  elif protocol == "CAN":
270
- from oscura.analyzers.protocols.can import CANDecoder
271
-
272
- can_decoder = CANDecoder(
273
- bitrate=config.get("baud_rate", 500000),
274
- sample_point=config.get("sample_point", 0.75),
275
- )
276
- frames = list(can_decoder.decode(digital_trace))
277
-
282
+ frames = _decode_can(digital_trace, config)
278
283
  else:
279
284
  errors.append(f"Unsupported protocol: {protocol}")
280
-
281
285
  except Exception as e:
282
286
  errors.append(f"Decoding error: {e!s}")
283
287
 
284
- # Calculate statistics
285
- error_frames = sum(1 for f in frames if hasattr(f, "errors") and f.errors)
286
- statistics = {
287
- "total_frames": len(frames),
288
- "error_frames": error_frames,
289
- "error_rate": error_frames / len(frames) if frames else 0,
290
- }
288
+ return frames, errors
291
289
 
292
- return DecodeResult(
293
- protocol=protocol,
294
- frames=frames,
295
- confidence=confidence,
296
- baud_rate=config.get("baud_rate"),
297
- config=config,
298
- errors=errors,
299
- statistics=statistics,
290
+
291
+ def _decode_uart(digital_trace: DigitalTrace, config: dict[str, Any]) -> list[Any]:
292
+ """Decode UART frames."""
293
+ from oscura.analyzers.protocols.uart import UARTDecoder
294
+
295
+ decoder = UARTDecoder(
296
+ baudrate=config.get("baud_rate", 115200),
297
+ data_bits=config.get("data_bits", 8),
298
+ parity=config.get("parity", "none"),
299
+ stop_bits=config.get("stop_bits", 1),
300
+ )
301
+ return list(decoder.decode(digital_trace))
302
+
303
+
304
+ def _decode_spi(digital_trace: DigitalTrace, config: dict[str, Any]) -> list[Any]:
305
+ """Decode SPI frames."""
306
+ from oscura.analyzers.protocols.spi import SPIDecoder
307
+
308
+ decoder = SPIDecoder(cpol=config.get("clock_polarity", 0), cpha=config.get("clock_phase", 0))
309
+ return list(decoder.decode(digital_trace, clk=digital_trace.data, mosi=digital_trace.data))
310
+
311
+
312
+ def _decode_i2c(digital_trace: DigitalTrace, config: dict[str, Any]) -> list[Any]:
313
+ """Decode I2C frames."""
314
+ from oscura.analyzers.protocols.i2c import I2CDecoder
315
+
316
+ decoder = I2CDecoder()
317
+ sda = digital_trace.data
318
+ edges = np.where(np.diff(sda.astype(int)) != 0)[0]
319
+ scl = np.ones_like(sda, dtype=bool)
320
+ for i, edge in enumerate(edges):
321
+ if i % 2 == 0 and edge + 10 < len(scl):
322
+ scl[edge : edge + 10] = False
323
+ return list(decoder.decode(digital_trace, scl=scl, sda=sda))
324
+
325
+
326
+ def _decode_can(digital_trace: DigitalTrace, config: dict[str, Any]) -> list[Any]:
327
+ """Decode CAN frames."""
328
+ from oscura.analyzers.protocols.can import CANDecoder
329
+
330
+ decoder = CANDecoder(
331
+ bitrate=config.get("baud_rate", 500000), sample_point=config.get("sample_point", 0.75)
300
332
  )
333
+ return list(decoder.decode(digital_trace))
301
334
 
302
335
 
303
336
  def smart_filter(
@@ -330,7 +363,7 @@ def smart_filter(
330
363
  >>> # Or auto-detect
331
364
  >>> clean = osc.smart_filter(noisy, target="auto")
332
365
  """
333
- from oscura.filtering.convenience import (
366
+ from oscura.utils.filtering.convenience import (
334
367
  high_pass,
335
368
  low_pass,
336
369
  median_filter,
@@ -405,11 +438,24 @@ def _detect_noise_type(
405
438
  # Get noise floor estimate
406
439
  noise_floor = np.median(mag_db[10 : len(mag_db) // 4])
407
440
 
408
- # Check for power line hum
409
- if idx_60 < len(mag_db) and mag_db[idx_60] > noise_floor + 20:
410
- return "60hz_hum"
411
- if idx_50 < len(mag_db) and mag_db[idx_50] > noise_floor + 20:
412
- return "50hz_hum"
441
+ # Check for power line hum - must be clear peak at exact frequency
442
+ # Verify it's a local maximum to avoid false positives from spectral leakage
443
+ if idx_60 < len(mag_db) - 5 and idx_60 > 5 and idx_60 < len(freq):
444
+ # Must be within 3 Hz of actual 60 Hz (tighter tolerance)
445
+ if abs(freq[idx_60] - 60) < 3:
446
+ # Check if it's a local maximum (peak, not leakage)
447
+ local_max = mag_db[idx_60 - 2 : idx_60 + 3].max()
448
+ is_peak = mag_db[idx_60] >= local_max - 0.1 # Allow tiny numerical error
449
+ if is_peak and mag_db[idx_60] > noise_floor + 20:
450
+ return "60hz_hum"
451
+ if idx_50 < len(mag_db) - 5 and idx_50 > 5 and idx_50 < len(freq):
452
+ # Must be within 3 Hz of actual 50 Hz (tighter tolerance)
453
+ if abs(freq[idx_50] - 50) < 3:
454
+ # Check if it's a local maximum (peak, not leakage)
455
+ local_max = mag_db[idx_50 - 2 : idx_50 + 3].max()
456
+ is_peak = mag_db[idx_50] >= local_max - 0.1 # Allow tiny numerical error
457
+ if is_peak and mag_db[idx_50] > noise_floor + 20:
458
+ return "50hz_hum"
413
459
 
414
460
  # Check frequency distribution
415
461
  low_power = np.mean(mag_db[1 : len(mag_db) // 10])
oscura/core/__init__.py CHANGED
@@ -39,8 +39,9 @@ from oscura.core.cross_domain import (
39
39
  correlate_results,
40
40
  )
41
41
  from oscura.core.debug import (
42
+ DebugContext,
42
43
  DebugLevel,
43
- debug_context,
44
+ debug_context, # Backward compatibility alias
44
45
  disable_debug,
45
46
  enable_debug,
46
47
  get_debug_level,
@@ -180,6 +181,7 @@ __all__ = [
180
181
  "CorrelationResult",
181
182
  "CrossDomainCorrelator",
182
183
  "CrossDomainInsight",
184
+ "DebugContext",
183
185
  "DebugLevel",
184
186
  "DigitalTrace",
185
187
  # Edge cases (EDGE-001, EDGE-002, EDGE-003)
@@ -251,7 +253,7 @@ __all__ = [
251
253
  "create_progress_tracker",
252
254
  "create_provenance",
253
255
  "create_simple_progress",
254
- "debug_context",
256
+ "debug_context", # Backward compatibility
255
257
  "disable_debug",
256
258
  # Debug (LOG-007)
257
259
  "enable_debug",
@@ -43,7 +43,7 @@ except (ImportError, AttributeError):
43
43
  HAS_GPU = False
44
44
 
45
45
  try:
46
- import numba # type: ignore[import-not-found]
46
+ import numba
47
47
 
48
48
  HAS_NUMBA = True
49
49
  del numba
@@ -51,10 +51,10 @@ except ImportError:
51
51
  HAS_NUMBA = False
52
52
 
53
53
  try:
54
- import dask.array # type: ignore[import-not-found, import-untyped]
54
+ import dask.array # type: ignore[import-not-found] # Optional dependency
55
55
 
56
56
  HAS_DASK = True
57
- del dask # type: ignore[name-defined]
57
+ del dask
58
58
  except ImportError:
59
59
  HAS_DASK = False
60
60