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

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