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
@@ -52,17 +52,9 @@ from oscura.analyzers.digital.edges import (
52
52
  from oscura.analyzers.digital.extraction import (
53
53
  LOGIC_FAMILIES,
54
54
  detect_edges,
55
- detect_logic_family,
56
- detect_open_collector,
57
55
  get_logic_threshold,
58
56
  to_digital,
59
57
  )
60
- from oscura.analyzers.digital.ic_database import (
61
- IC_DATABASE,
62
- ICTiming,
63
- identify_ic,
64
- validate_ic_timing,
65
- )
66
58
  from oscura.analyzers.digital.quality import (
67
59
  Glitch,
68
60
  NoiseMarginResult,
@@ -105,37 +97,13 @@ from oscura.analyzers.digital.timing import (
105
97
  skew,
106
98
  slew_rate,
107
99
  )
108
- from oscura.analyzers.digital.timing_paths import (
109
- ICStage,
110
- SetupHoldAnalysis,
111
- TimingPathResult,
112
- analyze_setup_hold,
113
- analyze_timing_path,
114
- calculate_timing_budget,
115
- find_critical_paths,
116
- )
117
- from oscura.analyzers.digital.vintage import (
118
- REPLACEMENT_DATABASE,
119
- analyze_vintage_logic,
120
- )
121
- from oscura.analyzers.digital.vintage_result import (
122
- BOMEntry,
123
- ICIdentificationResult,
124
- ModernReplacementIC,
125
- VintageLogicAnalysisResult,
126
- )
127
100
 
128
101
  __all__ = [
129
- # IC Database
130
- "IC_DATABASE",
131
102
  # Extraction
132
103
  "LOGIC_FAMILIES",
133
- "REPLACEMENT_DATABASE",
134
104
  "AdaptiveThresholdResult",
135
105
  # Adaptive Thresholds (RE-THR-001)
136
106
  "AdaptiveThresholder",
137
- # Vintage Logic Analysis
138
- "BOMEntry",
139
107
  # Clock Recovery (DSP-002)
140
108
  "BaudRateResult",
141
109
  # Bus Decoding (DSP-003)
@@ -157,10 +125,6 @@ __all__ = [
157
125
  "EdgeTimingViolation",
158
126
  # Quality
159
127
  "Glitch",
160
- "ICIdentificationResult",
161
- "ICStage",
162
- "ICTiming",
163
- "ModernReplacementIC",
164
128
  # Multi-Level Logic (RE-THR-002)
165
129
  "MultiLevelDetector",
166
130
  "MultiLevelResult",
@@ -168,25 +132,18 @@ __all__ = [
168
132
  # Signal Quality (DSP-005)
169
133
  "NoiseMargins",
170
134
  "ParallelBusConfig",
171
- "SetupHoldAnalysis",
172
135
  "SignalIntegrityReport",
173
136
  "SignalQualityAnalyzer",
174
137
  "SimpleQualityMetrics",
175
138
  "ThresholdConfig",
176
139
  "TimingConstraint",
177
- "TimingPathResult",
178
140
  "TimingViolation",
179
141
  "TransitionMetrics",
180
- "VintageLogicAnalysisResult",
181
142
  "Violation",
182
143
  "align_by_trigger",
183
- "analyze_setup_hold",
184
144
  "analyze_signal_integrity",
185
- "analyze_timing_path",
186
- "analyze_vintage_logic",
187
145
  "apply_adaptive_threshold",
188
146
  "calculate_threshold_snr",
189
- "calculate_timing_budget",
190
147
  "check_timing_constraints",
191
148
  "classify_edge_quality",
192
149
  "correlate_channels",
@@ -196,14 +153,10 @@ __all__ = [
196
153
  "detect_edges",
197
154
  "detect_edges_advanced",
198
155
  "detect_glitches",
199
- "detect_logic_family",
200
156
  "detect_multi_level",
201
- "detect_open_collector",
202
157
  "detect_violations",
203
- "find_critical_paths",
204
158
  "get_logic_threshold",
205
159
  "hold_time",
206
- "identify_ic",
207
160
  "interpolate_edge_time",
208
161
  "measure_clock_jitter",
209
162
  "measure_edge_timing",
@@ -221,5 +174,4 @@ __all__ = [
221
174
  "skew",
222
175
  "slew_rate",
223
176
  "to_digital",
224
- "validate_ic_timing",
225
177
  ]
@@ -24,6 +24,7 @@ from typing import TYPE_CHECKING, Literal
24
24
  import numpy as np
25
25
 
26
26
  from oscura.core.memoize import memoize_analysis
27
+ from oscura.core.numba_backend import njit
27
28
 
28
29
  if TYPE_CHECKING:
29
30
  from numpy.typing import NDArray
@@ -118,11 +119,19 @@ def detect_edges(
118
119
  threshold: float | Literal["auto"] = "auto",
119
120
  hysteresis: float = 0.0,
120
121
  sample_rate: float = 1.0,
122
+ use_numba: bool = True,
121
123
  ) -> list[Edge]:
122
124
  """Detect signal edges with configurable threshold.
123
125
 
124
126
  Detects rising and/or falling edges in a digital or analog signal with
125
- optional hysteresis for noise immunity.
127
+ optional hysteresis for noise immunity. Uses Numba JIT compilation for
128
+ 15-30x speedup on large signals (>1000 samples).
129
+
130
+ Performance characteristics:
131
+ - Small signals (<1000 samples): Pure Python (no overhead)
132
+ - Large signals (>=1000 samples): Numba JIT (15-30x faster)
133
+ - First Numba call: ~100-200ms compilation overhead (cached)
134
+ - Subsequent calls: <1ms for 100k samples
126
135
 
127
136
  Args:
128
137
  trace: Input signal trace (analog or digital).
@@ -130,6 +139,7 @@ def detect_edges(
130
139
  threshold: Detection threshold. 'auto' computes from signal midpoint.
131
140
  hysteresis: Hysteresis amount for noise immunity (signal units).
132
141
  sample_rate: Sample rate in Hz for time calculation.
142
+ use_numba: Use Numba JIT acceleration for large signals (default: True).
133
143
 
134
144
  Returns:
135
145
  List of Edge objects with detected edges.
@@ -139,93 +149,343 @@ def detect_edges(
139
149
  >>> edges = detect_edges(signal, edge_type='rising')
140
150
  >>> len(edges)
141
151
  1
152
+
153
+ Note:
154
+ Numba JIT compilation provides significant speedup for repeated edge
155
+ detection on large signals. The compilation overhead (~100-200ms) is
156
+ amortized across subsequent calls due to caching.
142
157
  """
143
158
  if len(trace) < 2:
144
159
  return []
145
160
 
146
- trace = np.asarray(trace, dtype=np.float64)
147
-
148
- # Compute threshold if auto
149
- thresh_val: float
150
- if threshold == "auto":
151
- thresh_val = float((np.max(trace) + np.min(trace)) / 2.0)
161
+ # Handle WaveformTrace objects by extracting data
162
+ if hasattr(trace, "data"):
163
+ trace_data: NDArray[np.float64] = np.asarray(trace.data, dtype=np.float64)
152
164
  else:
153
- thresh_val = threshold
165
+ trace_data = np.asarray(trace, dtype=np.float64)
166
+ thresh_val = _compute_threshold(trace_data, threshold)
167
+ thresh_high, thresh_low = _apply_hysteresis(thresh_val, hysteresis)
168
+ time_base = 1.0 / sample_rate
154
169
 
155
- # Apply hysteresis if specified
156
- if hysteresis > 0:
157
- thresh_high = thresh_val + hysteresis / 2.0
158
- thresh_low = thresh_val - hysteresis / 2.0
159
- else:
160
- thresh_high = thresh_val
161
- thresh_low = thresh_val
170
+ # Use Numba for large signals to achieve 15-30x speedup
171
+ if use_numba and len(trace_data) >= 1000:
172
+ detect_rising = edge_type in ["rising", "both"]
173
+ detect_falling = edge_type in ["falling", "both"]
162
174
 
163
- edges: list[Edge] = []
164
- time_base = 1.0 / sample_rate
175
+ edge_indices, edge_types = _find_edges_numba(
176
+ trace_data, thresh_high, thresh_low, detect_rising, detect_falling
177
+ )
178
+
179
+ # Build Edge objects from Numba results
180
+ edges: list[Edge] = []
181
+ for idx, is_rising in zip(edge_indices, edge_types, strict=True):
182
+ i = int(idx)
183
+ prev_val = trace_data[i - 1] if i > 0 else trace_data[i]
184
+ curr_val = trace_data[i]
185
+ edge_type_str: Literal["rising", "falling"] = "rising" if is_rising else "falling"
186
+
187
+ edge = _create_edge(
188
+ trace_data, i, edge_type_str, prev_val, curr_val, time_base, sample_rate
189
+ )
190
+ edges.append(edge)
165
191
 
166
- # State machine for hysteresis
167
- state = trace[0] > thresh_val # Initial state
192
+ return edges
168
193
 
169
- for i in range(1, len(trace)):
170
- prev_val = trace[i - 1]
171
- curr_val = trace[i]
194
+ # Fallback to pure Python for small signals or when Numba disabled
195
+ edges = []
196
+ state = trace_data[0] > thresh_val
197
+
198
+ for i in range(1, len(trace_data)):
199
+ prev_val, curr_val = trace_data[i - 1], trace_data[i]
172
200
 
173
- # Detect transitions with hysteresis
174
201
  if not state and curr_val > thresh_high:
175
- # Rising edge
176
202
  if edge_type in ["rising", "both"]:
177
- # Interpolate edge time
178
- interp_time = interpolate_edge_time(trace, i - 1, method="linear")
179
- time = (i - 1 + interp_time) * time_base
180
-
181
- # Calculate edge properties
182
- amplitude = curr_val - prev_val
183
- slew_rate = amplitude * sample_rate
184
-
185
- # Classify quality (simple heuristic)
186
- quality = classify_edge_quality(trace, i, sample_rate)
187
-
188
- edges.append(
189
- Edge(
190
- sample_index=i,
191
- time=time,
192
- edge_type="rising",
193
- amplitude=abs(amplitude),
194
- slew_rate=slew_rate,
195
- quality=quality,
196
- )
203
+ edge = _create_edge(
204
+ trace_data, i, "rising", prev_val, curr_val, time_base, sample_rate
197
205
  )
206
+ edges.append(edge)
198
207
  state = True
199
208
 
200
209
  elif state and curr_val < thresh_low:
201
- # Falling edge
202
210
  if edge_type in ["falling", "both"]:
203
- # Interpolate edge time
204
- interp_time = interpolate_edge_time(trace, i - 1, method="linear")
205
- time = (i - 1 + interp_time) * time_base
206
-
207
- # Calculate edge properties
208
- amplitude = prev_val - curr_val
209
- slew_rate = -amplitude * sample_rate
210
-
211
- # Classify quality (simple heuristic)
212
- quality = classify_edge_quality(trace, i, sample_rate)
213
-
214
- edges.append(
215
- Edge(
216
- sample_index=i,
217
- time=time,
218
- edge_type="falling",
219
- amplitude=abs(amplitude),
220
- slew_rate=slew_rate,
221
- quality=quality,
222
- )
211
+ edge = _create_edge(
212
+ trace_data, i, "falling", prev_val, curr_val, time_base, sample_rate
223
213
  )
214
+ edges.append(edge)
224
215
  state = False
225
216
 
226
217
  return edges
227
218
 
228
219
 
220
+ def _compute_threshold(trace: NDArray[np.float64], threshold: float | Literal["auto"]) -> float:
221
+ """Compute detection threshold.
222
+
223
+ Args:
224
+ trace: Signal trace.
225
+ threshold: Threshold value or "auto".
226
+
227
+ Returns:
228
+ Threshold value.
229
+ """
230
+ if threshold == "auto":
231
+ return float((np.max(trace) + np.min(trace)) / 2.0)
232
+ else:
233
+ return threshold
234
+
235
+
236
+ def _apply_hysteresis(thresh_val: float, hysteresis: float) -> tuple[float, float]:
237
+ """Apply hysteresis to threshold.
238
+
239
+ Args:
240
+ thresh_val: Base threshold.
241
+ hysteresis: Hysteresis amount.
242
+
243
+ Returns:
244
+ Tuple of (thresh_high, thresh_low).
245
+ """
246
+ if hysteresis > 0:
247
+ return thresh_val + hysteresis / 2.0, thresh_val - hysteresis / 2.0
248
+ else:
249
+ return thresh_val, thresh_val
250
+
251
+
252
+ @njit(cache=True) # type: ignore[untyped-decorator]
253
+ def _find_edges_numba(
254
+ data: NDArray[np.float64],
255
+ thresh_high: float,
256
+ thresh_low: float,
257
+ detect_rising: bool,
258
+ detect_falling: bool,
259
+ ) -> tuple[NDArray[np.int64], NDArray[np.bool_]]:
260
+ """Find edges with hysteresis using Numba JIT compilation.
261
+
262
+ This function provides 15-30x speedup vs pure Python loops for edge detection
263
+ on large signals. The first call includes ~100-200ms compilation overhead,
264
+ but subsequent calls with cached compilation are extremely fast.
265
+
266
+ Performance characteristics:
267
+ - First call: ~100-200ms compilation + execution
268
+ - Subsequent calls: <1ms for 100k samples (15-30x faster than Python)
269
+ - Memory efficient: O(n) space for edge storage
270
+ - Parallel execution: Uses single thread (hysteresis requires sequential state)
271
+
272
+ Args:
273
+ data: Input signal trace (must be contiguous float64 array).
274
+ thresh_high: Upper threshold for hysteresis (rising edge detection).
275
+ thresh_low: Lower threshold for hysteresis (falling edge detection).
276
+ detect_rising: Whether to detect rising edges.
277
+ detect_falling: Whether to detect falling edges.
278
+
279
+ Returns:
280
+ Tuple of (edge_indices, edge_types) where:
281
+ - edge_indices: Array of sample indices where edges occur
282
+ - edge_types: Boolean array (True=rising, False=falling)
283
+
284
+ Example:
285
+ >>> signal = np.array([0, 0, 1, 1, 0, 0], dtype=np.float64)
286
+ >>> indices, types = _find_edges_numba(signal, 0.5, 0.5, True, True)
287
+ >>> indices # [2, 4]
288
+ >>> types # [True, False]
289
+ """
290
+ n = len(data)
291
+ if n < 2:
292
+ return np.empty(0, dtype=np.int64), np.empty(0, dtype=np.bool_)
293
+
294
+ # Pre-allocate arrays for worst case (all samples are edges)
295
+ edge_indices = np.empty(n, dtype=np.int64)
296
+ edge_types = np.empty(n, dtype=np.bool_)
297
+ edge_count = 0
298
+
299
+ # Initial state based on first sample
300
+ state = data[0] > (thresh_high + thresh_low) / 2.0
301
+
302
+ for i in range(1, n):
303
+ curr_val = data[i]
304
+
305
+ if not state and curr_val > thresh_high:
306
+ # Rising edge detected
307
+ if detect_rising:
308
+ edge_indices[edge_count] = i
309
+ edge_types[edge_count] = True # Rising
310
+ edge_count += 1
311
+ state = True
312
+
313
+ elif state and curr_val < thresh_low:
314
+ # Falling edge detected
315
+ if detect_falling:
316
+ edge_indices[edge_count] = i
317
+ edge_types[edge_count] = False # Falling
318
+ edge_count += 1
319
+ state = False
320
+
321
+ # Return trimmed arrays
322
+ return edge_indices[:edge_count], edge_types[:edge_count]
323
+
324
+
325
+ @njit(cache=True) # type: ignore[untyped-decorator]
326
+ def _measure_pulse_widths_numba(
327
+ edge_indices: NDArray[np.int64],
328
+ edge_types: NDArray[np.bool_],
329
+ time_base: float,
330
+ ) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
331
+ """Measure pulse widths between edges using Numba JIT.
332
+
333
+ Computes high pulse widths (rising to falling) and low pulse widths
334
+ (falling to rising) from detected edges. Provides 10-20x speedup vs
335
+ Python loops for large edge lists.
336
+
337
+ Performance characteristics:
338
+ - First call: ~50-100ms compilation overhead
339
+ - Subsequent calls: <0.5ms for 10k edges
340
+ - Memory: O(n) space for pulse arrays
341
+
342
+ Args:
343
+ edge_indices: Array of edge sample indices.
344
+ edge_types: Boolean array (True=rising, False=falling).
345
+ time_base: Time per sample (1 / sample_rate).
346
+
347
+ Returns:
348
+ Tuple of (high_widths, low_widths) where:
349
+ - high_widths: Array of high pulse widths in seconds
350
+ - low_widths: Array of low pulse widths in seconds
351
+
352
+ Example:
353
+ >>> indices = np.array([100, 200, 300, 400], dtype=np.int64)
354
+ >>> types = np.array([True, False, True, False])
355
+ >>> high, low = _measure_pulse_widths_numba(indices, types, 1e-6)
356
+ >>> high # [100e-6, 100e-6] (200-100, 400-300)
357
+ >>> low # [100e-6] (300-200)
358
+ """
359
+ n_edges = len(edge_indices)
360
+ if n_edges < 2:
361
+ return np.empty(0, dtype=np.float64), np.empty(0, dtype=np.float64)
362
+
363
+ # Pre-allocate arrays
364
+ high_widths = np.empty(n_edges, dtype=np.float64)
365
+ low_widths = np.empty(n_edges, dtype=np.float64)
366
+ high_count = 0
367
+ low_count = 0
368
+
369
+ for i in range(n_edges - 1):
370
+ if edge_types[i]: # Rising edge
371
+ # Look for next falling edge
372
+ if not edge_types[i + 1]:
373
+ width = (edge_indices[i + 1] - edge_indices[i]) * time_base
374
+ high_widths[high_count] = width
375
+ high_count += 1
376
+ else: # Falling edge
377
+ # Look for next rising edge
378
+ if edge_types[i + 1]:
379
+ width = (edge_indices[i + 1] - edge_indices[i]) * time_base
380
+ low_widths[low_count] = width
381
+ low_count += 1
382
+
383
+ return high_widths[:high_count], low_widths[:low_count]
384
+
385
+
386
+ @njit(cache=True) # type: ignore[untyped-decorator]
387
+ def _compute_slew_rates_numba(
388
+ data: NDArray[np.float64],
389
+ edge_indices: NDArray[np.int64],
390
+ edge_types: NDArray[np.bool_],
391
+ sample_rate: float,
392
+ ) -> NDArray[np.float64]:
393
+ """Compute slew rates at edges using Numba JIT.
394
+
395
+ Calculates the rate of voltage change (dV/dt) at each edge by examining
396
+ the samples before and after the edge. Provides 15-25x speedup vs Python.
397
+
398
+ Performance characteristics:
399
+ - First call: ~50-100ms compilation overhead
400
+ - Subsequent calls: <0.3ms for 10k edges
401
+ - Memory: O(n) space for slew rate array
402
+
403
+ Args:
404
+ data: Input signal trace.
405
+ edge_indices: Array of edge sample indices.
406
+ edge_types: Boolean array (True=rising, False=falling).
407
+ sample_rate: Sample rate in Hz.
408
+
409
+ Returns:
410
+ Array of slew rates in signal units per second (V/s).
411
+ Positive for rising edges, negative for falling edges.
412
+
413
+ Example:
414
+ >>> signal = np.array([0, 0, 1, 1, 0, 0], dtype=np.float64)
415
+ >>> indices = np.array([2, 4], dtype=np.int64)
416
+ >>> types = np.array([True, False])
417
+ >>> slew = _compute_slew_rates_numba(signal, indices, types, 1e6)
418
+ >>> slew # [1e6, -1e6] (1V in 1us)
419
+ """
420
+ n_edges = len(edge_indices)
421
+ slew_rates = np.empty(n_edges, dtype=np.float64)
422
+
423
+ for i in range(n_edges):
424
+ idx = edge_indices[i]
425
+
426
+ if idx < 1 or idx >= len(data):
427
+ slew_rates[i] = 0.0
428
+ continue
429
+
430
+ # Compute amplitude change
431
+ prev_val = data[idx - 1]
432
+ curr_val = data[idx]
433
+ amplitude = abs(curr_val - prev_val)
434
+
435
+ # Compute slew rate
436
+ if edge_types[i]: # Rising
437
+ slew_rates[i] = amplitude * sample_rate
438
+ else: # Falling
439
+ slew_rates[i] = -amplitude * sample_rate
440
+
441
+ return slew_rates
442
+
443
+
444
+ def _create_edge(
445
+ trace: NDArray[np.float64],
446
+ index: int,
447
+ edge_type: Literal["rising", "falling"],
448
+ prev_val: float,
449
+ curr_val: float,
450
+ time_base: float,
451
+ sample_rate: float,
452
+ ) -> Edge:
453
+ """Create edge object from detected transition.
454
+
455
+ Args:
456
+ trace: Signal trace.
457
+ index: Sample index.
458
+ edge_type: Edge type.
459
+ prev_val: Previous value.
460
+ curr_val: Current value.
461
+ time_base: Time per sample.
462
+ sample_rate: Sample rate.
463
+
464
+ Returns:
465
+ Edge object.
466
+ """
467
+ interp_time = interpolate_edge_time(trace, index - 1, method="linear")
468
+ time = (index - 1 + interp_time) * time_base
469
+
470
+ if edge_type == "rising":
471
+ amplitude = curr_val - prev_val
472
+ slew_rate = amplitude * sample_rate
473
+ else: # falling
474
+ amplitude = prev_val - curr_val
475
+ slew_rate = -amplitude * sample_rate
476
+
477
+ quality = classify_edge_quality(trace, index, sample_rate)
478
+
479
+ return Edge(
480
+ sample_index=index,
481
+ time=time,
482
+ edge_type=edge_type,
483
+ amplitude=abs(amplitude),
484
+ slew_rate=slew_rate,
485
+ quality=quality,
486
+ )
487
+
488
+
229
489
  def interpolate_edge_time(
230
490
  trace: NDArray[np.float64], sample_index: int, method: Literal["linear", "quadratic"] = "linear"
231
491
  ) -> float: