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
@@ -17,7 +17,7 @@ References:
17
17
  from __future__ import annotations
18
18
 
19
19
  from enum import Enum
20
- from typing import TYPE_CHECKING, Literal
20
+ from typing import TYPE_CHECKING, Any, Literal
21
21
 
22
22
  from oscura.analyzers.protocols.base import (
23
23
  AnnotationLevel,
@@ -26,6 +26,7 @@ from oscura.analyzers.protocols.base import (
26
26
  SyncDecoder,
27
27
  )
28
28
  from oscura.core.types import DigitalTrace, ProtocolPacket
29
+ from oscura.utils.bitwise import bits_to_byte
29
30
 
30
31
  if TYPE_CHECKING:
31
32
  from collections.abc import Iterator
@@ -108,18 +109,18 @@ class USBDecoder(SyncDecoder):
108
109
  longname = "Universal Serial Bus"
109
110
  desc = "USB Low/Full Speed protocol decoder"
110
111
 
111
- channels = [ # noqa: RUF012
112
+ channels = [
112
113
  ChannelDef("dp", "D+", "USB D+ signal", required=True),
113
114
  ChannelDef("dm", "D-", "USB D- signal", required=True),
114
115
  ]
115
116
 
116
- optional_channels = [] # noqa: RUF012
117
+ optional_channels = []
117
118
 
118
- options = [ # noqa: RUF012
119
+ options = [
119
120
  OptionDef("speed", "Speed", "USB speed", default="full", values=["low", "full"]),
120
121
  ]
121
122
 
122
- annotations = [ # noqa: RUF012
123
+ annotations = [
123
124
  ("sync", "SYNC field"),
124
125
  ("pid", "Packet ID"),
125
126
  ("data", "Data payload"),
@@ -167,46 +168,16 @@ class USBDecoder(SyncDecoder):
167
168
  if dp is None or dm is None:
168
169
  return
169
170
 
170
- n_samples = min(len(dp), len(dm))
171
- dp = dp[:n_samples]
172
- dm = dm[:n_samples]
173
-
174
- # Decode differential signal to single-ended
175
- # J state: LS: D-=1, D+=0; FS: D+=1, D-=0
176
- # K state: LS: D+=1, D-=0; FS: D-=1, D+=0
177
- # SE0: D+=0, D-=0 (idle/EOP)
178
- # SE1: D+=1, D-=1 (illegal)
179
-
180
- if self._speed == USBSpeed.LOW_SPEED:
181
- # Low speed: J = D-, K = D+
182
- diff_signal = ~dp & dm # J=1, K=0
183
- else:
184
- # Full speed: J = D+, K = D-
185
- diff_signal = dp & ~dm # J=1, K=0
186
-
187
- # Find packet boundaries (SYNC followed by data, ending with EOP)
188
- # EOP is SE0 for at least 2 bit times
189
-
190
- bit_period = sample_rate / self._speed.value
191
- int(2 * bit_period)
192
-
193
- # Find SE0 regions (both D+ and D- are 0)
194
- se0 = ~dp & ~dm
171
+ diff_signal, se0, bit_period = self._prepare_signals(dp, dm, sample_rate)
195
172
 
196
173
  trans_num = 0
197
174
  idx = 0
198
175
 
199
176
  while idx < len(diff_signal):
200
- # Look for SYNC pattern in NRZI-decoded signal
201
- # SYNC is 0x80 (10000000) after NRZI and bit unstuffing
202
- # In NRZI: no transition = 1, transition = 0
203
-
204
- # Decode NRZI starting from current position
205
177
  nrzi_start = self._find_sync_pattern(diff_signal, idx, bit_period)
206
178
  if nrzi_start is None:
207
179
  break
208
180
 
209
- # Extract packet bits
210
181
  packet_bits, bit_errors = self._extract_packet_bits(
211
182
  diff_signal, nrzi_start, bit_period, se0
212
183
  )
@@ -215,90 +186,22 @@ class USBDecoder(SyncDecoder):
215
186
  idx = nrzi_start + int(bit_period)
216
187
  continue
217
188
 
218
- # Skip SYNC field (first 8 bits)
219
- data_bits = packet_bits[8:]
189
+ pid_value, pid_name, errors = self._parse_pid(packet_bits, bit_errors)
220
190
 
221
- # Extract PID (8 bits)
222
- if len(data_bits) < 8:
223
- idx = nrzi_start + int(bit_period)
224
- continue
225
-
226
- pid_byte = self._bits_to_byte(data_bits[:8])
227
- pid_value = pid_byte & 0x0F
228
- pid_check = (pid_byte >> 4) & 0x0F
229
-
230
- errors = list(bit_errors)
231
-
232
- # Validate PID (upper 4 bits should be complement of lower 4 bits)
233
- if pid_value ^ pid_check != 0x0F:
234
- errors.append("PID check failed")
235
-
236
- pid_name = PID_NAMES.get(pid_value, f"UNKNOWN(0x{pid_value:X})")
237
-
238
- # Extract payload based on PID type
239
- payload_bits = data_bits[8:]
240
- payload_bytes = []
241
- annotations = {
242
- "transaction_num": trans_num,
243
- "pid_value": pid_value,
244
- "pid_name": pid_name,
245
- }
246
-
247
- # Token packets: OUT, IN, SOF, SETUP
248
- if pid_value in [0b0001, 0b1001, 0b1101]: # OUT, IN, SETUP
249
- if len(payload_bits) >= 16: # 11-bit (addr+endp) + 5-bit CRC
250
- addr_endp = self._bits_to_value(payload_bits[:11])
251
- address = addr_endp & 0x7F
252
- endpoint = (addr_endp >> 7) & 0x0F
253
- crc5 = self._bits_to_value(payload_bits[11:16])
254
-
255
- # Validate CRC5
256
- expected_crc5 = self._crc5(addr_endp)
257
- if crc5 != expected_crc5:
258
- errors.append("CRC5 error")
259
-
260
- annotations["address"] = address
261
- annotations["endpoint"] = endpoint
262
-
263
- elif pid_value == 0b0101: # SOF
264
- if len(payload_bits) >= 16: # 11-bit frame number + 5-bit CRC
265
- frame_num = self._bits_to_value(payload_bits[:11])
266
- crc5 = self._bits_to_value(payload_bits[11:16])
267
- annotations["frame_number"] = frame_num
268
-
269
- # Data packets: DATA0, DATA1, DATA2, MDATA
270
- elif pid_value in [0b0011, 0b1011, 0b0111, 0b1111]:
271
- if len(payload_bits) >= 16: # At least CRC16
272
- # Data payload + CRC16
273
- data_bit_count = len(payload_bits) - 16
274
- if data_bit_count >= 0:
275
- for i in range(0, data_bit_count, 8):
276
- if i + 8 <= data_bit_count:
277
- byte_val = self._bits_to_byte(payload_bits[i : i + 8])
278
- payload_bytes.append(byte_val)
279
-
280
- self._bits_to_value(payload_bits[-16:])
281
- annotations["data_length"] = len(payload_bytes)
282
-
283
- # Calculate timing
284
- start_time = nrzi_start / sample_rate
285
- end_time = (nrzi_start + len(packet_bits) * bit_period) / sample_rate
286
-
287
- # Add annotation
288
- self.put_annotation(
289
- start_time,
290
- end_time,
291
- AnnotationLevel.PACKETS,
292
- f"{pid_name}",
191
+ payload_bits = packet_bits[16:] # Skip SYNC(8) + PID(8)
192
+ payload_bytes, annotations = self._parse_payload(
193
+ pid_value, pid_name, payload_bits, trans_num, errors
293
194
  )
294
195
 
295
- # Create packet
296
- packet = ProtocolPacket(
297
- timestamp=start_time,
298
- protocol="usb",
299
- data=bytes(payload_bytes),
300
- annotations=annotations,
301
- errors=errors,
196
+ packet = self._build_usb_packet(
197
+ nrzi_start,
198
+ packet_bits,
199
+ bit_period,
200
+ sample_rate,
201
+ pid_name,
202
+ payload_bytes,
203
+ annotations,
204
+ errors,
302
205
  )
303
206
 
304
207
  yield packet
@@ -306,6 +209,208 @@ class USBDecoder(SyncDecoder):
306
209
  trans_num += 1
307
210
  idx = int(nrzi_start + len(packet_bits) * bit_period)
308
211
 
212
+ def _prepare_signals(
213
+ self, dp: NDArray[np.bool_], dm: NDArray[np.bool_], sample_rate: float
214
+ ) -> tuple[NDArray[np.bool_], NDArray[np.bool_], float]:
215
+ """Prepare differential and SE0 signals.
216
+
217
+ Args:
218
+ dp: D+ signal.
219
+ dm: D- signal.
220
+ sample_rate: Sample rate in Hz.
221
+
222
+ Returns:
223
+ Tuple of (differential_signal, se0_signal, bit_period).
224
+ """
225
+ n_samples = min(len(dp), len(dm))
226
+ dp = dp[:n_samples]
227
+ dm = dm[:n_samples]
228
+
229
+ # Decode differential signal
230
+ if self._speed == USBSpeed.LOW_SPEED:
231
+ diff_signal = ~dp & dm # Low speed: J = D-, K = D+
232
+ else:
233
+ diff_signal = dp & ~dm # Full speed: J = D+, K = D-
234
+
235
+ se0 = ~dp & ~dm # SE0: both D+ and D- are 0
236
+ bit_period = sample_rate / self._speed.value
237
+
238
+ return diff_signal, se0, bit_period
239
+
240
+ def _parse_pid(
241
+ self, packet_bits: list[int], bit_errors: list[str]
242
+ ) -> tuple[int, str, list[str]]:
243
+ """Parse and validate PID field.
244
+
245
+ Args:
246
+ packet_bits: Full packet bits including SYNC.
247
+ bit_errors: Bit-level errors from extraction.
248
+
249
+ Returns:
250
+ Tuple of (pid_value, pid_name, errors).
251
+ """
252
+ data_bits = packet_bits[8:] # Skip SYNC
253
+ errors = list(bit_errors)
254
+
255
+ if len(data_bits) < 8:
256
+ return 0, "INVALID", errors
257
+
258
+ pid_byte = bits_to_byte(data_bits[:8])
259
+ pid_value = pid_byte & 0x0F
260
+ pid_check = (pid_byte >> 4) & 0x0F
261
+
262
+ # Validate PID (upper 4 bits should be complement of lower 4 bits)
263
+ if pid_value ^ pid_check != 0x0F:
264
+ errors.append("PID check failed")
265
+
266
+ pid_name = PID_NAMES.get(pid_value, f"UNKNOWN(0x{pid_value:X})")
267
+
268
+ return pid_value, pid_name, errors
269
+
270
+ def _parse_payload(
271
+ self,
272
+ pid_value: int,
273
+ pid_name: str,
274
+ payload_bits: list[int],
275
+ trans_num: int,
276
+ errors: list[str],
277
+ ) -> tuple[list[int], dict[str, Any]]:
278
+ """Parse payload based on PID type.
279
+
280
+ Args:
281
+ pid_value: PID value.
282
+ pid_name: PID name string.
283
+ payload_bits: Payload bits after PID.
284
+ trans_num: Transaction number.
285
+ errors: Error list to append to.
286
+
287
+ Returns:
288
+ Tuple of (payload_bytes, annotations).
289
+ """
290
+ annotations = {
291
+ "transaction_num": trans_num,
292
+ "pid_value": pid_value,
293
+ "pid_name": pid_name,
294
+ }
295
+ payload_bytes = []
296
+
297
+ # Token packets: OUT, IN, SETUP
298
+ if pid_value in [0b0001, 0b1001, 0b1101]:
299
+ self._parse_token_payload(payload_bits, annotations, errors)
300
+ # SOF packet
301
+ elif pid_value == 0b0101:
302
+ self._parse_sof_payload(payload_bits, annotations)
303
+ # Data packets: DATA0, DATA1, DATA2, MDATA
304
+ elif pid_value in [0b0011, 0b1011, 0b0111, 0b1111]:
305
+ payload_bytes = self._parse_data_payload(payload_bits, annotations)
306
+
307
+ return payload_bytes, annotations
308
+
309
+ def _parse_token_payload(
310
+ self, payload_bits: list[int], annotations: dict[str, Any], errors: list[str]
311
+ ) -> None:
312
+ """Parse token packet payload (address + endpoint + CRC5).
313
+
314
+ Args:
315
+ payload_bits: Payload bits.
316
+ annotations: Annotations dict to update.
317
+ errors: Error list to append to.
318
+ """
319
+ if len(payload_bits) >= 16: # 11-bit (addr+endp) + 5-bit CRC
320
+ addr_endp = self._bits_to_value(payload_bits[:11])
321
+ address = addr_endp & 0x7F
322
+ endpoint = (addr_endp >> 7) & 0x0F
323
+ crc5 = self._bits_to_value(payload_bits[11:16])
324
+
325
+ # Validate CRC5
326
+ expected_crc5 = self._crc5(addr_endp)
327
+ if crc5 != expected_crc5:
328
+ errors.append("CRC5 error")
329
+
330
+ annotations["address"] = address
331
+ annotations["endpoint"] = endpoint
332
+
333
+ def _parse_sof_payload(self, payload_bits: list[int], annotations: dict[str, Any]) -> None:
334
+ """Parse SOF packet payload (frame number + CRC5).
335
+
336
+ Args:
337
+ payload_bits: Payload bits.
338
+ annotations: Annotations dict to update.
339
+ """
340
+ if len(payload_bits) >= 16: # 11-bit frame number + 5-bit CRC
341
+ frame_num = self._bits_to_value(payload_bits[:11])
342
+ annotations["frame_number"] = frame_num
343
+
344
+ def _parse_data_payload(
345
+ self, payload_bits: list[int], annotations: dict[str, Any]
346
+ ) -> list[int]:
347
+ """Parse data packet payload (data + CRC16).
348
+
349
+ Args:
350
+ payload_bits: Payload bits.
351
+ annotations: Annotations dict to update.
352
+
353
+ Returns:
354
+ List of data bytes.
355
+ """
356
+ payload_bytes = []
357
+
358
+ if len(payload_bits) >= 16: # At least CRC16
359
+ data_bit_count = len(payload_bits) - 16
360
+ if data_bit_count >= 0:
361
+ for i in range(0, data_bit_count, 8):
362
+ if i + 8 <= data_bit_count:
363
+ byte_val = bits_to_byte(payload_bits[i : i + 8])
364
+ payload_bytes.append(byte_val)
365
+
366
+ annotations["data_length"] = len(payload_bytes)
367
+
368
+ return payload_bytes
369
+
370
+ def _build_usb_packet(
371
+ self,
372
+ start_idx: int,
373
+ packet_bits: list[int],
374
+ bit_period: float,
375
+ sample_rate: float,
376
+ pid_name: str,
377
+ payload_bytes: list[int],
378
+ annotations: dict[str, Any],
379
+ errors: list[str],
380
+ ) -> ProtocolPacket:
381
+ """Build USB protocol packet.
382
+
383
+ Args:
384
+ start_idx: Packet start index.
385
+ packet_bits: All packet bits.
386
+ bit_period: Bit period in samples.
387
+ sample_rate: Sample rate in Hz.
388
+ pid_name: PID name string.
389
+ payload_bytes: Payload data bytes.
390
+ annotations: Packet annotations.
391
+ errors: Error list.
392
+
393
+ Returns:
394
+ Protocol packet.
395
+ """
396
+ start_time = start_idx / sample_rate
397
+ end_time = (start_idx + len(packet_bits) * bit_period) / sample_rate
398
+
399
+ self.put_annotation(
400
+ start_time,
401
+ end_time,
402
+ AnnotationLevel.PACKETS,
403
+ f"{pid_name}",
404
+ )
405
+
406
+ return ProtocolPacket(
407
+ timestamp=start_time,
408
+ protocol="usb",
409
+ data=bytes(payload_bytes),
410
+ annotations=annotations,
411
+ errors=errors,
412
+ )
413
+
309
414
  def _find_sync_pattern(
310
415
  self,
311
416
  signal: NDArray[np.bool_],
@@ -418,20 +523,6 @@ class USBDecoder(SyncDecoder):
418
523
 
419
524
  return bits, errors
420
525
 
421
- def _bits_to_byte(self, bits: list[int]) -> int:
422
- """Convert 8 bits to byte (LSB first).
423
-
424
- Args:
425
- bits: List of 8 bits.
426
-
427
- Returns:
428
- Byte value.
429
- """
430
- value = 0
431
- for i in range(min(8, len(bits))):
432
- value |= bits[i] << i
433
- return value
434
-
435
526
  def _bits_to_value(self, bits: list[int]) -> int:
436
527
  """Convert bits to integer (LSB first).
437
528
 
@@ -446,6 +537,19 @@ class USBDecoder(SyncDecoder):
446
537
  value |= bit << i
447
538
  return value
448
539
 
540
+ def _bits_to_byte(self, bits: list[int]) -> int:
541
+ """Convert 8 bits to byte value (LSB first).
542
+
543
+ Alias for _bits_to_value for backward compatibility.
544
+
545
+ Args:
546
+ bits: List of 8 bits.
547
+
548
+ Returns:
549
+ Byte value (0-255).
550
+ """
551
+ return self._bits_to_value(bits[:8])
552
+
449
553
  def _crc5(self, data: int) -> int:
450
554
  """Compute USB CRC5.
451
555
 
@@ -73,20 +73,25 @@ def hamming_weight(value: int | NDArray[np.integer[Any]]) -> int | NDArray[np.in
73
73
  array([4, 8])
74
74
  """
75
75
  if isinstance(value, np.ndarray):
76
- # Vectorized implementation for arrays
77
- result = np.zeros(value.shape, dtype=np.int32)
78
- temp_arr = value.astype(np.uint32)
79
- while np.any(temp_arr):
80
- result += temp_arr & 1
81
- temp_arr >>= 1
76
+ # Optimized vectorized implementation using np.unpackbits
77
+ # Handle different integer sizes by converting to uint8 representation
78
+ arr = value.astype(np.uint64)
79
+
80
+ # Convert to bytes and unpack bits (much faster than loop)
81
+ # For each value, convert to 8 bytes (uint64) and count 1s
82
+ result = np.zeros(arr.shape, dtype=np.int32)
83
+
84
+ for byte_idx in range(8): # Process 8 bytes for uint64
85
+ byte_vals = ((arr >> (byte_idx * 8)) & 0xFF).astype(np.uint8)
86
+ # Unpack bits and sum for each value
87
+ for i, byte_val in enumerate(byte_vals):
88
+ result[i] += bin(byte_val).count("1")
89
+
82
90
  return result
83
91
  else:
84
- # Scalar implementation
85
- count: int = 0
86
- temp: int = int(value)
87
- while temp:
88
- count += temp & 1
89
- temp >>= 1
92
+ # Scalar implementation using Python's built-in bit_count (Python 3.10+)
93
+ # Fallback to bin().count() for compatibility
94
+ count: int = bin(int(value)).count("1")
90
95
  return count
91
96
 
92
97
 
@@ -0,0 +1,15 @@
1
+ """Signal-level analysis and processing.
2
+
3
+ This module provides signal-level analysis including timing analysis,
4
+ clock recovery, and signal quality assessment.
5
+ """
6
+
7
+ from oscura.analyzers.signal.timing_analysis import (
8
+ TimingAnalysisResult,
9
+ TimingAnalyzer,
10
+ )
11
+
12
+ __all__ = [
13
+ "TimingAnalysisResult",
14
+ "TimingAnalyzer",
15
+ ]