oscura 0.5.1__py3-none-any.whl → 0.7.0__py3-none-any.whl

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