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
@@ -9,7 +9,7 @@ non-expert users analyze their signals with intelligent recommendations.
9
9
  - Result interpretation
10
10
 
11
11
  Example:
12
- >>> from oscura.onboarding import run_wizard
12
+ >>> from oscura.cli.onboarding import run_wizard
13
13
  >>> run_wizard(trace)
14
14
  Analysis Wizard
15
15
  Step 1: What type of signal is this?
@@ -403,45 +403,64 @@ class AnalysisWizard:
403
403
  """Generate a human-readable summary of the analysis."""
404
404
  lines = ["Analysis Summary:"]
405
405
 
406
+ self._add_signal_type_summary(lines)
407
+ self._add_frequency_summary(lines)
408
+ self._add_rise_time_summary(lines)
409
+ self._add_thd_summary(lines)
410
+ self._add_snr_summary(lines)
411
+
412
+ self.result.summary = "\n".join(lines)
413
+
414
+ def _add_signal_type_summary(self, lines: list[str]) -> None:
415
+ """Add signal type to summary."""
406
416
  if "signal_type" in self.result.measurements:
407
417
  lines.append(f" Signal type: {self.result.measurements['signal_type']}")
408
418
 
409
- if "frequency" in self.result.measurements:
410
- freq = self.result.measurements["frequency"]
411
- # Handle dict format from measure()
412
- if isinstance(freq, dict):
413
- freq = freq.get("value", 0)
419
+ def _add_frequency_summary(self, lines: list[str]) -> None:
420
+ """Add frequency to summary with appropriate units."""
421
+ if "frequency" not in self.result.measurements:
422
+ return
414
423
 
415
- if freq >= 1e6:
416
- lines.append(f" Frequency: {freq / 1e6:.3f} MHz")
417
- elif freq >= 1e3:
418
- lines.append(f" Frequency: {freq / 1e3:.3f} kHz")
419
- else:
420
- lines.append(f" Frequency: {freq:.1f} Hz")
421
-
422
- if "rise_time" in self.result.measurements:
423
- rt = self.result.measurements["rise_time"]
424
- # Handle dict format from measure()
425
- if isinstance(rt, dict):
426
- rt = rt.get("value", 0)
427
-
428
- lines.append(f" Rise time: {rt * 1e9:.2f} ns")
429
-
430
- if "thd" in self.result.measurements:
431
- thd = self.result.measurements["thd"]
432
- # Handle dict format
433
- if isinstance(thd, dict):
434
- thd = thd.get("value", 0)
435
- lines.append(f" THD: {thd:.1f} dB")
436
-
437
- if "snr" in self.result.measurements:
438
- snr = self.result.measurements["snr"]
439
- # Handle dict format
440
- if isinstance(snr, dict):
441
- snr = snr.get("value", 0)
442
- lines.append(f" SNR: {snr:.1f} dB")
424
+ freq = self._extract_value(self.result.measurements["frequency"])
425
+ if freq >= 1e6:
426
+ lines.append(f" Frequency: {freq / 1e6:.3f} MHz")
427
+ elif freq >= 1e3:
428
+ lines.append(f" Frequency: {freq / 1e3:.3f} kHz")
429
+ else:
430
+ lines.append(f" Frequency: {freq:.1f} Hz")
431
+
432
+ def _add_rise_time_summary(self, lines: list[str]) -> None:
433
+ """Add rise time to summary."""
434
+ if "rise_time" not in self.result.measurements:
435
+ return
443
436
 
444
- self.result.summary = "\n".join(lines)
437
+ rt = self._extract_value(self.result.measurements["rise_time"])
438
+ lines.append(f" Rise time: {rt * 1e9:.2f} ns")
439
+
440
+ def _add_thd_summary(self, lines: list[str]) -> None:
441
+ """Add THD to summary."""
442
+ if "thd" not in self.result.measurements:
443
+ return
444
+
445
+ thd = self._extract_value(self.result.measurements["thd"])
446
+ lines.append(f" THD: {thd:.1f} dB")
447
+
448
+ def _add_snr_summary(self, lines: list[str]) -> None:
449
+ """Add SNR to summary."""
450
+ if "snr" not in self.result.measurements:
451
+ return
452
+
453
+ snr = self._extract_value(self.result.measurements["snr"])
454
+ lines.append(f" SNR: {snr:.1f} dB")
455
+
456
+ @staticmethod
457
+ def _extract_value(measurement: Any) -> float:
458
+ """Extract numeric value from measurement (handles dict format)."""
459
+ if isinstance(measurement, dict):
460
+ value: float = float(measurement.get("value", 0))
461
+ return value
462
+ result: float = float(measurement)
463
+ return result
445
464
 
446
465
 
447
466
  def run_wizard(trace: Any, interactive: bool = True) -> WizardResult:
@@ -458,7 +477,7 @@ def run_wizard(trace: Any, interactive: bool = True) -> WizardResult:
458
477
 
459
478
  Example:
460
479
  >>> import oscura as osc
461
- >>> from oscura.onboarding import run_wizard
480
+ >>> from oscura.cli.onboarding import run_wizard
462
481
  >>> trace = osc.load("signal.csv")
463
482
  >>> result = run_wizard(trace)
464
483
  """
oscura/cli/progress.py ADDED
@@ -0,0 +1,147 @@
1
+ """Progress reporting utilities for CLI commands.
2
+
3
+ Provides progress bars, status messages, and ETA calculations for long-running
4
+ operations.
5
+
6
+
7
+ Example:
8
+ >>> progress = ProgressReporter(stages=3)
9
+ >>> progress.start_stage("Loading data")
10
+ >>> # ... work ...
11
+ >>> progress.complete_stage()
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import sys
17
+ import time
18
+ from typing import Any
19
+
20
+ try:
21
+ from tqdm import tqdm
22
+
23
+ TQDM_AVAILABLE = True
24
+ except ImportError:
25
+ TQDM_AVAILABLE = False
26
+
27
+
28
+ class ProgressReporter:
29
+ """Progress reporter for multi-stage operations.
30
+
31
+ Displays progress bars (if tqdm available) or status messages for
32
+ long-running CLI operations.
33
+
34
+ Args:
35
+ quiet: If True, suppress all output.
36
+ stages: Number of stages in operation.
37
+ use_tqdm: Force tqdm usage (default: auto-detect).
38
+
39
+ Example:
40
+ >>> reporter = ProgressReporter(stages=3)
41
+ >>> reporter.start_stage("Stage 1")
42
+ >>> time.sleep(1)
43
+ >>> reporter.complete_stage()
44
+ >>> reporter.start_stage("Stage 2")
45
+ >>> time.sleep(1)
46
+ >>> reporter.complete_stage()
47
+ >>> reporter.finish()
48
+ """
49
+
50
+ def __init__(
51
+ self,
52
+ quiet: bool = False,
53
+ stages: int = 1,
54
+ use_tqdm: bool | None = None,
55
+ ) -> None:
56
+ """Initialize progress reporter.
57
+
58
+ Args:
59
+ quiet: Suppress all output.
60
+ stages: Total number of stages.
61
+ use_tqdm: Force tqdm usage (None = auto-detect).
62
+ """
63
+ self.quiet = quiet
64
+ self.total_stages = stages
65
+ self.current_stage = 0
66
+ self.stage_name = ""
67
+ self.start_time = time.time()
68
+ self.stage_start = time.time()
69
+
70
+ # Determine if we can use tqdm
71
+ if use_tqdm is None:
72
+ self.use_tqdm = TQDM_AVAILABLE and not quiet and sys.stdout.isatty()
73
+ else:
74
+ self.use_tqdm = use_tqdm and TQDM_AVAILABLE and not quiet
75
+
76
+ self.pbar: Any = None
77
+ if self.use_tqdm:
78
+ self.pbar = tqdm(total=stages, desc="Progress", unit="stage")
79
+
80
+ def start_stage(self, name: str) -> None:
81
+ """Start a new stage.
82
+
83
+ Args:
84
+ name: Stage name/description.
85
+ """
86
+ self.current_stage += 1
87
+ self.stage_name = name
88
+ self.stage_start = time.time()
89
+
90
+ if not self.quiet:
91
+ if self.use_tqdm and self.pbar:
92
+ self.pbar.set_description(f"{name}")
93
+ else:
94
+ timestamp = time.strftime("%H:%M:%S")
95
+ print(
96
+ f"[{timestamp}] [{self.current_stage}/{self.total_stages}] {name}...",
97
+ file=sys.stderr,
98
+ )
99
+
100
+ def complete_stage(self) -> None:
101
+ """Mark current stage as complete."""
102
+ stage_duration = time.time() - self.stage_start
103
+
104
+ if not self.quiet:
105
+ if self.use_tqdm and self.pbar:
106
+ self.pbar.update(1)
107
+ else:
108
+ timestamp = time.strftime("%H:%M:%S")
109
+ print(
110
+ f"[{timestamp}] {self.stage_name} completed ({stage_duration:.1f}s)",
111
+ file=sys.stderr,
112
+ )
113
+
114
+ def update_progress(self, current: int, total: int, message: str = "") -> None:
115
+ """Update progress within current stage.
116
+
117
+ Args:
118
+ current: Current progress value.
119
+ total: Total progress value.
120
+ message: Optional progress message.
121
+ """
122
+ if not self.quiet and not self.use_tqdm:
123
+ pct = (current / total * 100) if total > 0 else 0
124
+ msg = f"{message} " if message else ""
125
+ print(f" {msg}{current}/{total} ({pct:.1f}%)", file=sys.stderr, end="\r")
126
+
127
+ def finish(self) -> None:
128
+ """Finish progress reporting."""
129
+ total_duration = time.time() - self.start_time
130
+
131
+ if not self.quiet:
132
+ if self.use_tqdm and self.pbar:
133
+ self.pbar.close()
134
+ else:
135
+ timestamp = time.strftime("%H:%M:%S")
136
+ print(
137
+ f"[{timestamp}] All stages complete ({total_duration:.1f}s total)",
138
+ file=sys.stderr,
139
+ )
140
+
141
+ def __enter__(self) -> ProgressReporter:
142
+ """Context manager entry."""
143
+ return self
144
+
145
+ def __exit__(self, *args: Any) -> None:
146
+ """Context manager exit."""
147
+ self.finish()
oscura/cli/shell.py CHANGED
@@ -45,153 +45,177 @@ def get_oscura_namespace() -> dict[str, Any]:
45
45
  """
46
46
  namespace: dict[str, Any] = {}
47
47
 
48
- # Core imports
48
+ _import_core_oscura(namespace)
49
+ _import_protocols(namespace)
50
+ _import_discovery(namespace)
51
+ _import_common_utilities(namespace)
52
+
53
+ return namespace
54
+
55
+
56
+ def _import_core_oscura(namespace: dict[str, Any]) -> None:
57
+ """Import core Oscura functions and types."""
49
58
  try:
50
59
  import oscura as osc
51
60
 
52
61
  namespace["osc"] = osc
62
+ imports = _get_oscura_imports()
63
+ namespace.update(_build_namespace_dict(imports))
64
+ except ImportError as e:
65
+ print(f"Warning: Could not import Oscura: {e}")
66
+
67
+
68
+ def _get_oscura_imports() -> dict[str, Any]:
69
+ """Get all Oscura imports as dictionary.
53
70
 
54
- # Auto-import commonly used functions at top level
55
- from oscura import (
56
- DigitalTrace,
57
- ProtocolPacket,
58
- TraceMetadata,
59
- # Core types
60
- WaveformTrace,
61
- # Math
62
- add,
63
- amplitude,
64
- band_pass,
65
- band_stop,
66
- # Statistics
67
- basic_stats,
68
- detect_edges,
69
- differentiate,
70
- divide,
71
- duty_cycle,
72
- enob,
73
- fall_time,
74
- # Spectral
75
- fft,
76
- frequency,
77
- get_supported_formats,
78
- high_pass,
79
- histogram,
80
- integrate,
81
- # Loaders
82
- load,
83
- # Filtering
84
- low_pass,
85
- mean,
86
- measure,
87
- multiply,
88
- overshoot,
89
- percentiles,
90
- period,
91
- psd,
92
- pulse_width,
93
- # Measurements
94
- rise_time,
95
- rms,
96
- sfdr,
97
- sinad,
98
- snr,
99
- spectrogram,
100
- subtract,
101
- thd,
102
- # Digital
103
- to_digital,
104
- undershoot,
71
+ Returns:
72
+ Dictionary of imported symbols.
73
+ """
74
+ from oscura import (
75
+ DigitalTrace,
76
+ ProtocolPacket,
77
+ TraceMetadata,
78
+ WaveformTrace,
79
+ add,
80
+ amplitude,
81
+ band_pass,
82
+ band_stop,
83
+ basic_stats,
84
+ detect_edges,
85
+ differentiate,
86
+ divide,
87
+ duty_cycle,
88
+ enob,
89
+ fall_time,
90
+ fft,
91
+ frequency,
92
+ get_supported_formats,
93
+ high_pass,
94
+ histogram,
95
+ integrate,
96
+ load,
97
+ low_pass,
98
+ mean,
99
+ measure,
100
+ multiply,
101
+ overshoot,
102
+ percentiles,
103
+ period,
104
+ psd,
105
+ pulse_width,
106
+ rise_time,
107
+ rms,
108
+ sfdr,
109
+ sinad,
110
+ snr,
111
+ spectrogram,
112
+ subtract,
113
+ thd,
114
+ to_digital,
115
+ undershoot,
116
+ )
117
+
118
+ return {
119
+ "WaveformTrace": WaveformTrace,
120
+ "DigitalTrace": DigitalTrace,
121
+ "TraceMetadata": TraceMetadata,
122
+ "ProtocolPacket": ProtocolPacket,
123
+ "load": load,
124
+ "get_supported_formats": get_supported_formats,
125
+ "rise_time": rise_time,
126
+ "fall_time": fall_time,
127
+ "frequency": frequency,
128
+ "period": period,
129
+ "amplitude": amplitude,
130
+ "rms": rms,
131
+ "mean": mean,
132
+ "overshoot": overshoot,
133
+ "undershoot": undershoot,
134
+ "duty_cycle": duty_cycle,
135
+ "pulse_width": pulse_width,
136
+ "measure": measure,
137
+ "fft": fft,
138
+ "psd": psd,
139
+ "thd": thd,
140
+ "snr": snr,
141
+ "sinad": sinad,
142
+ "enob": enob,
143
+ "sfdr": sfdr,
144
+ "spectrogram": spectrogram,
145
+ "to_digital": to_digital,
146
+ "detect_edges": detect_edges,
147
+ "low_pass": low_pass,
148
+ "high_pass": high_pass,
149
+ "band_pass": band_pass,
150
+ "band_stop": band_stop,
151
+ "add": add,
152
+ "subtract": subtract,
153
+ "multiply": multiply,
154
+ "divide": divide,
155
+ "differentiate": differentiate,
156
+ "integrate": integrate,
157
+ "basic_stats": basic_stats,
158
+ "histogram": histogram,
159
+ "percentiles": percentiles,
160
+ }
161
+
162
+
163
+ def _build_namespace_dict(imports: dict[str, Any]) -> dict[str, Any]:
164
+ """Build namespace dictionary from imports.
165
+
166
+ Args:
167
+ imports: Dictionary of imported symbols.
168
+
169
+ Returns:
170
+ Namespace dictionary.
171
+ """
172
+ return imports
173
+
174
+
175
+ def _import_protocols(namespace: dict[str, Any]) -> None:
176
+ """Import protocol decoders."""
177
+ try:
178
+ from oscura.analyzers.protocols import (
179
+ decode_can,
180
+ decode_i2c,
181
+ decode_spi,
182
+ decode_uart,
105
183
  )
106
184
 
107
185
  namespace.update(
108
186
  {
109
- "WaveformTrace": WaveformTrace,
110
- "DigitalTrace": DigitalTrace,
111
- "TraceMetadata": TraceMetadata,
112
- "ProtocolPacket": ProtocolPacket,
113
- "load": load,
114
- "get_supported_formats": get_supported_formats,
115
- "rise_time": rise_time,
116
- "fall_time": fall_time,
117
- "frequency": frequency,
118
- "period": period,
119
- "amplitude": amplitude,
120
- "rms": rms,
121
- "mean": mean,
122
- "overshoot": overshoot,
123
- "undershoot": undershoot,
124
- "duty_cycle": duty_cycle,
125
- "pulse_width": pulse_width,
126
- "measure": measure,
127
- "fft": fft,
128
- "psd": psd,
129
- "thd": thd,
130
- "snr": snr,
131
- "sinad": sinad,
132
- "enob": enob,
133
- "sfdr": sfdr,
134
- "spectrogram": spectrogram,
135
- "to_digital": to_digital,
136
- "detect_edges": detect_edges,
137
- "low_pass": low_pass,
138
- "high_pass": high_pass,
139
- "band_pass": band_pass,
140
- "band_stop": band_stop,
141
- "add": add,
142
- "subtract": subtract,
143
- "multiply": multiply,
144
- "divide": divide,
145
- "differentiate": differentiate,
146
- "integrate": integrate,
147
- "basic_stats": basic_stats,
148
- "histogram": histogram,
149
- "percentiles": percentiles,
187
+ "decode_uart": decode_uart,
188
+ "decode_spi": decode_spi,
189
+ "decode_i2c": decode_i2c,
190
+ "decode_can": decode_can,
150
191
  }
151
192
  )
193
+ except ImportError:
194
+ pass
152
195
 
153
- # Protocol decoders
154
- try:
155
- from oscura.analyzers.protocols import (
156
- decode_can,
157
- decode_i2c,
158
- decode_spi,
159
- decode_uart,
160
- )
161
-
162
- namespace.update(
163
- {
164
- "decode_uart": decode_uart,
165
- "decode_spi": decode_spi,
166
- "decode_i2c": decode_i2c,
167
- "decode_can": decode_can,
168
- }
169
- )
170
- except ImportError:
171
- pass
172
-
173
- # Discovery
174
- try:
175
- from oscura.discovery import (
176
- characterize_signal,
177
- decode_protocol,
178
- find_anomalies,
179
- )
180
-
181
- namespace.update(
182
- {
183
- "characterize_signal": characterize_signal,
184
- "find_anomalies": find_anomalies,
185
- "decode_protocol": decode_protocol,
186
- }
187
- )
188
- except ImportError:
189
- pass
190
196
 
191
- except ImportError as e:
192
- print(f"Warning: Could not import Oscura: {e}")
197
+ def _import_discovery(namespace: dict[str, Any]) -> None:
198
+ """Import discovery functions."""
199
+ try:
200
+ from oscura.discovery import (
201
+ characterize_signal,
202
+ decode_protocol,
203
+ find_anomalies,
204
+ )
205
+
206
+ namespace.update(
207
+ {
208
+ "characterize_signal": characterize_signal,
209
+ "find_anomalies": find_anomalies,
210
+ "decode_protocol": decode_protocol,
211
+ }
212
+ )
213
+ except ImportError:
214
+ pass
215
+
193
216
 
194
- # Common utilities
217
+ def _import_common_utilities(namespace: dict[str, Any]) -> None:
218
+ """Import common utilities like numpy and matplotlib."""
195
219
  try:
196
220
  import matplotlib.pyplot as plt
197
221
 
@@ -206,8 +230,6 @@ def get_oscura_namespace() -> dict[str, Any]:
206
230
  except ImportError:
207
231
  pass
208
232
 
209
- return namespace
210
-
211
233
 
212
234
  def setup_history() -> None:
213
235
  """Set up readline history with persistence."""