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/cli/compare.py CHANGED
@@ -28,33 +28,33 @@ if TYPE_CHECKING:
28
28
  logger = logging.getLogger("oscura.cli.compare")
29
29
 
30
30
 
31
- @click.command() # type: ignore[misc]
32
- @click.argument("file1", type=click.Path(exists=True)) # type: ignore[misc]
33
- @click.argument("file2", type=click.Path(exists=True)) # type: ignore[misc]
34
- @click.option( # type: ignore[misc]
31
+ @click.command()
32
+ @click.argument("file1", type=click.Path(exists=True))
33
+ @click.argument("file2", type=click.Path(exists=True))
34
+ @click.option(
35
35
  "--threshold",
36
36
  type=float,
37
37
  default=5.0,
38
38
  help="Report differences greater than this percentage (default: 5%).",
39
39
  )
40
- @click.option( # type: ignore[misc]
40
+ @click.option(
41
41
  "--output",
42
42
  type=click.Choice(["json", "csv", "html", "table"], case_sensitive=False),
43
43
  default="table",
44
44
  help="Output format (default: table).",
45
45
  )
46
- @click.option( # type: ignore[misc]
46
+ @click.option(
47
47
  "--save-report",
48
48
  type=click.Path(),
49
49
  default=None,
50
50
  help="Save detailed HTML comparison report.",
51
51
  )
52
- @click.option( # type: ignore[misc]
52
+ @click.option(
53
53
  "--align",
54
54
  is_flag=True,
55
55
  help="Align signals using cross-correlation before comparison.",
56
56
  )
57
- @click.pass_context # type: ignore[misc]
57
+ @click.pass_context
58
58
  def compare(
59
59
  ctx: click.Context,
60
60
  file1: str,
@@ -381,60 +381,84 @@ def _perform_comparison(
381
381
  sample_rate1 = trace1.metadata.sample_rate
382
382
  sample_rate2 = trace2.metadata.sample_rate
383
383
 
384
- # Check sample rate compatibility
385
- rate_mismatch = False
386
- if sample_rate1 != sample_rate2:
387
- logger.warning(f"Sample rates differ: {sample_rate1:.2e} vs {sample_rate2:.2e} Hz")
388
- rate_mismatch = True
384
+ rate_mismatch = _check_sample_rate_mismatch(sample_rate1, sample_rate2)
389
385
 
390
- # Initialize results
391
386
  results: dict[str, Any] = {
392
387
  "threshold_percent": threshold,
393
388
  "aligned": align_signals,
394
389
  "sample_rate_mismatch": rate_mismatch,
395
390
  }
396
391
 
397
- # Basic statistics for each trace
398
- results["trace1_stats"] = {
399
- "samples": len(trace1.data),
400
- "sample_rate": f"{sample_rate1 / 1e6:.2f} MHz",
401
- "duration_ms": f"{len(trace1.data) / sample_rate1 * 1e3:.3f} ms",
402
- "mean": f"{float(trace1.data.mean()):.6f} V",
403
- "rms": f"{float(np.sqrt((trace1.data**2).mean())):.6f} V",
404
- "peak_to_peak": f"{float(trace1.data.max() - trace1.data.min()):.6f} V",
405
- "min": f"{float(trace1.data.min()):.6f} V",
406
- "max": f"{float(trace1.data.max()):.6f} V",
407
- }
392
+ # Add trace statistics
393
+ results["trace1_stats"] = _compute_trace_stats(trace1, sample_rate1)
394
+ results["trace2_stats"] = _compute_trace_stats(trace2, sample_rate2)
395
+
396
+ # Prepare and align data
397
+ data1, data2 = _prepare_comparison_data(trace1, trace2, sample_rate1, align_signals, results)
398
+
399
+ # Perform analyses
400
+ results["timing_drift"] = _compute_timing_drift(data1, data2, sample_rate1)
401
+ results["amplitude_difference"] = _compute_amplitude_difference(data1, data2, threshold)
402
+ results["noise_change"] = _compute_noise_change(data1, data2, sample_rate1, threshold)
403
+ results["correlation"] = _compute_correlation(data1, data2)
404
+ results["spectral_difference"] = _compute_spectral_difference(
405
+ data1, data2, sample_rate1, threshold
406
+ )
407
+
408
+ # Overall assessment
409
+ results["summary"] = _compute_summary(results)
410
+
411
+ return results
412
+
413
+
414
+ def _check_sample_rate_mismatch(sample_rate1: float, sample_rate2: float) -> bool:
415
+ """Check if sample rates differ between traces."""
416
+ if sample_rate1 != sample_rate2:
417
+ logger.warning(f"Sample rates differ: {sample_rate1:.2e} vs {sample_rate2:.2e} Hz")
418
+ return True
419
+ return False
420
+
408
421
 
409
- results["trace2_stats"] = {
410
- "samples": len(trace2.data),
411
- "sample_rate": f"{sample_rate2 / 1e6:.2f} MHz",
412
- "duration_ms": f"{len(trace2.data) / sample_rate2 * 1e3:.3f} ms",
413
- "mean": f"{float(trace2.data.mean()):.6f} V",
414
- "rms": f"{float(np.sqrt((trace2.data**2).mean())):.6f} V",
415
- "peak_to_peak": f"{float(trace2.data.max() - trace2.data.min()):.6f} V",
416
- "min": f"{float(trace2.data.min()):.6f} V",
417
- "max": f"{float(trace2.data.max()):.6f} V",
422
+ def _compute_trace_stats(trace: WaveformTrace, sample_rate: float) -> dict[str, str | int]:
423
+ """Compute statistics for a single trace."""
424
+ return {
425
+ "samples": len(trace.data),
426
+ "sample_rate": f"{sample_rate / 1e6:.2f} MHz",
427
+ "duration_ms": f"{len(trace.data) / sample_rate * 1e3:.3f} ms",
428
+ "mean": f"{float(trace.data.mean()):.6f} V",
429
+ "rms": f"{float(np.sqrt((trace.data**2).mean())):.6f} V",
430
+ "peak_to_peak": f"{float(trace.data.max() - trace.data.min()):.6f} V",
431
+ "min": f"{float(trace.data.min()):.6f} V",
432
+ "max": f"{float(trace.data.max()):.6f} V",
418
433
  }
419
434
 
420
- # Prepare data for comparison
435
+
436
+ def _prepare_comparison_data(
437
+ trace1: WaveformTrace,
438
+ trace2: WaveformTrace,
439
+ sample_rate: float,
440
+ align_signals: bool,
441
+ results: dict[str, Any],
442
+ ) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
443
+ """Prepare data arrays for comparison, with optional alignment."""
421
444
  data1 = trace1.data.astype(np.float64)
422
445
  data2 = trace2.data.astype(np.float64)
423
446
 
424
- # Signal alignment using cross-correlation
425
447
  if align_signals:
426
- data1, data2, alignment_info = _align_signals(data1, data2, sample_rate1)
448
+ data1, data2, alignment_info = _align_signals(data1, data2, sample_rate)
427
449
  results["alignment"] = alignment_info
428
450
  else:
429
- # Ensure equal length
430
451
  min_len = min(len(data1), len(data2))
431
452
  data1 = data1[:min_len]
432
453
  data2 = data2[:min_len]
433
454
 
434
- # Timing drift analysis
435
- results["timing_drift"] = _compute_timing_drift(data1, data2, sample_rate1)
455
+ return data1, data2
456
+
436
457
 
437
- # Amplitude difference analysis
458
+ def _compute_amplitude_difference(
459
+ data1: NDArray[np.float64], data2: NDArray[np.float64], threshold: float
460
+ ) -> dict[str, Any]:
461
+ """Compute amplitude differences between signals."""
438
462
  diff = data2 - data1
439
463
  abs_diff = np.abs(diff)
440
464
 
@@ -444,11 +468,10 @@ def _perform_comparison(
444
468
  max_diff = float(abs_diff.max())
445
469
  rms_diff = float(np.sqrt((diff**2).mean()))
446
470
 
447
- # Compare to reference RMS
448
471
  rms1 = float(np.sqrt((data1**2).mean()))
449
472
  rms_diff_percent = rms_diff / rms1 * 100 if rms1 > 0 else 0
450
473
 
451
- results["amplitude_difference"] = {
474
+ return {
452
475
  "mean_diff_v": f"{mean_diff:.6f}",
453
476
  "mean_diff_percent": f"{mean_diff_percent:.2f}%",
454
477
  "max_diff_v": f"{max_diff:.6f}",
@@ -457,10 +480,13 @@ def _perform_comparison(
457
480
  "significant": bool(mean_diff_percent > threshold),
458
481
  }
459
482
 
460
- # Noise analysis
461
- # Use high-pass filter to extract noise component
462
- nyquist = sample_rate1 / 2
463
- cutoff = min(1000, nyquist * 0.9) # 1kHz or 90% of Nyquist
483
+
484
+ def _compute_noise_change(
485
+ data1: NDArray[np.float64], data2: NDArray[np.float64], sample_rate: float, threshold: float
486
+ ) -> dict[str, Any]:
487
+ """Compute noise level changes between signals."""
488
+ nyquist = sample_rate / 2
489
+ cutoff = min(1000, nyquist * 0.9)
464
490
  b, a = signal.butter(4, cutoff / nyquist, btype="high")
465
491
 
466
492
  try:
@@ -469,40 +495,44 @@ def _perform_comparison(
469
495
  noise_std1 = float(np.std(noise1))
470
496
  noise_std2 = float(np.std(noise2))
471
497
  except Exception:
472
- # Fallback to simple std if filter fails
473
498
  noise_std1 = float(np.std(data1))
474
499
  noise_std2 = float(np.std(data2))
475
500
 
476
501
  noise_change = ((noise_std2 - noise_std1) / noise_std1 * 100) if noise_std1 != 0 else 0
477
502
 
478
- results["noise_change"] = {
503
+ return {
479
504
  "noise1_v": f"{noise_std1:.6f}",
480
505
  "noise2_v": f"{noise_std2:.6f}",
481
506
  "change_percent": f"{noise_change:.2f}%",
482
507
  "significant": bool(abs(noise_change) > threshold),
483
508
  }
484
509
 
485
- # Correlation coefficient
510
+
511
+ def _compute_correlation(data1: NDArray[np.float64], data2: NDArray[np.float64]) -> dict[str, str]:
512
+ """Compute correlation coefficient between signals."""
486
513
  if len(data1) > 1 and len(data2) > 1:
487
514
  with np.errstate(divide="ignore", invalid="ignore"):
488
515
  correlation = float(np.corrcoef(data1, data2)[0, 1])
489
- results["correlation"] = {
516
+
517
+ if correlation > 0.99:
518
+ quality = "excellent"
519
+ elif correlation > 0.95:
520
+ quality = "good"
521
+ elif correlation > 0.8:
522
+ quality = "fair"
523
+ else:
524
+ quality = "poor"
525
+
526
+ return {
490
527
  "coefficient": f"{correlation:.6f}",
491
- "quality": "excellent"
492
- if correlation > 0.99
493
- else "good"
494
- if correlation > 0.95
495
- else "fair"
496
- if correlation > 0.8
497
- else "poor",
528
+ "quality": quality,
498
529
  }
499
530
 
500
- # Spectral differences
501
- results["spectral_difference"] = _compute_spectral_difference(
502
- data1, data2, sample_rate1, threshold
503
- )
531
+ return {"coefficient": "N/A", "quality": "unknown"}
504
532
 
505
- # Overall assessment
533
+
534
+ def _compute_summary(results: dict[str, Any]) -> dict[str, Any]:
535
+ """Compute overall comparison summary."""
506
536
  significant_count = sum(
507
537
  [
508
538
  results.get("amplitude_difference", {}).get("significant", False),
@@ -521,7 +551,7 @@ def _perform_comparison(
521
551
  else:
522
552
  match_quality = "poor"
523
553
 
524
- results["summary"] = {
554
+ return {
525
555
  "significant_differences": significant_count,
526
556
  "overall_match": match_quality,
527
557
  "categories_with_differences": [
@@ -536,8 +566,6 @@ def _perform_comparison(
536
566
  ],
537
567
  }
538
568
 
539
- return results
540
-
541
569
 
542
570
  def _generate_html_report(
543
571
  results: dict[str, Any],
@@ -554,27 +582,49 @@ def _generate_html_report(
554
582
  Returns:
555
583
  HTML content as string.
556
584
  """
557
- # Get summary info
558
585
  summary = results.get("summary", {})
559
586
  match_quality = summary.get("overall_match", "unknown")
560
587
  significant_diffs = summary.get("significant_differences", 0)
561
588
 
562
- # Color based on quality
589
+ quality_color = _get_quality_color(match_quality)
590
+ css = _generate_comparison_report_css(quality_color)
591
+ header = _generate_report_header(file1, file2, match_quality, significant_diffs)
592
+ sections = _generate_report_sections(results)
593
+
594
+ return f"""<!DOCTYPE html>
595
+ <html lang="en">
596
+ <head>
597
+ <meta charset="UTF-8">
598
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
599
+ <title>Oscura Signal Comparison Report</title>
600
+ {css}
601
+ </head>
602
+ <body>
603
+ {header}
604
+ <div class="content">
605
+ {sections}
606
+ <footer style="margin-top: 30px; text-align: center; color: #6c757d;">
607
+ <p>Generated by Oscura - Signal Analysis Toolkit</p>
608
+ </footer>
609
+ </div>
610
+ </body>
611
+ </html>"""
612
+
613
+
614
+ def _get_quality_color(match_quality: str) -> str:
615
+ """Get color code for match quality level."""
563
616
  quality_colors = {
564
617
  "excellent": "#28a745",
565
618
  "good": "#17a2b8",
566
619
  "fair": "#ffc107",
567
620
  "poor": "#dc3545",
568
621
  }
569
- quality_color = quality_colors.get(match_quality, "#6c757d")
622
+ return quality_colors.get(match_quality, "#6c757d")
570
623
 
571
- html = f"""<!DOCTYPE html>
572
- <html lang="en">
573
- <head>
574
- <meta charset="UTF-8">
575
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
576
- <title>Oscura Signal Comparison Report</title>
577
- <style>
624
+
625
+ def _generate_comparison_report_css(quality_color: str) -> str:
626
+ """Generate CSS for comparison report."""
627
+ return f"""<style>
578
628
  body {{
579
629
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
580
630
  max-width: 1200px;
@@ -630,10 +680,14 @@ def _generate_html_report(
630
680
  .ok {{
631
681
  color: #28a745;
632
682
  }}
633
- </style>
634
- </head>
635
- <body>
636
- <div class="header">
683
+ </style>"""
684
+
685
+
686
+ def _generate_report_header(
687
+ file1: str, file2: str, match_quality: str, significant_diffs: int
688
+ ) -> str:
689
+ """Generate report header with summary."""
690
+ return f"""<div class="header">
637
691
  <h1>Oscura Signal Comparison Report</h1>
638
692
  <p>File 1: {Path(file1).name}</p>
639
693
  <p>File 2: {Path(file2).name}</p>
@@ -643,9 +697,27 @@ def _generate_html_report(
643
697
  <div class="summary">
644
698
  <h2>Overall Match: {match_quality.upper()}</h2>
645
699
  <p>{significant_diffs} significant difference(s) detected</p>
646
- </div>
700
+ </div>"""
701
+
702
+
703
+ def _generate_report_sections(results: dict[str, Any]) -> str:
704
+ """Generate all report sections."""
705
+ stats = _generate_trace_stats_section(results)
706
+ amplitude = _generate_amplitude_diff_section(results)
707
+ timing = _generate_timing_drift_section(results)
708
+ noise = _generate_noise_change_section(results)
709
+ spectral = _generate_spectral_diff_section(results)
710
+ correlation = _generate_correlation_section(results)
711
+
712
+ return f"{stats}\n{amplitude}\n{timing}\n{noise}\n{spectral}\n{correlation}"
713
+
714
+
715
+ def _generate_trace_stats_section(results: dict[str, Any]) -> str:
716
+ """Generate trace statistics comparison table."""
717
+ t1 = results.get("trace1_stats", {})
718
+ t2 = results.get("trace2_stats", {})
647
719
 
648
- <div class="section">
720
+ return f"""<div class="section">
649
721
  <h3>Trace Statistics</h3>
650
722
  <table>
651
723
  <tr>
@@ -655,121 +727,142 @@ def _generate_html_report(
655
727
  </tr>
656
728
  <tr>
657
729
  <td>Samples</td>
658
- <td>{results.get("trace1_stats", {}).get("samples", "N/A")}</td>
659
- <td>{results.get("trace2_stats", {}).get("samples", "N/A")}</td>
730
+ <td>{t1.get("samples", "N/A")}</td>
731
+ <td>{t2.get("samples", "N/A")}</td>
660
732
  </tr>
661
733
  <tr>
662
734
  <td>Sample Rate</td>
663
- <td>{results.get("trace1_stats", {}).get("sample_rate", "N/A")}</td>
664
- <td>{results.get("trace2_stats", {}).get("sample_rate", "N/A")}</td>
735
+ <td>{t1.get("sample_rate", "N/A")}</td>
736
+ <td>{t2.get("sample_rate", "N/A")}</td>
665
737
  </tr>
666
738
  <tr>
667
739
  <td>Mean</td>
668
- <td>{results.get("trace1_stats", {}).get("mean", "N/A")}</td>
669
- <td>{results.get("trace2_stats", {}).get("mean", "N/A")}</td>
740
+ <td>{t1.get("mean", "N/A")}</td>
741
+ <td>{t2.get("mean", "N/A")}</td>
670
742
  </tr>
671
743
  <tr>
672
744
  <td>RMS</td>
673
- <td>{results.get("trace1_stats", {}).get("rms", "N/A")}</td>
674
- <td>{results.get("trace2_stats", {}).get("rms", "N/A")}</td>
745
+ <td>{t1.get("rms", "N/A")}</td>
746
+ <td>{t2.get("rms", "N/A")}</td>
675
747
  </tr>
676
748
  <tr>
677
749
  <td>Peak-to-Peak</td>
678
- <td>{results.get("trace1_stats", {}).get("peak_to_peak", "N/A")}</td>
679
- <td>{results.get("trace2_stats", {}).get("peak_to_peak", "N/A")}</td>
750
+ <td>{t1.get("peak_to_peak", "N/A")}</td>
751
+ <td>{t2.get("peak_to_peak", "N/A")}</td>
680
752
  </tr>
681
753
  </table>
682
- </div>
754
+ </div>"""
683
755
 
684
- <div class="section">
756
+
757
+ def _generate_amplitude_diff_section(results: dict[str, Any]) -> str:
758
+ """Generate amplitude difference section."""
759
+ amp = results.get("amplitude_difference", {})
760
+ sig_class = "significant" if amp.get("significant") else "ok"
761
+
762
+ return f"""<div class="section">
685
763
  <h3>Amplitude Difference</h3>
686
764
  <table>
687
765
  <tr>
688
766
  <td>Mean Difference</td>
689
- <td>{results.get("amplitude_difference", {}).get("mean_diff_v", "N/A")}</td>
690
- <td class="{"significant" if results.get("amplitude_difference", {}).get("significant") else "ok"}">
691
- {results.get("amplitude_difference", {}).get("mean_diff_percent", "N/A")}
767
+ <td>{amp.get("mean_diff_v", "N/A")}</td>
768
+ <td class="{sig_class}">
769
+ {amp.get("mean_diff_percent", "N/A")}
692
770
  </td>
693
771
  </tr>
694
772
  <tr>
695
773
  <td>RMS Difference</td>
696
- <td>{results.get("amplitude_difference", {}).get("rms_diff_v", "N/A")}</td>
697
- <td>{results.get("amplitude_difference", {}).get("rms_diff_percent", "N/A")}</td>
774
+ <td>{amp.get("rms_diff_v", "N/A")}</td>
775
+ <td>{amp.get("rms_diff_percent", "N/A")}</td>
698
776
  </tr>
699
777
  <tr>
700
778
  <td>Max Difference</td>
701
- <td colspan="2">{results.get("amplitude_difference", {}).get("max_diff_v", "N/A")}</td>
779
+ <td colspan="2">{amp.get("max_diff_v", "N/A")}</td>
702
780
  </tr>
703
781
  </table>
704
- </div>
782
+ </div>"""
783
+
705
784
 
706
- <div class="section">
785
+ def _generate_timing_drift_section(results: dict[str, Any]) -> str:
786
+ """Generate timing drift section."""
787
+ drift = results.get("timing_drift", {})
788
+ sig_class = "significant" if drift.get("significant") else "ok"
789
+
790
+ return f"""<div class="section">
707
791
  <h3>Timing Drift</h3>
708
792
  <table>
709
793
  <tr>
710
794
  <td>Mean Drift</td>
711
- <td>{results.get("timing_drift", {}).get("value_ns", "N/A")} ns</td>
712
- <td class="{"significant" if results.get("timing_drift", {}).get("significant") else "ok"}">
713
- {results.get("timing_drift", {}).get("percentage", "N/A")}
795
+ <td>{drift.get("value_ns", "N/A")} ns</td>
796
+ <td class="{sig_class}">
797
+ {drift.get("percentage", "N/A")}
714
798
  </td>
715
799
  </tr>
716
800
  </table>
717
- </div>
801
+ </div>"""
802
+
803
+
804
+ def _generate_noise_change_section(results: dict[str, Any]) -> str:
805
+ """Generate noise change section."""
806
+ noise = results.get("noise_change", {})
807
+ sig_class = "significant" if noise.get("significant") else "ok"
718
808
 
719
- <div class="section">
809
+ return f"""<div class="section">
720
810
  <h3>Noise Change</h3>
721
811
  <table>
722
812
  <tr>
723
813
  <td>Trace 1 Noise</td>
724
- <td>{results.get("noise_change", {}).get("noise1_v", "N/A")}</td>
814
+ <td>{noise.get("noise1_v", "N/A")}</td>
725
815
  </tr>
726
816
  <tr>
727
817
  <td>Trace 2 Noise</td>
728
- <td>{results.get("noise_change", {}).get("noise2_v", "N/A")}</td>
818
+ <td>{noise.get("noise2_v", "N/A")}</td>
729
819
  </tr>
730
820
  <tr>
731
821
  <td>Change</td>
732
- <td class="{"significant" if results.get("noise_change", {}).get("significant") else "ok"}">
733
- {results.get("noise_change", {}).get("change_percent", "N/A")}
822
+ <td class="{sig_class}">
823
+ {noise.get("change_percent", "N/A")}
734
824
  </td>
735
825
  </tr>
736
826
  </table>
737
- </div>
827
+ </div>"""
738
828
 
739
- <div class="section">
829
+
830
+ def _generate_spectral_diff_section(results: dict[str, Any]) -> str:
831
+ """Generate spectral difference section."""
832
+ spec = results.get("spectral_difference", {})
833
+ sig_class = "significant" if spec.get("significant") else "ok"
834
+
835
+ return f"""<div class="section">
740
836
  <h3>Spectral Difference</h3>
741
837
  <table>
742
838
  <tr>
743
839
  <td>Dominant Frequency 1</td>
744
- <td>{results.get("spectral_difference", {}).get("dominant_freq1_hz", "N/A")} Hz</td>
840
+ <td>{spec.get("dominant_freq1_hz", "N/A")} Hz</td>
745
841
  </tr>
746
842
  <tr>
747
843
  <td>Dominant Frequency 2</td>
748
- <td>{results.get("spectral_difference", {}).get("dominant_freq2_hz", "N/A")} Hz</td>
844
+ <td>{spec.get("dominant_freq2_hz", "N/A")} Hz</td>
749
845
  </tr>
750
846
  <tr>
751
847
  <td>Frequency Difference</td>
752
- <td class="{"significant" if results.get("spectral_difference", {}).get("significant") else "ok"}">
753
- {results.get("spectral_difference", {}).get("freq_diff_percent", "N/A")}
848
+ <td class="{sig_class}">
849
+ {spec.get("freq_diff_percent", "N/A")}
754
850
  </td>
755
851
  </tr>
756
852
  <tr>
757
853
  <td>Max Magnitude Difference</td>
758
- <td>{results.get("spectral_difference", {}).get("max_magnitude_diff_db", "N/A")} dB</td>
854
+ <td>{spec.get("max_magnitude_diff_db", "N/A")} dB</td>
759
855
  </tr>
760
856
  </table>
761
- </div>
857
+ </div>"""
762
858
 
763
- <div class="section">
764
- <h3>Correlation</h3>
765
- <p>Coefficient: {results.get("correlation", {}).get("coefficient", "N/A")}</p>
766
- <p>Quality: {results.get("correlation", {}).get("quality", "N/A")}</p>
767
- </div>
768
859
 
769
- <footer style="margin-top: 30px; text-align: center; color: #6c757d;">
770
- <p>Generated by Oscura - Signal Analysis Toolkit</p>
771
- </footer>
772
- </div>
773
- </body>
774
- </html>"""
775
- return html
860
+ def _generate_correlation_section(results: dict[str, Any]) -> str:
861
+ """Generate correlation section."""
862
+ corr = results.get("correlation", {})
863
+
864
+ return f"""<div class="section">
865
+ <h3>Correlation</h3>
866
+ <p>Coefficient: {corr.get("coefficient", "N/A")}</p>
867
+ <p>Quality: {corr.get("quality", "N/A")}</p>
868
+ </div>"""