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
oscura/loaders/tss.py ADDED
@@ -0,0 +1,456 @@
1
+ """Tektronix Session File (.tss) Loader.
2
+
3
+ This module provides loading functionality for Tektronix session files (.tss),
4
+ which are ZIP archives containing multiple waveform captures, instrument
5
+ configuration, measurements, and annotations.
6
+
7
+ A .tss session file typically contains:
8
+ - Multiple .wfm waveform files (one per channel/capture)
9
+ - session.json: Instrument configuration and setup
10
+ - measurements.json: Stored measurement results (optional)
11
+ - annotations.json: User annotations and markers (optional)
12
+
13
+ Example:
14
+ >>> import oscura as osc
15
+ >>> trace = osc.load("oscilloscope_session.tss")
16
+ >>> print(f"Channel: {trace.metadata.channel_name}")
17
+ >>> print(f"Sample rate: {trace.metadata.sample_rate} Hz")
18
+
19
+ >>> # Load specific channel
20
+ >>> trace = osc.load("session.tss", channel="CH2")
21
+
22
+ >>> # Load all channels
23
+ >>> channels = osc.load_all_channels("session.tss")
24
+ >>> for name, trace in channels.items():
25
+ ... print(f"{name}: {len(trace.data)} samples")
26
+
27
+ References:
28
+ Tektronix Programming Manual for Session Files
29
+ TekScope PC Analysis Software Documentation
30
+ """
31
+
32
+ from __future__ import annotations
33
+
34
+ import json
35
+ import tempfile
36
+ import zipfile
37
+ from os import PathLike
38
+ from pathlib import Path
39
+ from typing import Any
40
+
41
+ from oscura.core.exceptions import FormatError, LoaderError
42
+ from oscura.core.types import DigitalTrace, IQTrace, WaveformTrace
43
+
44
+
45
+ def load_tss(
46
+ path: str | PathLike[str],
47
+ *,
48
+ channel: str | int | None = None,
49
+ ) -> WaveformTrace | DigitalTrace | IQTrace:
50
+ """Load a Tektronix session file (.tss).
51
+
52
+ Tektronix session files are ZIP archives containing multiple waveform
53
+ captures along with instrument configuration and analysis results.
54
+
55
+ Args:
56
+ path: Path to the Tektronix .tss session file.
57
+ channel: Optional channel name or index to load. If None,
58
+ loads the first waveform found (alphabetically).
59
+ String names are case-insensitive (e.g., "ch1", "CH1").
60
+ Integer index is 0-based.
61
+
62
+ Returns:
63
+ WaveformTrace, DigitalTrace, or IQTrace containing the channel data.
64
+
65
+ Raises:
66
+ LoaderError: If the file cannot be loaded or doesn't exist.
67
+ FormatError: If the file is not a valid Tektronix session.
68
+
69
+ Example:
70
+ >>> # Load first channel (default)
71
+ >>> trace = load_tss("session.tss")
72
+
73
+ >>> # Load specific channel by name
74
+ >>> trace = load_tss("session.tss", channel="CH2")
75
+
76
+ >>> # Load by index
77
+ >>> trace = load_tss("session.tss", channel=1) # Second channel
78
+ """
79
+ path = Path(path)
80
+
81
+ # Validate file
82
+ _validate_tss_file(path)
83
+
84
+ # Load all waveforms and select requested channel
85
+ waveforms = _load_all_waveforms(path)
86
+
87
+ if not waveforms:
88
+ raise FormatError(
89
+ "No waveforms found in session file",
90
+ file_path=str(path),
91
+ expected="At least one .wfm file in the archive",
92
+ got="Empty session or no waveform files",
93
+ )
94
+
95
+ # Select channel
96
+ trace, channel_name = _select_channel(waveforms, channel, path)
97
+
98
+ # Enrich metadata with session information
99
+ try:
100
+ with zipfile.ZipFile(path, "r") as zf:
101
+ session_metadata = _parse_session_metadata(zf, path)
102
+ trace = _enrich_metadata_from_session(trace, session_metadata, str(path))
103
+ except Exception:
104
+ # Session metadata is optional, continue with waveform metadata
105
+ pass
106
+
107
+ return trace
108
+
109
+
110
+ def load_all_channels_tss(
111
+ path: Path,
112
+ ) -> dict[str, WaveformTrace | DigitalTrace | IQTrace]:
113
+ """Load all channels from a Tektronix session file.
114
+
115
+ Args:
116
+ path: Path to the .tss session file.
117
+
118
+ Returns:
119
+ Dictionary mapping channel names to traces.
120
+ Channel names are derived from .wfm filenames (e.g., "ch1", "ch2").
121
+
122
+ Raises:
123
+ LoaderError: If the file cannot be loaded.
124
+ FormatError: If no waveforms found in session.
125
+
126
+ Example:
127
+ >>> channels = load_all_channels_tss(Path("session.tss"))
128
+ >>> for name, trace in channels.items():
129
+ ... print(f"{name}: {trace.metadata.sample_rate} Hz")
130
+ """
131
+ # Validate file
132
+ _validate_tss_file(path)
133
+
134
+ # Load all waveforms
135
+ waveforms = _load_all_waveforms(path)
136
+
137
+ if not waveforms:
138
+ raise FormatError(
139
+ "No waveforms found in session file",
140
+ file_path=str(path),
141
+ expected="At least one .wfm file",
142
+ got="Empty archive",
143
+ )
144
+
145
+ # Enrich all traces with session metadata
146
+ try:
147
+ with zipfile.ZipFile(path, "r") as zf:
148
+ session_metadata = _parse_session_metadata(zf, path)
149
+ for name in waveforms:
150
+ waveforms[name] = _enrich_metadata_from_session(
151
+ waveforms[name], session_metadata, str(path)
152
+ )
153
+ except Exception:
154
+ # Session metadata is optional
155
+ pass
156
+
157
+ return waveforms
158
+
159
+
160
+ def _validate_tss_file(path: Path) -> None:
161
+ """Validate that file exists and is a valid ZIP archive.
162
+
163
+ Args:
164
+ path: Path to validate.
165
+
166
+ Raises:
167
+ LoaderError: If file doesn't exist or can't be read.
168
+ FormatError: If file is not a ZIP archive.
169
+ """
170
+ if not path.exists():
171
+ raise LoaderError(
172
+ "File not found",
173
+ file_path=str(path),
174
+ fix_hint="Ensure the file path is correct and the file exists.",
175
+ )
176
+
177
+ if not zipfile.is_zipfile(path):
178
+ raise FormatError(
179
+ "Not a valid ZIP archive",
180
+ file_path=str(path),
181
+ expected="Tektronix session file (.tss) is a ZIP archive",
182
+ got="File is not a ZIP file",
183
+ )
184
+
185
+ try:
186
+ with zipfile.ZipFile(path, "r") as zf:
187
+ # Test archive integrity
188
+ bad_file = zf.testzip()
189
+ if bad_file is not None:
190
+ raise FormatError(
191
+ f"Corrupted ZIP archive: {bad_file}",
192
+ file_path=str(path),
193
+ expected="Valid ZIP archive",
194
+ got="Corrupted file detected",
195
+ )
196
+ except zipfile.BadZipFile as e:
197
+ raise FormatError(
198
+ "Corrupted or invalid ZIP archive",
199
+ file_path=str(path),
200
+ details=str(e),
201
+ ) from e
202
+
203
+
204
+ def _parse_session_metadata(zf: zipfile.ZipFile, path: Path) -> dict[str, Any]:
205
+ """Parse session.json metadata from the archive.
206
+
207
+ Args:
208
+ zf: Open ZipFile object.
209
+ path: Path to session file (for error messages).
210
+
211
+ Returns:
212
+ Dictionary containing session metadata.
213
+ Returns empty dict if session.json not found (non-fatal).
214
+ """
215
+ # Look for session metadata files
216
+ metadata_files = [
217
+ "session.json",
218
+ "Session.json",
219
+ "metadata.json",
220
+ "Metadata.json",
221
+ ]
222
+
223
+ for metadata_file in metadata_files:
224
+ try:
225
+ with zf.open(metadata_file) as f:
226
+ data: dict[str, Any] = json.load(f)
227
+ return data
228
+ except KeyError:
229
+ continue
230
+ except json.JSONDecodeError as e:
231
+ # Non-fatal: log warning and continue
232
+ import warnings
233
+
234
+ warnings.warn(
235
+ f"Failed to parse {metadata_file} in {path.name}: {e}",
236
+ stacklevel=2,
237
+ )
238
+ return {}
239
+
240
+ # Session metadata is optional
241
+ return {}
242
+
243
+
244
+ def _extract_waveform_list(zf: zipfile.ZipFile) -> list[str]:
245
+ """Extract list of .wfm files in the session.
246
+
247
+ Args:
248
+ zf: Open ZipFile object.
249
+
250
+ Returns:
251
+ List of .wfm file paths within the archive.
252
+ Excludes macOS metadata files (__MACOSX).
253
+ """
254
+ return [
255
+ name
256
+ for name in zf.namelist()
257
+ if name.lower().endswith(".wfm") and not name.startswith("__MACOSX")
258
+ ]
259
+
260
+
261
+ def _load_wfm_from_archive(
262
+ zf: zipfile.ZipFile,
263
+ wfm_name: str,
264
+ path: Path,
265
+ ) -> WaveformTrace | DigitalTrace | IQTrace:
266
+ """Extract and load a .wfm file from the archive.
267
+
268
+ Args:
269
+ zf: Open ZipFile object.
270
+ wfm_name: Name of .wfm file within archive.
271
+ path: Path to session file (for error messages).
272
+
273
+ Returns:
274
+ Loaded trace from the waveform file.
275
+
276
+ Raises:
277
+ LoaderError: If waveform cannot be loaded.
278
+ """
279
+ try:
280
+ # Extract waveform to temporary file
281
+ # (load_tektronix_wfm expects file path, not bytes)
282
+ wfm_bytes = zf.read(wfm_name)
283
+
284
+ with tempfile.NamedTemporaryFile(suffix=".wfm", delete=True) as tmp:
285
+ tmp.write(wfm_bytes)
286
+ tmp.flush()
287
+
288
+ # Use existing Tektronix loader
289
+ from oscura.loaders.tektronix import load_tektronix_wfm
290
+
291
+ trace = load_tektronix_wfm(tmp.name)
292
+
293
+ return trace
294
+
295
+ except Exception as e:
296
+ raise LoaderError(
297
+ f"Failed to load waveform from session: {wfm_name}",
298
+ file_path=str(path),
299
+ details=str(e),
300
+ fix_hint="Waveform file may be corrupted or incompatible.",
301
+ ) from e
302
+
303
+
304
+ def _load_all_waveforms(path: Path) -> dict[str, WaveformTrace | DigitalTrace | IQTrace]:
305
+ """Load all waveforms from the session file.
306
+
307
+ Args:
308
+ path: Path to .tss session file.
309
+
310
+ Returns:
311
+ Dictionary mapping channel names to traces.
312
+ """
313
+ waveforms: dict[str, WaveformTrace | DigitalTrace | IQTrace] = {}
314
+
315
+ with zipfile.ZipFile(path, "r") as zf:
316
+ wfm_files = _extract_waveform_list(zf)
317
+
318
+ for wfm_name in sorted(wfm_files): # Sort for consistent ordering
319
+ # Derive channel name from filename
320
+ channel_name = _derive_channel_name(wfm_name)
321
+
322
+ # Load waveform
323
+ trace = _load_wfm_from_archive(zf, wfm_name, path)
324
+
325
+ # Store with normalized channel name
326
+ waveforms[channel_name] = trace
327
+
328
+ return waveforms
329
+
330
+
331
+ def _derive_channel_name(wfm_filename: str) -> str:
332
+ """Derive channel name from .wfm filename.
333
+
334
+ Args:
335
+ wfm_filename: Filename like "CH1.wfm", "CH2_Voltage.wfm", etc.
336
+
337
+ Returns:
338
+ Normalized channel name (lowercase, e.g., "ch1", "ch2", "d0").
339
+
340
+ Examples:
341
+ >>> _derive_channel_name("CH1.wfm")
342
+ 'ch1'
343
+ >>> _derive_channel_name("subdir/CH2_Voltage.wfm")
344
+ 'ch2'
345
+ >>> _derive_channel_name("D0.wfm")
346
+ 'd0'
347
+ >>> _derive_channel_name("MATH1.wfm")
348
+ 'math1'
349
+ """
350
+ # Get base filename without path
351
+ basename = Path(wfm_filename).stem # Remove extension
352
+
353
+ # Remove path components if nested
354
+ basename = basename.split("/")[-1].split("\\")[-1]
355
+
356
+ # Extract channel identifier (first part before underscore)
357
+ channel_id = basename.split("_")[0]
358
+
359
+ # Normalize to lowercase
360
+ return channel_id.lower()
361
+
362
+
363
+ def _select_channel(
364
+ waveforms: dict[str, WaveformTrace | DigitalTrace | IQTrace],
365
+ channel: str | int | None,
366
+ path: Path,
367
+ ) -> tuple[WaveformTrace | DigitalTrace | IQTrace, str]:
368
+ """Select specific channel from waveforms dictionary.
369
+
370
+ Args:
371
+ waveforms: Dictionary of channel name to trace.
372
+ channel: Channel selector (name, index, or None for first).
373
+ path: Path to session file (for error messages).
374
+
375
+ Returns:
376
+ Tuple of (selected_trace, channel_name).
377
+
378
+ Raises:
379
+ LoaderError: If channel not found or index out of range.
380
+ """
381
+ if channel is None:
382
+ # Default: first channel (alphabetically sorted)
383
+ channel_name = sorted(waveforms.keys())[0]
384
+ return waveforms[channel_name], channel_name
385
+
386
+ if isinstance(channel, int):
387
+ # Select by index
388
+ channel_names = sorted(waveforms.keys())
389
+ if channel < 0 or channel >= len(channel_names):
390
+ raise LoaderError(
391
+ f"Channel index {channel} out of range",
392
+ file_path=str(path),
393
+ fix_hint=f"Available channels: {', '.join(channel_names)} (indices 0-{len(channel_names) - 1})",
394
+ )
395
+ channel_name = channel_names[channel]
396
+ return waveforms[channel_name], channel_name
397
+
398
+ # Select by name (case-insensitive)
399
+ channel_lower = channel.lower()
400
+ for name, trace in waveforms.items():
401
+ if name.lower() == channel_lower:
402
+ return trace, name
403
+
404
+ # Channel not found
405
+ available = ", ".join(sorted(waveforms.keys()))
406
+ raise LoaderError(
407
+ f"Channel '{channel}' not found in session",
408
+ file_path=str(path),
409
+ fix_hint=f"Available channels: {available}",
410
+ )
411
+
412
+
413
+ def _enrich_metadata_from_session(
414
+ trace: WaveformTrace | DigitalTrace | IQTrace,
415
+ session_metadata: dict[str, Any],
416
+ source_file: str,
417
+ ) -> WaveformTrace | DigitalTrace | IQTrace:
418
+ """Enrich waveform metadata with session-level information.
419
+
420
+ Args:
421
+ trace: Original trace from .wfm file.
422
+ session_metadata: Session metadata from session.json.
423
+ source_file: Path to .tss file (for source_file metadata).
424
+
425
+ Returns:
426
+ Trace with enriched metadata.
427
+ """
428
+ # Create new metadata with session information
429
+ from dataclasses import replace
430
+
431
+ metadata = trace.metadata
432
+
433
+ # Update source file to point to .tss instead of temp .wfm
434
+ metadata = replace(metadata, source_file=source_file)
435
+
436
+ # Add trigger info from session if available
437
+ if "trigger" in session_metadata and metadata.trigger_info is None:
438
+ metadata = replace(metadata, trigger_info=session_metadata["trigger"])
439
+
440
+ # Return trace with updated metadata
441
+ if isinstance(trace, WaveformTrace):
442
+ return WaveformTrace(data=trace.data, metadata=metadata)
443
+ if isinstance(trace, DigitalTrace):
444
+ return DigitalTrace(data=trace.data, metadata=metadata, edges=trace.edges)
445
+ # IQTrace
446
+ return IQTrace(
447
+ i_data=trace.i_data,
448
+ q_data=trace.q_data,
449
+ metadata=metadata,
450
+ )
451
+
452
+
453
+ __all__ = [
454
+ "load_all_channels_tss",
455
+ "load_tss",
456
+ ]