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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (497) hide show
  1. oscura/__init__.py +169 -167
  2. oscura/analyzers/__init__.py +3 -0
  3. oscura/analyzers/classification.py +659 -0
  4. oscura/analyzers/digital/edges.py +325 -65
  5. oscura/analyzers/digital/quality.py +293 -166
  6. oscura/analyzers/digital/timing.py +260 -115
  7. oscura/analyzers/digital/timing_numba.py +334 -0
  8. oscura/analyzers/entropy.py +605 -0
  9. oscura/analyzers/eye/diagram.py +176 -109
  10. oscura/analyzers/eye/metrics.py +5 -5
  11. oscura/analyzers/jitter/__init__.py +6 -4
  12. oscura/analyzers/jitter/ber.py +52 -52
  13. oscura/analyzers/jitter/classification.py +156 -0
  14. oscura/analyzers/jitter/decomposition.py +163 -113
  15. oscura/analyzers/jitter/spectrum.py +80 -64
  16. oscura/analyzers/ml/__init__.py +39 -0
  17. oscura/analyzers/ml/features.py +600 -0
  18. oscura/analyzers/ml/signal_classifier.py +604 -0
  19. oscura/analyzers/packet/daq.py +246 -158
  20. oscura/analyzers/packet/parser.py +12 -1
  21. oscura/analyzers/packet/payload.py +50 -2110
  22. oscura/analyzers/packet/payload_analysis.py +361 -181
  23. oscura/analyzers/packet/payload_patterns.py +133 -70
  24. oscura/analyzers/packet/stream.py +84 -23
  25. oscura/analyzers/patterns/__init__.py +26 -5
  26. oscura/analyzers/patterns/anomaly_detection.py +908 -0
  27. oscura/analyzers/patterns/clustering.py +169 -108
  28. oscura/analyzers/patterns/clustering_optimized.py +227 -0
  29. oscura/analyzers/patterns/discovery.py +1 -1
  30. oscura/analyzers/patterns/matching.py +581 -197
  31. oscura/analyzers/patterns/pattern_mining.py +778 -0
  32. oscura/analyzers/patterns/periodic.py +121 -38
  33. oscura/analyzers/patterns/sequences.py +175 -78
  34. oscura/analyzers/power/conduction.py +1 -1
  35. oscura/analyzers/power/soa.py +6 -6
  36. oscura/analyzers/power/switching.py +250 -110
  37. oscura/analyzers/protocol/__init__.py +17 -1
  38. oscura/analyzers/protocols/base.py +6 -6
  39. oscura/analyzers/protocols/ble/__init__.py +38 -0
  40. oscura/analyzers/protocols/ble/analyzer.py +809 -0
  41. oscura/analyzers/protocols/ble/uuids.py +288 -0
  42. oscura/analyzers/protocols/can.py +257 -127
  43. oscura/analyzers/protocols/can_fd.py +107 -80
  44. oscura/analyzers/protocols/flexray.py +139 -80
  45. oscura/analyzers/protocols/hdlc.py +93 -58
  46. oscura/analyzers/protocols/i2c.py +247 -106
  47. oscura/analyzers/protocols/i2s.py +138 -86
  48. oscura/analyzers/protocols/industrial/__init__.py +40 -0
  49. oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
  50. oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
  51. oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
  52. oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
  53. oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
  54. oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
  55. oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
  56. oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
  57. oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
  58. oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
  59. oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
  60. oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
  61. oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
  62. oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
  63. oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
  64. oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
  65. oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
  66. oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
  67. oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
  68. oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
  69. oscura/analyzers/protocols/jtag.py +180 -98
  70. oscura/analyzers/protocols/lin.py +219 -114
  71. oscura/analyzers/protocols/manchester.py +4 -4
  72. oscura/analyzers/protocols/onewire.py +253 -149
  73. oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
  74. oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
  75. oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
  76. oscura/analyzers/protocols/spi.py +192 -95
  77. oscura/analyzers/protocols/swd.py +321 -167
  78. oscura/analyzers/protocols/uart.py +267 -125
  79. oscura/analyzers/protocols/usb.py +235 -131
  80. oscura/analyzers/side_channel/power.py +17 -12
  81. oscura/analyzers/signal/__init__.py +15 -0
  82. oscura/analyzers/signal/timing_analysis.py +1086 -0
  83. oscura/analyzers/signal_integrity/__init__.py +4 -1
  84. oscura/analyzers/signal_integrity/sparams.py +2 -19
  85. oscura/analyzers/spectral/chunked.py +129 -60
  86. oscura/analyzers/spectral/chunked_fft.py +300 -94
  87. oscura/analyzers/spectral/chunked_wavelet.py +100 -80
  88. oscura/analyzers/statistical/checksum.py +376 -217
  89. oscura/analyzers/statistical/classification.py +229 -107
  90. oscura/analyzers/statistical/entropy.py +78 -53
  91. oscura/analyzers/statistics/correlation.py +407 -211
  92. oscura/analyzers/statistics/outliers.py +2 -2
  93. oscura/analyzers/statistics/streaming.py +30 -5
  94. oscura/analyzers/validation.py +216 -101
  95. oscura/analyzers/waveform/measurements.py +9 -0
  96. oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
  97. oscura/analyzers/waveform/spectral.py +500 -228
  98. oscura/api/__init__.py +31 -5
  99. oscura/api/dsl/__init__.py +582 -0
  100. oscura/{dsl → api/dsl}/commands.py +43 -76
  101. oscura/{dsl → api/dsl}/interpreter.py +26 -51
  102. oscura/{dsl → api/dsl}/parser.py +107 -77
  103. oscura/{dsl → api/dsl}/repl.py +2 -2
  104. oscura/api/dsl.py +1 -1
  105. oscura/{integrations → api/integrations}/__init__.py +1 -1
  106. oscura/{integrations → api/integrations}/llm.py +201 -102
  107. oscura/api/operators.py +3 -3
  108. oscura/api/optimization.py +144 -30
  109. oscura/api/rest_server.py +921 -0
  110. oscura/api/server/__init__.py +17 -0
  111. oscura/api/server/dashboard.py +850 -0
  112. oscura/api/server/static/README.md +34 -0
  113. oscura/api/server/templates/base.html +181 -0
  114. oscura/api/server/templates/export.html +120 -0
  115. oscura/api/server/templates/home.html +284 -0
  116. oscura/api/server/templates/protocols.html +58 -0
  117. oscura/api/server/templates/reports.html +43 -0
  118. oscura/api/server/templates/session_detail.html +89 -0
  119. oscura/api/server/templates/sessions.html +83 -0
  120. oscura/api/server/templates/waveforms.html +73 -0
  121. oscura/automotive/__init__.py +8 -1
  122. oscura/automotive/can/__init__.py +10 -0
  123. oscura/automotive/can/checksum.py +3 -1
  124. oscura/automotive/can/dbc_generator.py +590 -0
  125. oscura/automotive/can/message_wrapper.py +121 -74
  126. oscura/automotive/can/patterns.py +98 -21
  127. oscura/automotive/can/session.py +292 -56
  128. oscura/automotive/can/state_machine.py +6 -3
  129. oscura/automotive/can/stimulus_response.py +97 -75
  130. oscura/automotive/dbc/__init__.py +10 -2
  131. oscura/automotive/dbc/generator.py +84 -56
  132. oscura/automotive/dbc/parser.py +6 -6
  133. oscura/automotive/dtc/data.json +17 -102
  134. oscura/automotive/dtc/database.py +2 -2
  135. oscura/automotive/flexray/__init__.py +31 -0
  136. oscura/automotive/flexray/analyzer.py +504 -0
  137. oscura/automotive/flexray/crc.py +185 -0
  138. oscura/automotive/flexray/fibex.py +449 -0
  139. oscura/automotive/j1939/__init__.py +45 -8
  140. oscura/automotive/j1939/analyzer.py +605 -0
  141. oscura/automotive/j1939/spns.py +326 -0
  142. oscura/automotive/j1939/transport.py +306 -0
  143. oscura/automotive/lin/__init__.py +47 -0
  144. oscura/automotive/lin/analyzer.py +612 -0
  145. oscura/automotive/loaders/blf.py +13 -2
  146. oscura/automotive/loaders/csv_can.py +143 -72
  147. oscura/automotive/loaders/dispatcher.py +50 -2
  148. oscura/automotive/loaders/mdf.py +86 -45
  149. oscura/automotive/loaders/pcap.py +111 -61
  150. oscura/automotive/uds/__init__.py +4 -0
  151. oscura/automotive/uds/analyzer.py +725 -0
  152. oscura/automotive/uds/decoder.py +140 -58
  153. oscura/automotive/uds/models.py +7 -1
  154. oscura/automotive/visualization.py +1 -1
  155. oscura/cli/analyze.py +348 -0
  156. oscura/cli/batch.py +142 -122
  157. oscura/cli/benchmark.py +275 -0
  158. oscura/cli/characterize.py +137 -82
  159. oscura/cli/compare.py +224 -131
  160. oscura/cli/completion.py +250 -0
  161. oscura/cli/config_cmd.py +361 -0
  162. oscura/cli/decode.py +164 -87
  163. oscura/cli/export.py +286 -0
  164. oscura/cli/main.py +115 -31
  165. oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
  166. oscura/{onboarding → cli/onboarding}/help.py +80 -58
  167. oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
  168. oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
  169. oscura/cli/progress.py +147 -0
  170. oscura/cli/shell.py +157 -135
  171. oscura/cli/validate_cmd.py +204 -0
  172. oscura/cli/visualize.py +158 -0
  173. oscura/convenience.py +125 -79
  174. oscura/core/__init__.py +4 -2
  175. oscura/core/backend_selector.py +3 -3
  176. oscura/core/cache.py +126 -15
  177. oscura/core/cancellation.py +1 -1
  178. oscura/{config → core/config}/__init__.py +20 -11
  179. oscura/{config → core/config}/defaults.py +1 -1
  180. oscura/{config → core/config}/loader.py +7 -5
  181. oscura/{config → core/config}/memory.py +5 -5
  182. oscura/{config → core/config}/migration.py +1 -1
  183. oscura/{config → core/config}/pipeline.py +99 -23
  184. oscura/{config → core/config}/preferences.py +1 -1
  185. oscura/{config → core/config}/protocol.py +3 -3
  186. oscura/{config → core/config}/schema.py +426 -272
  187. oscura/{config → core/config}/settings.py +1 -1
  188. oscura/{config → core/config}/thresholds.py +195 -153
  189. oscura/core/correlation.py +5 -6
  190. oscura/core/cross_domain.py +0 -2
  191. oscura/core/debug.py +9 -5
  192. oscura/{extensibility → core/extensibility}/docs.py +158 -70
  193. oscura/{extensibility → core/extensibility}/extensions.py +160 -76
  194. oscura/{extensibility → core/extensibility}/logging.py +1 -1
  195. oscura/{extensibility → core/extensibility}/measurements.py +1 -1
  196. oscura/{extensibility → core/extensibility}/plugins.py +1 -1
  197. oscura/{extensibility → core/extensibility}/templates.py +73 -3
  198. oscura/{extensibility → core/extensibility}/validation.py +1 -1
  199. oscura/core/gpu_backend.py +11 -7
  200. oscura/core/log_query.py +101 -11
  201. oscura/core/logging.py +126 -54
  202. oscura/core/logging_advanced.py +5 -5
  203. oscura/core/memory_limits.py +108 -70
  204. oscura/core/memory_monitor.py +2 -2
  205. oscura/core/memory_progress.py +7 -7
  206. oscura/core/memory_warnings.py +1 -1
  207. oscura/core/numba_backend.py +13 -13
  208. oscura/{plugins → core/plugins}/__init__.py +9 -9
  209. oscura/{plugins → core/plugins}/base.py +7 -7
  210. oscura/{plugins → core/plugins}/cli.py +3 -3
  211. oscura/{plugins → core/plugins}/discovery.py +186 -106
  212. oscura/{plugins → core/plugins}/lifecycle.py +1 -1
  213. oscura/{plugins → core/plugins}/manager.py +7 -7
  214. oscura/{plugins → core/plugins}/registry.py +3 -3
  215. oscura/{plugins → core/plugins}/versioning.py +1 -1
  216. oscura/core/progress.py +16 -1
  217. oscura/core/provenance.py +8 -2
  218. oscura/{schemas → core/schemas}/__init__.py +2 -2
  219. oscura/{schemas → core/schemas}/device_mapping.json +2 -8
  220. oscura/{schemas → core/schemas}/packet_format.json +4 -24
  221. oscura/{schemas → core/schemas}/protocol_definition.json +2 -12
  222. oscura/core/types.py +4 -0
  223. oscura/core/uncertainty.py +3 -3
  224. oscura/correlation/__init__.py +52 -0
  225. oscura/correlation/multi_protocol.py +811 -0
  226. oscura/discovery/auto_decoder.py +117 -35
  227. oscura/discovery/comparison.py +191 -86
  228. oscura/discovery/quality_validator.py +155 -68
  229. oscura/discovery/signal_detector.py +196 -79
  230. oscura/export/__init__.py +18 -8
  231. oscura/export/kaitai_struct.py +513 -0
  232. oscura/export/scapy_layer.py +801 -0
  233. oscura/export/wireshark/generator.py +1 -1
  234. oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
  235. oscura/export/wireshark_dissector.py +746 -0
  236. oscura/guidance/wizard.py +207 -111
  237. oscura/hardware/__init__.py +19 -0
  238. oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
  239. oscura/{acquisition → hardware/acquisition}/file.py +2 -2
  240. oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
  241. oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
  242. oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
  243. oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
  244. oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
  245. oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
  246. oscura/hardware/firmware/__init__.py +29 -0
  247. oscura/hardware/firmware/pattern_recognition.py +874 -0
  248. oscura/hardware/hal_detector.py +736 -0
  249. oscura/hardware/security/__init__.py +37 -0
  250. oscura/hardware/security/side_channel_detector.py +1126 -0
  251. oscura/inference/__init__.py +4 -0
  252. oscura/inference/active_learning/observation_table.py +4 -1
  253. oscura/inference/alignment.py +216 -123
  254. oscura/inference/bayesian.py +113 -33
  255. oscura/inference/crc_reverse.py +101 -55
  256. oscura/inference/logic.py +6 -2
  257. oscura/inference/message_format.py +342 -183
  258. oscura/inference/protocol.py +95 -44
  259. oscura/inference/protocol_dsl.py +180 -82
  260. oscura/inference/signal_intelligence.py +1439 -706
  261. oscura/inference/spectral.py +99 -57
  262. oscura/inference/state_machine.py +810 -158
  263. oscura/inference/stream.py +270 -110
  264. oscura/iot/__init__.py +34 -0
  265. oscura/iot/coap/__init__.py +32 -0
  266. oscura/iot/coap/analyzer.py +668 -0
  267. oscura/iot/coap/options.py +212 -0
  268. oscura/iot/lorawan/__init__.py +21 -0
  269. oscura/iot/lorawan/crypto.py +206 -0
  270. oscura/iot/lorawan/decoder.py +801 -0
  271. oscura/iot/lorawan/mac_commands.py +341 -0
  272. oscura/iot/mqtt/__init__.py +27 -0
  273. oscura/iot/mqtt/analyzer.py +999 -0
  274. oscura/iot/mqtt/properties.py +315 -0
  275. oscura/iot/zigbee/__init__.py +31 -0
  276. oscura/iot/zigbee/analyzer.py +615 -0
  277. oscura/iot/zigbee/security.py +153 -0
  278. oscura/iot/zigbee/zcl.py +349 -0
  279. oscura/jupyter/display.py +125 -45
  280. oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
  281. oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
  282. oscura/jupyter/exploratory/fuzzy.py +746 -0
  283. oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
  284. oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
  285. oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
  286. oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
  287. oscura/jupyter/exploratory/sync.py +612 -0
  288. oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
  289. oscura/jupyter/magic.py +4 -4
  290. oscura/{ui → jupyter/ui}/__init__.py +2 -2
  291. oscura/{ui → jupyter/ui}/formatters.py +3 -3
  292. oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
  293. oscura/loaders/__init__.py +183 -67
  294. oscura/loaders/binary.py +88 -1
  295. oscura/loaders/chipwhisperer.py +153 -137
  296. oscura/loaders/configurable.py +208 -86
  297. oscura/loaders/csv_loader.py +458 -215
  298. oscura/loaders/hdf5_loader.py +278 -119
  299. oscura/loaders/lazy.py +87 -54
  300. oscura/loaders/mmap_loader.py +1 -1
  301. oscura/loaders/numpy_loader.py +253 -116
  302. oscura/loaders/pcap.py +226 -151
  303. oscura/loaders/rigol.py +110 -49
  304. oscura/loaders/sigrok.py +201 -78
  305. oscura/loaders/tdms.py +81 -58
  306. oscura/loaders/tektronix.py +291 -174
  307. oscura/loaders/touchstone.py +182 -87
  308. oscura/loaders/tss.py +456 -0
  309. oscura/loaders/vcd.py +215 -117
  310. oscura/loaders/wav.py +155 -68
  311. oscura/reporting/__init__.py +9 -0
  312. oscura/reporting/analyze.py +352 -146
  313. oscura/reporting/argument_preparer.py +69 -14
  314. oscura/reporting/auto_report.py +97 -61
  315. oscura/reporting/batch.py +131 -58
  316. oscura/reporting/chart_selection.py +57 -45
  317. oscura/reporting/comparison.py +63 -17
  318. oscura/reporting/content/executive.py +76 -24
  319. oscura/reporting/core_formats/multi_format.py +11 -8
  320. oscura/reporting/engine.py +312 -158
  321. oscura/reporting/enhanced_reports.py +949 -0
  322. oscura/reporting/export.py +86 -43
  323. oscura/reporting/formatting/numbers.py +69 -42
  324. oscura/reporting/html.py +139 -58
  325. oscura/reporting/index.py +137 -65
  326. oscura/reporting/output.py +158 -67
  327. oscura/reporting/pdf.py +67 -102
  328. oscura/reporting/plots.py +191 -112
  329. oscura/reporting/sections.py +88 -47
  330. oscura/reporting/standards.py +104 -61
  331. oscura/reporting/summary_generator.py +75 -55
  332. oscura/reporting/tables.py +138 -54
  333. oscura/reporting/templates/enhanced/protocol_re.html +525 -0
  334. oscura/sessions/__init__.py +14 -23
  335. oscura/sessions/base.py +3 -3
  336. oscura/sessions/blackbox.py +106 -10
  337. oscura/sessions/generic.py +2 -2
  338. oscura/sessions/legacy.py +783 -0
  339. oscura/side_channel/__init__.py +63 -0
  340. oscura/side_channel/dpa.py +1025 -0
  341. oscura/utils/__init__.py +15 -1
  342. oscura/utils/bitwise.py +118 -0
  343. oscura/{builders → utils/builders}/__init__.py +1 -1
  344. oscura/{comparison → utils/comparison}/__init__.py +6 -6
  345. oscura/{comparison → utils/comparison}/compare.py +202 -101
  346. oscura/{comparison → utils/comparison}/golden.py +83 -63
  347. oscura/{comparison → utils/comparison}/limits.py +313 -89
  348. oscura/{comparison → utils/comparison}/mask.py +151 -45
  349. oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
  350. oscura/{comparison → utils/comparison}/visualization.py +147 -89
  351. oscura/{component → utils/component}/__init__.py +3 -3
  352. oscura/{component → utils/component}/impedance.py +122 -58
  353. oscura/{component → utils/component}/reactive.py +165 -168
  354. oscura/{component → utils/component}/transmission_line.py +3 -3
  355. oscura/{filtering → utils/filtering}/__init__.py +6 -6
  356. oscura/{filtering → utils/filtering}/base.py +1 -1
  357. oscura/{filtering → utils/filtering}/convenience.py +2 -2
  358. oscura/{filtering → utils/filtering}/design.py +169 -93
  359. oscura/{filtering → utils/filtering}/filters.py +2 -2
  360. oscura/{filtering → utils/filtering}/introspection.py +2 -2
  361. oscura/utils/geometry.py +31 -0
  362. oscura/utils/imports.py +184 -0
  363. oscura/utils/lazy.py +1 -1
  364. oscura/{math → utils/math}/__init__.py +2 -2
  365. oscura/{math → utils/math}/arithmetic.py +114 -48
  366. oscura/{math → utils/math}/interpolation.py +139 -106
  367. oscura/utils/memory.py +129 -66
  368. oscura/utils/memory_advanced.py +92 -9
  369. oscura/utils/memory_extensions.py +10 -8
  370. oscura/{optimization → utils/optimization}/__init__.py +1 -1
  371. oscura/{optimization → utils/optimization}/search.py +2 -2
  372. oscura/utils/performance/__init__.py +58 -0
  373. oscura/utils/performance/caching.py +889 -0
  374. oscura/utils/performance/lsh_clustering.py +333 -0
  375. oscura/utils/performance/memory_optimizer.py +699 -0
  376. oscura/utils/performance/optimizations.py +675 -0
  377. oscura/utils/performance/parallel.py +654 -0
  378. oscura/utils/performance/profiling.py +661 -0
  379. oscura/{pipeline → utils/pipeline}/base.py +1 -1
  380. oscura/{pipeline → utils/pipeline}/composition.py +1 -1
  381. oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
  382. oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
  383. oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
  384. oscura/{search → utils/search}/__init__.py +3 -3
  385. oscura/{search → utils/search}/anomaly.py +188 -58
  386. oscura/utils/search/context.py +294 -0
  387. oscura/{search → utils/search}/pattern.py +138 -10
  388. oscura/utils/serial.py +51 -0
  389. oscura/utils/storage/__init__.py +61 -0
  390. oscura/utils/storage/database.py +1166 -0
  391. oscura/{streaming → utils/streaming}/chunked.py +302 -143
  392. oscura/{streaming → utils/streaming}/progressive.py +1 -1
  393. oscura/{streaming → utils/streaming}/realtime.py +3 -2
  394. oscura/{triggering → utils/triggering}/__init__.py +6 -6
  395. oscura/{triggering → utils/triggering}/base.py +6 -6
  396. oscura/{triggering → utils/triggering}/edge.py +2 -2
  397. oscura/{triggering → utils/triggering}/pattern.py +2 -2
  398. oscura/{triggering → utils/triggering}/pulse.py +115 -74
  399. oscura/{triggering → utils/triggering}/window.py +2 -2
  400. oscura/utils/validation.py +32 -0
  401. oscura/validation/__init__.py +121 -0
  402. oscura/{compliance → validation/compliance}/__init__.py +5 -5
  403. oscura/{compliance → validation/compliance}/advanced.py +5 -5
  404. oscura/{compliance → validation/compliance}/masks.py +1 -1
  405. oscura/{compliance → validation/compliance}/reporting.py +127 -53
  406. oscura/{compliance → validation/compliance}/testing.py +114 -52
  407. oscura/validation/compliance_tests.py +915 -0
  408. oscura/validation/fuzzer.py +990 -0
  409. oscura/validation/grammar_tests.py +596 -0
  410. oscura/validation/grammar_validator.py +904 -0
  411. oscura/validation/hil_testing.py +977 -0
  412. oscura/{quality → validation/quality}/__init__.py +4 -4
  413. oscura/{quality → validation/quality}/ensemble.py +251 -171
  414. oscura/{quality → validation/quality}/explainer.py +3 -3
  415. oscura/{quality → validation/quality}/scoring.py +1 -1
  416. oscura/{quality → validation/quality}/warnings.py +4 -4
  417. oscura/validation/regression_suite.py +808 -0
  418. oscura/validation/replay.py +788 -0
  419. oscura/{testing → validation/testing}/__init__.py +2 -2
  420. oscura/{testing → validation/testing}/synthetic.py +5 -5
  421. oscura/visualization/__init__.py +9 -0
  422. oscura/visualization/accessibility.py +1 -1
  423. oscura/visualization/annotations.py +64 -67
  424. oscura/visualization/colors.py +7 -7
  425. oscura/visualization/digital.py +180 -81
  426. oscura/visualization/eye.py +236 -85
  427. oscura/visualization/interactive.py +320 -143
  428. oscura/visualization/jitter.py +587 -247
  429. oscura/visualization/layout.py +169 -134
  430. oscura/visualization/optimization.py +103 -52
  431. oscura/visualization/palettes.py +1 -1
  432. oscura/visualization/power.py +427 -211
  433. oscura/visualization/power_extended.py +626 -297
  434. oscura/visualization/presets.py +2 -0
  435. oscura/visualization/protocols.py +495 -181
  436. oscura/visualization/render.py +79 -63
  437. oscura/visualization/reverse_engineering.py +171 -124
  438. oscura/visualization/signal_integrity.py +460 -279
  439. oscura/visualization/specialized.py +190 -100
  440. oscura/visualization/spectral.py +670 -255
  441. oscura/visualization/thumbnails.py +166 -137
  442. oscura/visualization/waveform.py +150 -63
  443. oscura/workflows/__init__.py +3 -0
  444. oscura/{batch → workflows/batch}/__init__.py +5 -5
  445. oscura/{batch → workflows/batch}/advanced.py +150 -75
  446. oscura/workflows/batch/aggregate.py +531 -0
  447. oscura/workflows/batch/analyze.py +236 -0
  448. oscura/{batch → workflows/batch}/logging.py +2 -2
  449. oscura/{batch → workflows/batch}/metrics.py +1 -1
  450. oscura/workflows/complete_re.py +1144 -0
  451. oscura/workflows/compliance.py +44 -54
  452. oscura/workflows/digital.py +197 -51
  453. oscura/workflows/legacy/__init__.py +12 -0
  454. oscura/{workflow → workflows/legacy}/dag.py +4 -1
  455. oscura/workflows/multi_trace.py +9 -9
  456. oscura/workflows/power.py +42 -62
  457. oscura/workflows/protocol.py +82 -49
  458. oscura/workflows/reverse_engineering.py +351 -150
  459. oscura/workflows/signal_integrity.py +157 -82
  460. oscura-0.7.0.dist-info/METADATA +661 -0
  461. oscura-0.7.0.dist-info/RECORD +591 -0
  462. oscura/batch/aggregate.py +0 -300
  463. oscura/batch/analyze.py +0 -139
  464. oscura/dsl/__init__.py +0 -73
  465. oscura/exceptions.py +0 -59
  466. oscura/exploratory/fuzzy.py +0 -513
  467. oscura/exploratory/sync.py +0 -384
  468. oscura/exporters/__init__.py +0 -94
  469. oscura/exporters/csv.py +0 -303
  470. oscura/exporters/exporters.py +0 -44
  471. oscura/exporters/hdf5.py +0 -217
  472. oscura/exporters/html_export.py +0 -701
  473. oscura/exporters/json_export.py +0 -291
  474. oscura/exporters/markdown_export.py +0 -367
  475. oscura/exporters/matlab_export.py +0 -354
  476. oscura/exporters/npz_export.py +0 -219
  477. oscura/exporters/spice_export.py +0 -210
  478. oscura/search/context.py +0 -149
  479. oscura/session/__init__.py +0 -34
  480. oscura/session/annotations.py +0 -289
  481. oscura/session/history.py +0 -313
  482. oscura/session/session.py +0 -520
  483. oscura/workflow/__init__.py +0 -13
  484. oscura-0.5.1.dist-info/METADATA +0 -583
  485. oscura-0.5.1.dist-info/RECORD +0 -481
  486. /oscura/core/{config.py → config/legacy.py} +0 -0
  487. /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
  488. /oscura/{extensibility → core/extensibility}/registry.py +0 -0
  489. /oscura/{plugins → core/plugins}/isolation.py +0 -0
  490. /oscura/{schemas → core/schemas}/bus_configuration.json +0 -0
  491. /oscura/{builders → utils/builders}/signal_builder.py +0 -0
  492. /oscura/{optimization → utils/optimization}/parallel.py +0 -0
  493. /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
  494. /oscura/{streaming → utils/streaming}/__init__.py +0 -0
  495. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/WHEEL +0 -0
  496. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/entry_points.txt +0 -0
  497. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -6,7 +6,7 @@ binary sequence alignment.
6
6
 
7
7
 
8
8
  Example:
9
- >>> from oscura.exploratory.fuzzy_advanced import (
9
+ >>> from oscura.jupyter.exploratory.fuzzy_advanced import (
10
10
  ... characterize_variants,
11
11
  ... align_sequences,
12
12
  ... )
@@ -20,7 +20,7 @@ from __future__ import annotations
20
20
  import logging
21
21
  from dataclasses import dataclass
22
22
  from enum import Enum
23
- from typing import TYPE_CHECKING
23
+ from typing import TYPE_CHECKING, Any
24
24
 
25
25
  import numpy as np
26
26
 
@@ -211,54 +211,79 @@ def characterize_variants(
211
211
  VariantCharacterization with analysis results
212
212
 
213
213
  Example:
214
- >>> patterns = [b'\\x12\\x34\\x56', b'\\x12\\x35\\x56', b'\\x12\\x34\\x57']
214
+ >>> patterns = [b'\\x12\\x34\\x56', b'\\x12\\x35\\x56']
215
215
  >>> result = characterize_variants(patterns)
216
- >>> print(f"Consensus: {result.consensus.hex()}")
217
- >>> print(f"Variable positions: {result.variable_positions}")
218
216
 
219
217
  References:
220
218
  FUZZY-004: Binary Pattern Variant Characterization and Consensus
221
219
  """
222
220
  if not patterns:
223
- return VariantCharacterization(
224
- consensus=b"",
225
- positions=[],
226
- constant_positions=[],
227
- variable_positions=[],
228
- suggested_boundaries=[],
229
- pattern_count=0,
230
- min_length=0,
231
- )
221
+ return _create_empty_characterization()
232
222
 
233
223
  pattern_count = len(patterns)
234
224
  min_length = min(len(p) for p in patterns)
235
225
 
226
+ positions, consensus_bytes, constant_positions, variable_positions = _analyze_positions(
227
+ patterns, min_length
228
+ )
229
+
230
+ boundaries = _suggest_field_boundaries(positions)
231
+
232
+ return VariantCharacterization(
233
+ consensus=bytes(consensus_bytes),
234
+ positions=positions,
235
+ constant_positions=constant_positions,
236
+ variable_positions=variable_positions,
237
+ suggested_boundaries=boundaries,
238
+ pattern_count=pattern_count,
239
+ min_length=min_length,
240
+ )
241
+
242
+
243
+ def _create_empty_characterization() -> VariantCharacterization:
244
+ """Create empty characterization result.
245
+
246
+ Returns:
247
+ Empty VariantCharacterization.
248
+ """
249
+ return VariantCharacterization(
250
+ consensus=b"",
251
+ positions=[],
252
+ constant_positions=[],
253
+ variable_positions=[],
254
+ suggested_boundaries=[],
255
+ pattern_count=0,
256
+ min_length=0,
257
+ )
258
+
259
+
260
+ def _analyze_positions(
261
+ patterns: Sequence[bytes | bytearray], min_length: int
262
+ ) -> tuple[list[PositionAnalysis], list[int], list[int], list[int]]:
263
+ """Analyze each position across all patterns.
264
+
265
+ Args:
266
+ patterns: Collection of patterns.
267
+ min_length: Minimum pattern length.
268
+
269
+ Returns:
270
+ Tuple of (positions, consensus_bytes, constant_positions, variable_positions).
271
+ """
236
272
  positions: list[PositionAnalysis] = []
237
273
  consensus_bytes: list[int] = []
238
274
  constant_positions: list[int] = []
239
275
  variable_positions: list[int] = []
240
276
 
241
277
  for pos in range(min_length):
242
- # Collect values at this position
243
278
  values = [p[pos] for p in patterns if pos < len(p)]
279
+ distribution = _build_distribution(values)
244
280
 
245
- # Count distribution
246
- distribution: dict[int, int] = {}
247
- for v in values:
248
- distribution[v] = distribution.get(v, 0) + 1
249
-
250
- # Find consensus (mode)
251
281
  consensus_byte = max(distribution, key=distribution.get) # type: ignore[arg-type]
252
282
  consensus_count = distribution[consensus_byte]
253
283
  consensus_confidence = consensus_count / len(values)
254
284
 
255
- # Compute entropy
256
285
  entropy = _compute_entropy(values)
257
-
258
- # Classify variation
259
286
  variation_type = _classify_variation(entropy, consensus_confidence)
260
-
261
- # Detect error vs intentional variation
262
287
  is_error = _detect_error_variation(values, consensus_byte, consensus_confidence)
263
288
 
264
289
  analysis = PositionAnalysis(
@@ -279,7 +304,33 @@ def characterize_variants(
279
304
  else:
280
305
  variable_positions.append(pos)
281
306
 
282
- # Suggest field boundaries (transitions between constant/variable)
307
+ return positions, consensus_bytes, constant_positions, variable_positions
308
+
309
+
310
+ def _build_distribution(values: list[int]) -> dict[int, int]:
311
+ """Build frequency distribution of values.
312
+
313
+ Args:
314
+ values: List of byte values.
315
+
316
+ Returns:
317
+ Dictionary mapping value to count.
318
+ """
319
+ distribution: dict[int, int] = {}
320
+ for v in values:
321
+ distribution[v] = distribution.get(v, 0) + 1
322
+ return distribution
323
+
324
+
325
+ def _suggest_field_boundaries(positions: list[PositionAnalysis]) -> list[int]:
326
+ """Suggest field boundaries from position analysis.
327
+
328
+ Args:
329
+ positions: List of position analyses.
330
+
331
+ Returns:
332
+ List of boundary positions.
333
+ """
283
334
  boundaries: list[int] = []
284
335
  prev_is_constant = None
285
336
 
@@ -289,15 +340,7 @@ def characterize_variants(
289
340
  boundaries.append(pos)
290
341
  prev_is_constant = is_constant
291
342
 
292
- return VariantCharacterization(
293
- consensus=bytes(consensus_bytes),
294
- positions=positions,
295
- constant_positions=constant_positions,
296
- variable_positions=variable_positions,
297
- suggested_boundaries=boundaries,
298
- pattern_count=pattern_count,
299
- min_length=min_length,
300
- )
343
+ return boundaries
301
344
 
302
345
 
303
346
  # =============================================================================
@@ -371,16 +414,47 @@ def _needleman_wunsch(
371
414
  Returns:
372
415
  (aligned_seq1, aligned_seq2, score)
373
416
  """
374
- m, n = len(seq1), len(seq2)
417
+ # Initialize matrices
418
+ DIAG, UP, LEFT = 0, 1, 2
419
+ score, traceback = _initialize_alignment_matrices(seq1, seq2, gap_open, gap_extend, UP, LEFT)
420
+
421
+ # Fill scoring matrix
422
+ _fill_alignment_matrix(
423
+ seq1,
424
+ seq2,
425
+ score,
426
+ traceback,
427
+ match_bonus,
428
+ mismatch_penalty,
429
+ gap_open,
430
+ gap_extend,
431
+ DIAG,
432
+ UP,
433
+ LEFT,
434
+ )
375
435
 
376
- # Initialize score matrix
436
+ # Traceback to build alignment
437
+ aligned1, aligned2 = _traceback_alignment(
438
+ seq1, seq2, traceback, len(seq1), len(seq2), DIAG, UP, LEFT
439
+ )
440
+
441
+ return bytes(aligned1), bytes(aligned2), float(score[len(seq1), len(seq2)])
442
+
443
+
444
+ def _initialize_alignment_matrices(
445
+ seq1: bytes,
446
+ seq2: bytes,
447
+ gap_open: int,
448
+ gap_extend: int,
449
+ UP: int,
450
+ LEFT: int,
451
+ ) -> tuple[np.ndarray[Any, Any], np.ndarray[Any, Any]]:
452
+ """Initialize score and traceback matrices."""
453
+ m, n = len(seq1), len(seq2)
377
454
  score = np.zeros((m + 1, n + 1), dtype=np.int32)
378
455
  traceback = np.zeros((m + 1, n + 1), dtype=np.int8)
379
456
 
380
- # Direction constants
381
- DIAG, UP, LEFT = 0, 1, 2
382
-
383
- # Initialize first row and column
457
+ # Initialize first row and column with gap penalties
384
458
  for i in range(1, m + 1):
385
459
  score[i, 0] = gap_open + (i - 1) * gap_extend
386
460
  traceback[i, 0] = UP
@@ -389,28 +463,33 @@ def _needleman_wunsch(
389
463
  score[0, j] = gap_open + (j - 1) * gap_extend
390
464
  traceback[0, j] = LEFT
391
465
 
392
- # Fill matrix
393
- for i in range(1, m + 1):
394
- for j in range(1, n + 1):
395
- # Match/mismatch
396
- if seq1[i - 1] == seq2[j - 1]:
397
- diag_score = score[i - 1, j - 1] + match_bonus
398
- else:
399
- diag_score = score[i - 1, j - 1] + mismatch_penalty
466
+ return score, traceback
400
467
 
401
- # Gap in seq2 (moving down)
402
- if traceback[i - 1, j] == UP:
403
- up_score = score[i - 1, j] + gap_extend
404
- else:
405
- up_score = score[i - 1, j] + gap_open
406
468
 
407
- # Gap in seq1 (moving right)
408
- if traceback[i, j - 1] == LEFT:
409
- left_score = score[i, j - 1] + gap_extend
410
- else:
411
- left_score = score[i, j - 1] + gap_open
469
+ def _fill_alignment_matrix(
470
+ seq1: bytes,
471
+ seq2: bytes,
472
+ score: np.ndarray[Any, Any],
473
+ traceback: np.ndarray[Any, Any],
474
+ match_bonus: int,
475
+ mismatch_penalty: int,
476
+ gap_open: int,
477
+ gap_extend: int,
478
+ DIAG: int,
479
+ UP: int,
480
+ LEFT: int,
481
+ ) -> None:
482
+ """Fill alignment matrix using dynamic programming."""
483
+ for i in range(1, len(seq1) + 1):
484
+ for j in range(1, len(seq2) + 1):
485
+ # Calculate scores for three possible moves
486
+ diag_score = score[i - 1, j - 1] + (
487
+ match_bonus if seq1[i - 1] == seq2[j - 1] else mismatch_penalty
488
+ )
489
+ up_score = score[i - 1, j] + (gap_extend if traceback[i - 1, j] == UP else gap_open)
490
+ left_score = score[i, j - 1] + (gap_extend if traceback[i, j - 1] == LEFT else gap_open)
412
491
 
413
- # Choose best
492
+ # Choose best score and direction
414
493
  best = max(diag_score, up_score, left_score)
415
494
  score[i, j] = best
416
495
 
@@ -421,10 +500,20 @@ def _needleman_wunsch(
421
500
  else:
422
501
  traceback[i, j] = LEFT
423
502
 
424
- # Traceback
503
+
504
+ def _traceback_alignment(
505
+ seq1: bytes,
506
+ seq2: bytes,
507
+ traceback: np.ndarray[Any, Any],
508
+ i: int,
509
+ j: int,
510
+ DIAG: int,
511
+ UP: int,
512
+ LEFT: int,
513
+ ) -> tuple[list[int], list[int]]:
514
+ """Traceback through matrix to build final alignment."""
425
515
  aligned1: list[int] = []
426
516
  aligned2: list[int] = []
427
- i, j = m, n
428
517
 
429
518
  while i > 0 or j > 0:
430
519
  if i > 0 and j > 0 and traceback[i, j] == DIAG:
@@ -441,11 +530,9 @@ def _needleman_wunsch(
441
530
  aligned2.append(seq2[j - 1])
442
531
  j -= 1
443
532
 
444
- # Reverse (traceback goes backwards)
445
533
  aligned1.reverse()
446
534
  aligned2.reverse()
447
-
448
- return bytes(aligned1), bytes(aligned2), float(score[m, n])
535
+ return aligned1, aligned2
449
536
 
450
537
 
451
538
  def _smith_waterman(
@@ -660,14 +747,47 @@ def _progressive_alignment(
660
747
  gap_extend: int,
661
748
  ) -> AlignmentResult:
662
749
  """Progressive multiple sequence alignment."""
663
- # Start with first sequence as reference
664
- ref = sequences[0]
665
- aligned_seqs: list[bytes] = [ref]
750
+ # Align sequences progressively
751
+ aligned_seqs, total_score = _align_progressively(sequences, gap_open, gap_extend)
752
+
753
+ # Build result sequences
754
+ result_seqs = _build_result_sequences(sequences, aligned_seqs)
755
+
756
+ # Analyze alignment
757
+ alignment_length = len(aligned_seqs[0]) if aligned_seqs else 0
758
+ conservation = compute_conservation_scores(aligned_seqs)
759
+ conserved_regions = _find_conserved_regions(conservation)
760
+ gap_positions = _find_gap_positions(aligned_seqs, alignment_length)
761
+
762
+ return AlignmentResult(
763
+ sequences=result_seqs,
764
+ conservation_scores=conservation,
765
+ conserved_regions=conserved_regions,
766
+ gap_positions=gap_positions,
767
+ alignment_score=total_score,
768
+ )
769
+
770
+
771
+ def _align_progressively(
772
+ sequences: Sequence[bytes],
773
+ gap_open: int,
774
+ gap_extend: int,
775
+ ) -> tuple[list[bytes], float]:
776
+ """Align sequences progressively.
777
+
778
+ Args:
779
+ sequences: Input sequences.
780
+ gap_open: Gap opening penalty.
781
+ gap_extend: Gap extension penalty.
782
+
783
+ Returns:
784
+ Tuple of (aligned sequences, total score).
785
+ """
786
+ aligned_seqs: list[bytes] = [sequences[0]]
666
787
  total_score = 0.0
667
788
 
668
- # Align each sequence to growing profile
669
789
  for seq in sequences[1:]:
670
- # Align to last aligned sequence (simplified - real MSA uses profiles)
790
+ # Align to reference
671
791
  ref_aligned, seq_aligned, score = _needleman_wunsch(
672
792
  aligned_seqs[0],
673
793
  seq,
@@ -675,25 +795,52 @@ def _progressive_alignment(
675
795
  gap_extend=gap_extend,
676
796
  )
677
797
 
678
- # Update all previous alignments to match new length
679
- new_aligned: list[bytes] = []
680
- for prev in aligned_seqs:
681
- # Insert gaps where ref got new gaps
682
- new_prev: list[int] = []
683
- prev_idx = 0
684
- for byte in ref_aligned:
685
- if byte == GAP_BYTE:
686
- new_prev.append(GAP_BYTE)
687
- elif prev_idx < len(prev):
688
- new_prev.append(prev[prev_idx])
689
- prev_idx += 1
690
- new_aligned.append(bytes(new_prev))
691
-
798
+ # Update existing alignments
799
+ new_aligned = _update_alignments(aligned_seqs, ref_aligned)
692
800
  aligned_seqs = new_aligned
693
801
  aligned_seqs.append(seq_aligned)
694
802
  total_score += score
695
803
 
696
- # Build result
804
+ return aligned_seqs, total_score
805
+
806
+
807
+ def _update_alignments(aligned_seqs: list[bytes], ref_aligned: bytes) -> list[bytes]:
808
+ """Update existing alignments to match reference.
809
+
810
+ Args:
811
+ aligned_seqs: Current aligned sequences.
812
+ ref_aligned: New reference alignment.
813
+
814
+ Returns:
815
+ Updated aligned sequences.
816
+ """
817
+ new_aligned: list[bytes] = []
818
+ for prev in aligned_seqs:
819
+ new_prev: list[int] = []
820
+ prev_idx = 0
821
+ for byte in ref_aligned:
822
+ if byte == GAP_BYTE:
823
+ new_prev.append(GAP_BYTE)
824
+ elif prev_idx < len(prev):
825
+ new_prev.append(prev[prev_idx])
826
+ prev_idx += 1
827
+ new_aligned.append(bytes(new_prev))
828
+ return new_aligned
829
+
830
+
831
+ def _build_result_sequences(
832
+ sequences: Sequence[bytes],
833
+ aligned_seqs: list[bytes],
834
+ ) -> list[AlignedSequence]:
835
+ """Build result sequences from alignment.
836
+
837
+ Args:
838
+ sequences: Original sequences.
839
+ aligned_seqs: Aligned sequences.
840
+
841
+ Returns:
842
+ List of AlignedSequence objects.
843
+ """
697
844
  result_seqs: list[AlignedSequence] = []
698
845
  for orig, aligned in zip(sequences, aligned_seqs, strict=False):
699
846
  gaps = [i for i, b in enumerate(aligned) if b == GAP_BYTE]
@@ -702,15 +849,21 @@ def _progressive_alignment(
702
849
  original=orig,
703
850
  aligned=aligned,
704
851
  gaps=gaps,
705
- score=0.0, # Individual scores not tracked in progressive
852
+ score=0.0,
706
853
  )
707
854
  )
855
+ return result_seqs
708
856
 
709
- # Compute conservation scores
710
- alignment_length = len(aligned_seqs[0]) if aligned_seqs else 0
711
- conservation = compute_conservation_scores(aligned_seqs)
712
857
 
713
- # Find conserved regions
858
+ def _find_conserved_regions(conservation: list[float]) -> list[tuple[int, int]]:
859
+ """Find conserved regions in alignment.
860
+
861
+ Args:
862
+ conservation: Conservation scores.
863
+
864
+ Returns:
865
+ List of (start, end) tuples.
866
+ """
714
867
  conserved_regions: list[tuple[int, int]] = []
715
868
  in_region = False
716
869
  region_start = 0
@@ -726,20 +879,25 @@ def _progressive_alignment(
726
879
  if in_region:
727
880
  conserved_regions.append((region_start, len(conservation) - 1))
728
881
 
729
- # Find common gap positions
882
+ return conserved_regions
883
+
884
+
885
+ def _find_gap_positions(aligned_seqs: list[bytes], alignment_length: int) -> list[int]:
886
+ """Find positions with common gaps.
887
+
888
+ Args:
889
+ aligned_seqs: Aligned sequences.
890
+ alignment_length: Length of alignment.
891
+
892
+ Returns:
893
+ List of gap positions.
894
+ """
730
895
  gap_positions: list[int] = []
731
896
  for pos in range(alignment_length):
732
897
  gap_count = sum(1 for seq in aligned_seqs if seq[pos] == GAP_BYTE)
733
898
  if gap_count > len(aligned_seqs) // 2:
734
899
  gap_positions.append(pos)
735
-
736
- return AlignmentResult(
737
- sequences=result_seqs,
738
- conservation_scores=conservation,
739
- conserved_regions=conserved_regions,
740
- gap_positions=gap_positions,
741
- alignment_score=total_score,
742
- )
900
+ return gap_positions
743
901
 
744
902
 
745
903
  def compute_conservation_scores(