oscura 0.5.0__py3-none-any.whl → 0.6.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (513) hide show
  1. oscura/__init__.py +169 -167
  2. oscura/analyzers/__init__.py +3 -0
  3. oscura/analyzers/classification.py +659 -0
  4. oscura/analyzers/digital/__init__.py +0 -48
  5. oscura/analyzers/digital/edges.py +325 -65
  6. oscura/analyzers/digital/extraction.py +0 -195
  7. oscura/analyzers/digital/quality.py +293 -166
  8. oscura/analyzers/digital/timing.py +260 -115
  9. oscura/analyzers/digital/timing_numba.py +334 -0
  10. oscura/analyzers/entropy.py +605 -0
  11. oscura/analyzers/eye/diagram.py +176 -109
  12. oscura/analyzers/eye/metrics.py +5 -5
  13. oscura/analyzers/jitter/__init__.py +6 -4
  14. oscura/analyzers/jitter/ber.py +52 -52
  15. oscura/analyzers/jitter/classification.py +156 -0
  16. oscura/analyzers/jitter/decomposition.py +163 -113
  17. oscura/analyzers/jitter/spectrum.py +80 -64
  18. oscura/analyzers/ml/__init__.py +39 -0
  19. oscura/analyzers/ml/features.py +600 -0
  20. oscura/analyzers/ml/signal_classifier.py +604 -0
  21. oscura/analyzers/packet/daq.py +246 -158
  22. oscura/analyzers/packet/parser.py +12 -1
  23. oscura/analyzers/packet/payload.py +50 -2110
  24. oscura/analyzers/packet/payload_analysis.py +361 -181
  25. oscura/analyzers/packet/payload_patterns.py +133 -70
  26. oscura/analyzers/packet/stream.py +84 -23
  27. oscura/analyzers/patterns/__init__.py +26 -5
  28. oscura/analyzers/patterns/anomaly_detection.py +908 -0
  29. oscura/analyzers/patterns/clustering.py +169 -108
  30. oscura/analyzers/patterns/clustering_optimized.py +227 -0
  31. oscura/analyzers/patterns/discovery.py +1 -1
  32. oscura/analyzers/patterns/matching.py +581 -197
  33. oscura/analyzers/patterns/pattern_mining.py +778 -0
  34. oscura/analyzers/patterns/periodic.py +121 -38
  35. oscura/analyzers/patterns/sequences.py +175 -78
  36. oscura/analyzers/power/conduction.py +1 -1
  37. oscura/analyzers/power/soa.py +6 -6
  38. oscura/analyzers/power/switching.py +250 -110
  39. oscura/analyzers/protocol/__init__.py +17 -1
  40. oscura/analyzers/protocols/__init__.py +1 -22
  41. oscura/analyzers/protocols/base.py +6 -6
  42. oscura/analyzers/protocols/ble/__init__.py +38 -0
  43. oscura/analyzers/protocols/ble/analyzer.py +809 -0
  44. oscura/analyzers/protocols/ble/uuids.py +288 -0
  45. oscura/analyzers/protocols/can.py +257 -127
  46. oscura/analyzers/protocols/can_fd.py +107 -80
  47. oscura/analyzers/protocols/flexray.py +139 -80
  48. oscura/analyzers/protocols/hdlc.py +93 -58
  49. oscura/analyzers/protocols/i2c.py +247 -106
  50. oscura/analyzers/protocols/i2s.py +138 -86
  51. oscura/analyzers/protocols/industrial/__init__.py +40 -0
  52. oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
  53. oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
  54. oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
  55. oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
  56. oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
  57. oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
  58. oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
  59. oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
  60. oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
  61. oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
  62. oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
  63. oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
  64. oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
  65. oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
  66. oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
  67. oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
  68. oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
  69. oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
  70. oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
  71. oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
  72. oscura/analyzers/protocols/jtag.py +180 -98
  73. oscura/analyzers/protocols/lin.py +219 -114
  74. oscura/analyzers/protocols/manchester.py +4 -4
  75. oscura/analyzers/protocols/onewire.py +253 -149
  76. oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
  77. oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
  78. oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
  79. oscura/analyzers/protocols/spi.py +192 -95
  80. oscura/analyzers/protocols/swd.py +321 -167
  81. oscura/analyzers/protocols/uart.py +267 -125
  82. oscura/analyzers/protocols/usb.py +235 -131
  83. oscura/analyzers/side_channel/power.py +17 -12
  84. oscura/analyzers/signal/__init__.py +15 -0
  85. oscura/analyzers/signal/timing_analysis.py +1086 -0
  86. oscura/analyzers/signal_integrity/__init__.py +4 -1
  87. oscura/analyzers/signal_integrity/sparams.py +2 -19
  88. oscura/analyzers/spectral/chunked.py +129 -60
  89. oscura/analyzers/spectral/chunked_fft.py +300 -94
  90. oscura/analyzers/spectral/chunked_wavelet.py +100 -80
  91. oscura/analyzers/statistical/checksum.py +376 -217
  92. oscura/analyzers/statistical/classification.py +229 -107
  93. oscura/analyzers/statistical/entropy.py +78 -53
  94. oscura/analyzers/statistics/correlation.py +407 -211
  95. oscura/analyzers/statistics/outliers.py +2 -2
  96. oscura/analyzers/statistics/streaming.py +30 -5
  97. oscura/analyzers/validation.py +216 -101
  98. oscura/analyzers/waveform/measurements.py +9 -0
  99. oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
  100. oscura/analyzers/waveform/spectral.py +500 -228
  101. oscura/api/__init__.py +31 -5
  102. oscura/api/dsl/__init__.py +582 -0
  103. oscura/{dsl → api/dsl}/commands.py +43 -76
  104. oscura/{dsl → api/dsl}/interpreter.py +26 -51
  105. oscura/{dsl → api/dsl}/parser.py +107 -77
  106. oscura/{dsl → api/dsl}/repl.py +2 -2
  107. oscura/api/dsl.py +1 -1
  108. oscura/{integrations → api/integrations}/__init__.py +1 -1
  109. oscura/{integrations → api/integrations}/llm.py +201 -102
  110. oscura/api/operators.py +3 -3
  111. oscura/api/optimization.py +144 -30
  112. oscura/api/rest_server.py +921 -0
  113. oscura/api/server/__init__.py +17 -0
  114. oscura/api/server/dashboard.py +850 -0
  115. oscura/api/server/static/README.md +34 -0
  116. oscura/api/server/templates/base.html +181 -0
  117. oscura/api/server/templates/export.html +120 -0
  118. oscura/api/server/templates/home.html +284 -0
  119. oscura/api/server/templates/protocols.html +58 -0
  120. oscura/api/server/templates/reports.html +43 -0
  121. oscura/api/server/templates/session_detail.html +89 -0
  122. oscura/api/server/templates/sessions.html +83 -0
  123. oscura/api/server/templates/waveforms.html +73 -0
  124. oscura/automotive/__init__.py +8 -1
  125. oscura/automotive/can/__init__.py +10 -0
  126. oscura/automotive/can/checksum.py +3 -1
  127. oscura/automotive/can/dbc_generator.py +590 -0
  128. oscura/automotive/can/message_wrapper.py +121 -74
  129. oscura/automotive/can/patterns.py +98 -21
  130. oscura/automotive/can/session.py +292 -56
  131. oscura/automotive/can/state_machine.py +6 -3
  132. oscura/automotive/can/stimulus_response.py +97 -75
  133. oscura/automotive/dbc/__init__.py +10 -2
  134. oscura/automotive/dbc/generator.py +84 -56
  135. oscura/automotive/dbc/parser.py +6 -6
  136. oscura/automotive/dtc/data.json +2763 -0
  137. oscura/automotive/dtc/database.py +2 -2
  138. oscura/automotive/flexray/__init__.py +31 -0
  139. oscura/automotive/flexray/analyzer.py +504 -0
  140. oscura/automotive/flexray/crc.py +185 -0
  141. oscura/automotive/flexray/fibex.py +449 -0
  142. oscura/automotive/j1939/__init__.py +45 -8
  143. oscura/automotive/j1939/analyzer.py +605 -0
  144. oscura/automotive/j1939/spns.py +326 -0
  145. oscura/automotive/j1939/transport.py +306 -0
  146. oscura/automotive/lin/__init__.py +47 -0
  147. oscura/automotive/lin/analyzer.py +612 -0
  148. oscura/automotive/loaders/blf.py +13 -2
  149. oscura/automotive/loaders/csv_can.py +143 -72
  150. oscura/automotive/loaders/dispatcher.py +50 -2
  151. oscura/automotive/loaders/mdf.py +86 -45
  152. oscura/automotive/loaders/pcap.py +111 -61
  153. oscura/automotive/uds/__init__.py +4 -0
  154. oscura/automotive/uds/analyzer.py +725 -0
  155. oscura/automotive/uds/decoder.py +140 -58
  156. oscura/automotive/uds/models.py +7 -1
  157. oscura/automotive/visualization.py +1 -1
  158. oscura/cli/analyze.py +348 -0
  159. oscura/cli/batch.py +142 -122
  160. oscura/cli/benchmark.py +275 -0
  161. oscura/cli/characterize.py +137 -82
  162. oscura/cli/compare.py +224 -131
  163. oscura/cli/completion.py +250 -0
  164. oscura/cli/config_cmd.py +361 -0
  165. oscura/cli/decode.py +164 -87
  166. oscura/cli/export.py +286 -0
  167. oscura/cli/main.py +115 -31
  168. oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
  169. oscura/{onboarding → cli/onboarding}/help.py +80 -58
  170. oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
  171. oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
  172. oscura/cli/progress.py +147 -0
  173. oscura/cli/shell.py +157 -135
  174. oscura/cli/validate_cmd.py +204 -0
  175. oscura/cli/visualize.py +158 -0
  176. oscura/convenience.py +125 -79
  177. oscura/core/__init__.py +4 -2
  178. oscura/core/backend_selector.py +3 -3
  179. oscura/core/cache.py +126 -15
  180. oscura/core/cancellation.py +1 -1
  181. oscura/{config → core/config}/__init__.py +20 -11
  182. oscura/{config → core/config}/defaults.py +1 -1
  183. oscura/{config → core/config}/loader.py +7 -5
  184. oscura/{config → core/config}/memory.py +5 -5
  185. oscura/{config → core/config}/migration.py +1 -1
  186. oscura/{config → core/config}/pipeline.py +99 -23
  187. oscura/{config → core/config}/preferences.py +1 -1
  188. oscura/{config → core/config}/protocol.py +3 -3
  189. oscura/{config → core/config}/schema.py +426 -272
  190. oscura/{config → core/config}/settings.py +1 -1
  191. oscura/{config → core/config}/thresholds.py +195 -153
  192. oscura/core/correlation.py +5 -6
  193. oscura/core/cross_domain.py +0 -2
  194. oscura/core/debug.py +9 -5
  195. oscura/{extensibility → core/extensibility}/docs.py +158 -70
  196. oscura/{extensibility → core/extensibility}/extensions.py +160 -76
  197. oscura/{extensibility → core/extensibility}/logging.py +1 -1
  198. oscura/{extensibility → core/extensibility}/measurements.py +1 -1
  199. oscura/{extensibility → core/extensibility}/plugins.py +1 -1
  200. oscura/{extensibility → core/extensibility}/templates.py +73 -3
  201. oscura/{extensibility → core/extensibility}/validation.py +1 -1
  202. oscura/core/gpu_backend.py +11 -7
  203. oscura/core/log_query.py +101 -11
  204. oscura/core/logging.py +126 -54
  205. oscura/core/logging_advanced.py +5 -5
  206. oscura/core/memory_limits.py +108 -70
  207. oscura/core/memory_monitor.py +2 -2
  208. oscura/core/memory_progress.py +7 -7
  209. oscura/core/memory_warnings.py +1 -1
  210. oscura/core/numba_backend.py +13 -13
  211. oscura/{plugins → core/plugins}/__init__.py +9 -9
  212. oscura/{plugins → core/plugins}/base.py +7 -7
  213. oscura/{plugins → core/plugins}/cli.py +3 -3
  214. oscura/{plugins → core/plugins}/discovery.py +186 -106
  215. oscura/{plugins → core/plugins}/lifecycle.py +1 -1
  216. oscura/{plugins → core/plugins}/manager.py +7 -7
  217. oscura/{plugins → core/plugins}/registry.py +3 -3
  218. oscura/{plugins → core/plugins}/versioning.py +1 -1
  219. oscura/core/progress.py +16 -1
  220. oscura/core/provenance.py +8 -2
  221. oscura/{schemas → core/schemas}/__init__.py +2 -2
  222. oscura/core/schemas/bus_configuration.json +322 -0
  223. oscura/core/schemas/device_mapping.json +182 -0
  224. oscura/core/schemas/packet_format.json +418 -0
  225. oscura/core/schemas/protocol_definition.json +363 -0
  226. oscura/core/types.py +4 -0
  227. oscura/core/uncertainty.py +3 -3
  228. oscura/correlation/__init__.py +52 -0
  229. oscura/correlation/multi_protocol.py +811 -0
  230. oscura/discovery/auto_decoder.py +117 -35
  231. oscura/discovery/comparison.py +191 -86
  232. oscura/discovery/quality_validator.py +155 -68
  233. oscura/discovery/signal_detector.py +196 -79
  234. oscura/export/__init__.py +18 -20
  235. oscura/export/kaitai_struct.py +513 -0
  236. oscura/export/scapy_layer.py +801 -0
  237. oscura/export/wireshark/README.md +15 -15
  238. oscura/export/wireshark/generator.py +1 -1
  239. oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
  240. oscura/export/wireshark_dissector.py +746 -0
  241. oscura/guidance/wizard.py +207 -111
  242. oscura/hardware/__init__.py +19 -0
  243. oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
  244. oscura/{acquisition → hardware/acquisition}/file.py +2 -2
  245. oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
  246. oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
  247. oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
  248. oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
  249. oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
  250. oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
  251. oscura/hardware/firmware/__init__.py +29 -0
  252. oscura/hardware/firmware/pattern_recognition.py +874 -0
  253. oscura/hardware/hal_detector.py +736 -0
  254. oscura/hardware/security/__init__.py +37 -0
  255. oscura/hardware/security/side_channel_detector.py +1126 -0
  256. oscura/inference/__init__.py +4 -0
  257. oscura/inference/active_learning/README.md +7 -7
  258. oscura/inference/active_learning/observation_table.py +4 -1
  259. oscura/inference/alignment.py +216 -123
  260. oscura/inference/bayesian.py +113 -33
  261. oscura/inference/crc_reverse.py +101 -55
  262. oscura/inference/logic.py +6 -2
  263. oscura/inference/message_format.py +342 -183
  264. oscura/inference/protocol.py +95 -44
  265. oscura/inference/protocol_dsl.py +180 -82
  266. oscura/inference/signal_intelligence.py +1439 -706
  267. oscura/inference/spectral.py +99 -57
  268. oscura/inference/state_machine.py +810 -158
  269. oscura/inference/stream.py +270 -110
  270. oscura/iot/__init__.py +34 -0
  271. oscura/iot/coap/__init__.py +32 -0
  272. oscura/iot/coap/analyzer.py +668 -0
  273. oscura/iot/coap/options.py +212 -0
  274. oscura/iot/lorawan/__init__.py +21 -0
  275. oscura/iot/lorawan/crypto.py +206 -0
  276. oscura/iot/lorawan/decoder.py +801 -0
  277. oscura/iot/lorawan/mac_commands.py +341 -0
  278. oscura/iot/mqtt/__init__.py +27 -0
  279. oscura/iot/mqtt/analyzer.py +999 -0
  280. oscura/iot/mqtt/properties.py +315 -0
  281. oscura/iot/zigbee/__init__.py +31 -0
  282. oscura/iot/zigbee/analyzer.py +615 -0
  283. oscura/iot/zigbee/security.py +153 -0
  284. oscura/iot/zigbee/zcl.py +349 -0
  285. oscura/jupyter/display.py +125 -45
  286. oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
  287. oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
  288. oscura/jupyter/exploratory/fuzzy.py +746 -0
  289. oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
  290. oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
  291. oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
  292. oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
  293. oscura/jupyter/exploratory/sync.py +612 -0
  294. oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
  295. oscura/jupyter/magic.py +4 -4
  296. oscura/{ui → jupyter/ui}/__init__.py +2 -2
  297. oscura/{ui → jupyter/ui}/formatters.py +3 -3
  298. oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
  299. oscura/loaders/__init__.py +171 -63
  300. oscura/loaders/binary.py +88 -1
  301. oscura/loaders/chipwhisperer.py +153 -137
  302. oscura/loaders/configurable.py +208 -86
  303. oscura/loaders/csv_loader.py +458 -215
  304. oscura/loaders/hdf5_loader.py +278 -119
  305. oscura/loaders/lazy.py +87 -54
  306. oscura/loaders/mmap_loader.py +1 -1
  307. oscura/loaders/numpy_loader.py +253 -116
  308. oscura/loaders/pcap.py +226 -151
  309. oscura/loaders/rigol.py +110 -49
  310. oscura/loaders/sigrok.py +201 -78
  311. oscura/loaders/tdms.py +81 -58
  312. oscura/loaders/tektronix.py +291 -174
  313. oscura/loaders/touchstone.py +182 -87
  314. oscura/loaders/vcd.py +215 -117
  315. oscura/loaders/wav.py +155 -68
  316. oscura/reporting/__init__.py +9 -7
  317. oscura/reporting/analyze.py +352 -146
  318. oscura/reporting/argument_preparer.py +69 -14
  319. oscura/reporting/auto_report.py +97 -61
  320. oscura/reporting/batch.py +131 -58
  321. oscura/reporting/chart_selection.py +57 -45
  322. oscura/reporting/comparison.py +63 -17
  323. oscura/reporting/content/executive.py +76 -24
  324. oscura/reporting/core_formats/multi_format.py +11 -8
  325. oscura/reporting/engine.py +312 -158
  326. oscura/reporting/enhanced_reports.py +949 -0
  327. oscura/reporting/export.py +86 -43
  328. oscura/reporting/formatting/numbers.py +69 -42
  329. oscura/reporting/html.py +139 -58
  330. oscura/reporting/index.py +137 -65
  331. oscura/reporting/output.py +158 -67
  332. oscura/reporting/pdf.py +67 -102
  333. oscura/reporting/plots.py +191 -112
  334. oscura/reporting/sections.py +88 -47
  335. oscura/reporting/standards.py +104 -61
  336. oscura/reporting/summary_generator.py +75 -55
  337. oscura/reporting/tables.py +138 -54
  338. oscura/reporting/templates/enhanced/protocol_re.html +525 -0
  339. oscura/reporting/templates/index.md +13 -13
  340. oscura/sessions/__init__.py +14 -23
  341. oscura/sessions/base.py +3 -3
  342. oscura/sessions/blackbox.py +106 -10
  343. oscura/sessions/generic.py +2 -2
  344. oscura/sessions/legacy.py +783 -0
  345. oscura/side_channel/__init__.py +63 -0
  346. oscura/side_channel/dpa.py +1025 -0
  347. oscura/utils/__init__.py +15 -1
  348. oscura/utils/autodetect.py +1 -5
  349. oscura/utils/bitwise.py +118 -0
  350. oscura/{builders → utils/builders}/__init__.py +1 -1
  351. oscura/{comparison → utils/comparison}/__init__.py +6 -6
  352. oscura/{comparison → utils/comparison}/compare.py +202 -101
  353. oscura/{comparison → utils/comparison}/golden.py +83 -63
  354. oscura/{comparison → utils/comparison}/limits.py +313 -89
  355. oscura/{comparison → utils/comparison}/mask.py +151 -45
  356. oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
  357. oscura/{comparison → utils/comparison}/visualization.py +147 -89
  358. oscura/{component → utils/component}/__init__.py +3 -3
  359. oscura/{component → utils/component}/impedance.py +122 -58
  360. oscura/{component → utils/component}/reactive.py +165 -168
  361. oscura/{component → utils/component}/transmission_line.py +3 -3
  362. oscura/{filtering → utils/filtering}/__init__.py +6 -6
  363. oscura/{filtering → utils/filtering}/base.py +1 -1
  364. oscura/{filtering → utils/filtering}/convenience.py +2 -2
  365. oscura/{filtering → utils/filtering}/design.py +169 -93
  366. oscura/{filtering → utils/filtering}/filters.py +2 -2
  367. oscura/{filtering → utils/filtering}/introspection.py +2 -2
  368. oscura/utils/geometry.py +31 -0
  369. oscura/utils/imports.py +184 -0
  370. oscura/utils/lazy.py +1 -1
  371. oscura/{math → utils/math}/__init__.py +2 -2
  372. oscura/{math → utils/math}/arithmetic.py +114 -48
  373. oscura/{math → utils/math}/interpolation.py +139 -106
  374. oscura/utils/memory.py +129 -66
  375. oscura/utils/memory_advanced.py +92 -9
  376. oscura/utils/memory_extensions.py +10 -8
  377. oscura/{optimization → utils/optimization}/__init__.py +1 -1
  378. oscura/{optimization → utils/optimization}/search.py +2 -2
  379. oscura/utils/performance/__init__.py +58 -0
  380. oscura/utils/performance/caching.py +889 -0
  381. oscura/utils/performance/lsh_clustering.py +333 -0
  382. oscura/utils/performance/memory_optimizer.py +699 -0
  383. oscura/utils/performance/optimizations.py +675 -0
  384. oscura/utils/performance/parallel.py +654 -0
  385. oscura/utils/performance/profiling.py +661 -0
  386. oscura/{pipeline → utils/pipeline}/base.py +1 -1
  387. oscura/{pipeline → utils/pipeline}/composition.py +11 -3
  388. oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
  389. oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
  390. oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
  391. oscura/{search → utils/search}/__init__.py +3 -3
  392. oscura/{search → utils/search}/anomaly.py +188 -58
  393. oscura/utils/search/context.py +294 -0
  394. oscura/{search → utils/search}/pattern.py +138 -10
  395. oscura/utils/serial.py +51 -0
  396. oscura/utils/storage/__init__.py +61 -0
  397. oscura/utils/storage/database.py +1166 -0
  398. oscura/{streaming → utils/streaming}/chunked.py +302 -143
  399. oscura/{streaming → utils/streaming}/progressive.py +1 -1
  400. oscura/{streaming → utils/streaming}/realtime.py +3 -2
  401. oscura/{triggering → utils/triggering}/__init__.py +6 -6
  402. oscura/{triggering → utils/triggering}/base.py +6 -6
  403. oscura/{triggering → utils/triggering}/edge.py +2 -2
  404. oscura/{triggering → utils/triggering}/pattern.py +2 -2
  405. oscura/{triggering → utils/triggering}/pulse.py +115 -74
  406. oscura/{triggering → utils/triggering}/window.py +2 -2
  407. oscura/utils/validation.py +32 -0
  408. oscura/validation/__init__.py +121 -0
  409. oscura/{compliance → validation/compliance}/__init__.py +5 -5
  410. oscura/{compliance → validation/compliance}/advanced.py +5 -5
  411. oscura/{compliance → validation/compliance}/masks.py +1 -1
  412. oscura/{compliance → validation/compliance}/reporting.py +127 -53
  413. oscura/{compliance → validation/compliance}/testing.py +114 -52
  414. oscura/validation/compliance_tests.py +915 -0
  415. oscura/validation/fuzzer.py +990 -0
  416. oscura/validation/grammar_tests.py +596 -0
  417. oscura/validation/grammar_validator.py +904 -0
  418. oscura/validation/hil_testing.py +977 -0
  419. oscura/{quality → validation/quality}/__init__.py +4 -4
  420. oscura/{quality → validation/quality}/ensemble.py +251 -171
  421. oscura/{quality → validation/quality}/explainer.py +3 -3
  422. oscura/{quality → validation/quality}/scoring.py +1 -1
  423. oscura/{quality → validation/quality}/warnings.py +4 -4
  424. oscura/validation/regression_suite.py +808 -0
  425. oscura/validation/replay.py +788 -0
  426. oscura/{testing → validation/testing}/__init__.py +2 -2
  427. oscura/{testing → validation/testing}/synthetic.py +5 -5
  428. oscura/visualization/__init__.py +9 -0
  429. oscura/visualization/accessibility.py +1 -1
  430. oscura/visualization/annotations.py +64 -67
  431. oscura/visualization/colors.py +7 -7
  432. oscura/visualization/digital.py +180 -81
  433. oscura/visualization/eye.py +236 -85
  434. oscura/visualization/interactive.py +320 -143
  435. oscura/visualization/jitter.py +587 -247
  436. oscura/visualization/layout.py +169 -134
  437. oscura/visualization/optimization.py +103 -52
  438. oscura/visualization/palettes.py +1 -1
  439. oscura/visualization/power.py +427 -211
  440. oscura/visualization/power_extended.py +626 -297
  441. oscura/visualization/presets.py +2 -0
  442. oscura/visualization/protocols.py +495 -181
  443. oscura/visualization/render.py +79 -63
  444. oscura/visualization/reverse_engineering.py +171 -124
  445. oscura/visualization/signal_integrity.py +460 -279
  446. oscura/visualization/specialized.py +190 -100
  447. oscura/visualization/spectral.py +670 -255
  448. oscura/visualization/thumbnails.py +166 -137
  449. oscura/visualization/waveform.py +150 -63
  450. oscura/workflows/__init__.py +3 -0
  451. oscura/{batch → workflows/batch}/__init__.py +5 -5
  452. oscura/{batch → workflows/batch}/advanced.py +150 -75
  453. oscura/workflows/batch/aggregate.py +531 -0
  454. oscura/workflows/batch/analyze.py +236 -0
  455. oscura/{batch → workflows/batch}/logging.py +2 -2
  456. oscura/{batch → workflows/batch}/metrics.py +1 -1
  457. oscura/workflows/complete_re.py +1144 -0
  458. oscura/workflows/compliance.py +44 -54
  459. oscura/workflows/digital.py +197 -51
  460. oscura/workflows/legacy/__init__.py +12 -0
  461. oscura/{workflow → workflows/legacy}/dag.py +4 -1
  462. oscura/workflows/multi_trace.py +9 -9
  463. oscura/workflows/power.py +42 -62
  464. oscura/workflows/protocol.py +82 -49
  465. oscura/workflows/reverse_engineering.py +351 -150
  466. oscura/workflows/signal_integrity.py +157 -82
  467. oscura-0.6.0.dist-info/METADATA +643 -0
  468. oscura-0.6.0.dist-info/RECORD +590 -0
  469. oscura/analyzers/digital/ic_database.py +0 -498
  470. oscura/analyzers/digital/timing_paths.py +0 -339
  471. oscura/analyzers/digital/vintage.py +0 -377
  472. oscura/analyzers/digital/vintage_result.py +0 -148
  473. oscura/analyzers/protocols/parallel_bus.py +0 -449
  474. oscura/batch/aggregate.py +0 -300
  475. oscura/batch/analyze.py +0 -139
  476. oscura/dsl/__init__.py +0 -73
  477. oscura/exceptions.py +0 -59
  478. oscura/exploratory/fuzzy.py +0 -513
  479. oscura/exploratory/sync.py +0 -384
  480. oscura/export/wavedrom.py +0 -430
  481. oscura/exporters/__init__.py +0 -94
  482. oscura/exporters/csv.py +0 -303
  483. oscura/exporters/exporters.py +0 -44
  484. oscura/exporters/hdf5.py +0 -217
  485. oscura/exporters/html_export.py +0 -701
  486. oscura/exporters/json_export.py +0 -338
  487. oscura/exporters/markdown_export.py +0 -367
  488. oscura/exporters/matlab_export.py +0 -354
  489. oscura/exporters/npz_export.py +0 -219
  490. oscura/exporters/spice_export.py +0 -210
  491. oscura/exporters/vintage_logic_csv.py +0 -247
  492. oscura/reporting/vintage_logic_report.py +0 -523
  493. oscura/search/context.py +0 -149
  494. oscura/session/__init__.py +0 -34
  495. oscura/session/annotations.py +0 -289
  496. oscura/session/history.py +0 -313
  497. oscura/session/session.py +0 -520
  498. oscura/visualization/digital_advanced.py +0 -718
  499. oscura/visualization/figure_manager.py +0 -156
  500. oscura/workflow/__init__.py +0 -13
  501. oscura-0.5.0.dist-info/METADATA +0 -407
  502. oscura-0.5.0.dist-info/RECORD +0 -486
  503. /oscura/core/{config.py → config/legacy.py} +0 -0
  504. /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
  505. /oscura/{extensibility → core/extensibility}/registry.py +0 -0
  506. /oscura/{plugins → core/plugins}/isolation.py +0 -0
  507. /oscura/{builders → utils/builders}/signal_builder.py +0 -0
  508. /oscura/{optimization → utils/optimization}/parallel.py +0 -0
  509. /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
  510. /oscura/{streaming → utils/streaming}/__init__.py +0 -0
  511. {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/WHEEL +0 -0
  512. {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/entry_points.txt +0 -0
  513. {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/licenses/LICENSE +0 -0
oscura/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
  ]
@@ -317,11 +317,7 @@ def detect_logic_family(
317
317
  # Score based on how well levels match
318
318
  low_match = 1.0 - min(1.0, abs(v_low - vol) / 0.5)
319
319
  high_match = 1.0 - min(1.0, abs(v_high - voh) / 0.5)
320
- # Handle VCC=0 for families like ECL
321
- if vcc != 0:
322
- vcc_match = 1.0 - min(1.0, abs(v_cc_est - vcc) / abs(vcc))
323
- else:
324
- vcc_match = 1.0 if abs(v_cc_est) < 0.5 else 0.0
320
+ vcc_match = 1.0 - min(1.0, abs(v_cc_est - vcc) / vcc)
325
321
 
326
322
  score = (low_match + high_match + vcc_match) / 3
327
323
 
@@ -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,