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
@@ -5,7 +5,7 @@ This module provides impedance extraction from Time Domain Reflectometry
5
5
 
6
6
 
7
7
  Example:
8
- >>> from oscura.component import extract_impedance
8
+ >>> from oscura.utils.component import extract_impedance
9
9
  >>> z0, z_profile = extract_impedance(tdr_trace)
10
10
 
11
11
  References:
@@ -124,10 +124,36 @@ def extract_impedance(
124
124
  References:
125
125
  IPC-TM-650 2.5.5.7
126
126
  """
127
- data = trace.data.astype(np.float64)
128
- sample_rate = trace.metadata.sample_rate
129
- dt = 1.0 / sample_rate
127
+ data, sample_rate = _prepare_tdr_data(trace)
128
+ velocity_val = _compute_velocity(velocity, velocity_factor)
129
+ time_axis, distance_axis = _create_axes(data, sample_rate, velocity_val)
130
+ start_idx, end_idx = _compute_analysis_window(len(data), sample_rate, start_time, end_time)
131
+ impedance = _compute_impedance_profile(data, z0_source)
132
+ z0, stats = _extract_impedance_statistics(impedance, start_idx, end_idx, distance_axis)
133
+ profile = ImpedanceProfile(
134
+ distance=distance_axis,
135
+ time=time_axis,
136
+ impedance=impedance,
137
+ z0_source=z0_source,
138
+ velocity=velocity_val,
139
+ statistics=stats,
140
+ )
141
+ return z0, profile
142
+
143
+
144
+ def _prepare_tdr_data(trace: WaveformTrace) -> tuple[NDArray[np.float64], float]:
145
+ """Prepare TDR data for analysis.
146
+
147
+ Args:
148
+ trace: TDR reflection waveform.
149
+
150
+ Returns:
151
+ Tuple of (data array, sample rate).
130
152
 
153
+ Raises:
154
+ InsufficientDataError: If trace has fewer than 10 samples.
155
+ """
156
+ data = trace.data.astype(np.float64)
131
157
  if len(data) < 10:
132
158
  raise InsufficientDataError(
133
159
  "TDR analysis requires at least 10 samples",
@@ -135,72 +161,110 @@ def extract_impedance(
135
161
  available=len(data),
136
162
  analysis_type="tdr_impedance",
137
163
  )
164
+ return data, trace.metadata.sample_rate
165
+
166
+
167
+ def _compute_velocity(velocity: float | None, velocity_factor: float) -> float:
168
+ """Compute propagation velocity.
169
+
170
+ Args:
171
+ velocity: Explicit velocity or None.
172
+ velocity_factor: Fraction of speed of light.
138
173
 
139
- # Calculate propagation velocity
174
+ Returns:
175
+ Propagation velocity in m/s.
176
+ """
177
+ if velocity is not None:
178
+ return velocity
140
179
  c = 299792458.0 # Speed of light in m/s
141
- if velocity is None:
142
- velocity = c * velocity_factor
180
+ return c * velocity_factor
181
+
182
+
183
+ def _create_axes(
184
+ data: NDArray[np.float64], sample_rate: float, velocity: float
185
+ ) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
186
+ """Create time and distance axes for TDR.
187
+
188
+ Args:
189
+ data: TDR data array.
190
+ sample_rate: Sample rate in Hz.
191
+ velocity: Propagation velocity in m/s.
143
192
 
144
- # Create time and distance axes
193
+ Returns:
194
+ Tuple of (time_axis, distance_axis).
195
+ """
196
+ dt = 1.0 / sample_rate
145
197
  time_axis = np.arange(len(data)) * dt
146
- # TDR: distance is velocity * time / 2 (round trip)
147
- distance_axis = velocity * time_axis / 2.0
148
-
149
- # Apply time window if specified
150
- start_idx = 0
151
- end_idx = len(data)
152
- if start_time is not None:
153
- start_idx = int(start_time * sample_rate)
154
- if end_time is not None:
155
- end_idx = int(end_time * sample_rate)
156
-
157
- start_idx = max(0, min(start_idx, len(data) - 1))
158
- end_idx = max(start_idx + 1, min(end_idx, len(data)))
159
-
160
- # Find the incident step level in TDR data
161
- # For TDR with a matched load (Z = Z0), the steady-state voltage is V_source/2
162
- incident_level = _find_incident_level(data)
198
+ distance_axis = velocity * time_axis / 2.0 # Round trip
199
+ return time_axis, distance_axis
200
+
201
+
202
+ def _compute_analysis_window(
203
+ data_len: int, sample_rate: float, start_time: float | None, end_time: float | None
204
+ ) -> tuple[int, int]:
205
+ """Compute analysis window indices.
206
+
207
+ Args:
208
+ data_len: Length of data array.
209
+ sample_rate: Sample rate in Hz.
210
+ start_time: Start time in seconds.
211
+ end_time: End time in seconds.
163
212
 
164
- # Calculate reflection coefficient from TDR waveform
165
- # For TDR: V_measured = V_incident * (1 + rho)
166
- # where rho is the reflection coefficient
167
- # So: rho = (V_measured / V_incident) - 1
213
+ Returns:
214
+ Tuple of (start_idx, end_idx).
215
+ """
216
+ start_idx = 0 if start_time is None else int(start_time * sample_rate)
217
+ end_idx = data_len if end_time is None else int(end_time * sample_rate)
218
+ start_idx = max(0, min(start_idx, data_len - 1))
219
+ end_idx = max(start_idx + 1, min(end_idx, data_len))
220
+ return start_idx, end_idx
221
+
222
+
223
+ def _compute_impedance_profile(data: NDArray[np.float64], z0_source: float) -> NDArray[np.float64]:
224
+ """Compute impedance profile from TDR data.
168
225
 
169
- if incident_level > 0:
170
- rho = (data / incident_level) - 1.0
171
- else:
172
- # Fallback: assume data is already normalized
173
- rho = data - 1.0
226
+ Args:
227
+ data: TDR voltage data.
228
+ z0_source: Source impedance.
174
229
 
175
- # Calculate impedance from reflection coefficient
176
- # Z = Z0 * (1 + rho) / (1 - rho)
230
+ Returns:
231
+ Impedance profile array.
232
+ """
233
+ incident_level = _find_incident_level(data)
234
+ rho = (data / incident_level) - 1.0 if incident_level > 0 else data - 1.0
177
235
  with np.errstate(divide="ignore", invalid="ignore"):
178
236
  impedance = z0_source * (1 + rho) / (1 - rho)
179
- # Clip unreasonable values
180
- impedance = np.clip(impedance, 1.0, 10000.0)
237
+ return np.clip(impedance, 1.0, 10000.0)
181
238
 
182
- # Extract characteristic impedance from stable region
183
- stable_region = impedance[start_idx:end_idx]
184
- z0 = float(np.median(stable_region))
185
239
 
186
- # Create profile
187
- profile = ImpedanceProfile(
188
- distance=distance_axis,
189
- time=time_axis,
190
- impedance=impedance,
191
- z0_source=z0_source,
192
- velocity=velocity,
193
- statistics={
194
- "z0_measured": z0,
195
- "z0_std": float(np.std(stable_region)),
196
- "z0_min": float(np.min(stable_region)),
197
- "z0_max": float(np.max(stable_region)),
198
- "analysis_start_m": float(distance_axis[start_idx]),
199
- "analysis_end_m": float(distance_axis[end_idx - 1]),
200
- },
201
- )
240
+ def _extract_impedance_statistics(
241
+ impedance: NDArray[np.float64],
242
+ start_idx: int,
243
+ end_idx: int,
244
+ distance_axis: NDArray[np.float64],
245
+ ) -> tuple[float, dict[str, float]]:
246
+ """Extract impedance statistics from stable region.
202
247
 
203
- return z0, profile
248
+ Args:
249
+ impedance: Impedance profile.
250
+ start_idx: Start index of analysis window.
251
+ end_idx: End index of analysis window.
252
+ distance_axis: Distance array.
253
+
254
+ Returns:
255
+ Tuple of (characteristic impedance, statistics dict).
256
+ """
257
+ stable_region = impedance[start_idx:end_idx]
258
+ z0 = float(np.median(stable_region))
259
+ stats = {
260
+ "z0_measured": z0,
261
+ "z0_std": float(np.std(stable_region)),
262
+ "z0_min": float(np.min(stable_region)),
263
+ "z0_max": float(np.max(stable_region)),
264
+ "analysis_start_m": float(distance_axis[start_idx]),
265
+ "analysis_end_m": float(distance_axis[end_idx - 1]),
266
+ }
267
+ return z0, stats
204
268
 
205
269
 
206
270
  def impedance_profile(
@@ -5,7 +5,7 @@ waveform data, including parasitic extraction.
5
5
 
6
6
 
7
7
  Example:
8
- >>> from oscura.component import measure_capacitance, measure_inductance
8
+ >>> from oscura.utils.component import measure_capacitance, measure_inductance
9
9
  >>> C = measure_capacitance(voltage_trace, current_trace)
10
10
  >>> L = measure_inductance(voltage_trace, current_trace)
11
11
 
@@ -137,95 +137,95 @@ def measure_capacitance(
137
137
  )
138
138
 
139
139
  if method == "charge" and current_trace is not None:
140
- # C = Q / V = integral(I*dt) / delta_V
141
- current = current_trace.data.astype(np.float64)
142
- min_len = min(len(voltage), len(current))
143
- voltage = voltage[:min_len]
144
- current = current[:min_len]
145
-
146
- # Integrate current to get charge
147
- charge = np.cumsum(current) * dt
148
- delta_v = np.max(voltage) - np.min(voltage)
149
-
150
- if delta_v > 1e-10:
151
- delta_q = np.max(charge) - np.min(charge)
152
- capacitance = delta_q / delta_v
153
- else:
154
- raise AnalysisError("Voltage change too small for capacitance measurement")
155
-
156
- # Estimate ESR from phase relationship
157
- esr = _estimate_esr(voltage, current, sample_rate)
158
-
159
- return CapacitanceMeasurement(
160
- capacitance=float(abs(capacitance)),
161
- esr=esr,
162
- method="charge_integration",
163
- confidence=0.9,
164
- statistics={
165
- "delta_v": delta_v,
166
- "delta_q": delta_q,
167
- "num_samples": min_len,
168
- },
140
+ return _measure_capacitance_charge(
141
+ voltage, current_trace.data.astype(np.float64), dt, sample_rate
169
142
  )
170
-
171
143
  elif method == "slope" and current_trace is not None:
172
- # C = I / (dV/dt)
173
- current = current_trace.data.astype(np.float64)
174
- min_len = min(len(voltage), len(current))
175
- voltage = voltage[:min_len]
176
- current = current[:min_len]
177
-
178
- # Calculate dV/dt
179
- dv_dt = np.diff(voltage) / dt
180
-
181
- # Find region where dV/dt is significant
182
- significant_mask = np.abs(dv_dt) > np.max(np.abs(dv_dt)) * 0.1
183
- if np.sum(significant_mask) < 5:
184
- raise AnalysisError("Insufficient voltage slope for capacitance measurement")
185
-
186
- # Use corresponding current values
187
- current_for_slope = current[:-1][significant_mask]
188
- dv_dt_significant = dv_dt[significant_mask]
189
-
190
- # C = I / (dV/dt)
191
- capacitance_values = current_for_slope / dv_dt_significant
192
- capacitance = float(np.median(np.abs(capacitance_values)))
193
-
194
- return CapacitanceMeasurement(
195
- capacitance=capacitance,
196
- method="slope",
197
- confidence=0.85,
198
- statistics={
199
- "num_valid_points": int(np.sum(significant_mask)),
200
- "capacitance_std": float(np.std(np.abs(capacitance_values))),
201
- },
202
- )
203
-
144
+ return _measure_capacitance_slope(voltage, current_trace.data.astype(np.float64), dt)
204
145
  elif method == "frequency":
205
- # Extract from RC time constant
206
- if resistance is None:
207
- raise AnalysisError("Resistance value required for frequency method")
208
-
209
- # Find time constant from step response
210
- tau = _extract_time_constant(voltage, sample_rate)
211
-
212
- # C = tau / R
213
- capacitance = tau / resistance
214
-
215
- return CapacitanceMeasurement(
216
- capacitance=float(capacitance),
217
- method="time_constant",
218
- confidence=0.8,
219
- statistics={
220
- "time_constant": tau,
221
- "resistance": resistance,
222
- },
223
- )
224
-
146
+ return _measure_capacitance_frequency(voltage, sample_rate, resistance)
225
147
  else:
226
148
  raise AnalysisError(f"Method '{method}' requires current_trace or resistance parameter")
227
149
 
228
150
 
151
+ def _measure_capacitance_charge(
152
+ voltage: NDArray[np.float64],
153
+ current: NDArray[np.float64],
154
+ dt: float,
155
+ sample_rate: float,
156
+ ) -> CapacitanceMeasurement:
157
+ """Measure capacitance using charge integration method."""
158
+ min_len = min(len(voltage), len(current))
159
+ voltage, current = voltage[:min_len], current[:min_len]
160
+
161
+ charge = np.cumsum(current) * dt
162
+ delta_v = np.max(voltage) - np.min(voltage)
163
+
164
+ if delta_v <= 1e-10:
165
+ raise AnalysisError("Voltage change too small for capacitance measurement")
166
+
167
+ delta_q = np.max(charge) - np.min(charge)
168
+ capacitance = delta_q / delta_v
169
+ esr = _estimate_esr(voltage, current, sample_rate)
170
+
171
+ return CapacitanceMeasurement(
172
+ capacitance=float(abs(capacitance)),
173
+ esr=esr,
174
+ method="charge_integration",
175
+ confidence=0.9,
176
+ statistics={"delta_v": delta_v, "delta_q": delta_q, "num_samples": min_len},
177
+ )
178
+
179
+
180
+ def _measure_capacitance_slope(
181
+ voltage: NDArray[np.float64],
182
+ current: NDArray[np.float64],
183
+ dt: float,
184
+ ) -> CapacitanceMeasurement:
185
+ """Measure capacitance using slope method."""
186
+ min_len = min(len(voltage), len(current))
187
+ voltage, current = voltage[:min_len], current[:min_len]
188
+
189
+ dv_dt = np.diff(voltage) / dt
190
+ significant_mask = np.abs(dv_dt) > np.max(np.abs(dv_dt)) * 0.1
191
+
192
+ if np.sum(significant_mask) < 5:
193
+ raise AnalysisError("Insufficient voltage slope for capacitance measurement")
194
+
195
+ capacitance_values = current[:-1][significant_mask] / dv_dt[significant_mask]
196
+ capacitance = float(np.median(np.abs(capacitance_values)))
197
+
198
+ return CapacitanceMeasurement(
199
+ capacitance=capacitance,
200
+ method="slope",
201
+ confidence=0.85,
202
+ statistics={
203
+ "num_valid_points": int(np.sum(significant_mask)),
204
+ "capacitance_std": float(np.std(np.abs(capacitance_values))),
205
+ },
206
+ )
207
+
208
+
209
+ def _measure_capacitance_frequency(
210
+ voltage: NDArray[np.float64],
211
+ sample_rate: float,
212
+ resistance: float | None,
213
+ ) -> CapacitanceMeasurement:
214
+ """Measure capacitance using time constant method."""
215
+ if resistance is None:
216
+ raise AnalysisError("Resistance value required for frequency method")
217
+
218
+ tau = _extract_time_constant(voltage, sample_rate)
219
+ capacitance = tau / resistance
220
+
221
+ return CapacitanceMeasurement(
222
+ capacitance=float(capacitance),
223
+ method="time_constant",
224
+ confidence=0.8,
225
+ statistics={"time_constant": tau, "resistance": resistance},
226
+ )
227
+
228
+
229
229
  def measure_inductance(
230
230
  voltage_trace: WaveformTrace,
231
231
  current_trace: WaveformTrace | None = None,
@@ -274,95 +274,92 @@ def measure_inductance(
274
274
  )
275
275
 
276
276
  if method == "flux" and current_trace is not None:
277
- # L = flux / I = integral(V*dt) / delta_I
278
- current = current_trace.data.astype(np.float64)
279
- min_len = min(len(voltage), len(current))
280
- voltage = voltage[:min_len]
281
- current = current[:min_len]
282
-
283
- # Integrate voltage to get flux linkage
284
- flux = np.cumsum(voltage) * dt
285
- delta_i = np.max(current) - np.min(current)
286
-
287
- if delta_i > 1e-10:
288
- delta_flux = np.max(flux) - np.min(flux)
289
- inductance = delta_flux / delta_i
290
- else:
291
- raise AnalysisError("Current change too small for inductance measurement")
292
-
293
- # Estimate DCR from steady-state
294
- dcr = _estimate_dcr(voltage, current)
295
-
296
- return InductanceMeasurement(
297
- inductance=float(abs(inductance)),
298
- dcr=dcr,
299
- method="flux_integration",
300
- confidence=0.9,
301
- statistics={
302
- "delta_i": delta_i,
303
- "delta_flux": delta_flux,
304
- "num_samples": min_len,
305
- },
306
- )
307
-
277
+ return _measure_inductance_flux(voltage, current_trace.data.astype(np.float64), dt)
308
278
  elif method == "slope" and current_trace is not None:
309
- # L = V / (dI/dt)
310
- current = current_trace.data.astype(np.float64)
311
- min_len = min(len(voltage), len(current))
312
- voltage = voltage[:min_len]
313
- current = current[:min_len]
314
-
315
- # Calculate dI/dt
316
- di_dt = np.diff(current) / dt
317
-
318
- # Find region where dI/dt is significant
319
- significant_mask = np.abs(di_dt) > np.max(np.abs(di_dt)) * 0.1
320
- if np.sum(significant_mask) < 5:
321
- raise AnalysisError("Insufficient current slope for inductance measurement")
322
-
323
- # Use corresponding voltage values
324
- voltage_for_slope = voltage[:-1][significant_mask]
325
- di_dt_significant = di_dt[significant_mask]
326
-
327
- # L = V / (dI/dt)
328
- inductance_values = voltage_for_slope / di_dt_significant
329
- inductance = float(np.median(np.abs(inductance_values)))
330
-
331
- return InductanceMeasurement(
332
- inductance=inductance,
333
- method="slope",
334
- confidence=0.85,
335
- statistics={
336
- "num_valid_points": int(np.sum(significant_mask)),
337
- "inductance_std": float(np.std(np.abs(inductance_values))),
338
- },
339
- )
340
-
279
+ return _measure_inductance_slope(voltage, current_trace.data.astype(np.float64), dt)
341
280
  elif method == "frequency":
342
- # Extract from RL time constant
343
- if resistance is None:
344
- raise AnalysisError("Resistance value required for frequency method")
345
-
346
- # Find time constant from step response
347
- tau = _extract_time_constant(voltage, sample_rate)
348
-
349
- # L = tau * R
350
- inductance = tau * resistance
351
-
352
- return InductanceMeasurement(
353
- inductance=float(inductance),
354
- method="time_constant",
355
- confidence=0.8,
356
- statistics={
357
- "time_constant": tau,
358
- "resistance": resistance,
359
- },
360
- )
361
-
281
+ return _measure_inductance_frequency(voltage, sample_rate, resistance)
362
282
  else:
363
283
  raise AnalysisError(f"Method '{method}' requires current_trace or resistance parameter")
364
284
 
365
285
 
286
+ def _measure_inductance_flux(
287
+ voltage: NDArray[np.float64],
288
+ current: NDArray[np.float64],
289
+ dt: float,
290
+ ) -> InductanceMeasurement:
291
+ """Measure inductance using flux integration method."""
292
+ min_len = min(len(voltage), len(current))
293
+ voltage, current = voltage[:min_len], current[:min_len]
294
+
295
+ flux = np.cumsum(voltage) * dt
296
+ delta_i = np.max(current) - np.min(current)
297
+
298
+ if delta_i <= 1e-10:
299
+ raise AnalysisError("Current change too small for inductance measurement")
300
+
301
+ delta_flux = np.max(flux) - np.min(flux)
302
+ inductance = delta_flux / delta_i
303
+ dcr = _estimate_dcr(voltage, current)
304
+
305
+ return InductanceMeasurement(
306
+ inductance=float(abs(inductance)),
307
+ dcr=dcr,
308
+ method="flux_integration",
309
+ confidence=0.9,
310
+ statistics={"delta_i": delta_i, "delta_flux": delta_flux, "num_samples": min_len},
311
+ )
312
+
313
+
314
+ def _measure_inductance_slope(
315
+ voltage: NDArray[np.float64],
316
+ current: NDArray[np.float64],
317
+ dt: float,
318
+ ) -> InductanceMeasurement:
319
+ """Measure inductance using slope method."""
320
+ min_len = min(len(voltage), len(current))
321
+ voltage, current = voltage[:min_len], current[:min_len]
322
+
323
+ di_dt = np.diff(current) / dt
324
+ significant_mask = np.abs(di_dt) > np.max(np.abs(di_dt)) * 0.1
325
+
326
+ if np.sum(significant_mask) < 5:
327
+ raise AnalysisError("Insufficient current slope for inductance measurement")
328
+
329
+ inductance_values = voltage[:-1][significant_mask] / di_dt[significant_mask]
330
+ inductance = float(np.median(np.abs(inductance_values)))
331
+
332
+ return InductanceMeasurement(
333
+ inductance=inductance,
334
+ method="slope",
335
+ confidence=0.85,
336
+ statistics={
337
+ "num_valid_points": int(np.sum(significant_mask)),
338
+ "inductance_std": float(np.std(np.abs(inductance_values))),
339
+ },
340
+ )
341
+
342
+
343
+ def _measure_inductance_frequency(
344
+ voltage: NDArray[np.float64],
345
+ sample_rate: float,
346
+ resistance: float | None,
347
+ ) -> InductanceMeasurement:
348
+ """Measure inductance using time constant method."""
349
+ if resistance is None:
350
+ raise AnalysisError("Resistance value required for frequency method")
351
+
352
+ tau = _extract_time_constant(voltage, sample_rate)
353
+ inductance = tau * resistance
354
+
355
+ return InductanceMeasurement(
356
+ inductance=float(inductance),
357
+ method="time_constant",
358
+ confidence=0.8,
359
+ statistics={"time_constant": tau, "resistance": resistance},
360
+ )
361
+
362
+
366
363
  def extract_parasitics(
367
364
  voltage_trace: WaveformTrace,
368
365
  current_trace: WaveformTrace,
@@ -530,7 +527,7 @@ def _fit_series_rlc(
530
527
  # Simple optimization
531
528
  from scipy.optimize import minimize
532
529
 
533
- def objective(params: NDArray[np.float64]) -> np.floating[Any]: # type: ignore[name-defined]
530
+ def objective(params: NDArray[np.float64]) -> np.floating[Any]:
534
531
  R, L, C = params
535
532
  Z_model = model(omega, R, L, C)
536
533
  return float(np.sum(np.abs(Z - Z_model) ** 2)) # type: ignore[return-value]
@@ -563,7 +560,7 @@ def _fit_parallel_rlc(
563
560
  try:
564
561
  from scipy.optimize import minimize
565
562
 
566
- def objective(params: NDArray[np.float64]) -> np.floating[Any]: # type: ignore[name-defined]
563
+ def objective(params: NDArray[np.float64]) -> np.floating[Any]:
567
564
  R, L, C = params
568
565
  Y_model = 1 / R + 1j * omega * C + 1 / (1j * omega * L + 1e-20)
569
566
  Z_model = 1 / (Y_model + 1e-20)
@@ -5,7 +5,7 @@ characteristic impedance, propagation delay, and velocity factor.
5
5
 
6
6
 
7
7
  Example:
8
- >>> from oscura.component import transmission_line_analysis
8
+ >>> from oscura.utils.component import transmission_line_analysis
9
9
  >>> result = transmission_line_analysis(tdr_trace)
10
10
 
11
11
  References:
@@ -79,7 +79,7 @@ def transmission_line_analysis(
79
79
  >>> result = transmission_line_analysis(tdr_trace, line_length=0.1)
80
80
  >>> print(f"Z0 = {result.z0:.1f} ohms, delay = {result.propagation_delay*1e9:.2f} ns")
81
81
  """
82
- from oscura.component.impedance import extract_impedance
82
+ from oscura.utils.component.impedance import extract_impedance
83
83
 
84
84
  # Speed of light
85
85
  c = 299792458.0
@@ -162,7 +162,7 @@ def characteristic_impedance(
162
162
  >>> z0 = characteristic_impedance(tdr_trace)
163
163
  >>> print(f"Z0 = {z0:.1f} ohms")
164
164
  """
165
- from oscura.component.impedance import extract_impedance
165
+ from oscura.utils.component.impedance import extract_impedance
166
166
 
167
167
  z0, _ = extract_impedance(
168
168
  trace,
@@ -6,20 +6,20 @@ Bessel, Elliptic), and convenience filters (moving average, median).
6
6
 
7
7
 
8
8
  Example:
9
- >>> from oscura.filtering import LowPassFilter, design_filter
9
+ >>> from oscura.utils.filtering import LowPassFilter, design_filter
10
10
  >>> lpf = LowPassFilter(cutoff=1e6, sample_rate=10e6, order=4)
11
11
  >>> filtered_trace = lpf.apply(trace)
12
12
  >>> w, h = lpf.get_frequency_response()
13
13
  """
14
14
 
15
15
  # Import filters module as namespace for DSL compatibility
16
- from oscura.filtering import filters
17
- from oscura.filtering.base import (
16
+ from oscura.utils.filtering import filters
17
+ from oscura.utils.filtering.base import (
18
18
  Filter,
19
19
  FIRFilter,
20
20
  IIRFilter,
21
21
  )
22
- from oscura.filtering.convenience import (
22
+ from oscura.utils.filtering.convenience import (
23
23
  band_pass,
24
24
  band_stop,
25
25
  high_pass,
@@ -30,7 +30,7 @@ from oscura.filtering.convenience import (
30
30
  notch_filter,
31
31
  savgol_filter,
32
32
  )
33
- from oscura.filtering.design import (
33
+ from oscura.utils.filtering.design import (
34
34
  BandPassFilter,
35
35
  BandStopFilter,
36
36
  BesselFilter,
@@ -43,7 +43,7 @@ from oscura.filtering.design import (
43
43
  design_filter,
44
44
  design_filter_spec,
45
45
  )
46
- from oscura.filtering.introspection import (
46
+ from oscura.utils.filtering.introspection import (
47
47
  FilterIntrospection,
48
48
  plot_bode,
49
49
  plot_impulse,
@@ -453,7 +453,7 @@ class FIRFilter(Filter):
453
453
  # Check symmetry
454
454
  symmetric = np.allclose(self._coeffs, self._coeffs[::-1])
455
455
  antisymmetric = np.allclose(self._coeffs, -self._coeffs[::-1])
456
- return symmetric or antisymmetric # type: ignore[no-any-return]
456
+ return symmetric or antisymmetric
457
457
 
458
458
  def apply(
459
459
  self,
@@ -5,7 +5,7 @@ moving average, median filter, Savitzky-Golay smoothing, and matched
5
5
  filtering.
6
6
 
7
7
  Example:
8
- >>> from oscura.filtering.convenience import low_pass, moving_average
8
+ >>> from oscura.utils.filtering.convenience import low_pass, moving_average
9
9
  >>> filtered = low_pass(trace, cutoff=1e6)
10
10
  >>> smoothed = moving_average(trace, window_size=11)
11
11
  """
@@ -19,7 +19,7 @@ from scipy import ndimage, signal
19
19
 
20
20
  from oscura.core.exceptions import AnalysisError
21
21
  from oscura.core.types import WaveformTrace
22
- from oscura.filtering.design import (
22
+ from oscura.utils.filtering.design import (
23
23
  BandPassFilter,
24
24
  BandStopFilter,
25
25
  HighPassFilter,