oscura 0.5.1__py3-none-any.whl → 0.7.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 (497) 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/edges.py +325 -65
  5. oscura/analyzers/digital/quality.py +293 -166
  6. oscura/analyzers/digital/timing.py +260 -115
  7. oscura/analyzers/digital/timing_numba.py +334 -0
  8. oscura/analyzers/entropy.py +605 -0
  9. oscura/analyzers/eye/diagram.py +176 -109
  10. oscura/analyzers/eye/metrics.py +5 -5
  11. oscura/analyzers/jitter/__init__.py +6 -4
  12. oscura/analyzers/jitter/ber.py +52 -52
  13. oscura/analyzers/jitter/classification.py +156 -0
  14. oscura/analyzers/jitter/decomposition.py +163 -113
  15. oscura/analyzers/jitter/spectrum.py +80 -64
  16. oscura/analyzers/ml/__init__.py +39 -0
  17. oscura/analyzers/ml/features.py +600 -0
  18. oscura/analyzers/ml/signal_classifier.py +604 -0
  19. oscura/analyzers/packet/daq.py +246 -158
  20. oscura/analyzers/packet/parser.py +12 -1
  21. oscura/analyzers/packet/payload.py +50 -2110
  22. oscura/analyzers/packet/payload_analysis.py +361 -181
  23. oscura/analyzers/packet/payload_patterns.py +133 -70
  24. oscura/analyzers/packet/stream.py +84 -23
  25. oscura/analyzers/patterns/__init__.py +26 -5
  26. oscura/analyzers/patterns/anomaly_detection.py +908 -0
  27. oscura/analyzers/patterns/clustering.py +169 -108
  28. oscura/analyzers/patterns/clustering_optimized.py +227 -0
  29. oscura/analyzers/patterns/discovery.py +1 -1
  30. oscura/analyzers/patterns/matching.py +581 -197
  31. oscura/analyzers/patterns/pattern_mining.py +778 -0
  32. oscura/analyzers/patterns/periodic.py +121 -38
  33. oscura/analyzers/patterns/sequences.py +175 -78
  34. oscura/analyzers/power/conduction.py +1 -1
  35. oscura/analyzers/power/soa.py +6 -6
  36. oscura/analyzers/power/switching.py +250 -110
  37. oscura/analyzers/protocol/__init__.py +17 -1
  38. oscura/analyzers/protocols/base.py +6 -6
  39. oscura/analyzers/protocols/ble/__init__.py +38 -0
  40. oscura/analyzers/protocols/ble/analyzer.py +809 -0
  41. oscura/analyzers/protocols/ble/uuids.py +288 -0
  42. oscura/analyzers/protocols/can.py +257 -127
  43. oscura/analyzers/protocols/can_fd.py +107 -80
  44. oscura/analyzers/protocols/flexray.py +139 -80
  45. oscura/analyzers/protocols/hdlc.py +93 -58
  46. oscura/analyzers/protocols/i2c.py +247 -106
  47. oscura/analyzers/protocols/i2s.py +138 -86
  48. oscura/analyzers/protocols/industrial/__init__.py +40 -0
  49. oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
  50. oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
  51. oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
  52. oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
  53. oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
  54. oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
  55. oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
  56. oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
  57. oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
  58. oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
  59. oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
  60. oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
  61. oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
  62. oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
  63. oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
  64. oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
  65. oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
  66. oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
  67. oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
  68. oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
  69. oscura/analyzers/protocols/jtag.py +180 -98
  70. oscura/analyzers/protocols/lin.py +219 -114
  71. oscura/analyzers/protocols/manchester.py +4 -4
  72. oscura/analyzers/protocols/onewire.py +253 -149
  73. oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
  74. oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
  75. oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
  76. oscura/analyzers/protocols/spi.py +192 -95
  77. oscura/analyzers/protocols/swd.py +321 -167
  78. oscura/analyzers/protocols/uart.py +267 -125
  79. oscura/analyzers/protocols/usb.py +235 -131
  80. oscura/analyzers/side_channel/power.py +17 -12
  81. oscura/analyzers/signal/__init__.py +15 -0
  82. oscura/analyzers/signal/timing_analysis.py +1086 -0
  83. oscura/analyzers/signal_integrity/__init__.py +4 -1
  84. oscura/analyzers/signal_integrity/sparams.py +2 -19
  85. oscura/analyzers/spectral/chunked.py +129 -60
  86. oscura/analyzers/spectral/chunked_fft.py +300 -94
  87. oscura/analyzers/spectral/chunked_wavelet.py +100 -80
  88. oscura/analyzers/statistical/checksum.py +376 -217
  89. oscura/analyzers/statistical/classification.py +229 -107
  90. oscura/analyzers/statistical/entropy.py +78 -53
  91. oscura/analyzers/statistics/correlation.py +407 -211
  92. oscura/analyzers/statistics/outliers.py +2 -2
  93. oscura/analyzers/statistics/streaming.py +30 -5
  94. oscura/analyzers/validation.py +216 -101
  95. oscura/analyzers/waveform/measurements.py +9 -0
  96. oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
  97. oscura/analyzers/waveform/spectral.py +500 -228
  98. oscura/api/__init__.py +31 -5
  99. oscura/api/dsl/__init__.py +582 -0
  100. oscura/{dsl → api/dsl}/commands.py +43 -76
  101. oscura/{dsl → api/dsl}/interpreter.py +26 -51
  102. oscura/{dsl → api/dsl}/parser.py +107 -77
  103. oscura/{dsl → api/dsl}/repl.py +2 -2
  104. oscura/api/dsl.py +1 -1
  105. oscura/{integrations → api/integrations}/__init__.py +1 -1
  106. oscura/{integrations → api/integrations}/llm.py +201 -102
  107. oscura/api/operators.py +3 -3
  108. oscura/api/optimization.py +144 -30
  109. oscura/api/rest_server.py +921 -0
  110. oscura/api/server/__init__.py +17 -0
  111. oscura/api/server/dashboard.py +850 -0
  112. oscura/api/server/static/README.md +34 -0
  113. oscura/api/server/templates/base.html +181 -0
  114. oscura/api/server/templates/export.html +120 -0
  115. oscura/api/server/templates/home.html +284 -0
  116. oscura/api/server/templates/protocols.html +58 -0
  117. oscura/api/server/templates/reports.html +43 -0
  118. oscura/api/server/templates/session_detail.html +89 -0
  119. oscura/api/server/templates/sessions.html +83 -0
  120. oscura/api/server/templates/waveforms.html +73 -0
  121. oscura/automotive/__init__.py +8 -1
  122. oscura/automotive/can/__init__.py +10 -0
  123. oscura/automotive/can/checksum.py +3 -1
  124. oscura/automotive/can/dbc_generator.py +590 -0
  125. oscura/automotive/can/message_wrapper.py +121 -74
  126. oscura/automotive/can/patterns.py +98 -21
  127. oscura/automotive/can/session.py +292 -56
  128. oscura/automotive/can/state_machine.py +6 -3
  129. oscura/automotive/can/stimulus_response.py +97 -75
  130. oscura/automotive/dbc/__init__.py +10 -2
  131. oscura/automotive/dbc/generator.py +84 -56
  132. oscura/automotive/dbc/parser.py +6 -6
  133. oscura/automotive/dtc/data.json +17 -102
  134. oscura/automotive/dtc/database.py +2 -2
  135. oscura/automotive/flexray/__init__.py +31 -0
  136. oscura/automotive/flexray/analyzer.py +504 -0
  137. oscura/automotive/flexray/crc.py +185 -0
  138. oscura/automotive/flexray/fibex.py +449 -0
  139. oscura/automotive/j1939/__init__.py +45 -8
  140. oscura/automotive/j1939/analyzer.py +605 -0
  141. oscura/automotive/j1939/spns.py +326 -0
  142. oscura/automotive/j1939/transport.py +306 -0
  143. oscura/automotive/lin/__init__.py +47 -0
  144. oscura/automotive/lin/analyzer.py +612 -0
  145. oscura/automotive/loaders/blf.py +13 -2
  146. oscura/automotive/loaders/csv_can.py +143 -72
  147. oscura/automotive/loaders/dispatcher.py +50 -2
  148. oscura/automotive/loaders/mdf.py +86 -45
  149. oscura/automotive/loaders/pcap.py +111 -61
  150. oscura/automotive/uds/__init__.py +4 -0
  151. oscura/automotive/uds/analyzer.py +725 -0
  152. oscura/automotive/uds/decoder.py +140 -58
  153. oscura/automotive/uds/models.py +7 -1
  154. oscura/automotive/visualization.py +1 -1
  155. oscura/cli/analyze.py +348 -0
  156. oscura/cli/batch.py +142 -122
  157. oscura/cli/benchmark.py +275 -0
  158. oscura/cli/characterize.py +137 -82
  159. oscura/cli/compare.py +224 -131
  160. oscura/cli/completion.py +250 -0
  161. oscura/cli/config_cmd.py +361 -0
  162. oscura/cli/decode.py +164 -87
  163. oscura/cli/export.py +286 -0
  164. oscura/cli/main.py +115 -31
  165. oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
  166. oscura/{onboarding → cli/onboarding}/help.py +80 -58
  167. oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
  168. oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
  169. oscura/cli/progress.py +147 -0
  170. oscura/cli/shell.py +157 -135
  171. oscura/cli/validate_cmd.py +204 -0
  172. oscura/cli/visualize.py +158 -0
  173. oscura/convenience.py +125 -79
  174. oscura/core/__init__.py +4 -2
  175. oscura/core/backend_selector.py +3 -3
  176. oscura/core/cache.py +126 -15
  177. oscura/core/cancellation.py +1 -1
  178. oscura/{config → core/config}/__init__.py +20 -11
  179. oscura/{config → core/config}/defaults.py +1 -1
  180. oscura/{config → core/config}/loader.py +7 -5
  181. oscura/{config → core/config}/memory.py +5 -5
  182. oscura/{config → core/config}/migration.py +1 -1
  183. oscura/{config → core/config}/pipeline.py +99 -23
  184. oscura/{config → core/config}/preferences.py +1 -1
  185. oscura/{config → core/config}/protocol.py +3 -3
  186. oscura/{config → core/config}/schema.py +426 -272
  187. oscura/{config → core/config}/settings.py +1 -1
  188. oscura/{config → core/config}/thresholds.py +195 -153
  189. oscura/core/correlation.py +5 -6
  190. oscura/core/cross_domain.py +0 -2
  191. oscura/core/debug.py +9 -5
  192. oscura/{extensibility → core/extensibility}/docs.py +158 -70
  193. oscura/{extensibility → core/extensibility}/extensions.py +160 -76
  194. oscura/{extensibility → core/extensibility}/logging.py +1 -1
  195. oscura/{extensibility → core/extensibility}/measurements.py +1 -1
  196. oscura/{extensibility → core/extensibility}/plugins.py +1 -1
  197. oscura/{extensibility → core/extensibility}/templates.py +73 -3
  198. oscura/{extensibility → core/extensibility}/validation.py +1 -1
  199. oscura/core/gpu_backend.py +11 -7
  200. oscura/core/log_query.py +101 -11
  201. oscura/core/logging.py +126 -54
  202. oscura/core/logging_advanced.py +5 -5
  203. oscura/core/memory_limits.py +108 -70
  204. oscura/core/memory_monitor.py +2 -2
  205. oscura/core/memory_progress.py +7 -7
  206. oscura/core/memory_warnings.py +1 -1
  207. oscura/core/numba_backend.py +13 -13
  208. oscura/{plugins → core/plugins}/__init__.py +9 -9
  209. oscura/{plugins → core/plugins}/base.py +7 -7
  210. oscura/{plugins → core/plugins}/cli.py +3 -3
  211. oscura/{plugins → core/plugins}/discovery.py +186 -106
  212. oscura/{plugins → core/plugins}/lifecycle.py +1 -1
  213. oscura/{plugins → core/plugins}/manager.py +7 -7
  214. oscura/{plugins → core/plugins}/registry.py +3 -3
  215. oscura/{plugins → core/plugins}/versioning.py +1 -1
  216. oscura/core/progress.py +16 -1
  217. oscura/core/provenance.py +8 -2
  218. oscura/{schemas → core/schemas}/__init__.py +2 -2
  219. oscura/{schemas → core/schemas}/device_mapping.json +2 -8
  220. oscura/{schemas → core/schemas}/packet_format.json +4 -24
  221. oscura/{schemas → core/schemas}/protocol_definition.json +2 -12
  222. oscura/core/types.py +4 -0
  223. oscura/core/uncertainty.py +3 -3
  224. oscura/correlation/__init__.py +52 -0
  225. oscura/correlation/multi_protocol.py +811 -0
  226. oscura/discovery/auto_decoder.py +117 -35
  227. oscura/discovery/comparison.py +191 -86
  228. oscura/discovery/quality_validator.py +155 -68
  229. oscura/discovery/signal_detector.py +196 -79
  230. oscura/export/__init__.py +18 -8
  231. oscura/export/kaitai_struct.py +513 -0
  232. oscura/export/scapy_layer.py +801 -0
  233. oscura/export/wireshark/generator.py +1 -1
  234. oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
  235. oscura/export/wireshark_dissector.py +746 -0
  236. oscura/guidance/wizard.py +207 -111
  237. oscura/hardware/__init__.py +19 -0
  238. oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
  239. oscura/{acquisition → hardware/acquisition}/file.py +2 -2
  240. oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
  241. oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
  242. oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
  243. oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
  244. oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
  245. oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
  246. oscura/hardware/firmware/__init__.py +29 -0
  247. oscura/hardware/firmware/pattern_recognition.py +874 -0
  248. oscura/hardware/hal_detector.py +736 -0
  249. oscura/hardware/security/__init__.py +37 -0
  250. oscura/hardware/security/side_channel_detector.py +1126 -0
  251. oscura/inference/__init__.py +4 -0
  252. oscura/inference/active_learning/observation_table.py +4 -1
  253. oscura/inference/alignment.py +216 -123
  254. oscura/inference/bayesian.py +113 -33
  255. oscura/inference/crc_reverse.py +101 -55
  256. oscura/inference/logic.py +6 -2
  257. oscura/inference/message_format.py +342 -183
  258. oscura/inference/protocol.py +95 -44
  259. oscura/inference/protocol_dsl.py +180 -82
  260. oscura/inference/signal_intelligence.py +1439 -706
  261. oscura/inference/spectral.py +99 -57
  262. oscura/inference/state_machine.py +810 -158
  263. oscura/inference/stream.py +270 -110
  264. oscura/iot/__init__.py +34 -0
  265. oscura/iot/coap/__init__.py +32 -0
  266. oscura/iot/coap/analyzer.py +668 -0
  267. oscura/iot/coap/options.py +212 -0
  268. oscura/iot/lorawan/__init__.py +21 -0
  269. oscura/iot/lorawan/crypto.py +206 -0
  270. oscura/iot/lorawan/decoder.py +801 -0
  271. oscura/iot/lorawan/mac_commands.py +341 -0
  272. oscura/iot/mqtt/__init__.py +27 -0
  273. oscura/iot/mqtt/analyzer.py +999 -0
  274. oscura/iot/mqtt/properties.py +315 -0
  275. oscura/iot/zigbee/__init__.py +31 -0
  276. oscura/iot/zigbee/analyzer.py +615 -0
  277. oscura/iot/zigbee/security.py +153 -0
  278. oscura/iot/zigbee/zcl.py +349 -0
  279. oscura/jupyter/display.py +125 -45
  280. oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
  281. oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
  282. oscura/jupyter/exploratory/fuzzy.py +746 -0
  283. oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
  284. oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
  285. oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
  286. oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
  287. oscura/jupyter/exploratory/sync.py +612 -0
  288. oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
  289. oscura/jupyter/magic.py +4 -4
  290. oscura/{ui → jupyter/ui}/__init__.py +2 -2
  291. oscura/{ui → jupyter/ui}/formatters.py +3 -3
  292. oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
  293. oscura/loaders/__init__.py +183 -67
  294. oscura/loaders/binary.py +88 -1
  295. oscura/loaders/chipwhisperer.py +153 -137
  296. oscura/loaders/configurable.py +208 -86
  297. oscura/loaders/csv_loader.py +458 -215
  298. oscura/loaders/hdf5_loader.py +278 -119
  299. oscura/loaders/lazy.py +87 -54
  300. oscura/loaders/mmap_loader.py +1 -1
  301. oscura/loaders/numpy_loader.py +253 -116
  302. oscura/loaders/pcap.py +226 -151
  303. oscura/loaders/rigol.py +110 -49
  304. oscura/loaders/sigrok.py +201 -78
  305. oscura/loaders/tdms.py +81 -58
  306. oscura/loaders/tektronix.py +291 -174
  307. oscura/loaders/touchstone.py +182 -87
  308. oscura/loaders/tss.py +456 -0
  309. oscura/loaders/vcd.py +215 -117
  310. oscura/loaders/wav.py +155 -68
  311. oscura/reporting/__init__.py +9 -0
  312. oscura/reporting/analyze.py +352 -146
  313. oscura/reporting/argument_preparer.py +69 -14
  314. oscura/reporting/auto_report.py +97 -61
  315. oscura/reporting/batch.py +131 -58
  316. oscura/reporting/chart_selection.py +57 -45
  317. oscura/reporting/comparison.py +63 -17
  318. oscura/reporting/content/executive.py +76 -24
  319. oscura/reporting/core_formats/multi_format.py +11 -8
  320. oscura/reporting/engine.py +312 -158
  321. oscura/reporting/enhanced_reports.py +949 -0
  322. oscura/reporting/export.py +86 -43
  323. oscura/reporting/formatting/numbers.py +69 -42
  324. oscura/reporting/html.py +139 -58
  325. oscura/reporting/index.py +137 -65
  326. oscura/reporting/output.py +158 -67
  327. oscura/reporting/pdf.py +67 -102
  328. oscura/reporting/plots.py +191 -112
  329. oscura/reporting/sections.py +88 -47
  330. oscura/reporting/standards.py +104 -61
  331. oscura/reporting/summary_generator.py +75 -55
  332. oscura/reporting/tables.py +138 -54
  333. oscura/reporting/templates/enhanced/protocol_re.html +525 -0
  334. oscura/sessions/__init__.py +14 -23
  335. oscura/sessions/base.py +3 -3
  336. oscura/sessions/blackbox.py +106 -10
  337. oscura/sessions/generic.py +2 -2
  338. oscura/sessions/legacy.py +783 -0
  339. oscura/side_channel/__init__.py +63 -0
  340. oscura/side_channel/dpa.py +1025 -0
  341. oscura/utils/__init__.py +15 -1
  342. oscura/utils/bitwise.py +118 -0
  343. oscura/{builders → utils/builders}/__init__.py +1 -1
  344. oscura/{comparison → utils/comparison}/__init__.py +6 -6
  345. oscura/{comparison → utils/comparison}/compare.py +202 -101
  346. oscura/{comparison → utils/comparison}/golden.py +83 -63
  347. oscura/{comparison → utils/comparison}/limits.py +313 -89
  348. oscura/{comparison → utils/comparison}/mask.py +151 -45
  349. oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
  350. oscura/{comparison → utils/comparison}/visualization.py +147 -89
  351. oscura/{component → utils/component}/__init__.py +3 -3
  352. oscura/{component → utils/component}/impedance.py +122 -58
  353. oscura/{component → utils/component}/reactive.py +165 -168
  354. oscura/{component → utils/component}/transmission_line.py +3 -3
  355. oscura/{filtering → utils/filtering}/__init__.py +6 -6
  356. oscura/{filtering → utils/filtering}/base.py +1 -1
  357. oscura/{filtering → utils/filtering}/convenience.py +2 -2
  358. oscura/{filtering → utils/filtering}/design.py +169 -93
  359. oscura/{filtering → utils/filtering}/filters.py +2 -2
  360. oscura/{filtering → utils/filtering}/introspection.py +2 -2
  361. oscura/utils/geometry.py +31 -0
  362. oscura/utils/imports.py +184 -0
  363. oscura/utils/lazy.py +1 -1
  364. oscura/{math → utils/math}/__init__.py +2 -2
  365. oscura/{math → utils/math}/arithmetic.py +114 -48
  366. oscura/{math → utils/math}/interpolation.py +139 -106
  367. oscura/utils/memory.py +129 -66
  368. oscura/utils/memory_advanced.py +92 -9
  369. oscura/utils/memory_extensions.py +10 -8
  370. oscura/{optimization → utils/optimization}/__init__.py +1 -1
  371. oscura/{optimization → utils/optimization}/search.py +2 -2
  372. oscura/utils/performance/__init__.py +58 -0
  373. oscura/utils/performance/caching.py +889 -0
  374. oscura/utils/performance/lsh_clustering.py +333 -0
  375. oscura/utils/performance/memory_optimizer.py +699 -0
  376. oscura/utils/performance/optimizations.py +675 -0
  377. oscura/utils/performance/parallel.py +654 -0
  378. oscura/utils/performance/profiling.py +661 -0
  379. oscura/{pipeline → utils/pipeline}/base.py +1 -1
  380. oscura/{pipeline → utils/pipeline}/composition.py +1 -1
  381. oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
  382. oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
  383. oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
  384. oscura/{search → utils/search}/__init__.py +3 -3
  385. oscura/{search → utils/search}/anomaly.py +188 -58
  386. oscura/utils/search/context.py +294 -0
  387. oscura/{search → utils/search}/pattern.py +138 -10
  388. oscura/utils/serial.py +51 -0
  389. oscura/utils/storage/__init__.py +61 -0
  390. oscura/utils/storage/database.py +1166 -0
  391. oscura/{streaming → utils/streaming}/chunked.py +302 -143
  392. oscura/{streaming → utils/streaming}/progressive.py +1 -1
  393. oscura/{streaming → utils/streaming}/realtime.py +3 -2
  394. oscura/{triggering → utils/triggering}/__init__.py +6 -6
  395. oscura/{triggering → utils/triggering}/base.py +6 -6
  396. oscura/{triggering → utils/triggering}/edge.py +2 -2
  397. oscura/{triggering → utils/triggering}/pattern.py +2 -2
  398. oscura/{triggering → utils/triggering}/pulse.py +115 -74
  399. oscura/{triggering → utils/triggering}/window.py +2 -2
  400. oscura/utils/validation.py +32 -0
  401. oscura/validation/__init__.py +121 -0
  402. oscura/{compliance → validation/compliance}/__init__.py +5 -5
  403. oscura/{compliance → validation/compliance}/advanced.py +5 -5
  404. oscura/{compliance → validation/compliance}/masks.py +1 -1
  405. oscura/{compliance → validation/compliance}/reporting.py +127 -53
  406. oscura/{compliance → validation/compliance}/testing.py +114 -52
  407. oscura/validation/compliance_tests.py +915 -0
  408. oscura/validation/fuzzer.py +990 -0
  409. oscura/validation/grammar_tests.py +596 -0
  410. oscura/validation/grammar_validator.py +904 -0
  411. oscura/validation/hil_testing.py +977 -0
  412. oscura/{quality → validation/quality}/__init__.py +4 -4
  413. oscura/{quality → validation/quality}/ensemble.py +251 -171
  414. oscura/{quality → validation/quality}/explainer.py +3 -3
  415. oscura/{quality → validation/quality}/scoring.py +1 -1
  416. oscura/{quality → validation/quality}/warnings.py +4 -4
  417. oscura/validation/regression_suite.py +808 -0
  418. oscura/validation/replay.py +788 -0
  419. oscura/{testing → validation/testing}/__init__.py +2 -2
  420. oscura/{testing → validation/testing}/synthetic.py +5 -5
  421. oscura/visualization/__init__.py +9 -0
  422. oscura/visualization/accessibility.py +1 -1
  423. oscura/visualization/annotations.py +64 -67
  424. oscura/visualization/colors.py +7 -7
  425. oscura/visualization/digital.py +180 -81
  426. oscura/visualization/eye.py +236 -85
  427. oscura/visualization/interactive.py +320 -143
  428. oscura/visualization/jitter.py +587 -247
  429. oscura/visualization/layout.py +169 -134
  430. oscura/visualization/optimization.py +103 -52
  431. oscura/visualization/palettes.py +1 -1
  432. oscura/visualization/power.py +427 -211
  433. oscura/visualization/power_extended.py +626 -297
  434. oscura/visualization/presets.py +2 -0
  435. oscura/visualization/protocols.py +495 -181
  436. oscura/visualization/render.py +79 -63
  437. oscura/visualization/reverse_engineering.py +171 -124
  438. oscura/visualization/signal_integrity.py +460 -279
  439. oscura/visualization/specialized.py +190 -100
  440. oscura/visualization/spectral.py +670 -255
  441. oscura/visualization/thumbnails.py +166 -137
  442. oscura/visualization/waveform.py +150 -63
  443. oscura/workflows/__init__.py +3 -0
  444. oscura/{batch → workflows/batch}/__init__.py +5 -5
  445. oscura/{batch → workflows/batch}/advanced.py +150 -75
  446. oscura/workflows/batch/aggregate.py +531 -0
  447. oscura/workflows/batch/analyze.py +236 -0
  448. oscura/{batch → workflows/batch}/logging.py +2 -2
  449. oscura/{batch → workflows/batch}/metrics.py +1 -1
  450. oscura/workflows/complete_re.py +1144 -0
  451. oscura/workflows/compliance.py +44 -54
  452. oscura/workflows/digital.py +197 -51
  453. oscura/workflows/legacy/__init__.py +12 -0
  454. oscura/{workflow → workflows/legacy}/dag.py +4 -1
  455. oscura/workflows/multi_trace.py +9 -9
  456. oscura/workflows/power.py +42 -62
  457. oscura/workflows/protocol.py +82 -49
  458. oscura/workflows/reverse_engineering.py +351 -150
  459. oscura/workflows/signal_integrity.py +157 -82
  460. oscura-0.7.0.dist-info/METADATA +661 -0
  461. oscura-0.7.0.dist-info/RECORD +591 -0
  462. oscura/batch/aggregate.py +0 -300
  463. oscura/batch/analyze.py +0 -139
  464. oscura/dsl/__init__.py +0 -73
  465. oscura/exceptions.py +0 -59
  466. oscura/exploratory/fuzzy.py +0 -513
  467. oscura/exploratory/sync.py +0 -384
  468. oscura/exporters/__init__.py +0 -94
  469. oscura/exporters/csv.py +0 -303
  470. oscura/exporters/exporters.py +0 -44
  471. oscura/exporters/hdf5.py +0 -217
  472. oscura/exporters/html_export.py +0 -701
  473. oscura/exporters/json_export.py +0 -291
  474. oscura/exporters/markdown_export.py +0 -367
  475. oscura/exporters/matlab_export.py +0 -354
  476. oscura/exporters/npz_export.py +0 -219
  477. oscura/exporters/spice_export.py +0 -210
  478. oscura/search/context.py +0 -149
  479. oscura/session/__init__.py +0 -34
  480. oscura/session/annotations.py +0 -289
  481. oscura/session/history.py +0 -313
  482. oscura/session/session.py +0 -520
  483. oscura/workflow/__init__.py +0 -13
  484. oscura-0.5.1.dist-info/METADATA +0 -583
  485. oscura-0.5.1.dist-info/RECORD +0 -481
  486. /oscura/core/{config.py → config/legacy.py} +0 -0
  487. /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
  488. /oscura/{extensibility → core/extensibility}/registry.py +0 -0
  489. /oscura/{plugins → core/plugins}/isolation.py +0 -0
  490. /oscura/{schemas → core/schemas}/bus_configuration.json +0 -0
  491. /oscura/{builders → utils/builders}/signal_builder.py +0 -0
  492. /oscura/{optimization → utils/optimization}/parallel.py +0 -0
  493. /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
  494. /oscura/{streaming → utils/streaming}/__init__.py +0 -0
  495. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/WHEEL +0 -0
  496. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/entry_points.txt +0 -0
  497. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/licenses/LICENSE +0 -0
oscura/utils/__init__.py CHANGED
@@ -1,8 +1,11 @@
1
1
  """Oscura utilities package.
2
2
 
3
- Provides utility functions for memory management, windowing, and progressive analysis.
3
+ Provides utility functions for memory management, windowing, progressive analysis,
4
+ geometry, serial communication, validation, and bitwise operations.
4
5
  """
5
6
 
7
+ from oscura.utils.bitwise import bits_to_byte, bits_to_value
8
+ from oscura.utils.geometry import generate_leader_line
6
9
  from oscura.utils.memory import (
7
10
  MemoryCheck,
8
11
  MemoryCheckError,
@@ -50,6 +53,8 @@ from oscura.utils.progressive import (
50
53
  progressive_analysis,
51
54
  select_roi,
52
55
  )
56
+ from oscura.utils.serial import connect_serial_port
57
+ from oscura.utils.validation import validate_protocol_spec
53
58
 
54
59
  __all__ = [
55
60
  # Advanced memory management (MEM-014, 020, 023, 025, 028, 030-033)
@@ -75,13 +80,20 @@ __all__ = [
75
80
  "ROISelection",
76
81
  "WSLSwapChecker",
77
82
  "analyze_roi",
83
+ # Bitwise operations
84
+ "bits_to_byte",
85
+ "bits_to_value",
78
86
  "check_memory_available",
79
87
  "configure_memory",
88
+ # Serial communication
89
+ "connect_serial_port",
80
90
  "create_preview",
81
91
  "detect_wsl",
82
92
  "estimate_memory",
83
93
  "estimate_optimal_preview_factor",
84
94
  "gc_aggressive",
95
+ # Geometry
96
+ "generate_leader_line",
85
97
  "get_available_memory",
86
98
  "get_max_memory",
87
99
  "get_memory_config",
@@ -96,4 +108,6 @@ __all__ = [
96
108
  "select_roi",
97
109
  "set_max_memory",
98
110
  "suggest_downsampling",
111
+ # Validation
112
+ "validate_protocol_spec",
99
113
  ]
@@ -0,0 +1,118 @@
1
+ """Bitwise operation utilities for protocol decoding.
2
+
3
+ This module provides bitwise conversion utilities used across protocol analyzers.
4
+ Performance-optimized with NumPy vectorized operations (10-100x faster for large arrays).
5
+ """
6
+
7
+ import numpy as np
8
+ from numpy.typing import NDArray
9
+
10
+
11
+ def bits_to_byte(bits: list[int] | NDArray[np.uint8], lsb_first: bool = True) -> int:
12
+ """Convert up to 8 bits to byte value.
13
+
14
+ Performance: Uses NumPy vectorized operations for 10-100x speedup vs loops.
15
+
16
+ Args:
17
+ bits: List or array of bits (0 or 1)
18
+ lsb_first: If True, bits[0] is LSB. If False, bits[0] is MSB.
19
+
20
+ Returns:
21
+ Byte value (0-255)
22
+
23
+ Raises:
24
+ ValueError: If bits contain values other than 0 or 1
25
+
26
+ Example:
27
+ >>> bits_to_byte([1, 0, 1, 0, 1, 0, 1, 0]) # LSB first
28
+ 85
29
+ >>> bits_to_byte([1, 0, 1, 0, 1, 0, 1, 0], lsb_first=False) # MSB first
30
+ 170
31
+ """
32
+ # Validate input types before conversion
33
+ if isinstance(bits, (list, tuple)):
34
+ # Check for non-integer types in list/tuple
35
+ if any(not isinstance(b, (int, np.integer)) for b in bits):
36
+ raise ValueError("All bits must be 0 or 1")
37
+
38
+ # Convert to numpy array for vectorized operations
39
+ bits_arr = np.asarray(bits, dtype=np.uint8)
40
+
41
+ if not np.all((bits_arr == 0) | (bits_arr == 1)):
42
+ raise ValueError("All bits must be 0 or 1")
43
+
44
+ num_bits = min(8, len(bits_arr))
45
+ bits_arr = bits_arr[:num_bits]
46
+
47
+ if lsb_first:
48
+ # Vectorized: bits[i] * 2^i
49
+ shifts = np.arange(num_bits, dtype=np.uint8)
50
+ value = np.sum(bits_arr << shifts)
51
+ else:
52
+ # Vectorized: bits[i] * 2^(7-i)
53
+ shifts = np.arange(7, 7 - num_bits, -1, dtype=np.uint8)
54
+ value = np.sum(bits_arr << shifts)
55
+
56
+ return int(value)
57
+
58
+
59
+ def bits_to_value(bits: list[int] | NDArray[np.uint8], lsb_first: bool = True) -> int:
60
+ """Convert arbitrary number of bits to integer value.
61
+
62
+ Performance: Uses NumPy vectorized operations for 10-100x speedup vs loops.
63
+
64
+ Args:
65
+ bits: List or array of bits (0 or 1)
66
+ lsb_first: If True, bits[0] is LSB. If False, bits[0] is MSB.
67
+
68
+ Returns:
69
+ Integer value
70
+
71
+ Raises:
72
+ ValueError: If bits contain values other than 0 or 1
73
+
74
+ Example:
75
+ >>> bits_to_value([1, 1, 1, 1, 1, 1, 1, 1, 1, 1]) # 10 bits
76
+ 1023
77
+ >>> bits_to_value([1, 0, 1, 0], lsb_first=False)
78
+ 10
79
+ """
80
+ # Validate input types before conversion
81
+ if isinstance(bits, (list, tuple)):
82
+ # Check for non-integer types in list/tuple
83
+ if any(not isinstance(b, (int, np.integer)) for b in bits):
84
+ raise ValueError("All bits must be 0 or 1")
85
+
86
+ # Convert to numpy array for vectorized operations
87
+ bits_arr = np.asarray(bits, dtype=np.uint8)
88
+
89
+ if not np.all((bits_arr == 0) | (bits_arr == 1)):
90
+ raise ValueError("All bits must be 0 or 1")
91
+
92
+ num_bits = len(bits_arr)
93
+
94
+ # For very large bit arrays (>64 bits), use packbits for efficiency
95
+ if num_bits > 64:
96
+ if lsb_first:
97
+ # Reverse for LSB-first interpretation
98
+ bits_arr = bits_arr[::-1]
99
+ # Pack into bytes (MSB first)
100
+ packed = np.packbits(bits_arr, bitorder="big")
101
+ # Convert bytes to integer
102
+ value = int.from_bytes(packed.tobytes(), byteorder="big")
103
+ # Adjust for partial byte
104
+ if not lsb_first:
105
+ value >>= (8 - (num_bits % 8)) % 8
106
+ return value
107
+
108
+ # For smaller arrays, use vectorized shift and sum
109
+ if lsb_first:
110
+ # Vectorized: bits[i] * 2^i
111
+ shifts = np.arange(num_bits, dtype=np.uint64)
112
+ value = np.sum(bits_arr.astype(np.uint64) << shifts)
113
+ else:
114
+ # Vectorized: bits[i] * 2^(num_bits-1-i)
115
+ shifts = np.arange(num_bits - 1, -1, -1, dtype=np.uint64)
116
+ value = np.sum(bits_arr.astype(np.uint64) << shifts)
117
+
118
+ return int(value)
@@ -32,7 +32,7 @@ References:
32
32
  - Protocol Test Signal Specifications
33
33
  """
34
34
 
35
- from oscura.builders.signal_builder import SignalBuilder
35
+ from oscura.utils.builders.signal_builder import SignalBuilder
36
36
 
37
37
  __all__ = [
38
38
  "SignalBuilder",
@@ -4,31 +4,31 @@ This module provides waveform comparison, limit testing, mask testing,
4
4
  and golden waveform comparison functionality.
5
5
  """
6
6
 
7
- from oscura.comparison.compare import (
7
+ from oscura.utils.comparison.compare import (
8
8
  compare_traces,
9
9
  correlation,
10
10
  difference,
11
11
  similarity_score,
12
12
  )
13
- from oscura.comparison.golden import (
13
+ from oscura.utils.comparison.golden import (
14
14
  GoldenReference,
15
15
  compare_to_golden,
16
16
  create_golden,
17
17
  tolerance_envelope,
18
18
  )
19
- from oscura.comparison.limits import (
19
+ from oscura.utils.comparison.limits import (
20
20
  LimitSpec,
21
21
  check_limits,
22
22
  create_limit_spec,
23
23
  margin_analysis,
24
24
  )
25
- from oscura.comparison.mask import (
25
+ from oscura.utils.comparison.mask import (
26
26
  Mask,
27
27
  create_mask,
28
28
  eye_mask,
29
29
  mask_test,
30
30
  )
31
- from oscura.comparison.trace_diff import (
31
+ from oscura.utils.comparison.trace_diff import (
32
32
  Difference,
33
33
  TraceDiff,
34
34
  )
@@ -36,7 +36,7 @@ from oscura.comparison.trace_diff import (
36
36
  # Note: compare_traces is imported from both compare.py and trace_diff.py
37
37
  # The trace_diff version is from discovery.comparison (intelligent comparison)
38
38
  # Import as compare_traces_intelligent to avoid conflict
39
- from oscura.comparison.trace_diff import compare_traces as compare_traces_intelligent
39
+ from oscura.utils.comparison.trace_diff import compare_traces as compare_traces_intelligent
40
40
 
41
41
  __all__ = [
42
42
  # Intelligent trace diff (DISC-004)
@@ -5,7 +5,7 @@ difference calculation, correlation, and similarity scoring.
5
5
 
6
6
 
7
7
  Example:
8
- >>> from oscura.comparison import compare_traces, similarity_score
8
+ >>> from oscura.utils.comparison import compare_traces, similarity_score
9
9
  >>> result = compare_traces(trace1, trace2)
10
10
  >>> score = similarity_score(trace1, trace2)
11
11
 
@@ -205,11 +205,30 @@ def similarity_score(
205
205
  >>> if score > 0.95:
206
206
  ... print("Traces match")
207
207
  """
208
- # Get data
208
+ data1, data2 = _prepare_trace_data(trace1, trace2, normalize_offset, normalize_amplitude)
209
+
210
+ if method == "correlation":
211
+ return _correlation_similarity(data1, data2)
212
+ elif method == "rms":
213
+ return _rms_similarity(data1, data2)
214
+ elif method == "mse":
215
+ return _mse_similarity(data1, data2)
216
+ elif method == "cosine":
217
+ return _cosine_similarity(data1, data2)
218
+ else:
219
+ raise ValueError(f"Unknown similarity method: {method}")
220
+
221
+
222
+ def _prepare_trace_data(
223
+ trace1: WaveformTrace,
224
+ trace2: WaveformTrace,
225
+ normalize_offset: bool,
226
+ normalize_amplitude: bool,
227
+ ) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
228
+ """Prepare trace data for similarity comparison."""
209
229
  data1 = trace1.data.astype(np.float64).copy()
210
230
  data2 = trace2.data.astype(np.float64).copy()
211
231
 
212
- # Check for NaN/Inf values
213
232
  if np.any(~np.isfinite(data1)) or np.any(~np.isfinite(data2)):
214
233
  raise ValueError("Input traces contain NaN or Inf values")
215
234
 
@@ -232,151 +251,180 @@ def similarity_score(
232
251
  if std2 > 0:
233
252
  data2 = data2 / std2
234
253
 
235
- if method == "correlation":
236
- # Pearson correlation coefficient
237
- # Handle constant inputs gracefully
238
- with warnings.catch_warnings():
239
- warnings.filterwarnings("ignore", category=stats.ConstantInputWarning)
240
- try:
241
- r, _ = stats.pearsonr(data1, data2)
242
- # Handle NaN result (constant traces after normalization)
243
- if np.isnan(r):
244
- # If both traces are constant and identical, perfect match
245
- if np.allclose(data1, data2, equal_nan=False):
246
- r = 1.0
247
- else:
248
- r = 0.0
249
- except Exception:
250
- r = 0.0
251
- # Map from [-1, 1] to [0, 1]
252
- return float((r + 1) / 2)
254
+ return data1, data2
253
255
 
254
- elif method == "rms":
255
- # RMS-based similarity
256
- rms_diff = np.sqrt(np.mean((data1 - data2) ** 2))
257
- rms_ref = np.sqrt(np.mean(data2**2)) + 1e-10
258
- return float(max(0, 1 - rms_diff / rms_ref))
259
256
 
260
- elif method == "mse":
261
- # MSE-based similarity
262
- mse = np.mean((data1 - data2) ** 2)
263
- var_ref = np.var(data2) + 1e-10
264
- return float(max(0, 1 - mse / var_ref))
257
+ def _correlation_similarity(data1: NDArray[np.float64], data2: NDArray[np.float64]) -> float:
258
+ """Compute Pearson correlation-based similarity."""
259
+ with warnings.catch_warnings():
260
+ warnings.filterwarnings("ignore", category=stats.ConstantInputWarning)
261
+ try:
262
+ r, _ = stats.pearsonr(data1, data2)
263
+ if np.isnan(r):
264
+ r = 1.0 if np.allclose(data1, data2, equal_nan=False) else 0.0
265
+ except Exception:
266
+ r = 0.0
267
+ return float((r + 1) / 2)
265
268
 
266
- elif method == "cosine":
267
- # Cosine similarity
268
- dot = np.dot(data1, data2)
269
- norm1 = np.linalg.norm(data1) + 1e-10
270
- norm2 = np.linalg.norm(data2) + 1e-10
271
- cosine = dot / (norm1 * norm2)
272
- # Map from [-1, 1] to [0, 1]
273
- return float((cosine + 1) / 2)
274
269
 
275
- else:
276
- raise ValueError(f"Unknown similarity method: {method}")
270
+ def _rms_similarity(data1: NDArray[np.float64], data2: NDArray[np.float64]) -> float:
271
+ """Compute RMS-based similarity."""
272
+ rms_diff = np.sqrt(np.mean((data1 - data2) ** 2))
273
+ rms_ref = np.sqrt(np.mean(data2**2)) + 1e-10
274
+ return float(max(0, 1 - rms_diff / rms_ref))
277
275
 
278
276
 
279
- def compare_traces(
280
- trace1: WaveformTrace,
281
- trace2: WaveformTrace,
282
- *,
283
- tolerance: float | None = None,
284
- tolerance_pct: float | None = None,
285
- method: Literal["absolute", "relative", "statistical"] = "absolute",
286
- include_difference: bool = True,
287
- ) -> ComparisonResult:
288
- """Compare two traces and determine if they match.
277
+ def _mse_similarity(data1: NDArray[np.float64], data2: NDArray[np.float64]) -> float:
278
+ """Compute MSE-based similarity."""
279
+ mse = np.mean((data1 - data2) ** 2)
280
+ var_ref = np.var(data2) + 1e-10
281
+ return float(max(0, 1 - mse / var_ref))
289
282
 
290
- Comprehensive comparison of two waveforms including difference
291
- analysis, correlation, and match determination.
292
283
 
293
- Args:
294
- trace1: First trace (typically measured).
295
- trace2: Second trace (typically reference).
296
- tolerance: Absolute tolerance for matching.
297
- tolerance_pct: Percentage tolerance (0-100) relative to reference range.
298
- method: Comparison method:
299
- - "absolute": Compare absolute values
300
- - "relative": Compare relative to reference
301
- - "statistical": Use statistical tests
302
- include_difference: Include difference trace in result.
284
+ def _cosine_similarity(data1: NDArray[np.float64], data2: NDArray[np.float64]) -> float:
285
+ """Compute cosine similarity."""
286
+ dot = np.dot(data1, data2)
287
+ norm1 = np.linalg.norm(data1) + 1e-10
288
+ norm2 = np.linalg.norm(data2) + 1e-10
289
+ cosine = dot / (norm1 * norm2)
290
+ return float((cosine + 1) / 2)
303
291
 
304
- Returns:
305
- ComparisonResult with match status and statistics.
306
292
 
307
- Raises:
308
- ValueError: If method is unknown.
293
+ def _align_trace_data(
294
+ trace1: WaveformTrace, trace2: WaveformTrace
295
+ ) -> tuple[NDArray[np.float64], NDArray[np.float64], int]:
296
+ """Align trace data to same length.
309
297
 
310
- Example:
311
- >>> result = compare_traces(measured, golden, tolerance=0.01)
312
- >>> if result.match:
313
- ... print(f"Match! Similarity: {result.similarity:.1%}")
298
+ Args:
299
+ trace1: First trace.
300
+ trace2: Second trace.
301
+
302
+ Returns:
303
+ Tuple of (data1, data2, min_len).
314
304
  """
315
- # Get data
316
305
  data1 = trace1.data.astype(np.float64)
317
306
  data2 = trace2.data.astype(np.float64)
318
-
319
- # Align lengths
320
307
  min_len = min(len(data1), len(data2))
321
- data1 = data1[:min_len]
322
- data2 = data2[:min_len]
308
+ return data1[:min_len], data2[:min_len], min_len
323
309
 
324
- # Compute difference
325
- diff = data1 - data2
326
310
 
327
- # Compute statistics
311
+ def _compute_difference_stats(diff: NDArray[np.float64]) -> tuple[float, float]:
312
+ """Compute max and RMS of difference.
313
+
314
+ Args:
315
+ diff: Difference array.
316
+
317
+ Returns:
318
+ Tuple of (max_diff, rms_diff).
319
+ """
328
320
  max_diff = float(np.max(np.abs(diff)))
329
321
  rms_diff = float(np.sqrt(np.mean(diff**2)))
322
+ return max_diff, rms_diff
323
+
330
324
 
331
- # Compute correlation
325
+ def _compute_correlation_coefficient(
326
+ data1: NDArray[np.float64], data2: NDArray[np.float64]
327
+ ) -> float:
328
+ """Compute Pearson correlation coefficient.
329
+
330
+ Args:
331
+ data1: First data array.
332
+ data2: Second data array.
333
+
334
+ Returns:
335
+ Correlation coefficient.
336
+ """
332
337
  if len(data1) > 1:
333
- # Handle constant inputs (e.g., DC signals) gracefully
334
338
  with warnings.catch_warnings():
335
339
  warnings.filterwarnings("ignore", category=stats.ConstantInputWarning)
336
340
  try:
337
341
  corr, _ = stats.pearsonr(data1, data2)
338
342
  except Exception:
339
- # Fallback for any correlation computation issues
340
343
  corr = 0.0
341
344
  else:
342
345
  corr = 1.0 if data1[0] == data2[0] else 0.0
346
+ return float(corr)
343
347
 
344
- # Compute similarity score
345
- sim_score = similarity_score(trace1, trace2)
346
348
 
347
- # Determine tolerance
349
+ def _determine_tolerance(
350
+ tolerance: float | None, tolerance_pct: float | None, data2: NDArray[np.float64]
351
+ ) -> float:
352
+ """Determine effective tolerance value.
353
+
354
+ Args:
355
+ tolerance: Absolute tolerance value.
356
+ tolerance_pct: Percentage tolerance.
357
+ data2: Reference data array.
358
+
359
+ Returns:
360
+ Effective tolerance value.
361
+ """
348
362
  if tolerance is None and tolerance_pct is not None:
349
363
  ref_range = float(np.ptp(data2))
350
- tolerance = ref_range * tolerance_pct / 100.0
364
+ return ref_range * tolerance_pct / 100.0
351
365
  elif tolerance is None:
352
- # Default: 1% of reference range
353
366
  ref_range = float(np.ptp(data2))
354
- tolerance = ref_range * 0.01
367
+ return ref_range * 0.01
368
+ return tolerance
355
369
 
356
- # Find violations
357
- violations = np.where(np.abs(diff) > tolerance)[0]
358
370
 
359
- # Determine match
371
+ def _determine_match(
372
+ method: str,
373
+ max_diff: float,
374
+ tolerance: float,
375
+ tolerance_pct: float | None,
376
+ data1: NDArray[np.float64],
377
+ data2: NDArray[np.float64],
378
+ ) -> bool:
379
+ """Determine if traces match based on method.
380
+
381
+ Args:
382
+ method: Comparison method.
383
+ max_diff: Maximum difference.
384
+ tolerance: Tolerance value.
385
+ tolerance_pct: Percentage tolerance.
386
+ data1: First data array.
387
+ data2: Second data array.
388
+
389
+ Returns:
390
+ True if traces match.
391
+
392
+ Raises:
393
+ ValueError: If method is unknown.
394
+ """
360
395
  if method == "absolute":
361
- match = max_diff <= tolerance
396
+ return max_diff <= tolerance
362
397
  elif method == "relative":
363
398
  ref_range = float(np.ptp(data2)) + 1e-10
364
399
  relative_max = max_diff / ref_range
365
- match = relative_max <= (tolerance_pct or 1.0) / 100.0
400
+ return relative_max <= (tolerance_pct or 1.0) / 100.0
366
401
  elif method == "statistical":
367
- # Use t-test for statistical matching
368
402
  _, p_value = stats.ttest_rel(data1, data2)
369
- match = p_value > 0.05 # No significant difference
403
+ return bool(p_value > 0.05)
370
404
  else:
371
405
  raise ValueError(f"Unknown method: {method}")
372
406
 
373
- # Create difference trace if requested
374
- diff_trace = None
375
- if include_difference:
376
- diff_trace = difference(trace1, trace2, channel_name="comparison_diff")
377
407
 
378
- # Compute additional statistics
379
- statistics = {
408
+ def _compute_comparison_statistics(
409
+ diff: NDArray[np.float64],
410
+ violations: NDArray[np.int64],
411
+ min_len: int,
412
+ data1: NDArray[np.float64],
413
+ data2: NDArray[np.float64],
414
+ ) -> dict[str, float]:
415
+ """Compute additional comparison statistics.
416
+
417
+ Args:
418
+ diff: Difference array.
419
+ violations: Violation indices.
420
+ min_len: Minimum length.
421
+ data1: First data array.
422
+ data2: Second data array.
423
+
424
+ Returns:
425
+ Dictionary of statistics.
426
+ """
427
+ return {
380
428
  "mean_difference": float(np.mean(diff)),
381
429
  "std_difference": float(np.std(diff)),
382
430
  "median_difference": float(np.median(diff)),
@@ -385,6 +433,59 @@ def compare_traces(
385
433
  "p_value": float(stats.ttest_rel(data1, data2)[1]) if len(data1) > 1 else 1.0,
386
434
  }
387
435
 
436
+
437
+ def compare_traces(
438
+ trace1: WaveformTrace,
439
+ trace2: WaveformTrace,
440
+ *,
441
+ tolerance: float | None = None,
442
+ tolerance_pct: float | None = None,
443
+ method: Literal["absolute", "relative", "statistical"] = "absolute",
444
+ include_difference: bool = True,
445
+ ) -> ComparisonResult:
446
+ """Compare two traces and determine if they match.
447
+
448
+ Comprehensive comparison of two waveforms including difference
449
+ analysis, correlation, and match determination.
450
+
451
+ Args:
452
+ trace1: First trace (typically measured).
453
+ trace2: Second trace (typically reference).
454
+ tolerance: Absolute tolerance for matching.
455
+ tolerance_pct: Percentage tolerance (0-100) relative to reference range.
456
+ method: Comparison method:
457
+ - "absolute": Compare absolute values
458
+ - "relative": Compare relative to reference
459
+ - "statistical": Use statistical tests
460
+ include_difference: Include difference trace in result.
461
+
462
+ Returns:
463
+ ComparisonResult with match status and statistics.
464
+
465
+ Raises:
466
+ ValueError: If method is unknown.
467
+
468
+ Example:
469
+ >>> result = compare_traces(measured, golden, tolerance=0.01)
470
+ >>> if result.match:
471
+ ... print(f"Match! Similarity: {result.similarity:.1%}")
472
+ """
473
+ data1, data2, min_len = _align_trace_data(trace1, trace2)
474
+ diff = data1 - data2
475
+
476
+ max_diff, rms_diff = _compute_difference_stats(diff)
477
+ corr = _compute_correlation_coefficient(data1, data2)
478
+ sim_score = similarity_score(trace1, trace2)
479
+
480
+ tolerance = _determine_tolerance(tolerance, tolerance_pct, data2)
481
+ violations = np.where(np.abs(diff) > tolerance)[0]
482
+ match = _determine_match(method, max_diff, tolerance, tolerance_pct, data1, data2)
483
+
484
+ diff_trace = (
485
+ difference(trace1, trace2, channel_name="comparison_diff") if include_difference else None
486
+ )
487
+ statistics = _compute_comparison_statistics(diff, violations, min_len, data1, data2)
488
+
388
489
  return ComparisonResult(
389
490
  match=match,
390
491
  similarity=sim_score,