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
@@ -79,13 +79,13 @@ def correct_timestamp_jitter(
79
79
  ) -> TimestampCorrection:
80
80
  """Correct timestamp jitter using filtering or PLL model.
81
81
 
82
- : Compensates for clock jitter in logic analyzer
82
+ Compensates for clock jitter in logic analyzer
83
83
  captures (e.g., USB transmission jitter) while preserving phase.
84
84
 
85
85
  Correction constraints (DAQ-003):
86
- - Max correction per sample: ±max_correction_factor × expected_period # noqa: RUF002, RUF003
87
- - Filter cutoff: expected_rate / 10 (removes 10× jitter frequency) # noqa: RUF002, RUF003
88
- - Target reduction: ≥5× for typical USB jitter # noqa: RUF002, RUF003
86
+ - Max correction per sample: ±max_correction_factor x expected_period
87
+ - Filter cutoff: expected_rate / 10 (removes 10x jitter frequency)
88
+ - Target reduction: >=5x for typical USB jitter
89
89
 
90
90
  Args:
91
91
  timestamps: Original jittery timestamps in seconds
@@ -113,109 +113,149 @@ def correct_timestamp_jitter(
113
113
  References:
114
114
  DAQ-003: Timestamp Jitter Compensation and Clock Correction
115
115
  """
116
- if len(timestamps) == 0:
117
- raise ValueError("Timestamps array cannot be empty")
118
-
119
- if expected_rate <= 0:
120
- raise ValueError("expected_rate must be positive")
121
-
122
- if max_correction_factor <= 0:
123
- raise ValueError("max_correction_factor must be positive")
116
+ # Validate inputs
117
+ _validate_jitter_correction_inputs(timestamps, expected_rate, max_correction_factor)
124
118
 
119
+ # Handle edge cases
125
120
  if len(timestamps) < 3:
126
- # Not enough data to filter
127
- return TimestampCorrection(
128
- corrected_timestamps=timestamps.copy(),
129
- original_jitter_rms=0.0,
130
- corrected_jitter_rms=0.0,
131
- reduction_ratio=1.0,
132
- samples_corrected=0,
133
- max_correction=0.0,
134
- )
121
+ return _create_no_correction_result(timestamps)
135
122
 
136
123
  expected_period = 1.0 / expected_rate
137
124
  max_correction = max_correction_factor * expected_period
138
125
 
139
126
  # Calculate original jitter
140
- diffs = np.diff(timestamps)
141
- original_jitter = diffs - expected_period
142
- original_jitter_rms = float(np.sqrt(np.mean(original_jitter**2)))
127
+ original_jitter_rms = _calculate_original_jitter_rms(timestamps, expected_period)
143
128
 
144
- # If jitter is negligible (below 1 ns), no correction needed
145
- # This avoids correcting floating-point rounding errors in perfect timestamps
129
+ # Skip correction if jitter is negligible
146
130
  if original_jitter_rms < 1e-9:
147
- return TimestampCorrection(
148
- corrected_timestamps=timestamps.copy(),
149
- original_jitter_rms=original_jitter_rms,
150
- corrected_jitter_rms=original_jitter_rms,
151
- reduction_ratio=1.0,
152
- samples_corrected=0,
153
- max_correction=0.0,
154
- )
131
+ return _create_negligible_jitter_result(timestamps, original_jitter_rms)
155
132
 
156
- if method == "lowpass":
157
- # Low-pass filter approach
158
- # Design Butterworth filter: cutoff at expected_rate / 10
159
- cutoff_freq = expected_rate / 10.0
160
- nyquist = 0.5 * expected_rate
133
+ # Apply correction method
134
+ corrected = _apply_jitter_correction(
135
+ timestamps, expected_rate, expected_period, max_correction, method
136
+ )
161
137
 
162
- # Ensure cutoff is valid
163
- if cutoff_freq >= nyquist:
164
- cutoff_freq = nyquist * 0.8
138
+ # Build and return result
139
+ return _build_jitter_correction_result(
140
+ timestamps, corrected, expected_period, original_jitter_rms, max_correction
141
+ )
165
142
 
166
- # Design 2nd order Butterworth
167
- sos = signal.butter(2, cutoff_freq / nyquist, btype="low", output="sos")
168
143
 
169
- # Filter the timestamps
170
- # Need to detrend first to avoid edge effects
171
- t_mean = np.mean(timestamps)
172
- t_detrended = timestamps - t_mean
144
+ def _validate_jitter_correction_inputs(
145
+ timestamps: NDArray[np.float64], expected_rate: float, max_correction_factor: float
146
+ ) -> None:
147
+ """Validate jitter correction input parameters."""
148
+ if len(timestamps) == 0:
149
+ raise ValueError("Timestamps array cannot be empty")
150
+ if expected_rate <= 0:
151
+ raise ValueError("expected_rate must be positive")
152
+ if max_correction_factor <= 0:
153
+ raise ValueError("max_correction_factor must be positive")
173
154
 
174
- # Apply filter
175
- filtered = signal.sosfiltfilt(sos, t_detrended)
176
- corrected = filtered + t_mean
177
155
 
178
- else: # pll
179
- # Phase-locked loop model
180
- # Simple PLL: track expected phase and correct deviations
181
- corrected = np.zeros_like(timestamps)
182
- corrected[0] = timestamps[0]
156
+ def _create_no_correction_result(timestamps: NDArray[np.float64]) -> TimestampCorrection:
157
+ """Create result for insufficient data case."""
158
+ return TimestampCorrection(
159
+ corrected_timestamps=timestamps.copy(),
160
+ original_jitter_rms=0.0,
161
+ corrected_jitter_rms=0.0,
162
+ reduction_ratio=1.0,
163
+ samples_corrected=0,
164
+ max_correction=0.0,
165
+ )
183
166
 
184
- # PLL state
185
- phase = 0.0
186
- phase_increment = 2 * np.pi * expected_rate
187
167
 
188
- for i in range(1, len(timestamps)):
189
- # Predict next timestamp based on expected rate
190
- predicted = corrected[i - 1] + expected_period
168
+ def _calculate_original_jitter_rms(
169
+ timestamps: NDArray[np.float64], expected_period: float
170
+ ) -> float:
171
+ """Calculate RMS jitter from timestamps."""
172
+ diffs = np.diff(timestamps)
173
+ original_jitter = diffs - expected_period
174
+ return float(np.sqrt(np.mean(original_jitter**2)))
191
175
 
192
- # Measure phase error
193
- actual = timestamps[i]
194
- error = actual - predicted
195
176
 
196
- # Apply correction with limiting
197
- correction = np.clip(error * 0.5, -max_correction, max_correction)
198
- corrected[i] = predicted + correction
177
+ def _create_negligible_jitter_result(
178
+ timestamps: NDArray[np.float64], original_jitter_rms: float
179
+ ) -> TimestampCorrection:
180
+ """Create result for negligible jitter case."""
181
+ return TimestampCorrection(
182
+ corrected_timestamps=timestamps.copy(),
183
+ original_jitter_rms=original_jitter_rms,
184
+ corrected_jitter_rms=original_jitter_rms,
185
+ reduction_ratio=1.0,
186
+ samples_corrected=0,
187
+ max_correction=0.0,
188
+ )
199
189
 
200
- # Update phase
201
- phase += phase_increment * (corrected[i] - corrected[i - 1])
190
+
191
+ def _apply_jitter_correction(
192
+ timestamps: NDArray[np.float64],
193
+ expected_rate: float,
194
+ expected_period: float,
195
+ max_correction: float,
196
+ method: Literal["lowpass", "pll"],
197
+ ) -> NDArray[np.float64]:
198
+ """Apply jitter correction using selected method."""
199
+ if method == "lowpass":
200
+ corrected = _apply_lowpass_correction(timestamps, expected_rate)
201
+ else: # pll
202
+ corrected = _apply_pll_correction(timestamps, expected_period, max_correction)
202
203
 
203
204
  # Limit corrections to max_correction
204
205
  corrections = corrected - timestamps
205
206
  exceeded = np.abs(corrections) > max_correction
206
207
  corrections[exceeded] = np.sign(corrections[exceeded]) * max_correction
207
- corrected = timestamps + corrections
208
+ return timestamps + corrections
209
+
210
+
211
+ def _apply_lowpass_correction(
212
+ timestamps: NDArray[np.float64], expected_rate: float
213
+ ) -> NDArray[np.float64]:
214
+ """Apply low-pass filter correction."""
215
+ cutoff_freq = expected_rate / 10.0
216
+ nyquist = 0.5 * expected_rate
217
+ if cutoff_freq >= nyquist:
218
+ cutoff_freq = nyquist * 0.8
219
+
220
+ sos = signal.butter(2, cutoff_freq / nyquist, btype="low", output="sos")
221
+ t_mean: np.floating[Any] = np.mean(timestamps)
222
+ t_detrended = timestamps - t_mean
223
+ filtered = signal.sosfiltfilt(sos, t_detrended)
224
+ result: NDArray[np.float64] = np.asarray(filtered + t_mean, dtype=np.float64)
225
+ return result
226
+
227
+
228
+ def _apply_pll_correction(
229
+ timestamps: NDArray[np.float64], expected_period: float, max_correction: float
230
+ ) -> NDArray[np.float64]:
231
+ """Apply phase-locked loop correction."""
232
+ corrected = np.zeros_like(timestamps)
233
+ corrected[0] = timestamps[0]
234
+
235
+ for i in range(1, len(timestamps)):
236
+ predicted = corrected[i - 1] + expected_period
237
+ error = timestamps[i] - predicted
238
+ correction = np.clip(error * 0.5, -max_correction, max_correction)
239
+ corrected[i] = predicted + correction
240
+
241
+ return corrected
208
242
 
209
- # Calculate corrected jitter
243
+
244
+ def _build_jitter_correction_result(
245
+ timestamps: NDArray[np.float64],
246
+ corrected: NDArray[np.float64],
247
+ expected_period: float,
248
+ original_jitter_rms: float,
249
+ max_correction: float,
250
+ ) -> TimestampCorrection:
251
+ """Build final jitter correction result."""
210
252
  corrected_diffs = np.diff(corrected)
211
253
  corrected_jitter = corrected_diffs - expected_period
212
254
  corrected_jitter_rms = float(np.sqrt(np.mean(corrected_jitter**2)))
213
255
 
214
- # Calculate metrics
256
+ corrections = corrected - timestamps
215
257
  samples_corrected = int(np.sum(np.abs(corrections) > 1e-12))
216
258
  max_correction_applied = float(np.max(np.abs(corrections)))
217
-
218
- # original_jitter_rms is always > 0 here (early return handles negligible jitter)
219
259
  reduction_ratio = original_jitter_rms / max(corrected_jitter_rms, 1e-15)
220
260
 
221
261
  return TimestampCorrection(
@@ -228,6 +268,52 @@ def correct_timestamp_jitter(
228
268
  )
229
269
 
230
270
 
271
+ def _decode_uart_tolerant(
272
+ data: NDArray[np.uint8],
273
+ tolerance: ErrorTolerance,
274
+ baud: float,
275
+ ) -> list[DecodedFrame]:
276
+ """Decode UART frames with error tolerance (simplified implementation)."""
277
+ frames: list[DecodedFrame] = []
278
+ pos = 0
279
+
280
+ while pos < len(data):
281
+ try:
282
+ if pos + 1 >= len(data):
283
+ break
284
+
285
+ frame_data = bytes([data[pos]])
286
+ timestamp = float(pos) / baud
287
+
288
+ # Validate frame (simplified - real decoder checks start/stop bits)
289
+ is_valid = data[pos] != 0xFF # Example error condition
290
+ error_type = "framing" if not is_valid else None
291
+
292
+ frames.append(
293
+ DecodedFrame(
294
+ data=frame_data,
295
+ timestamp=timestamp,
296
+ valid=is_valid,
297
+ error_type=error_type,
298
+ position=pos,
299
+ )
300
+ )
301
+
302
+ if not is_valid and tolerance == ErrorTolerance.STRICT:
303
+ break
304
+ elif not is_valid and tolerance == ErrorTolerance.TOLERANT:
305
+ pos += 1 # Skip error frame, resync
306
+ else:
307
+ pos += 1 # Permissive: record error, continue
308
+
309
+ except Exception:
310
+ if tolerance == ErrorTolerance.STRICT:
311
+ raise
312
+ pos += 1
313
+
314
+ return frames
315
+
316
+
231
317
  def decode_with_error_tolerance(
232
318
  data: NDArray[np.uint8],
233
319
  protocol: Literal["uart", "spi", "i2c", "can"],
@@ -279,82 +365,18 @@ def decode_with_error_tolerance(
279
365
  if protocol not in ("uart", "spi", "i2c", "can"):
280
366
  raise ValueError(f"Unsupported protocol: {protocol}")
281
367
 
282
- frames: list[DecodedFrame] = []
283
- pos = 0
284
-
285
368
  # Protocol-specific decode logic
286
369
  # This is a simplified implementation showing the error handling pattern
287
370
  # Full protocol decoders are in oscura.analyzers.protocols
288
371
 
289
372
  if protocol == "uart":
290
- # UART parameters
291
373
  if "baud" not in protocol_params:
292
374
  raise ValueError("UART requires 'baud' parameter")
375
+ return _decode_uart_tolerant(data, tolerance, protocol_params["baud"])
293
376
 
294
- # Simplified UART frame extraction with error tolerance
295
- while pos < len(data):
296
- try:
297
- # Try to decode frame at current position
298
- # This is simplified - real UART decoder would analyze bit timing
299
-
300
- # Check for valid frame (simplified)
301
- if pos + 1 >= len(data):
302
- break
303
-
304
- frame_data = bytes([data[pos]])
305
- timestamp = float(pos) / protocol_params["baud"]
306
-
307
- # Validate frame (simplified - would check start/stop bits)
308
- is_valid = True
309
- error_type = None
310
-
311
- # Example: detect framing error (no proper stop bit)
312
- if data[pos] == 0xFF: # Example error condition
313
- is_valid = False
314
- error_type = "framing"
315
-
316
- frames.append(
317
- DecodedFrame(
318
- data=frame_data,
319
- timestamp=timestamp,
320
- valid=is_valid,
321
- error_type=error_type,
322
- position=pos,
323
- )
324
- )
325
-
326
- if not is_valid and tolerance == ErrorTolerance.STRICT:
327
- # Strict mode: abort on error
328
- break
329
- elif not is_valid and tolerance == ErrorTolerance.TOLERANT:
330
- # Tolerant: skip error frame, resync
331
- # Search for next valid start bit
332
- pos += 1
333
- # In real implementation, would search for start bit pattern
334
- else:
335
- # Permissive: record error, continue
336
- pos += 1
337
-
338
- except Exception:
339
- if tolerance == ErrorTolerance.STRICT:
340
- raise
341
- else:
342
- # Log error and continue
343
- pos += 1
344
-
345
- elif protocol == "spi":
346
- # SPI: Re-align on CS edge
347
- # Simplified placeholder
348
- pass
349
-
350
- elif protocol == "i2c":
351
- # I2C: Search for START condition
352
- # Simplified placeholder
353
- pass
354
-
355
- elif protocol == "can":
356
- # CAN: Wait for SOF after error
357
- # Simplified placeholder
358
- pass
377
+ elif protocol in ("spi", "i2c", "can"):
378
+ # SPI/I2C/CAN: Simplified placeholders
379
+ # Real implementations would have protocol-specific resync logic
380
+ return []
359
381
 
360
- return frames
382
+ return []
@@ -113,102 +113,23 @@ def analyze_bit_errors(
113
113
  References:
114
114
  DAQ-005: Bit Error Pattern Analysis and Capture Diagnostics
115
115
  """
116
- if len(received) != len(expected):
117
- raise ValueError("Received and expected arrays must have same length")
118
-
119
- if len(received) == 0:
120
- raise ValueError("Arrays cannot be empty")
116
+ _validate_inputs(received, expected)
121
117
 
122
- # Find bit errors (XOR)
123
118
  errors = received != expected
124
119
  error_positions = np.where(errors)[0]
125
120
  error_count = len(error_positions)
126
121
  total_bits = len(received)
127
-
128
- # Calculate BER
129
122
  bit_error_rate = error_count / total_bits if total_bits > 0 else 0.0
130
123
 
131
124
  if error_count == 0:
132
- # No errors
133
- return ErrorAnalysis(
134
- bit_error_rate=0.0,
135
- error_count=0,
136
- total_bits=total_bits,
137
- pattern_type=ErrorPattern.RANDOM,
138
- mean_error_gap=float(total_bits),
139
- error_positions=error_positions,
140
- diagnosis="No errors detected - good capture quality",
141
- severity="low",
142
- )
143
-
144
- # Calculate error gaps
145
- if error_count > 1:
146
- error_gaps = np.diff(error_positions)
147
- mean_gap = float(np.mean(error_gaps))
148
- else:
149
- mean_gap = float(total_bits)
150
-
151
- # Classify error pattern
152
- pattern_type = ErrorPattern.UNKNOWN
153
- diagnosis = ""
125
+ return _create_no_errors_analysis(total_bits, error_positions)
154
126
 
155
- # Check for burst pattern (errors clustered)
156
- if error_count > 1 and mean_gap < burst_threshold:
157
- pattern_type = ErrorPattern.BURST
158
- diagnosis = "Burst errors detected - likely USB transmission issue"
159
-
160
- # Check for periodic pattern (FFT analysis)
161
- # Need at least 10 errors for reliable periodicity detection
162
- elif error_count >= 10:
163
- # Create binary error signal
164
- error_signal = errors.astype(float)
165
-
166
- # Compute FFT to detect periodicity
167
- fft = np.fft.rfft(error_signal)
168
- fft_mag = np.abs(fft[1:]) # Skip DC component
169
-
170
- if len(fft_mag) > 0:
171
- # Check if there's a strong peak relative to mean (not just max)
172
- mean_mag = np.mean(fft_mag)
173
- max_mag = np.max(fft_mag)
174
- peak_ratio = max_mag / (mean_mag + 1e-12)
175
-
176
- # Require strong peak (>10x mean) and exceeds threshold
177
- if peak_ratio > 10 and (max_mag / (np.max(fft_mag) + 1e-12)) > periodicity_threshold:
178
- pattern_type = ErrorPattern.PERIODIC
179
- diagnosis = "Periodic errors detected - likely clock jitter or interference"
180
-
181
- # If not burst or periodic, classify as random
182
- if pattern_type == ErrorPattern.UNKNOWN:
183
- # Check if errors are uniformly distributed
184
- if error_count > 2:
185
- # Use coefficient of variation of gaps
186
- if error_count > 1:
187
- gap_std = float(np.std(error_gaps))
188
- gap_cv = gap_std / (mean_gap + 1e-12)
189
-
190
- if gap_cv < 1.0: # Relatively uniform spacing
191
- pattern_type = ErrorPattern.RANDOM
192
- diagnosis = "Random errors detected - likely EMI or noise"
193
- else:
194
- diagnosis = "Mixed error pattern - multiple causes possible"
195
- else:
196
- pattern_type = ErrorPattern.RANDOM
197
- diagnosis = "Single error - insufficient data for classification"
198
- else:
199
- pattern_type = ErrorPattern.RANDOM
200
- diagnosis = "Few errors - likely random EMI or noise"
201
-
202
- # Determine severity based on BER
203
- if bit_error_rate > 0.01:
204
- severity = "severe"
205
- diagnosis += ". SEVERE: Check connections and hardware"
206
- elif bit_error_rate > 0.001:
207
- severity = "moderate"
208
- diagnosis += ". MODERATE: Consider reducing sample rate"
209
- else:
210
- severity = "low"
211
- diagnosis += ". Acceptable error rate"
127
+ mean_gap = _calculate_mean_gap(error_count, error_positions, total_bits)
128
+ pattern_type, diagnosis = _classify_error_pattern(
129
+ errors, error_count, error_positions, mean_gap, burst_threshold, periodicity_threshold
130
+ )
131
+ severity = _determine_severity(bit_error_rate)
132
+ diagnosis = _append_severity_message(diagnosis, severity)
212
133
 
213
134
  return ErrorAnalysis(
214
135
  bit_error_rate=bit_error_rate,
@@ -222,6 +143,117 @@ def analyze_bit_errors(
222
143
  )
223
144
 
224
145
 
146
+ def _validate_inputs(received: NDArray[np.uint8], expected: NDArray[np.uint8]) -> None:
147
+ """Validate input arrays for error analysis."""
148
+ if len(received) != len(expected):
149
+ raise ValueError("Received and expected arrays must have same length")
150
+ if len(received) == 0:
151
+ raise ValueError("Arrays cannot be empty")
152
+
153
+
154
+ def _create_no_errors_analysis(
155
+ total_bits: int, error_positions: NDArray[np.int64]
156
+ ) -> ErrorAnalysis:
157
+ """Create ErrorAnalysis for zero errors case."""
158
+ return ErrorAnalysis(
159
+ bit_error_rate=0.0,
160
+ error_count=0,
161
+ total_bits=total_bits,
162
+ pattern_type=ErrorPattern.RANDOM,
163
+ mean_error_gap=float(total_bits),
164
+ error_positions=error_positions,
165
+ diagnosis="No errors detected - good capture quality",
166
+ severity="low",
167
+ )
168
+
169
+
170
+ def _calculate_mean_gap(
171
+ error_count: int, error_positions: NDArray[np.int64], total_bits: int
172
+ ) -> float:
173
+ """Calculate mean gap between errors."""
174
+ if error_count > 1:
175
+ error_gaps = np.diff(error_positions)
176
+ return float(np.mean(error_gaps))
177
+ return float(total_bits)
178
+
179
+
180
+ def _classify_error_pattern(
181
+ errors: NDArray[np.bool_],
182
+ error_count: int,
183
+ error_positions: NDArray[np.int64],
184
+ mean_gap: float,
185
+ burst_threshold: int,
186
+ periodicity_threshold: float,
187
+ ) -> tuple[ErrorPattern, str]:
188
+ """Classify error pattern type and generate diagnosis."""
189
+ if error_count > 1 and mean_gap < burst_threshold:
190
+ return ErrorPattern.BURST, "Burst errors detected - likely USB transmission issue"
191
+
192
+ if error_count >= 10:
193
+ pattern, diagnosis = _check_periodic_pattern(errors, periodicity_threshold)
194
+ if pattern == ErrorPattern.PERIODIC:
195
+ return pattern, diagnosis
196
+
197
+ return _classify_random_pattern(error_count, error_positions, mean_gap)
198
+
199
+
200
+ def _check_periodic_pattern(
201
+ errors: NDArray[np.bool_], periodicity_threshold: float
202
+ ) -> tuple[ErrorPattern, str]:
203
+ """Check if errors show periodic pattern via FFT."""
204
+ error_signal = errors.astype(float)
205
+ fft = np.fft.rfft(error_signal)
206
+ fft_mag = np.abs(fft[1:])
207
+
208
+ if len(fft_mag) > 0:
209
+ mean_mag = np.mean(fft_mag)
210
+ max_mag = np.max(fft_mag)
211
+ peak_ratio = max_mag / (mean_mag + 1e-12)
212
+
213
+ if peak_ratio > 10 and (max_mag / (np.max(fft_mag) + 1e-12)) > periodicity_threshold:
214
+ return (
215
+ ErrorPattern.PERIODIC,
216
+ "Periodic errors detected - likely clock jitter or interference",
217
+ )
218
+
219
+ return ErrorPattern.UNKNOWN, ""
220
+
221
+
222
+ def _classify_random_pattern(
223
+ error_count: int, error_positions: NDArray[np.int64], mean_gap: float
224
+ ) -> tuple[ErrorPattern, str]:
225
+ """Classify as random pattern with appropriate diagnosis."""
226
+ if error_count <= 2:
227
+ return ErrorPattern.RANDOM, "Few errors - likely random EMI or noise"
228
+
229
+ error_gaps = np.diff(error_positions)
230
+ gap_std = float(np.std(error_gaps))
231
+ gap_cv = gap_std / (mean_gap + 1e-12)
232
+
233
+ if gap_cv < 1.0:
234
+ return ErrorPattern.RANDOM, "Random errors detected - likely EMI or noise"
235
+
236
+ return ErrorPattern.UNKNOWN, "Mixed error pattern - multiple causes possible"
237
+
238
+
239
+ def _determine_severity(bit_error_rate: float) -> str:
240
+ """Determine severity level based on BER."""
241
+ if bit_error_rate > 0.01:
242
+ return "severe"
243
+ if bit_error_rate > 0.001:
244
+ return "moderate"
245
+ return "low"
246
+
247
+
248
+ def _append_severity_message(diagnosis: str, severity: str) -> str:
249
+ """Append severity-specific message to diagnosis."""
250
+ if severity == "severe":
251
+ return diagnosis + ". SEVERE: Check connections and hardware"
252
+ if severity == "moderate":
253
+ return diagnosis + ". MODERATE: Consider reducing sample rate"
254
+ return diagnosis + ". Acceptable error rate"
255
+
256
+
225
257
  def generate_error_visualization_data(
226
258
  analysis: ErrorAnalysis,
227
259
  *,