oscura 0.5.0__py3-none-any.whl → 0.6.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (513) hide show
  1. oscura/__init__.py +169 -167
  2. oscura/analyzers/__init__.py +3 -0
  3. oscura/analyzers/classification.py +659 -0
  4. oscura/analyzers/digital/__init__.py +0 -48
  5. oscura/analyzers/digital/edges.py +325 -65
  6. oscura/analyzers/digital/extraction.py +0 -195
  7. oscura/analyzers/digital/quality.py +293 -166
  8. oscura/analyzers/digital/timing.py +260 -115
  9. oscura/analyzers/digital/timing_numba.py +334 -0
  10. oscura/analyzers/entropy.py +605 -0
  11. oscura/analyzers/eye/diagram.py +176 -109
  12. oscura/analyzers/eye/metrics.py +5 -5
  13. oscura/analyzers/jitter/__init__.py +6 -4
  14. oscura/analyzers/jitter/ber.py +52 -52
  15. oscura/analyzers/jitter/classification.py +156 -0
  16. oscura/analyzers/jitter/decomposition.py +163 -113
  17. oscura/analyzers/jitter/spectrum.py +80 -64
  18. oscura/analyzers/ml/__init__.py +39 -0
  19. oscura/analyzers/ml/features.py +600 -0
  20. oscura/analyzers/ml/signal_classifier.py +604 -0
  21. oscura/analyzers/packet/daq.py +246 -158
  22. oscura/analyzers/packet/parser.py +12 -1
  23. oscura/analyzers/packet/payload.py +50 -2110
  24. oscura/analyzers/packet/payload_analysis.py +361 -181
  25. oscura/analyzers/packet/payload_patterns.py +133 -70
  26. oscura/analyzers/packet/stream.py +84 -23
  27. oscura/analyzers/patterns/__init__.py +26 -5
  28. oscura/analyzers/patterns/anomaly_detection.py +908 -0
  29. oscura/analyzers/patterns/clustering.py +169 -108
  30. oscura/analyzers/patterns/clustering_optimized.py +227 -0
  31. oscura/analyzers/patterns/discovery.py +1 -1
  32. oscura/analyzers/patterns/matching.py +581 -197
  33. oscura/analyzers/patterns/pattern_mining.py +778 -0
  34. oscura/analyzers/patterns/periodic.py +121 -38
  35. oscura/analyzers/patterns/sequences.py +175 -78
  36. oscura/analyzers/power/conduction.py +1 -1
  37. oscura/analyzers/power/soa.py +6 -6
  38. oscura/analyzers/power/switching.py +250 -110
  39. oscura/analyzers/protocol/__init__.py +17 -1
  40. oscura/analyzers/protocols/__init__.py +1 -22
  41. oscura/analyzers/protocols/base.py +6 -6
  42. oscura/analyzers/protocols/ble/__init__.py +38 -0
  43. oscura/analyzers/protocols/ble/analyzer.py +809 -0
  44. oscura/analyzers/protocols/ble/uuids.py +288 -0
  45. oscura/analyzers/protocols/can.py +257 -127
  46. oscura/analyzers/protocols/can_fd.py +107 -80
  47. oscura/analyzers/protocols/flexray.py +139 -80
  48. oscura/analyzers/protocols/hdlc.py +93 -58
  49. oscura/analyzers/protocols/i2c.py +247 -106
  50. oscura/analyzers/protocols/i2s.py +138 -86
  51. oscura/analyzers/protocols/industrial/__init__.py +40 -0
  52. oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
  53. oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
  54. oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
  55. oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
  56. oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
  57. oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
  58. oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
  59. oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
  60. oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
  61. oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
  62. oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
  63. oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
  64. oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
  65. oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
  66. oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
  67. oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
  68. oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
  69. oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
  70. oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
  71. oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
  72. oscura/analyzers/protocols/jtag.py +180 -98
  73. oscura/analyzers/protocols/lin.py +219 -114
  74. oscura/analyzers/protocols/manchester.py +4 -4
  75. oscura/analyzers/protocols/onewire.py +253 -149
  76. oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
  77. oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
  78. oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
  79. oscura/analyzers/protocols/spi.py +192 -95
  80. oscura/analyzers/protocols/swd.py +321 -167
  81. oscura/analyzers/protocols/uart.py +267 -125
  82. oscura/analyzers/protocols/usb.py +235 -131
  83. oscura/analyzers/side_channel/power.py +17 -12
  84. oscura/analyzers/signal/__init__.py +15 -0
  85. oscura/analyzers/signal/timing_analysis.py +1086 -0
  86. oscura/analyzers/signal_integrity/__init__.py +4 -1
  87. oscura/analyzers/signal_integrity/sparams.py +2 -19
  88. oscura/analyzers/spectral/chunked.py +129 -60
  89. oscura/analyzers/spectral/chunked_fft.py +300 -94
  90. oscura/analyzers/spectral/chunked_wavelet.py +100 -80
  91. oscura/analyzers/statistical/checksum.py +376 -217
  92. oscura/analyzers/statistical/classification.py +229 -107
  93. oscura/analyzers/statistical/entropy.py +78 -53
  94. oscura/analyzers/statistics/correlation.py +407 -211
  95. oscura/analyzers/statistics/outliers.py +2 -2
  96. oscura/analyzers/statistics/streaming.py +30 -5
  97. oscura/analyzers/validation.py +216 -101
  98. oscura/analyzers/waveform/measurements.py +9 -0
  99. oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
  100. oscura/analyzers/waveform/spectral.py +500 -228
  101. oscura/api/__init__.py +31 -5
  102. oscura/api/dsl/__init__.py +582 -0
  103. oscura/{dsl → api/dsl}/commands.py +43 -76
  104. oscura/{dsl → api/dsl}/interpreter.py +26 -51
  105. oscura/{dsl → api/dsl}/parser.py +107 -77
  106. oscura/{dsl → api/dsl}/repl.py +2 -2
  107. oscura/api/dsl.py +1 -1
  108. oscura/{integrations → api/integrations}/__init__.py +1 -1
  109. oscura/{integrations → api/integrations}/llm.py +201 -102
  110. oscura/api/operators.py +3 -3
  111. oscura/api/optimization.py +144 -30
  112. oscura/api/rest_server.py +921 -0
  113. oscura/api/server/__init__.py +17 -0
  114. oscura/api/server/dashboard.py +850 -0
  115. oscura/api/server/static/README.md +34 -0
  116. oscura/api/server/templates/base.html +181 -0
  117. oscura/api/server/templates/export.html +120 -0
  118. oscura/api/server/templates/home.html +284 -0
  119. oscura/api/server/templates/protocols.html +58 -0
  120. oscura/api/server/templates/reports.html +43 -0
  121. oscura/api/server/templates/session_detail.html +89 -0
  122. oscura/api/server/templates/sessions.html +83 -0
  123. oscura/api/server/templates/waveforms.html +73 -0
  124. oscura/automotive/__init__.py +8 -1
  125. oscura/automotive/can/__init__.py +10 -0
  126. oscura/automotive/can/checksum.py +3 -1
  127. oscura/automotive/can/dbc_generator.py +590 -0
  128. oscura/automotive/can/message_wrapper.py +121 -74
  129. oscura/automotive/can/patterns.py +98 -21
  130. oscura/automotive/can/session.py +292 -56
  131. oscura/automotive/can/state_machine.py +6 -3
  132. oscura/automotive/can/stimulus_response.py +97 -75
  133. oscura/automotive/dbc/__init__.py +10 -2
  134. oscura/automotive/dbc/generator.py +84 -56
  135. oscura/automotive/dbc/parser.py +6 -6
  136. oscura/automotive/dtc/data.json +2763 -0
  137. oscura/automotive/dtc/database.py +2 -2
  138. oscura/automotive/flexray/__init__.py +31 -0
  139. oscura/automotive/flexray/analyzer.py +504 -0
  140. oscura/automotive/flexray/crc.py +185 -0
  141. oscura/automotive/flexray/fibex.py +449 -0
  142. oscura/automotive/j1939/__init__.py +45 -8
  143. oscura/automotive/j1939/analyzer.py +605 -0
  144. oscura/automotive/j1939/spns.py +326 -0
  145. oscura/automotive/j1939/transport.py +306 -0
  146. oscura/automotive/lin/__init__.py +47 -0
  147. oscura/automotive/lin/analyzer.py +612 -0
  148. oscura/automotive/loaders/blf.py +13 -2
  149. oscura/automotive/loaders/csv_can.py +143 -72
  150. oscura/automotive/loaders/dispatcher.py +50 -2
  151. oscura/automotive/loaders/mdf.py +86 -45
  152. oscura/automotive/loaders/pcap.py +111 -61
  153. oscura/automotive/uds/__init__.py +4 -0
  154. oscura/automotive/uds/analyzer.py +725 -0
  155. oscura/automotive/uds/decoder.py +140 -58
  156. oscura/automotive/uds/models.py +7 -1
  157. oscura/automotive/visualization.py +1 -1
  158. oscura/cli/analyze.py +348 -0
  159. oscura/cli/batch.py +142 -122
  160. oscura/cli/benchmark.py +275 -0
  161. oscura/cli/characterize.py +137 -82
  162. oscura/cli/compare.py +224 -131
  163. oscura/cli/completion.py +250 -0
  164. oscura/cli/config_cmd.py +361 -0
  165. oscura/cli/decode.py +164 -87
  166. oscura/cli/export.py +286 -0
  167. oscura/cli/main.py +115 -31
  168. oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
  169. oscura/{onboarding → cli/onboarding}/help.py +80 -58
  170. oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
  171. oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
  172. oscura/cli/progress.py +147 -0
  173. oscura/cli/shell.py +157 -135
  174. oscura/cli/validate_cmd.py +204 -0
  175. oscura/cli/visualize.py +158 -0
  176. oscura/convenience.py +125 -79
  177. oscura/core/__init__.py +4 -2
  178. oscura/core/backend_selector.py +3 -3
  179. oscura/core/cache.py +126 -15
  180. oscura/core/cancellation.py +1 -1
  181. oscura/{config → core/config}/__init__.py +20 -11
  182. oscura/{config → core/config}/defaults.py +1 -1
  183. oscura/{config → core/config}/loader.py +7 -5
  184. oscura/{config → core/config}/memory.py +5 -5
  185. oscura/{config → core/config}/migration.py +1 -1
  186. oscura/{config → core/config}/pipeline.py +99 -23
  187. oscura/{config → core/config}/preferences.py +1 -1
  188. oscura/{config → core/config}/protocol.py +3 -3
  189. oscura/{config → core/config}/schema.py +426 -272
  190. oscura/{config → core/config}/settings.py +1 -1
  191. oscura/{config → core/config}/thresholds.py +195 -153
  192. oscura/core/correlation.py +5 -6
  193. oscura/core/cross_domain.py +0 -2
  194. oscura/core/debug.py +9 -5
  195. oscura/{extensibility → core/extensibility}/docs.py +158 -70
  196. oscura/{extensibility → core/extensibility}/extensions.py +160 -76
  197. oscura/{extensibility → core/extensibility}/logging.py +1 -1
  198. oscura/{extensibility → core/extensibility}/measurements.py +1 -1
  199. oscura/{extensibility → core/extensibility}/plugins.py +1 -1
  200. oscura/{extensibility → core/extensibility}/templates.py +73 -3
  201. oscura/{extensibility → core/extensibility}/validation.py +1 -1
  202. oscura/core/gpu_backend.py +11 -7
  203. oscura/core/log_query.py +101 -11
  204. oscura/core/logging.py +126 -54
  205. oscura/core/logging_advanced.py +5 -5
  206. oscura/core/memory_limits.py +108 -70
  207. oscura/core/memory_monitor.py +2 -2
  208. oscura/core/memory_progress.py +7 -7
  209. oscura/core/memory_warnings.py +1 -1
  210. oscura/core/numba_backend.py +13 -13
  211. oscura/{plugins → core/plugins}/__init__.py +9 -9
  212. oscura/{plugins → core/plugins}/base.py +7 -7
  213. oscura/{plugins → core/plugins}/cli.py +3 -3
  214. oscura/{plugins → core/plugins}/discovery.py +186 -106
  215. oscura/{plugins → core/plugins}/lifecycle.py +1 -1
  216. oscura/{plugins → core/plugins}/manager.py +7 -7
  217. oscura/{plugins → core/plugins}/registry.py +3 -3
  218. oscura/{plugins → core/plugins}/versioning.py +1 -1
  219. oscura/core/progress.py +16 -1
  220. oscura/core/provenance.py +8 -2
  221. oscura/{schemas → core/schemas}/__init__.py +2 -2
  222. oscura/core/schemas/bus_configuration.json +322 -0
  223. oscura/core/schemas/device_mapping.json +182 -0
  224. oscura/core/schemas/packet_format.json +418 -0
  225. oscura/core/schemas/protocol_definition.json +363 -0
  226. oscura/core/types.py +4 -0
  227. oscura/core/uncertainty.py +3 -3
  228. oscura/correlation/__init__.py +52 -0
  229. oscura/correlation/multi_protocol.py +811 -0
  230. oscura/discovery/auto_decoder.py +117 -35
  231. oscura/discovery/comparison.py +191 -86
  232. oscura/discovery/quality_validator.py +155 -68
  233. oscura/discovery/signal_detector.py +196 -79
  234. oscura/export/__init__.py +18 -20
  235. oscura/export/kaitai_struct.py +513 -0
  236. oscura/export/scapy_layer.py +801 -0
  237. oscura/export/wireshark/README.md +15 -15
  238. oscura/export/wireshark/generator.py +1 -1
  239. oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
  240. oscura/export/wireshark_dissector.py +746 -0
  241. oscura/guidance/wizard.py +207 -111
  242. oscura/hardware/__init__.py +19 -0
  243. oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
  244. oscura/{acquisition → hardware/acquisition}/file.py +2 -2
  245. oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
  246. oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
  247. oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
  248. oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
  249. oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
  250. oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
  251. oscura/hardware/firmware/__init__.py +29 -0
  252. oscura/hardware/firmware/pattern_recognition.py +874 -0
  253. oscura/hardware/hal_detector.py +736 -0
  254. oscura/hardware/security/__init__.py +37 -0
  255. oscura/hardware/security/side_channel_detector.py +1126 -0
  256. oscura/inference/__init__.py +4 -0
  257. oscura/inference/active_learning/README.md +7 -7
  258. oscura/inference/active_learning/observation_table.py +4 -1
  259. oscura/inference/alignment.py +216 -123
  260. oscura/inference/bayesian.py +113 -33
  261. oscura/inference/crc_reverse.py +101 -55
  262. oscura/inference/logic.py +6 -2
  263. oscura/inference/message_format.py +342 -183
  264. oscura/inference/protocol.py +95 -44
  265. oscura/inference/protocol_dsl.py +180 -82
  266. oscura/inference/signal_intelligence.py +1439 -706
  267. oscura/inference/spectral.py +99 -57
  268. oscura/inference/state_machine.py +810 -158
  269. oscura/inference/stream.py +270 -110
  270. oscura/iot/__init__.py +34 -0
  271. oscura/iot/coap/__init__.py +32 -0
  272. oscura/iot/coap/analyzer.py +668 -0
  273. oscura/iot/coap/options.py +212 -0
  274. oscura/iot/lorawan/__init__.py +21 -0
  275. oscura/iot/lorawan/crypto.py +206 -0
  276. oscura/iot/lorawan/decoder.py +801 -0
  277. oscura/iot/lorawan/mac_commands.py +341 -0
  278. oscura/iot/mqtt/__init__.py +27 -0
  279. oscura/iot/mqtt/analyzer.py +999 -0
  280. oscura/iot/mqtt/properties.py +315 -0
  281. oscura/iot/zigbee/__init__.py +31 -0
  282. oscura/iot/zigbee/analyzer.py +615 -0
  283. oscura/iot/zigbee/security.py +153 -0
  284. oscura/iot/zigbee/zcl.py +349 -0
  285. oscura/jupyter/display.py +125 -45
  286. oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
  287. oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
  288. oscura/jupyter/exploratory/fuzzy.py +746 -0
  289. oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
  290. oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
  291. oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
  292. oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
  293. oscura/jupyter/exploratory/sync.py +612 -0
  294. oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
  295. oscura/jupyter/magic.py +4 -4
  296. oscura/{ui → jupyter/ui}/__init__.py +2 -2
  297. oscura/{ui → jupyter/ui}/formatters.py +3 -3
  298. oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
  299. oscura/loaders/__init__.py +171 -63
  300. oscura/loaders/binary.py +88 -1
  301. oscura/loaders/chipwhisperer.py +153 -137
  302. oscura/loaders/configurable.py +208 -86
  303. oscura/loaders/csv_loader.py +458 -215
  304. oscura/loaders/hdf5_loader.py +278 -119
  305. oscura/loaders/lazy.py +87 -54
  306. oscura/loaders/mmap_loader.py +1 -1
  307. oscura/loaders/numpy_loader.py +253 -116
  308. oscura/loaders/pcap.py +226 -151
  309. oscura/loaders/rigol.py +110 -49
  310. oscura/loaders/sigrok.py +201 -78
  311. oscura/loaders/tdms.py +81 -58
  312. oscura/loaders/tektronix.py +291 -174
  313. oscura/loaders/touchstone.py +182 -87
  314. oscura/loaders/vcd.py +215 -117
  315. oscura/loaders/wav.py +155 -68
  316. oscura/reporting/__init__.py +9 -7
  317. oscura/reporting/analyze.py +352 -146
  318. oscura/reporting/argument_preparer.py +69 -14
  319. oscura/reporting/auto_report.py +97 -61
  320. oscura/reporting/batch.py +131 -58
  321. oscura/reporting/chart_selection.py +57 -45
  322. oscura/reporting/comparison.py +63 -17
  323. oscura/reporting/content/executive.py +76 -24
  324. oscura/reporting/core_formats/multi_format.py +11 -8
  325. oscura/reporting/engine.py +312 -158
  326. oscura/reporting/enhanced_reports.py +949 -0
  327. oscura/reporting/export.py +86 -43
  328. oscura/reporting/formatting/numbers.py +69 -42
  329. oscura/reporting/html.py +139 -58
  330. oscura/reporting/index.py +137 -65
  331. oscura/reporting/output.py +158 -67
  332. oscura/reporting/pdf.py +67 -102
  333. oscura/reporting/plots.py +191 -112
  334. oscura/reporting/sections.py +88 -47
  335. oscura/reporting/standards.py +104 -61
  336. oscura/reporting/summary_generator.py +75 -55
  337. oscura/reporting/tables.py +138 -54
  338. oscura/reporting/templates/enhanced/protocol_re.html +525 -0
  339. oscura/reporting/templates/index.md +13 -13
  340. oscura/sessions/__init__.py +14 -23
  341. oscura/sessions/base.py +3 -3
  342. oscura/sessions/blackbox.py +106 -10
  343. oscura/sessions/generic.py +2 -2
  344. oscura/sessions/legacy.py +783 -0
  345. oscura/side_channel/__init__.py +63 -0
  346. oscura/side_channel/dpa.py +1025 -0
  347. oscura/utils/__init__.py +15 -1
  348. oscura/utils/autodetect.py +1 -5
  349. oscura/utils/bitwise.py +118 -0
  350. oscura/{builders → utils/builders}/__init__.py +1 -1
  351. oscura/{comparison → utils/comparison}/__init__.py +6 -6
  352. oscura/{comparison → utils/comparison}/compare.py +202 -101
  353. oscura/{comparison → utils/comparison}/golden.py +83 -63
  354. oscura/{comparison → utils/comparison}/limits.py +313 -89
  355. oscura/{comparison → utils/comparison}/mask.py +151 -45
  356. oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
  357. oscura/{comparison → utils/comparison}/visualization.py +147 -89
  358. oscura/{component → utils/component}/__init__.py +3 -3
  359. oscura/{component → utils/component}/impedance.py +122 -58
  360. oscura/{component → utils/component}/reactive.py +165 -168
  361. oscura/{component → utils/component}/transmission_line.py +3 -3
  362. oscura/{filtering → utils/filtering}/__init__.py +6 -6
  363. oscura/{filtering → utils/filtering}/base.py +1 -1
  364. oscura/{filtering → utils/filtering}/convenience.py +2 -2
  365. oscura/{filtering → utils/filtering}/design.py +169 -93
  366. oscura/{filtering → utils/filtering}/filters.py +2 -2
  367. oscura/{filtering → utils/filtering}/introspection.py +2 -2
  368. oscura/utils/geometry.py +31 -0
  369. oscura/utils/imports.py +184 -0
  370. oscura/utils/lazy.py +1 -1
  371. oscura/{math → utils/math}/__init__.py +2 -2
  372. oscura/{math → utils/math}/arithmetic.py +114 -48
  373. oscura/{math → utils/math}/interpolation.py +139 -106
  374. oscura/utils/memory.py +129 -66
  375. oscura/utils/memory_advanced.py +92 -9
  376. oscura/utils/memory_extensions.py +10 -8
  377. oscura/{optimization → utils/optimization}/__init__.py +1 -1
  378. oscura/{optimization → utils/optimization}/search.py +2 -2
  379. oscura/utils/performance/__init__.py +58 -0
  380. oscura/utils/performance/caching.py +889 -0
  381. oscura/utils/performance/lsh_clustering.py +333 -0
  382. oscura/utils/performance/memory_optimizer.py +699 -0
  383. oscura/utils/performance/optimizations.py +675 -0
  384. oscura/utils/performance/parallel.py +654 -0
  385. oscura/utils/performance/profiling.py +661 -0
  386. oscura/{pipeline → utils/pipeline}/base.py +1 -1
  387. oscura/{pipeline → utils/pipeline}/composition.py +11 -3
  388. oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
  389. oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
  390. oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
  391. oscura/{search → utils/search}/__init__.py +3 -3
  392. oscura/{search → utils/search}/anomaly.py +188 -58
  393. oscura/utils/search/context.py +294 -0
  394. oscura/{search → utils/search}/pattern.py +138 -10
  395. oscura/utils/serial.py +51 -0
  396. oscura/utils/storage/__init__.py +61 -0
  397. oscura/utils/storage/database.py +1166 -0
  398. oscura/{streaming → utils/streaming}/chunked.py +302 -143
  399. oscura/{streaming → utils/streaming}/progressive.py +1 -1
  400. oscura/{streaming → utils/streaming}/realtime.py +3 -2
  401. oscura/{triggering → utils/triggering}/__init__.py +6 -6
  402. oscura/{triggering → utils/triggering}/base.py +6 -6
  403. oscura/{triggering → utils/triggering}/edge.py +2 -2
  404. oscura/{triggering → utils/triggering}/pattern.py +2 -2
  405. oscura/{triggering → utils/triggering}/pulse.py +115 -74
  406. oscura/{triggering → utils/triggering}/window.py +2 -2
  407. oscura/utils/validation.py +32 -0
  408. oscura/validation/__init__.py +121 -0
  409. oscura/{compliance → validation/compliance}/__init__.py +5 -5
  410. oscura/{compliance → validation/compliance}/advanced.py +5 -5
  411. oscura/{compliance → validation/compliance}/masks.py +1 -1
  412. oscura/{compliance → validation/compliance}/reporting.py +127 -53
  413. oscura/{compliance → validation/compliance}/testing.py +114 -52
  414. oscura/validation/compliance_tests.py +915 -0
  415. oscura/validation/fuzzer.py +990 -0
  416. oscura/validation/grammar_tests.py +596 -0
  417. oscura/validation/grammar_validator.py +904 -0
  418. oscura/validation/hil_testing.py +977 -0
  419. oscura/{quality → validation/quality}/__init__.py +4 -4
  420. oscura/{quality → validation/quality}/ensemble.py +251 -171
  421. oscura/{quality → validation/quality}/explainer.py +3 -3
  422. oscura/{quality → validation/quality}/scoring.py +1 -1
  423. oscura/{quality → validation/quality}/warnings.py +4 -4
  424. oscura/validation/regression_suite.py +808 -0
  425. oscura/validation/replay.py +788 -0
  426. oscura/{testing → validation/testing}/__init__.py +2 -2
  427. oscura/{testing → validation/testing}/synthetic.py +5 -5
  428. oscura/visualization/__init__.py +9 -0
  429. oscura/visualization/accessibility.py +1 -1
  430. oscura/visualization/annotations.py +64 -67
  431. oscura/visualization/colors.py +7 -7
  432. oscura/visualization/digital.py +180 -81
  433. oscura/visualization/eye.py +236 -85
  434. oscura/visualization/interactive.py +320 -143
  435. oscura/visualization/jitter.py +587 -247
  436. oscura/visualization/layout.py +169 -134
  437. oscura/visualization/optimization.py +103 -52
  438. oscura/visualization/palettes.py +1 -1
  439. oscura/visualization/power.py +427 -211
  440. oscura/visualization/power_extended.py +626 -297
  441. oscura/visualization/presets.py +2 -0
  442. oscura/visualization/protocols.py +495 -181
  443. oscura/visualization/render.py +79 -63
  444. oscura/visualization/reverse_engineering.py +171 -124
  445. oscura/visualization/signal_integrity.py +460 -279
  446. oscura/visualization/specialized.py +190 -100
  447. oscura/visualization/spectral.py +670 -255
  448. oscura/visualization/thumbnails.py +166 -137
  449. oscura/visualization/waveform.py +150 -63
  450. oscura/workflows/__init__.py +3 -0
  451. oscura/{batch → workflows/batch}/__init__.py +5 -5
  452. oscura/{batch → workflows/batch}/advanced.py +150 -75
  453. oscura/workflows/batch/aggregate.py +531 -0
  454. oscura/workflows/batch/analyze.py +236 -0
  455. oscura/{batch → workflows/batch}/logging.py +2 -2
  456. oscura/{batch → workflows/batch}/metrics.py +1 -1
  457. oscura/workflows/complete_re.py +1144 -0
  458. oscura/workflows/compliance.py +44 -54
  459. oscura/workflows/digital.py +197 -51
  460. oscura/workflows/legacy/__init__.py +12 -0
  461. oscura/{workflow → workflows/legacy}/dag.py +4 -1
  462. oscura/workflows/multi_trace.py +9 -9
  463. oscura/workflows/power.py +42 -62
  464. oscura/workflows/protocol.py +82 -49
  465. oscura/workflows/reverse_engineering.py +351 -150
  466. oscura/workflows/signal_integrity.py +157 -82
  467. oscura-0.6.0.dist-info/METADATA +643 -0
  468. oscura-0.6.0.dist-info/RECORD +590 -0
  469. oscura/analyzers/digital/ic_database.py +0 -498
  470. oscura/analyzers/digital/timing_paths.py +0 -339
  471. oscura/analyzers/digital/vintage.py +0 -377
  472. oscura/analyzers/digital/vintage_result.py +0 -148
  473. oscura/analyzers/protocols/parallel_bus.py +0 -449
  474. oscura/batch/aggregate.py +0 -300
  475. oscura/batch/analyze.py +0 -139
  476. oscura/dsl/__init__.py +0 -73
  477. oscura/exceptions.py +0 -59
  478. oscura/exploratory/fuzzy.py +0 -513
  479. oscura/exploratory/sync.py +0 -384
  480. oscura/export/wavedrom.py +0 -430
  481. oscura/exporters/__init__.py +0 -94
  482. oscura/exporters/csv.py +0 -303
  483. oscura/exporters/exporters.py +0 -44
  484. oscura/exporters/hdf5.py +0 -217
  485. oscura/exporters/html_export.py +0 -701
  486. oscura/exporters/json_export.py +0 -338
  487. oscura/exporters/markdown_export.py +0 -367
  488. oscura/exporters/matlab_export.py +0 -354
  489. oscura/exporters/npz_export.py +0 -219
  490. oscura/exporters/spice_export.py +0 -210
  491. oscura/exporters/vintage_logic_csv.py +0 -247
  492. oscura/reporting/vintage_logic_report.py +0 -523
  493. oscura/search/context.py +0 -149
  494. oscura/session/__init__.py +0 -34
  495. oscura/session/annotations.py +0 -289
  496. oscura/session/history.py +0 -313
  497. oscura/session/session.py +0 -520
  498. oscura/visualization/digital_advanced.py +0 -718
  499. oscura/visualization/figure_manager.py +0 -156
  500. oscura/workflow/__init__.py +0 -13
  501. oscura-0.5.0.dist-info/METADATA +0 -407
  502. oscura-0.5.0.dist-info/RECORD +0 -486
  503. /oscura/core/{config.py → config/legacy.py} +0 -0
  504. /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
  505. /oscura/{extensibility → core/extensibility}/registry.py +0 -0
  506. /oscura/{plugins → core/plugins}/isolation.py +0 -0
  507. /oscura/{builders → utils/builders}/signal_builder.py +0 -0
  508. /oscura/{optimization → utils/optimization}/parallel.py +0 -0
  509. /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
  510. /oscura/{streaming → utils/streaming}/__init__.py +0 -0
  511. {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/WHEEL +0 -0
  512. {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/entry_points.txt +0 -0
  513. {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/licenses/LICENSE +0 -0
@@ -6,7 +6,7 @@ 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(