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
@@ -21,6 +21,7 @@ from __future__ import annotations
21
21
  from typing import TYPE_CHECKING, Any, Literal
22
22
 
23
23
  import numpy as np
24
+ from numpy.typing import NDArray
24
25
 
25
26
  from oscura.core.exceptions import AnalysisError
26
27
 
@@ -69,19 +70,6 @@ def emc_compliance_test(
69
70
  - limit_freq: Frequency array for limit mask
70
71
  - limit_mag: Magnitude array for limit mask
71
72
 
72
- Returns:
73
- Dictionary containing:
74
- - status: 'PASS' or 'FAIL'
75
- - standard: Standard tested against
76
- - violations: List of frequency violations
77
- - margin_to_limit: Minimum margin in dB (negative if failing)
78
- - worst_frequency: Frequency with worst margin
79
- - worst_margin: Worst margin value in dB
80
- - spectrum_freq: Frequency array for spectrum
81
- - spectrum_mag: Magnitude array for spectrum (dBµV or dBm)
82
- - limit_freq: Frequency array for limit mask
83
- - limit_mag: Magnitude array for limit mask
84
-
85
73
  Example:
86
74
  >>> trace = osc.load('radiated_emissions.wfm')
87
75
  >>> result = osc.emc_compliance_test(trace, standard='FCC_Part15_ClassB')
@@ -95,65 +83,33 @@ def emc_compliance_test(
95
83
  CISPR 22/32 (Information Technology Equipment EMC)
96
84
  MIL-STD-461G (Military EMC Requirements)
97
85
  """
98
- # Import spectral analysis
99
86
  from oscura.analyzers.waveform.spectral import fft
100
87
 
101
- # Calculate spectrum
88
+ # Calculate spectrum and convert to dBµV
102
89
  freq, mag_db = fft(trace) # type: ignore[misc]
103
- # Note: fft() returns magnitude_db (already in dB relative to 1V)
104
-
105
- # Convert to dBµV (typical EMC unit)
106
- # mag_db is in dBV, convert to dBµV: dBµV = dBV + 120
107
- # (since 1µV = 1e-6 V, and 20*log10(1e6) = 120 dB)
108
- spectrum_dbuv = mag_db + 120
90
+ spectrum_dbuv = mag_db + 120 # Convert dBV to dBµV
109
91
 
110
- # Load limit mask for standard
92
+ # Load limit and apply frequency range
111
93
  limit_freq, limit_mag = _load_emc_mask(standard)
94
+ freq, spectrum_dbuv = _apply_frequency_range(freq, spectrum_dbuv, frequency_range)
112
95
 
113
- # Apply frequency range if specified
114
- if frequency_range is not None:
115
- f_min, f_max = frequency_range
116
- mask = (freq >= f_min) & (freq <= f_max)
117
- freq = freq[mask]
118
- spectrum_dbuv = spectrum_dbuv[mask]
119
-
120
- # Interpolate limit to spectrum frequencies
96
+ # Check compliance
121
97
  limit_interp = np.interp(freq, limit_freq, limit_mag)
122
-
123
- # Find violations (spectrum exceeds limit)
124
98
  margin = limit_interp - spectrum_dbuv
125
- violations_mask = margin < 0
126
-
127
- # Build violations list
128
- violations = []
129
- if np.any(violations_mask):
130
- violation_indices = np.where(violations_mask)[0]
131
- for idx in violation_indices:
132
- violations.append(
133
- {
134
- "frequency": freq[idx],
135
- "measured_dbuv": spectrum_dbuv[idx],
136
- "limit_dbuv": limit_interp[idx],
137
- "excess_db": -margin[idx], # Positive value for excess
138
- }
139
- )
140
-
141
- # Overall status
99
+ violations = _build_violations_list(freq, spectrum_dbuv, limit_interp, margin)
142
100
  status = "FAIL" if violations else "PASS"
143
101
 
144
102
  # Margin analysis
145
103
  margin_to_limit = np.min(margin)
146
104
  worst_idx = np.argmin(margin)
147
- worst_frequency = freq[worst_idx]
148
- worst_margin = margin[worst_idx]
149
105
 
150
106
  result = {
151
107
  "status": status,
152
108
  "standard": standard,
153
109
  "violations": violations,
154
110
  "margin_to_limit": margin_to_limit,
155
- "worst_frequency": worst_frequency,
156
- "worst_margin": worst_margin,
111
+ "worst_frequency": freq[worst_idx],
112
+ "worst_margin": margin[worst_idx],
157
113
  "spectrum_freq": freq,
158
114
  "spectrum_mag": spectrum_dbuv,
159
115
  "limit_freq": limit_freq,
@@ -161,13 +117,47 @@ def emc_compliance_test(
161
117
  "detector": detector,
162
118
  }
163
119
 
164
- # Generate report if requested
165
120
  if report is not None:
166
121
  _generate_compliance_report(result, report)
167
122
 
168
123
  return result
169
124
 
170
125
 
126
+ def _apply_frequency_range(
127
+ freq: NDArray[Any],
128
+ spectrum: NDArray[Any],
129
+ frequency_range: tuple[float, float] | None,
130
+ ) -> tuple[NDArray[Any], NDArray[Any]]:
131
+ """Apply frequency range filter if specified."""
132
+ if frequency_range is not None:
133
+ f_min, f_max = frequency_range
134
+ mask = (freq >= f_min) & (freq <= f_max)
135
+ return freq[mask], spectrum[mask]
136
+ return freq, spectrum
137
+
138
+
139
+ def _build_violations_list(
140
+ freq: NDArray[Any],
141
+ spectrum_dbuv: NDArray[Any],
142
+ limit_interp: NDArray[Any],
143
+ margin: NDArray[Any],
144
+ ) -> list[dict[str, float]]:
145
+ """Build list of compliance violations."""
146
+ violations = []
147
+ violations_mask = margin < 0
148
+ if np.any(violations_mask):
149
+ for idx in np.where(violations_mask)[0]:
150
+ violations.append(
151
+ {
152
+ "frequency": freq[idx],
153
+ "measured_dbuv": spectrum_dbuv[idx],
154
+ "limit_dbuv": limit_interp[idx],
155
+ "excess_db": -margin[idx],
156
+ }
157
+ )
158
+ return violations
159
+
160
+
171
161
  def _load_emc_mask(
172
162
  standard: str,
173
163
  ) -> tuple[np.ndarray[Any, np.dtype[np.float64]], np.ndarray[Any, np.dtype[np.float64]]]:
@@ -85,17 +85,67 @@ def characterize_buffer(
85
85
  IEEE 181-2011 Section 5.2 (Edge timing)
86
86
  JEDEC Standard No. 65B (Logic family specifications)
87
87
  """
88
- # Import here to avoid circular dependencies
89
- from oscura.analyzers.waveform.measurements import (
90
- fall_time,
91
- overshoot,
92
- rise_time,
93
- undershoot,
88
+ # Detect or use specified logic family
89
+ logic_family, confidence, voh, vol = _determine_logic_family(trace, logic_family)
90
+
91
+ # Measure timing parameters
92
+ t_rise, t_fall = _measure_timing_params(trace)
93
+
94
+ # Measure voltage overshoots
95
+ v_overshoot, v_undershoot, overshoot_pct, undershoot_pct = _measure_overshoots(trace, voh, vol)
96
+
97
+ # Calculate noise margins
98
+ logic_specs = _get_logic_specs(logic_family)
99
+ noise_margin_high = voh - logic_specs["vih"]
100
+ noise_margin_low = logic_specs["vil"] - vol
101
+
102
+ # Measure propagation delay if reference provided
103
+ propagation_delay = _measure_propagation_delay(reference_trace, trace)
104
+
105
+ # Determine pass/fail status
106
+ status = _evaluate_pass_fail(t_rise, t_fall, overshoot_pct, thresholds, logic_specs)
107
+
108
+ # Build result dictionary
109
+ result = _build_result_dict(
110
+ logic_family,
111
+ confidence,
112
+ t_rise,
113
+ t_fall,
114
+ propagation_delay,
115
+ v_overshoot,
116
+ overshoot_pct,
117
+ v_undershoot,
118
+ undershoot_pct,
119
+ noise_margin_high,
120
+ noise_margin_low,
121
+ voh,
122
+ vol,
123
+ status,
124
+ reference_trace,
94
125
  )
95
- from oscura.inference.logic import detect_logic_family
96
126
 
97
- # Auto-detect logic family if not specified
127
+ # Generate report if requested
128
+ if report is not None:
129
+ _generate_buffer_report(result, report)
130
+
131
+ return result
132
+
133
+
134
+ def _determine_logic_family(
135
+ trace: WaveformTrace, logic_family: str | None
136
+ ) -> tuple[str, float, float, float]:
137
+ """Determine logic family and voltage levels.
138
+
139
+ Args:
140
+ trace: Input waveform trace.
141
+ logic_family: Optional logic family override.
142
+
143
+ Returns:
144
+ Tuple of (logic_family, confidence, voh, vol).
145
+ """
98
146
  if logic_family is None:
147
+ from oscura.inference.logic import detect_logic_family
148
+
99
149
  detection = detect_logic_family(trace)
100
150
  logic_family = detection["primary"]["name"]
101
151
  confidence = detection["primary"]["confidence"]
@@ -103,67 +153,167 @@ def characterize_buffer(
103
153
  vol = detection["primary"]["vol"]
104
154
  else:
105
155
  confidence = 1.0
106
- # Measure VOH/VOL from trace
107
156
  voh = np.percentile(trace.data, 95)
108
157
  vol = np.percentile(trace.data, 5)
109
158
 
110
- # Measure timing parameters
159
+ return logic_family, confidence, voh, vol
160
+
161
+
162
+ def _measure_timing_params(trace: WaveformTrace) -> tuple[float, float]:
163
+ """Measure rise and fall times.
164
+
165
+ Args:
166
+ trace: Input waveform trace.
167
+
168
+ Returns:
169
+ Tuple of (rise_time, fall_time).
170
+
171
+ Raises:
172
+ AnalysisError: If measurement fails.
173
+ """
174
+ from oscura.analyzers.waveform.measurements import fall_time, rise_time
175
+
111
176
  try:
112
- t_rise = rise_time(trace)
113
- t_fall = fall_time(trace)
177
+ t_rise_raw = rise_time(trace)
178
+ t_fall_raw = fall_time(trace)
179
+ t_rise: float = float(t_rise_raw)
180
+ t_fall: float = float(t_fall_raw)
114
181
  except Exception as e:
115
182
  raise AnalysisError(f"Failed to measure rise/fall time: {e}") from e
116
183
 
117
- # Measure overshoot/undershoot
118
- v_overshoot = overshoot(trace)
119
- v_undershoot = undershoot(trace)
184
+ return t_rise, t_fall
185
+
186
+
187
+ def _measure_overshoots(
188
+ trace: WaveformTrace, voh: float, vol: float
189
+ ) -> tuple[float, float, float, float]:
190
+ """Measure voltage overshoots and undershoots.
191
+
192
+ Args:
193
+ trace: Input waveform trace.
194
+ voh: High voltage level.
195
+ vol: Low voltage level.
196
+
197
+ Returns:
198
+ Tuple of (v_overshoot, v_undershoot, overshoot_pct, undershoot_pct).
199
+ """
200
+ from oscura.analyzers.waveform.measurements import overshoot, undershoot
201
+
202
+ v_overshoot_raw = overshoot(trace)
203
+ v_undershoot_raw = undershoot(trace)
204
+ v_overshoot: float = float(v_overshoot_raw)
205
+ v_undershoot: float = float(v_undershoot_raw)
120
206
 
121
- # Calculate percentages
122
207
  swing = voh - vol
123
208
  if swing > 0:
124
- overshoot_pct = (v_overshoot / swing) * 100.0
125
- undershoot_pct = (v_undershoot / swing) * 100.0
209
+ overshoot_pct: float = (v_overshoot / swing) * 100.0
210
+ undershoot_pct: float = (v_undershoot / swing) * 100.0
126
211
  else:
127
212
  overshoot_pct = 0.0
128
213
  undershoot_pct = 0.0
129
214
 
130
- # Calculate noise margins (simplified - uses typical values)
131
- # In a real implementation, these would come from LOGIC_FAMILIES constants
132
- logic_specs = _get_logic_specs(logic_family)
133
- noise_margin_high = voh - logic_specs["vih"]
134
- noise_margin_low = logic_specs["vil"] - vol
215
+ return v_overshoot, v_undershoot, overshoot_pct, undershoot_pct
216
+
217
+
218
+ def _measure_propagation_delay(
219
+ reference_trace: WaveformTrace | None, trace: WaveformTrace
220
+ ) -> float | None:
221
+ """Measure propagation delay if reference trace provided.
222
+
223
+ Args:
224
+ reference_trace: Optional reference trace.
225
+ trace: Output trace.
226
+
227
+ Returns:
228
+ Propagation delay in seconds, or None if not measurable.
229
+ """
230
+ if reference_trace is None:
231
+ return None
232
+
233
+ try:
234
+ from oscura.analyzers.digital.timing import propagation_delay as prop_delay
235
+
236
+ delay_raw = prop_delay(reference_trace, trace)
237
+ delay: float = float(delay_raw)
238
+ return delay
239
+ except Exception:
240
+ return None
241
+
135
242
 
136
- # Propagation delay if reference provided
137
- propagation_delay = None
138
- timing_drift = None
139
- if reference_trace is not None:
140
- try:
141
- from oscura.analyzers.digital.timing import (
142
- propagation_delay as prop_delay,
143
- )
144
-
145
- propagation_delay = prop_delay(reference_trace, trace)
146
- except Exception:
147
- # If propagation delay measurement fails, set to None
148
- propagation_delay = None
149
-
150
- # Apply thresholds and determine pass/fail
151
- status = "PASS"
243
+ def _evaluate_pass_fail(
244
+ t_rise: float,
245
+ t_fall: float,
246
+ overshoot_pct: float,
247
+ thresholds: dict[str, float] | None,
248
+ logic_specs: dict[str, float],
249
+ ) -> str:
250
+ """Evaluate pass/fail status based on thresholds.
251
+
252
+ Args:
253
+ t_rise: Rise time measurement.
254
+ t_fall: Fall time measurement.
255
+ overshoot_pct: Overshoot percentage.
256
+ thresholds: Optional custom thresholds.
257
+ logic_specs: Logic family specifications.
258
+
259
+ Returns:
260
+ "PASS" or "FAIL" status string.
261
+ """
152
262
  if thresholds is not None:
153
263
  if "rise_time" in thresholds and t_rise > thresholds["rise_time"]:
154
- status = "FAIL"
264
+ return "FAIL"
155
265
  if "fall_time" in thresholds and t_fall > thresholds["fall_time"]:
156
- status = "FAIL"
266
+ return "FAIL"
157
267
  if "overshoot_percent" in thresholds and overshoot_pct > thresholds["overshoot_percent"]:
158
- status = "FAIL"
268
+ return "FAIL"
159
269
  else:
160
- # Use logic family defaults
161
270
  if t_rise > logic_specs.get("max_rise_time", float("inf")):
162
- status = "FAIL"
271
+ return "FAIL"
163
272
  if t_fall > logic_specs.get("max_fall_time", float("inf")):
164
- status = "FAIL"
273
+ return "FAIL"
274
+
275
+ return "PASS"
276
+
277
+
278
+ def _build_result_dict(
279
+ logic_family: str,
280
+ confidence: float,
281
+ t_rise: float,
282
+ t_fall: float,
283
+ propagation_delay: float | None,
284
+ v_overshoot: float,
285
+ overshoot_pct: float,
286
+ v_undershoot: float,
287
+ undershoot_pct: float,
288
+ noise_margin_high: float,
289
+ noise_margin_low: float,
290
+ voh: float,
291
+ vol: float,
292
+ status: str,
293
+ reference_trace: WaveformTrace | None,
294
+ ) -> dict[str, Any]:
295
+ """Build result dictionary.
165
296
 
166
- # Build result dictionary
297
+ Args:
298
+ logic_family: Logic family name.
299
+ confidence: Detection confidence.
300
+ t_rise: Rise time.
301
+ t_fall: Fall time.
302
+ propagation_delay: Propagation delay measurement.
303
+ v_overshoot: Overshoot voltage.
304
+ overshoot_pct: Overshoot percentage.
305
+ v_undershoot: Undershoot voltage.
306
+ undershoot_pct: Undershoot percentage.
307
+ noise_margin_high: High noise margin.
308
+ noise_margin_low: Low noise margin.
309
+ voh: High output voltage.
310
+ vol: Low output voltage.
311
+ status: Pass/fail status.
312
+ reference_trace: Optional reference trace.
313
+
314
+ Returns:
315
+ Result dictionary with all measurements.
316
+ """
167
317
  result = {
168
318
  "logic_family": logic_family,
169
319
  "confidence": confidence,
@@ -185,13 +335,9 @@ def characterize_buffer(
185
335
  if reference_trace is not None and propagation_delay is not None:
186
336
  result["reference_comparison"] = {
187
337
  "propagation_delay": propagation_delay,
188
- "timing_drift": timing_drift,
338
+ "timing_drift": None,
189
339
  }
190
340
 
191
- # Generate report if requested
192
- if report is not None:
193
- _generate_buffer_report(result, report)
194
-
195
341
  return result
196
342
 
197
343
 
@@ -0,0 +1,12 @@
1
+ """Legacy workflow patterns for backward compatibility.
2
+
3
+ This module provides legacy workflow execution patterns that have been
4
+ superseded by modern composition patterns. Kept for backward compatibility
5
+ with existing code and tests.
6
+
7
+ For new code, use the modern workflow patterns in the parent module.
8
+ """
9
+
10
+ from oscura.workflows.legacy.dag import TaskNode, WorkflowDAG
11
+
12
+ __all__ = ["TaskNode", "WorkflowDAG"]
@@ -2,6 +2,9 @@
2
2
 
3
3
  This module provides DAG-based workflow execution with automatic dependency
4
4
  resolution and parallel execution of independent tasks.
5
+
6
+ NOTE: This is legacy API for backward compatibility.
7
+ For new code, use the modern workflow composition patterns.
5
8
  """
6
9
 
7
10
  from __future__ import annotations
@@ -51,7 +54,7 @@ class WorkflowDAG:
51
54
  with automatic parallelization of independent tasks.
52
55
 
53
56
  Example:
54
- >>> from oscura.workflow.dag import WorkflowDAG
57
+ >>> from oscura.workflows.legacy.dag import WorkflowDAG
55
58
  >>> dag = WorkflowDAG()
56
59
  >>> dag.add_task('load', load_trace, depends_on=[])
57
60
  >>> dag.add_task('fft', compute_fft, depends_on=['load'])
@@ -107,7 +107,7 @@ class MultiTraceWorkflow:
107
107
  if not self.pattern:
108
108
  return
109
109
 
110
- paths = glob_func(self.pattern) # noqa: PTH207
110
+ paths = glob_func(self.pattern)
111
111
  if not paths:
112
112
  raise OscuraError(f"No files match pattern: {self.pattern}")
113
113
 
@@ -131,20 +131,20 @@ class MultiTraceWorkflow:
131
131
 
132
132
  try:
133
133
  if ext == ".csv":
134
- from oscura.loaders.csv import ( # type: ignore[import-not-found]
135
- load_csv, # type: ignore[import-not-found]
134
+ from oscura.loaders.csv import (
135
+ load_csv,
136
136
  )
137
137
 
138
138
  return load_csv(str(path))
139
139
  elif ext == ".bin":
140
- from oscura.loaders.binary import ( # type: ignore[import-not-found]
141
- load_binary, # type: ignore[import-not-found]
140
+ from oscura.loaders.binary import (
141
+ load_binary,
142
142
  )
143
143
 
144
144
  return load_binary(str(path))
145
145
  elif ext in (".h5", ".hdf5"):
146
- from oscura.loaders.hdf5 import ( # type: ignore[import-not-found]
147
- load_hdf5, # type: ignore[import-not-found]
146
+ from oscura.loaders.hdf5 import (
147
+ load_hdf5,
148
148
  )
149
149
 
150
150
  return load_hdf5(str(path))
@@ -152,7 +152,7 @@ class MultiTraceWorkflow:
152
152
  raise OscuraError(f"Unsupported format: {ext}")
153
153
 
154
154
  except ImportError as e:
155
- raise OscuraError(f"Loader not available for {ext}: {e}") # noqa: B904
155
+ raise OscuraError(f"Loader not available for {ext}: {e}")
156
156
 
157
157
  def _iter_traces(self, lazy: bool = False) -> Iterator[tuple[str, Any]]:
158
158
  """Iterate over traces.
@@ -495,7 +495,7 @@ def load_all(pattern: str, lazy: bool = True) -> list[Any]:
495
495
  Raises:
496
496
  OscuraError: If no traces found
497
497
  """
498
- paths = glob_func(pattern) # noqa: PTH207
498
+ paths = glob_func(pattern)
499
499
  if not paths:
500
500
  raise OscuraError(f"No files match pattern: {pattern}")
501
501
 
oscura/workflows/power.py CHANGED
@@ -38,12 +38,7 @@ def power_analysis(
38
38
  ) -> dict[str, Any]:
39
39
  """Comprehensive power consumption analysis.
40
40
 
41
- Analyzes power consumption from voltage and current measurements:
42
- - Instantaneous power calculation
43
- - Average, RMS, and peak power
44
- - Energy consumption
45
- - Efficiency (if input power provided)
46
- - Power profile generation
41
+ Analyzes power consumption from voltage and current measurements.
47
42
 
48
43
  Args:
49
44
  voltage: Output voltage trace.
@@ -53,56 +48,47 @@ def power_analysis(
53
48
  report: Optional path to save HTML report.
54
49
 
55
50
  Returns:
56
- Dictionary containing:
57
- - power_trace: WaveformTrace of instantaneous power P(t)
58
- - average_power: Mean power in watts
59
- - output_power_avg: Average output power (same as average_power)
60
- - output_power_rms: RMS output power in watts
61
- - peak_power: Maximum power in watts
62
- - min_power: Minimum power in watts
63
- - energy: Total energy in joules
64
- - duration: Measurement duration in seconds
65
- - efficiency: Efficiency percentage (if input provided)
66
- - power_loss: Power loss in watts (if input provided)
67
- - input_power_avg: Average input power (if input provided)
51
+ Dictionary with power_trace, average_power, output_power_avg, output_power_rms,
52
+ peak_power, min_power, energy, duration, and optionally efficiency, power_loss, input_power_avg.
68
53
 
69
54
  Raises:
70
- AnalysisError: If traces have incompatible sample rates or lengths.
55
+ AnalysisError: If traces have incompatible sample rates.
71
56
 
72
57
  Example:
73
- >>> voltage = osc.load('vout.wfm')
74
- >>> current = osc.load('iout.wfm')
75
- >>> result = osc.power_analysis(voltage, current)
58
+ >>> result = osc.power_analysis(v_trace, i_trace)
76
59
  >>> print(f"Average: {result['average_power']*1e3:.2f} mW")
77
- >>> print(f"Peak: {result['peak_power']*1e3:.2f} mW")
78
- >>> print(f"Energy: {result['energy']*1e6:.2f} µJ")
79
60
 
80
61
  References:
81
- IEC 61000-4-7: Harmonics and interharmonics measurement
82
- IEEE 1459-2010: Definitions for measurement of electric power
62
+ IEC 61000-4-7, IEEE 1459-2010
83
63
  """
84
- # Import power analysis functions
85
- from oscura.analyzers.power.basic import (
86
- instantaneous_power,
87
- power_statistics,
88
- )
64
+ from oscura.analyzers.power.basic import instantaneous_power, power_statistics
89
65
 
90
- # Validate traces
66
+ _validate_sample_rates(voltage, current)
67
+ power_trace = instantaneous_power(voltage, current)
68
+ stats = power_statistics(power_trace)
69
+
70
+ result = _build_power_result(power_trace, stats)
71
+
72
+ if input_voltage is not None and input_current is not None:
73
+ result.update(_calculate_efficiency(input_voltage, input_current, stats["average"]))
74
+
75
+ if report is not None:
76
+ _generate_power_report(result, report)
77
+
78
+ return result
79
+
80
+
81
+ def _validate_sample_rates(voltage: WaveformTrace, current: WaveformTrace) -> None:
82
+ """Validate that traces have matching sample rates."""
91
83
  if voltage.metadata.sample_rate != current.metadata.sample_rate:
92
- # Would need interpolation in real implementation
93
84
  raise AnalysisError(
94
- "Voltage and current traces must have same sample rate. "
95
- f"Got {voltage.metadata.sample_rate} and {current.metadata.sample_rate}"
85
+ f"Sample rate mismatch: {voltage.metadata.sample_rate} vs {current.metadata.sample_rate}"
96
86
  )
97
87
 
98
- # Calculate instantaneous power
99
- power_trace = instantaneous_power(voltage, current)
100
-
101
- # Calculate power statistics
102
- stats = power_statistics(power_trace)
103
88
 
104
- # Build result with output power
105
- result = {
89
+ def _build_power_result(power_trace: WaveformTrace, stats: dict[str, Any]) -> dict[str, Any]:
90
+ """Build power analysis result dictionary."""
91
+ return {
106
92
  "power_trace": power_trace,
107
93
  "average_power": stats["average"],
108
94
  "output_power_avg": stats["average"],
@@ -113,30 +99,24 @@ def power_analysis(
113
99
  "duration": stats["duration"],
114
100
  }
115
101
 
116
- # Calculate efficiency if input provided
117
- if input_voltage is not None and input_current is not None:
118
- input_power_trace = instantaneous_power(input_voltage, input_current)
119
- input_stats = power_statistics(input_power_trace)
120
102
 
121
- input_power_avg = input_stats["average"]
122
- output_power_avg = stats["average"]
103
+ def _calculate_efficiency(
104
+ input_voltage: WaveformTrace, input_current: WaveformTrace, output_power_avg: float
105
+ ) -> dict[str, float]:
106
+ """Calculate power efficiency metrics."""
107
+ from oscura.analyzers.power.basic import instantaneous_power, power_statistics
123
108
 
124
- if input_power_avg > 0:
125
- efficiency = (output_power_avg / input_power_avg) * 100.0
126
- power_loss = input_power_avg - output_power_avg
127
- else:
128
- efficiency = 0.0
129
- power_loss = 0.0
109
+ input_power_trace = instantaneous_power(input_voltage, input_current)
110
+ input_stats = power_statistics(input_power_trace)
111
+ input_power_avg = input_stats["average"]
130
112
 
131
- result["efficiency"] = efficiency
132
- result["power_loss"] = power_loss
133
- result["input_power_avg"] = input_power_avg
113
+ if input_power_avg > 0:
114
+ efficiency = (output_power_avg / input_power_avg) * 100.0
115
+ power_loss = input_power_avg - output_power_avg
116
+ else:
117
+ efficiency = power_loss = 0.0
134
118
 
135
- # Generate report if requested
136
- if report is not None:
137
- _generate_power_report(result, report)
138
-
139
- return result
119
+ return {"efficiency": efficiency, "power_loss": power_loss, "input_power_avg": input_power_avg}
140
120
 
141
121
 
142
122
  def _generate_power_report(result: dict[str, Any], output_path: str) -> None: