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
@@ -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,