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
@@ -180,33 +180,41 @@ def decode_protocol(
180
180
  )
181
181
 
182
182
 
183
- def _decode_uart_auto(
183
+ def _extract_uart_data_and_sample_rate(
184
184
  trace: WaveformTrace | DigitalTrace,
185
- params_hint: dict[str, Any] | None,
186
- confidence_threshold: float,
187
- return_errors: bool,
188
- ) -> DecodeResult:
189
- """Auto-decode UART with parameter detection.
185
+ ) -> tuple[NDArray[np.floating[Any]], float]:
186
+ """Extract data array and sample rate from trace.
190
187
 
191
188
  Args:
192
- trace: Input trace.
193
- params_hint: Optional parameter hints.
194
- confidence_threshold: Minimum confidence threshold.
195
- return_errors: Whether to include errors.
189
+ trace: Input trace
196
190
 
197
191
  Returns:
198
- DecodeResult for UART.
192
+ Tuple of (data array, sample rate)
199
193
  """
200
- from oscura.analyzers.protocols.uart import UARTDecoder
201
-
202
- # Get data array
203
194
  if isinstance(trace, WaveformTrace):
204
195
  data = trace.data
205
196
  sample_rate = trace.metadata.sample_rate
206
197
  else:
207
198
  data = trace.data.astype(np.float64)
208
199
  sample_rate = trace.metadata.sample_rate
200
+ return data, sample_rate
201
+
202
+
203
+ def _determine_uart_parameters(
204
+ params_hint: dict[str, Any] | None,
205
+ data: NDArray[np.floating[Any]],
206
+ sample_rate: float,
207
+ ) -> tuple[int, int, str, int]:
208
+ """Determine UART parameters from hints or auto-detection.
209
+
210
+ Args:
211
+ params_hint: Optional parameter hints
212
+ data: Signal data
213
+ sample_rate: Sampling rate
209
214
 
215
+ Returns:
216
+ Tuple of (baud_rate, data_bits, parity, stop_bits)
217
+ """
210
218
  # Auto-detect baud rate if not provided
211
219
  if params_hint and "baud_rate" in params_hint:
212
220
  baud_rate = params_hint["baud_rate"]
@@ -218,27 +226,41 @@ def _decode_uart_auto(
218
226
  parity = params_hint.get("parity", "none") if params_hint else "none"
219
227
  stop_bits = params_hint.get("stop_bits", 1) if params_hint else 1
220
228
 
221
- # Create decoder with detected parameters
222
- decoder = UARTDecoder(
223
- baudrate=baud_rate,
224
- data_bits=data_bits,
225
- parity=parity, # type: ignore[arg-type]
226
- stop_bits=stop_bits,
227
- )
229
+ return baud_rate, data_bits, parity, stop_bits
230
+
231
+
232
+ def _decode_uart_packets(
233
+ decoder: Any,
234
+ trace: WaveformTrace | DigitalTrace,
235
+ baud_rate: int,
236
+ ) -> list[Any]:
237
+ """Decode UART packets from trace.
228
238
 
229
- # Decode
239
+ Args:
240
+ decoder: UART decoder instance
241
+ trace: Input trace
242
+ baud_rate: Baud rate for error reporting
243
+
244
+ Returns:
245
+ List of decoded packets (empty on failure)
246
+ """
230
247
  try:
231
- packets = list(decoder.decode(trace))
248
+ return list(decoder.decode(trace))
232
249
  except Exception:
233
- # Decode failed
234
- return DecodeResult(
235
- protocol="UART",
236
- overall_confidence=0.3,
237
- detected_params={"baud_rate": baud_rate},
238
- data=[],
239
- )
250
+ return []
251
+
252
+
253
+ def _convert_uart_packets_to_bytes(
254
+ packets: list[Any],
255
+ ) -> tuple[list[DecodedByte], int]:
256
+ """Convert UART packets to DecodedByte format.
240
257
 
241
- # Convert to DecodedByte format
258
+ Args:
259
+ packets: List of decoded packets
260
+
261
+ Returns:
262
+ Tuple of (decoded bytes list, error count)
263
+ """
242
264
  decoded_bytes: list[DecodedByte] = []
243
265
  error_count = 0
244
266
 
@@ -263,12 +285,72 @@ def _decode_uart_auto(
263
285
  )
264
286
  )
265
287
 
266
- # Calculate overall confidence
288
+ return decoded_bytes, error_count
289
+
290
+
291
+ def _calculate_uart_confidence(decoded_bytes: list[DecodedByte]) -> float:
292
+ """Calculate overall confidence from decoded bytes.
293
+
294
+ Args:
295
+ decoded_bytes: List of decoded bytes
296
+
297
+ Returns:
298
+ Overall confidence score
299
+ """
267
300
  if decoded_bytes:
268
301
  avg_confidence = np.mean([b.confidence for b in decoded_bytes])
269
- overall_confidence = float(round(float(avg_confidence), 2))
270
- else:
271
- overall_confidence = 0.0
302
+ return float(round(float(avg_confidence), 2))
303
+ return 0.0
304
+
305
+
306
+ def _decode_uart_auto(
307
+ trace: WaveformTrace | DigitalTrace,
308
+ params_hint: dict[str, Any] | None,
309
+ confidence_threshold: float,
310
+ return_errors: bool,
311
+ ) -> DecodeResult:
312
+ """Auto-decode UART with parameter detection.
313
+
314
+ Args:
315
+ trace: Input trace.
316
+ params_hint: Optional parameter hints.
317
+ confidence_threshold: Minimum confidence threshold.
318
+ return_errors: Whether to include errors.
319
+
320
+ Returns:
321
+ DecodeResult for UART.
322
+ """
323
+ from oscura.analyzers.protocols.uart import UARTDecoder
324
+
325
+ # Extract data and determine parameters
326
+ data, sample_rate = _extract_uart_data_and_sample_rate(trace)
327
+ baud_rate, data_bits, parity, stop_bits = _determine_uart_parameters(
328
+ params_hint, data, sample_rate
329
+ )
330
+
331
+ # Create decoder with detected parameters
332
+ decoder = UARTDecoder(
333
+ baudrate=baud_rate,
334
+ data_bits=data_bits,
335
+ parity=parity, # type: ignore[arg-type]
336
+ stop_bits=stop_bits,
337
+ )
338
+
339
+ # Decode packets
340
+ packets = _decode_uart_packets(decoder, trace, baud_rate)
341
+
342
+ # Handle decode failure
343
+ if not packets:
344
+ return DecodeResult(
345
+ protocol="UART",
346
+ overall_confidence=0.3,
347
+ detected_params={"baud_rate": baud_rate},
348
+ data=[],
349
+ )
350
+
351
+ # Convert packets to bytes and calculate confidence
352
+ decoded_bytes, error_count = _convert_uart_packets_to_bytes(packets)
353
+ overall_confidence = _calculate_uart_confidence(decoded_bytes)
272
354
 
273
355
  return DecodeResult(
274
356
  protocol="UART",
@@ -336,6 +336,187 @@ def _detect_pattern_differences(
336
336
  return differences
337
337
 
338
338
 
339
+ def _compute_correlation(data1: NDArray[np.float64], data2: NDArray[np.float64]) -> float:
340
+ """Compute normalized correlation between two signals.
341
+
342
+ Args:
343
+ data1: First signal array.
344
+ data2: Second signal array.
345
+
346
+ Returns:
347
+ Correlation coefficient in range [-1, 1].
348
+ """
349
+ if len(data1) <= 1:
350
+ return 1.0 if len(data1) == 0 or data1[0] == data2[0] else 0.0
351
+
352
+ d1_norm = (data1 - np.mean(data1)) / (np.std(data1) + 1e-10)
353
+ d2_norm = (data2 - np.mean(data2)) / (np.std(data2) + 1e-10)
354
+ return float(np.corrcoef(d1_norm, d2_norm)[0, 1])
355
+
356
+
357
+ def _try_alignment_method(
358
+ trace1: WaveformTrace, trace2: WaveformTrace, method: str
359
+ ) -> tuple[NDArray[np.float64], NDArray[np.float64], int, float]:
360
+ """Try single alignment method and return correlation.
361
+
362
+ Args:
363
+ trace1: First waveform trace.
364
+ trace2: Second waveform trace.
365
+ method: Alignment method ("time", "trigger", or "pattern").
366
+
367
+ Returns:
368
+ Tuple of (aligned_data1, aligned_data2, offset, correlation).
369
+ """
370
+ if method == "time":
371
+ d1, d2, offset = _align_time_based(trace1, trace2)
372
+ elif method == "trigger":
373
+ d1, d2, offset = _align_trigger_based(trace1, trace2)
374
+ else: # pattern
375
+ d1, d2, offset = _align_pattern_based(trace1, trace2)
376
+
377
+ corr = _compute_correlation(d1, d2)
378
+ return d1, d2, offset, corr
379
+
380
+
381
+ def _auto_align_traces(
382
+ trace1: WaveformTrace, trace2: WaveformTrace
383
+ ) -> tuple[NDArray[np.float64], NDArray[np.float64], int, str]:
384
+ """Automatically choose best alignment method.
385
+
386
+ Args:
387
+ trace1: First waveform trace.
388
+ trace2: Second waveform trace.
389
+
390
+ Returns:
391
+ Tuple of (aligned_data1, aligned_data2, offset, method_name).
392
+ """
393
+ methods = ["time", "trigger", "pattern"]
394
+ best_corr = -1.0
395
+ best_result = None
396
+ best_method = "time"
397
+
398
+ for method in methods:
399
+ d1, d2, offset, corr = _try_alignment_method(trace1, trace2, method)
400
+
401
+ if corr > best_corr:
402
+ best_corr = corr
403
+ best_method = method
404
+ best_result = (d1, d2, offset)
405
+
406
+ data1, data2, offset = best_result # type: ignore[misc]
407
+ return data1, data2, offset, f"{best_method}-based"
408
+
409
+
410
+ def _align_traces_by_method(
411
+ trace1: WaveformTrace, trace2: WaveformTrace, alignment: str
412
+ ) -> tuple[NDArray[np.float64], NDArray[np.float64], int, str]:
413
+ """Align traces using specified method.
414
+
415
+ Args:
416
+ trace1: First waveform trace.
417
+ trace2: Second waveform trace.
418
+ alignment: Alignment method name.
419
+
420
+ Returns:
421
+ Tuple of (aligned_data1, aligned_data2, offset, method_description).
422
+ """
423
+ if alignment == "auto":
424
+ return _auto_align_traces(trace1, trace2)
425
+
426
+ if alignment == "time":
427
+ data1, data2, offset = _align_time_based(trace1, trace2)
428
+ elif alignment == "trigger":
429
+ data1, data2, offset = _align_trigger_based(trace1, trace2)
430
+ else: # pattern
431
+ data1, data2, offset = _align_pattern_based(trace1, trace2)
432
+
433
+ return data1, data2, offset, f"{alignment}-based"
434
+
435
+
436
+ def _collect_differences(
437
+ data1: NDArray[np.float64],
438
+ data2: NDArray[np.float64],
439
+ sample_rate: float,
440
+ difference_types: list[str],
441
+ ) -> list[Difference]:
442
+ """Detect all requested difference types.
443
+
444
+ Args:
445
+ data1: First aligned signal.
446
+ data2: Second aligned signal.
447
+ sample_rate: Sample rate in Hz.
448
+ difference_types: Types to detect.
449
+
450
+ Returns:
451
+ List of detected differences sorted by impact.
452
+ """
453
+ all_differences = []
454
+
455
+ if "timing" in difference_types:
456
+ all_differences.extend(_detect_timing_differences(data1, data2, sample_rate))
457
+
458
+ if "amplitude" in difference_types:
459
+ all_differences.extend(_detect_amplitude_differences(data1, data2, sample_rate))
460
+
461
+ if "pattern" in difference_types:
462
+ all_differences.extend(_detect_pattern_differences(data1, data2, sample_rate))
463
+
464
+ # Sort by impact score (descending)
465
+ all_differences.sort(key=lambda d: d.impact_score, reverse=True)
466
+ return all_differences
467
+
468
+
469
+ def _filter_by_severity(
470
+ differences: list[Difference], severity_threshold: str | None
471
+ ) -> list[Difference]:
472
+ """Filter differences by severity threshold.
473
+
474
+ Args:
475
+ differences: List of detected differences.
476
+ severity_threshold: Minimum severity level.
477
+
478
+ Returns:
479
+ Filtered list of differences.
480
+ """
481
+ if not severity_threshold:
482
+ return differences
483
+
484
+ severity_order = {"INFO": 0, "WARNING": 1, "CRITICAL": 2}
485
+ threshold_level = severity_order.get(severity_threshold, 0)
486
+
487
+ return [d for d in differences if severity_order.get(d.severity, 0) >= threshold_level]
488
+
489
+
490
+ def _build_summary(similarity_score: float, differences: list[Difference]) -> str:
491
+ """Build human-readable summary of comparison.
492
+
493
+ Args:
494
+ similarity_score: Overall similarity score [0, 1].
495
+ differences: List of detected differences.
496
+
497
+ Returns:
498
+ Plain-language summary string.
499
+ """
500
+ if similarity_score > 0.95:
501
+ summary = "Signals are very similar"
502
+ elif similarity_score > 0.85:
503
+ summary = "Signals are similar with minor differences"
504
+ elif similarity_score > 0.70:
505
+ summary = "Signals show moderate differences"
506
+ else:
507
+ summary = "Signals are significantly different"
508
+
509
+ critical_count = sum(1 for d in differences if d.severity == "CRITICAL")
510
+ warning_count = sum(1 for d in differences if d.severity == "WARNING")
511
+
512
+ if critical_count > 0:
513
+ summary += f" ({critical_count} critical issue(s))"
514
+ elif warning_count > 0:
515
+ summary += f" ({warning_count} warning(s))"
516
+
517
+ return summary
518
+
519
+
339
520
  def compare_traces(
340
521
  trace1: WaveformTrace,
341
522
  trace2: WaveformTrace,
@@ -378,103 +559,27 @@ def compare_traces(
378
559
  "transitions",
379
560
  ]
380
561
 
381
- # Try alignment methods
382
- if alignment == "auto":
383
- # Try all methods and pick best correlation
384
- methods = ["time", "trigger", "pattern"]
385
- best_corr = -1
386
- best_method = "time"
387
- best_aligned = None
388
-
389
- for method in methods:
390
- if method == "time":
391
- d1, d2, offset = _align_time_based(trace1, trace2)
392
- elif method == "trigger":
393
- d1, d2, offset = _align_trigger_based(trace1, trace2)
394
- else: # pattern
395
- d1, d2, offset = _align_pattern_based(trace1, trace2)
396
-
397
- # Compute correlation
398
- if len(d1) > 1:
399
- d1_norm = (d1 - np.mean(d1)) / (np.std(d1) + 1e-10)
400
- d2_norm = (d2 - np.mean(d2)) / (np.std(d2) + 1e-10)
401
- corr = np.corrcoef(d1_norm, d2_norm)[0, 1]
402
-
403
- if corr > best_corr:
404
- best_corr = corr
405
- best_method = method
406
- best_aligned = (d1, d2, offset)
407
-
408
- data1, data2, offset = best_aligned # type: ignore[misc]
409
- alignment_method = f"{best_method}-based"
410
- else:
411
- # Use specified method
412
- if alignment == "time":
413
- data1, data2, offset = _align_time_based(trace1, trace2)
414
- elif alignment == "trigger":
415
- data1, data2, offset = _align_trigger_based(trace1, trace2)
416
- else: # pattern
417
- data1, data2, offset = _align_pattern_based(trace1, trace2)
418
-
419
- alignment_method = f"{alignment}-based"
562
+ # Align traces
563
+ data1, data2, offset, alignment_method = _align_traces_by_method(trace1, trace2, alignment)
420
564
 
421
565
  sample_rate = trace1.metadata.sample_rate
422
566
 
423
567
  # Detect differences
424
- all_differences = []
425
-
426
- if "timing" in difference_types:
427
- all_differences.extend(_detect_timing_differences(data1, data2, sample_rate))
568
+ all_differences = _collect_differences(data1, data2, sample_rate, difference_types)
428
569
 
429
- if "amplitude" in difference_types:
430
- all_differences.extend(_detect_amplitude_differences(data1, data2, sample_rate))
431
-
432
- if "pattern" in difference_types:
433
- all_differences.extend(_detect_pattern_differences(data1, data2, sample_rate))
570
+ # Filter by severity
571
+ all_differences = _filter_by_severity(all_differences, severity_threshold)
434
572
 
435
- # Sort by impact score (descending)
436
- all_differences.sort(key=lambda d: d.impact_score, reverse=True)
437
-
438
- # Filter by severity threshold
439
- if severity_threshold:
440
- severity_order = {"INFO": 0, "WARNING": 1, "CRITICAL": 2}
441
- threshold_level = severity_order.get(severity_threshold, 0)
442
-
443
- filtered = [
444
- d for d in all_differences if severity_order.get(d.severity, 0) >= threshold_level
445
- ]
446
- all_differences = filtered
447
-
448
- # Compute similarity score
449
- if len(data1) > 1:
450
- data1_norm = (data1 - np.mean(data1)) / (np.std(data1) + 1e-10)
451
- data2_norm = (data2 - np.mean(data2)) / (np.std(data2) + 1e-10)
452
- correlation = np.corrcoef(data1_norm, data2_norm)[0, 1]
453
- similarity_score = float((correlation + 1) / 2) # Map [-1,1] to [0,1]
454
- else:
455
- similarity_score = 1.0 if len(data1) == 0 or data1[0] == data2[0] else 0.0
573
+ # Compute similarity
574
+ correlation = _compute_correlation(data1, data2)
575
+ similarity_score = (correlation + 1) / 2 # Map [-1,1] to [0,1]
456
576
 
457
577
  # Build summary
458
- if similarity_score > 0.95:
459
- summary = "Signals are very similar"
460
- elif similarity_score > 0.85:
461
- summary = "Signals are similar with minor differences"
462
- elif similarity_score > 0.70:
463
- summary = "Signals show moderate differences"
464
- else:
465
- summary = "Signals are significantly different"
466
-
467
- critical_count = sum(1 for d in all_differences if d.severity == "CRITICAL")
468
- warning_count = sum(1 for d in all_differences if d.severity == "WARNING")
469
-
470
- if critical_count > 0:
471
- summary += f" ({critical_count} critical issue(s))"
472
- elif warning_count > 0:
473
- summary += f" ({warning_count} warning(s))"
578
+ summary = _build_summary(similarity_score, all_differences)
474
579
 
475
580
  # Statistics
476
581
  stats = {
477
- "correlation": float(correlation) if len(data1) > 1 else 1.0,
582
+ "correlation": float(correlation),
478
583
  "rms_error": float(np.sqrt(np.mean((data1 - data2) ** 2))),
479
584
  "max_deviation": float(np.max(np.abs(data1 - data2))),
480
585
  "max_deviation_time": float(np.argmax(np.abs(data1 - data2)) / sample_rate),