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

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