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
@@ -6,7 +6,7 @@ order calculation from specifications.
6
6
 
7
7
 
8
8
  Example:
9
- >>> from oscura.filtering.design import LowPassFilter, design_filter
9
+ >>> from oscura.utils.filtering.design import LowPassFilter, design_filter
10
10
  >>> # Simple filter creation
11
11
  >>> lpf = LowPassFilter(cutoff=1e6, sample_rate=10e6, order=4)
12
12
  >>> # Spec-based design
@@ -25,12 +25,164 @@ import numpy as np
25
25
  from scipy import signal
26
26
 
27
27
  from oscura.core.exceptions import AnalysisError
28
- from oscura.filtering.base import IIRFilter
28
+ from oscura.utils.filtering.base import IIRFilter
29
29
 
30
30
  FilterType = Literal["butterworth", "chebyshev1", "chebyshev2", "bessel", "elliptic"]
31
31
  BandType = Literal["lowpass", "highpass", "bandpass", "bandstop"]
32
32
 
33
33
 
34
+ def _normalize_cutoff(
35
+ cutoff: float | tuple[float, float],
36
+ sample_rate: float,
37
+ analog: bool,
38
+ ) -> float | tuple[float, float]:
39
+ """Normalize cutoff frequency for digital filters.
40
+
41
+ Args:
42
+ cutoff: Cutoff frequency in Hz.
43
+ sample_rate: Sample rate in Hz.
44
+ analog: If True, return as-is (analog filter).
45
+
46
+ Returns:
47
+ Normalized cutoff frequency (0-1 for digital, Hz for analog).
48
+ """
49
+ if analog:
50
+ return cutoff
51
+
52
+ nyquist = sample_rate / 2
53
+ if isinstance(cutoff, tuple):
54
+ return (cutoff[0] / nyquist, cutoff[1] / nyquist)
55
+ else:
56
+ return cutoff / nyquist
57
+
58
+
59
+ def _validate_normalized_cutoff(
60
+ Wn: float | tuple[float, float],
61
+ cutoff: float | tuple[float, float],
62
+ sample_rate: float,
63
+ analog: bool,
64
+ ) -> None:
65
+ """Validate normalized cutoff frequency is in valid range.
66
+
67
+ Args:
68
+ Wn: Normalized cutoff frequency.
69
+ cutoff: Original cutoff in Hz.
70
+ sample_rate: Sample rate in Hz.
71
+ analog: If True, skip validation (analog filter).
72
+
73
+ Raises:
74
+ AnalysisError: If cutoff is out of range.
75
+ """
76
+ if analog:
77
+ return
78
+
79
+ nyquist = sample_rate / 2
80
+ if isinstance(Wn, tuple):
81
+ if not (0 < Wn[0] < 1 and 0 < Wn[1] < 1):
82
+ raise AnalysisError(
83
+ f"Normalized cutoff must be in (0, 1), got {Wn}. "
84
+ f"Cutoff {cutoff} Hz must be less than Nyquist {nyquist} Hz."
85
+ )
86
+ elif not 0 < Wn < 1:
87
+ raise AnalysisError(
88
+ f"Normalized cutoff must be in (0, 1), got {Wn}. "
89
+ f"Cutoff {cutoff} Hz must be less than Nyquist {nyquist} Hz."
90
+ )
91
+
92
+
93
+ def _design_butterworth(
94
+ order: int,
95
+ Wn: float | tuple[float, float],
96
+ btype: BandType,
97
+ analog: bool,
98
+ output: Literal["sos", "ba"],
99
+ sample_rate: float,
100
+ ) -> IIRFilter:
101
+ """Design Butterworth filter."""
102
+ if output == "sos":
103
+ sos = signal.butter(order, Wn, btype=btype, analog=analog, output="sos")
104
+ return IIRFilter(sample_rate=sample_rate, sos=sos)
105
+ else:
106
+ b, a = signal.butter(order, Wn, btype=btype, analog=analog, output="ba")
107
+ return IIRFilter(sample_rate=sample_rate, ba=(b, a))
108
+
109
+
110
+ def _design_chebyshev1(
111
+ order: int,
112
+ ripple_db: float,
113
+ Wn: float | tuple[float, float],
114
+ btype: BandType,
115
+ analog: bool,
116
+ output: Literal["sos", "ba"],
117
+ sample_rate: float,
118
+ ) -> IIRFilter:
119
+ """Design Chebyshev Type I filter."""
120
+ if output == "sos":
121
+ sos = signal.cheby1(order, ripple_db, Wn, btype=btype, analog=analog, output="sos")
122
+ return IIRFilter(sample_rate=sample_rate, sos=sos)
123
+ else:
124
+ b, a = signal.cheby1(order, ripple_db, Wn, btype=btype, analog=analog, output="ba")
125
+ return IIRFilter(sample_rate=sample_rate, ba=(b, a))
126
+
127
+
128
+ def _design_chebyshev2(
129
+ order: int,
130
+ stopband_atten_db: float,
131
+ Wn: float | tuple[float, float],
132
+ btype: BandType,
133
+ analog: bool,
134
+ output: Literal["sos", "ba"],
135
+ sample_rate: float,
136
+ ) -> IIRFilter:
137
+ """Design Chebyshev Type II filter."""
138
+ if output == "sos":
139
+ sos = signal.cheby2(order, stopband_atten_db, Wn, btype=btype, analog=analog, output="sos")
140
+ return IIRFilter(sample_rate=sample_rate, sos=sos)
141
+ else:
142
+ b, a = signal.cheby2(order, stopband_atten_db, Wn, btype=btype, analog=analog, output="ba")
143
+ return IIRFilter(sample_rate=sample_rate, ba=(b, a))
144
+
145
+
146
+ def _design_bessel(
147
+ order: int,
148
+ Wn: float | tuple[float, float],
149
+ btype: BandType,
150
+ analog: bool,
151
+ output: Literal["sos", "ba"],
152
+ sample_rate: float,
153
+ ) -> IIRFilter:
154
+ """Design Bessel filter."""
155
+ if output == "sos":
156
+ sos = signal.bessel(order, Wn, btype=btype, analog=analog, output="sos", norm="phase")
157
+ return IIRFilter(sample_rate=sample_rate, sos=sos)
158
+ else:
159
+ b, a = signal.bessel(order, Wn, btype=btype, analog=analog, output="ba", norm="phase")
160
+ return IIRFilter(sample_rate=sample_rate, ba=(b, a))
161
+
162
+
163
+ def _design_elliptic(
164
+ order: int,
165
+ ripple_db: float,
166
+ stopband_atten_db: float,
167
+ Wn: float | tuple[float, float],
168
+ btype: BandType,
169
+ analog: bool,
170
+ output: Literal["sos", "ba"],
171
+ sample_rate: float,
172
+ ) -> IIRFilter:
173
+ """Design Elliptic filter."""
174
+ if output == "sos":
175
+ sos = signal.ellip(
176
+ order, ripple_db, stopband_atten_db, Wn, btype=btype, analog=analog, output="sos"
177
+ )
178
+ return IIRFilter(sample_rate=sample_rate, sos=sos)
179
+ else:
180
+ b, a = signal.ellip(
181
+ order, ripple_db, stopband_atten_db, Wn, btype=btype, analog=analog, output="ba"
182
+ )
183
+ return IIRFilter(sample_rate=sample_rate, ba=(b, a))
184
+
185
+
34
186
  def design_filter(
35
187
  filter_type: FilterType,
36
188
  cutoff: float | tuple[float, float],
@@ -71,27 +223,11 @@ def design_filter(
71
223
  References:
72
224
  scipy.signal.iirfilter, butter, cheby1, cheby2, ellip, bessel
73
225
  """
74
- # Normalize cutoff frequency
75
- if isinstance(cutoff, tuple):
76
- Wn = cutoff if analog else (cutoff[0] / (sample_rate / 2), cutoff[1] / (sample_rate / 2))
77
- else:
78
- Wn = cutoff if analog else cutoff / (sample_rate / 2) # type: ignore[assignment]
79
-
80
- # Validate normalized frequency
81
- if not analog:
82
- if isinstance(Wn, tuple):
83
- if not (0 < Wn[0] < 1 and 0 < Wn[1] < 1):
84
- raise AnalysisError(
85
- f"Normalized cutoff must be in (0, 1), got {Wn}. "
86
- f"Cutoff {cutoff} Hz must be less than Nyquist {sample_rate / 2} Hz."
87
- )
88
- elif not 0 < Wn < 1: # type: ignore[unreachable]
89
- raise AnalysisError(
90
- f"Normalized cutoff must be in (0, 1), got {Wn}. "
91
- f"Cutoff {cutoff} Hz must be less than Nyquist {sample_rate / 2} Hz."
92
- )
226
+ # Normalize and validate cutoff
227
+ Wn = _normalize_cutoff(cutoff, sample_rate, analog)
228
+ _validate_normalized_cutoff(Wn, cutoff, sample_rate, analog)
93
229
 
94
- # Design filter
230
+ # Validate filter type
95
231
  ftype_map = {
96
232
  "butterworth": "butter",
97
233
  "chebyshev1": "cheby1",
@@ -99,85 +235,25 @@ def design_filter(
99
235
  "bessel": "bessel",
100
236
  "elliptic": "ellip",
101
237
  }
102
- ftype = ftype_map.get(filter_type)
103
- if ftype is None:
238
+ if filter_type not in ftype_map:
104
239
  raise AnalysisError(f"Unknown filter type: {filter_type}")
105
240
 
241
+ # Design filter
106
242
  try:
107
243
  if filter_type == "butterworth":
108
- if output == "sos":
109
- sos = signal.butter(order, Wn, btype=btype, analog=analog, output="sos")
110
- return IIRFilter(sample_rate=sample_rate, sos=sos)
111
- else:
112
- b, a = signal.butter(order, Wn, btype=btype, analog=analog, output="ba")
113
- return IIRFilter(sample_rate=sample_rate, ba=(b, a))
114
-
244
+ return _design_butterworth(order, Wn, btype, analog, output, sample_rate)
115
245
  elif filter_type == "chebyshev1":
116
- if output == "sos":
117
- sos = signal.cheby1(order, ripple_db, Wn, btype=btype, analog=analog, output="sos")
118
- return IIRFilter(sample_rate=sample_rate, sos=sos)
119
- else:
120
- b, a = signal.cheby1(order, ripple_db, Wn, btype=btype, analog=analog, output="ba")
121
- return IIRFilter(sample_rate=sample_rate, ba=(b, a))
122
-
246
+ return _design_chebyshev1(order, ripple_db, Wn, btype, analog, output, sample_rate)
123
247
  elif filter_type == "chebyshev2":
124
- if output == "sos":
125
- sos = signal.cheby2(
126
- order,
127
- stopband_atten_db,
128
- Wn,
129
- btype=btype,
130
- analog=analog,
131
- output="sos",
132
- )
133
- return IIRFilter(sample_rate=sample_rate, sos=sos)
134
- else:
135
- b, a = signal.cheby2(
136
- order,
137
- stopband_atten_db,
138
- Wn,
139
- btype=btype,
140
- analog=analog,
141
- output="ba",
142
- )
143
- return IIRFilter(sample_rate=sample_rate, ba=(b, a))
144
-
248
+ return _design_chebyshev2(
249
+ order, stopband_atten_db, Wn, btype, analog, output, sample_rate
250
+ )
145
251
  elif filter_type == "bessel":
146
- if output == "sos":
147
- sos = signal.bessel(
148
- order, Wn, btype=btype, analog=analog, output="sos", norm="phase"
149
- )
150
- return IIRFilter(sample_rate=sample_rate, sos=sos)
151
- else:
152
- b, a = signal.bessel(
153
- order, Wn, btype=btype, analog=analog, output="ba", norm="phase"
154
- )
155
- return IIRFilter(sample_rate=sample_rate, ba=(b, a))
156
-
252
+ return _design_bessel(order, Wn, btype, analog, output, sample_rate)
157
253
  elif filter_type == "elliptic":
158
- if output == "sos":
159
- sos = signal.ellip(
160
- order,
161
- ripple_db,
162
- stopband_atten_db,
163
- Wn,
164
- btype=btype,
165
- analog=analog,
166
- output="sos",
167
- )
168
- return IIRFilter(sample_rate=sample_rate, sos=sos)
169
- else:
170
- b, a = signal.ellip(
171
- order,
172
- ripple_db,
173
- stopband_atten_db,
174
- Wn,
175
- btype=btype,
176
- analog=analog,
177
- output="ba",
178
- )
179
- return IIRFilter(sample_rate=sample_rate, ba=(b, a))
180
-
254
+ return _design_elliptic(
255
+ order, ripple_db, stopband_atten_db, Wn, btype, analog, output, sample_rate
256
+ )
181
257
  else:
182
258
  raise AnalysisError(f"Unsupported filter type: {filter_type}")
183
259
 
@@ -1,13 +1,13 @@
1
1
  """Filter convenience functions namespace.
2
2
 
3
3
  This module provides a namespace for filter functions to support:
4
- from oscura.filtering import filters
4
+ from oscura.utils.filtering import filters
5
5
  filters.low_pass(trace, cutoff=1000)
6
6
 
7
7
  Re-exports convenience functions from the filtering package.
8
8
  """
9
9
 
10
- from oscura.filtering.convenience import (
10
+ from oscura.utils.filtering.convenience import (
11
11
  band_pass,
12
12
  band_stop,
13
13
  high_pass,
@@ -5,7 +5,7 @@ step response, and pole-zero diagrams.
5
5
 
6
6
 
7
7
  Example:
8
- >>> from oscura.filtering import LowPassFilter, plot_bode
8
+ >>> from oscura.utils.filtering import LowPassFilter, plot_bode
9
9
  >>> filt = LowPassFilter(cutoff=1e6, sample_rate=10e6, order=4)
10
10
  >>> fig = plot_bode(filt)
11
11
  >>> plt.show()
@@ -21,7 +21,7 @@ if TYPE_CHECKING:
21
21
  from matplotlib.figure import Figure
22
22
  from numpy.typing import NDArray
23
23
 
24
- from oscura.filtering.base import Filter, IIRFilter
24
+ from oscura.utils.filtering.base import Filter, IIRFilter
25
25
 
26
26
 
27
27
  class FilterIntrospection:
@@ -0,0 +1,31 @@
1
+ """Geometric utility functions for visualization.
2
+
3
+ This module provides geometric calculation utilities used across visualization modules.
4
+ """
5
+
6
+
7
+ def generate_leader_line(
8
+ anchor: tuple[float, float],
9
+ label: tuple[float, float],
10
+ ) -> list[tuple[float, float]]:
11
+ """Generate orthogonal leader line from anchor to label.
12
+
13
+ Creates an L-shaped leader line connecting an anchor point to a label position.
14
+ The line is orthogonal (horizontal then vertical or vice versa).
15
+
16
+ Args:
17
+ anchor: Anchor point (x, y)
18
+ label: Label position (x, y)
19
+
20
+ Returns:
21
+ List of points for leader line [(x1, y1), (x2, y2), ...]
22
+
23
+ Example:
24
+ >>> generate_leader_line((0, 0), (10, 5))
25
+ [(0, 0), (10, 0), (10, 5)]
26
+ """
27
+ ax, ay = anchor
28
+ lx, ly = label
29
+
30
+ # Create L-shaped line: horizontal then vertical
31
+ return [(ax, ay), (lx, ay), (lx, ly)]
@@ -0,0 +1,184 @@
1
+ """Lazy import utilities for optional dependencies.
2
+
3
+ This module provides utilities for handling optional dependencies gracefully,
4
+ with helpful error messages directing users to install missing extras.
5
+ """
6
+
7
+ from typing import Any
8
+
9
+
10
+ class MissingOptionalDependency(ImportError):
11
+ """Raised when an optional dependency is required but not installed."""
12
+
13
+
14
+ def require_matplotlib() -> Any:
15
+ """Import and return matplotlib, or raise helpful error if not installed.
16
+
17
+ Returns:
18
+ matplotlib module
19
+
20
+ Raises:
21
+ MissingOptionalDependency: If matplotlib is not installed
22
+
23
+ Example:
24
+ >>> plt = require_matplotlib().pyplot
25
+ >>> plt.plot([1, 2, 3])
26
+ """
27
+ try:
28
+ import matplotlib
29
+
30
+ return matplotlib
31
+ except ImportError as e:
32
+ raise MissingOptionalDependency(
33
+ "Visualization features require matplotlib.\n\n"
34
+ "Install with:\n"
35
+ " pip install oscura[visualization] # Just matplotlib\n"
36
+ " pip install oscura[standard] # Recommended for most users\n"
37
+ " pip install oscura[all] # Everything\n\n"
38
+ "Or install matplotlib directly:\n"
39
+ " pip install matplotlib\n"
40
+ ) from e
41
+
42
+
43
+ def require_pandas() -> Any:
44
+ """Import and return pandas, or raise helpful error if not installed.
45
+
46
+ Returns:
47
+ pandas module
48
+
49
+ Raises:
50
+ MissingOptionalDependency: If pandas is not installed
51
+
52
+ Example:
53
+ >>> pd = require_pandas()
54
+ >>> df = pd.DataFrame({'a': [1, 2, 3]})
55
+ """
56
+ try:
57
+ import pandas
58
+
59
+ return pandas
60
+ except ImportError as e:
61
+ raise MissingOptionalDependency(
62
+ "DataFrame features require pandas.\n\n"
63
+ "Install with:\n"
64
+ " pip install oscura[dataframes] # Pandas + Excel export\n"
65
+ " pip install oscura[standard] # Recommended for most users\n"
66
+ " pip install oscura[all] # Everything\n\n"
67
+ "Or install pandas directly:\n"
68
+ " pip install pandas\n"
69
+ ) from e
70
+
71
+
72
+ def require_psutil() -> Any:
73
+ """Import and return psutil, or raise helpful error if not installed.
74
+
75
+ Returns:
76
+ psutil module
77
+
78
+ Raises:
79
+ MissingOptionalDependency: If psutil is not installed
80
+
81
+ Example:
82
+ >>> psutil = require_psutil()
83
+ >>> psutil.virtual_memory()
84
+ """
85
+ try:
86
+ import psutil
87
+
88
+ return psutil
89
+ except ImportError as e:
90
+ raise MissingOptionalDependency(
91
+ "System monitoring features require psutil.\n\n"
92
+ "Install with:\n"
93
+ " pip install oscura[system] # System utilities\n"
94
+ " pip install oscura[standard] # Recommended for most users\n"
95
+ " pip install oscura[all] # Everything\n\n"
96
+ "Or install psutil directly:\n"
97
+ " pip install psutil\n"
98
+ ) from e
99
+
100
+
101
+ def require_jinja2() -> Any:
102
+ """Import and return jinja2, or raise helpful error if not installed.
103
+
104
+ Returns:
105
+ jinja2 module
106
+
107
+ Raises:
108
+ MissingOptionalDependency: If jinja2 is not installed
109
+
110
+ Example:
111
+ >>> jinja2 = require_jinja2()
112
+ >>> template = jinja2.Template("Hello {{ name }}")
113
+ """
114
+ try:
115
+ import jinja2
116
+
117
+ return jinja2
118
+ except ImportError as e:
119
+ raise MissingOptionalDependency(
120
+ "Report generation features require jinja2.\n\n"
121
+ "Install with:\n"
122
+ " pip install oscura[reporting] # Report generation\n"
123
+ " pip install oscura[standard] # Recommended for most users\n"
124
+ " pip install oscura[all] # Everything\n\n"
125
+ "Or install jinja2 directly:\n"
126
+ " pip install jinja2\n"
127
+ ) from e
128
+
129
+
130
+ # Convenience functions for checking availability without raising errors
131
+ def has_matplotlib() -> bool:
132
+ """Check if matplotlib is available.
133
+
134
+ Returns:
135
+ True if matplotlib can be imported, False otherwise
136
+ """
137
+ try:
138
+ import matplotlib # noqa: F401
139
+
140
+ return True
141
+ except ImportError:
142
+ return False
143
+
144
+
145
+ def has_pandas() -> bool:
146
+ """Check if pandas is available.
147
+
148
+ Returns:
149
+ True if pandas can be imported, False otherwise
150
+ """
151
+ try:
152
+ import pandas # noqa: F401
153
+
154
+ return True
155
+ except ImportError:
156
+ return False
157
+
158
+
159
+ def has_psutil() -> bool:
160
+ """Check if psutil is available.
161
+
162
+ Returns:
163
+ True if psutil can be imported, False otherwise
164
+ """
165
+ try:
166
+ import psutil # noqa: F401
167
+
168
+ return True
169
+ except ImportError:
170
+ return False
171
+
172
+
173
+ def has_jinja2() -> bool:
174
+ """Check if jinja2 is available.
175
+
176
+ Returns:
177
+ True if jinja2 can be imported, False otherwise
178
+ """
179
+ try:
180
+ import jinja2 # noqa: F401
181
+
182
+ return True
183
+ except ImportError:
184
+ return False
oscura/utils/lazy.py CHANGED
@@ -120,7 +120,7 @@ class LazyArray(LazyProxy[NDArray[np.floating[Any]]]):
120
120
 
121
121
  def shape(self) -> tuple[int, ...]:
122
122
  """Get shape (triggers computation)."""
123
- return self.compute().shape # type: ignore[no-any-return]
123
+ return self.compute().shape
124
124
 
125
125
  def dtype(self) -> np.dtype[Any]:
126
126
  """Get dtype (triggers computation)."""
@@ -4,7 +4,7 @@ This module provides waveform math operations including arithmetic,
4
4
  interpolation, and mathematical transformations.
5
5
  """
6
6
 
7
- from oscura.math.arithmetic import (
7
+ from oscura.utils.math.arithmetic import (
8
8
  absolute,
9
9
  add,
10
10
  differentiate,
@@ -17,7 +17,7 @@ from oscura.math.arithmetic import (
17
17
  scale,
18
18
  subtract,
19
19
  )
20
- from oscura.math.interpolation import (
20
+ from oscura.utils.math.interpolation import (
21
21
  align_traces,
22
22
  downsample,
23
23
  interpolate,