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.
- oscura/__init__.py +169 -167
- oscura/analyzers/__init__.py +3 -0
- oscura/analyzers/classification.py +659 -0
- oscura/analyzers/digital/edges.py +325 -65
- oscura/analyzers/digital/quality.py +293 -166
- oscura/analyzers/digital/timing.py +260 -115
- oscura/analyzers/digital/timing_numba.py +334 -0
- oscura/analyzers/entropy.py +605 -0
- oscura/analyzers/eye/diagram.py +176 -109
- oscura/analyzers/eye/metrics.py +5 -5
- oscura/analyzers/jitter/__init__.py +6 -4
- oscura/analyzers/jitter/ber.py +52 -52
- oscura/analyzers/jitter/classification.py +156 -0
- oscura/analyzers/jitter/decomposition.py +163 -113
- oscura/analyzers/jitter/spectrum.py +80 -64
- oscura/analyzers/ml/__init__.py +39 -0
- oscura/analyzers/ml/features.py +600 -0
- oscura/analyzers/ml/signal_classifier.py +604 -0
- oscura/analyzers/packet/daq.py +246 -158
- oscura/analyzers/packet/parser.py +12 -1
- oscura/analyzers/packet/payload.py +50 -2110
- oscura/analyzers/packet/payload_analysis.py +361 -181
- oscura/analyzers/packet/payload_patterns.py +133 -70
- oscura/analyzers/packet/stream.py +84 -23
- oscura/analyzers/patterns/__init__.py +26 -5
- oscura/analyzers/patterns/anomaly_detection.py +908 -0
- oscura/analyzers/patterns/clustering.py +169 -108
- oscura/analyzers/patterns/clustering_optimized.py +227 -0
- oscura/analyzers/patterns/discovery.py +1 -1
- oscura/analyzers/patterns/matching.py +581 -197
- oscura/analyzers/patterns/pattern_mining.py +778 -0
- oscura/analyzers/patterns/periodic.py +121 -38
- oscura/analyzers/patterns/sequences.py +175 -78
- oscura/analyzers/power/conduction.py +1 -1
- oscura/analyzers/power/soa.py +6 -6
- oscura/analyzers/power/switching.py +250 -110
- oscura/analyzers/protocol/__init__.py +17 -1
- oscura/analyzers/protocols/base.py +6 -6
- oscura/analyzers/protocols/ble/__init__.py +38 -0
- oscura/analyzers/protocols/ble/analyzer.py +809 -0
- oscura/analyzers/protocols/ble/uuids.py +288 -0
- oscura/analyzers/protocols/can.py +257 -127
- oscura/analyzers/protocols/can_fd.py +107 -80
- oscura/analyzers/protocols/flexray.py +139 -80
- oscura/analyzers/protocols/hdlc.py +93 -58
- oscura/analyzers/protocols/i2c.py +247 -106
- oscura/analyzers/protocols/i2s.py +138 -86
- oscura/analyzers/protocols/industrial/__init__.py +40 -0
- oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
- oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
- oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
- oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
- oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
- oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
- oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
- oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
- oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
- oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
- oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
- oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
- oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
- oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
- oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
- oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
- oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
- oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
- oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
- oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
- oscura/analyzers/protocols/jtag.py +180 -98
- oscura/analyzers/protocols/lin.py +219 -114
- oscura/analyzers/protocols/manchester.py +4 -4
- oscura/analyzers/protocols/onewire.py +253 -149
- oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
- oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
- oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
- oscura/analyzers/protocols/spi.py +192 -95
- oscura/analyzers/protocols/swd.py +321 -167
- oscura/analyzers/protocols/uart.py +267 -125
- oscura/analyzers/protocols/usb.py +235 -131
- oscura/analyzers/side_channel/power.py +17 -12
- oscura/analyzers/signal/__init__.py +15 -0
- oscura/analyzers/signal/timing_analysis.py +1086 -0
- oscura/analyzers/signal_integrity/__init__.py +4 -1
- oscura/analyzers/signal_integrity/sparams.py +2 -19
- oscura/analyzers/spectral/chunked.py +129 -60
- oscura/analyzers/spectral/chunked_fft.py +300 -94
- oscura/analyzers/spectral/chunked_wavelet.py +100 -80
- oscura/analyzers/statistical/checksum.py +376 -217
- oscura/analyzers/statistical/classification.py +229 -107
- oscura/analyzers/statistical/entropy.py +78 -53
- oscura/analyzers/statistics/correlation.py +407 -211
- oscura/analyzers/statistics/outliers.py +2 -2
- oscura/analyzers/statistics/streaming.py +30 -5
- oscura/analyzers/validation.py +216 -101
- oscura/analyzers/waveform/measurements.py +9 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
- oscura/analyzers/waveform/spectral.py +500 -228
- oscura/api/__init__.py +31 -5
- oscura/api/dsl/__init__.py +582 -0
- oscura/{dsl → api/dsl}/commands.py +43 -76
- oscura/{dsl → api/dsl}/interpreter.py +26 -51
- oscura/{dsl → api/dsl}/parser.py +107 -77
- oscura/{dsl → api/dsl}/repl.py +2 -2
- oscura/api/dsl.py +1 -1
- oscura/{integrations → api/integrations}/__init__.py +1 -1
- oscura/{integrations → api/integrations}/llm.py +201 -102
- oscura/api/operators.py +3 -3
- oscura/api/optimization.py +144 -30
- oscura/api/rest_server.py +921 -0
- oscura/api/server/__init__.py +17 -0
- oscura/api/server/dashboard.py +850 -0
- oscura/api/server/static/README.md +34 -0
- oscura/api/server/templates/base.html +181 -0
- oscura/api/server/templates/export.html +120 -0
- oscura/api/server/templates/home.html +284 -0
- oscura/api/server/templates/protocols.html +58 -0
- oscura/api/server/templates/reports.html +43 -0
- oscura/api/server/templates/session_detail.html +89 -0
- oscura/api/server/templates/sessions.html +83 -0
- oscura/api/server/templates/waveforms.html +73 -0
- oscura/automotive/__init__.py +8 -1
- oscura/automotive/can/__init__.py +10 -0
- oscura/automotive/can/checksum.py +3 -1
- oscura/automotive/can/dbc_generator.py +590 -0
- oscura/automotive/can/message_wrapper.py +121 -74
- oscura/automotive/can/patterns.py +98 -21
- oscura/automotive/can/session.py +292 -56
- oscura/automotive/can/state_machine.py +6 -3
- oscura/automotive/can/stimulus_response.py +97 -75
- oscura/automotive/dbc/__init__.py +10 -2
- oscura/automotive/dbc/generator.py +84 -56
- oscura/automotive/dbc/parser.py +6 -6
- oscura/automotive/dtc/data.json +17 -102
- oscura/automotive/dtc/database.py +2 -2
- oscura/automotive/flexray/__init__.py +31 -0
- oscura/automotive/flexray/analyzer.py +504 -0
- oscura/automotive/flexray/crc.py +185 -0
- oscura/automotive/flexray/fibex.py +449 -0
- oscura/automotive/j1939/__init__.py +45 -8
- oscura/automotive/j1939/analyzer.py +605 -0
- oscura/automotive/j1939/spns.py +326 -0
- oscura/automotive/j1939/transport.py +306 -0
- oscura/automotive/lin/__init__.py +47 -0
- oscura/automotive/lin/analyzer.py +612 -0
- oscura/automotive/loaders/blf.py +13 -2
- oscura/automotive/loaders/csv_can.py +143 -72
- oscura/automotive/loaders/dispatcher.py +50 -2
- oscura/automotive/loaders/mdf.py +86 -45
- oscura/automotive/loaders/pcap.py +111 -61
- oscura/automotive/uds/__init__.py +4 -0
- oscura/automotive/uds/analyzer.py +725 -0
- oscura/automotive/uds/decoder.py +140 -58
- oscura/automotive/uds/models.py +7 -1
- oscura/automotive/visualization.py +1 -1
- oscura/cli/analyze.py +348 -0
- oscura/cli/batch.py +142 -122
- oscura/cli/benchmark.py +275 -0
- oscura/cli/characterize.py +137 -82
- oscura/cli/compare.py +224 -131
- oscura/cli/completion.py +250 -0
- oscura/cli/config_cmd.py +361 -0
- oscura/cli/decode.py +164 -87
- oscura/cli/export.py +286 -0
- oscura/cli/main.py +115 -31
- oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
- oscura/{onboarding → cli/onboarding}/help.py +80 -58
- oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
- oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
- oscura/cli/progress.py +147 -0
- oscura/cli/shell.py +157 -135
- oscura/cli/validate_cmd.py +204 -0
- oscura/cli/visualize.py +158 -0
- oscura/convenience.py +125 -79
- oscura/core/__init__.py +4 -2
- oscura/core/backend_selector.py +3 -3
- oscura/core/cache.py +126 -15
- oscura/core/cancellation.py +1 -1
- oscura/{config → core/config}/__init__.py +20 -11
- oscura/{config → core/config}/defaults.py +1 -1
- oscura/{config → core/config}/loader.py +7 -5
- oscura/{config → core/config}/memory.py +5 -5
- oscura/{config → core/config}/migration.py +1 -1
- oscura/{config → core/config}/pipeline.py +99 -23
- oscura/{config → core/config}/preferences.py +1 -1
- oscura/{config → core/config}/protocol.py +3 -3
- oscura/{config → core/config}/schema.py +426 -272
- oscura/{config → core/config}/settings.py +1 -1
- oscura/{config → core/config}/thresholds.py +195 -153
- oscura/core/correlation.py +5 -6
- oscura/core/cross_domain.py +0 -2
- oscura/core/debug.py +9 -5
- oscura/{extensibility → core/extensibility}/docs.py +158 -70
- oscura/{extensibility → core/extensibility}/extensions.py +160 -76
- oscura/{extensibility → core/extensibility}/logging.py +1 -1
- oscura/{extensibility → core/extensibility}/measurements.py +1 -1
- oscura/{extensibility → core/extensibility}/plugins.py +1 -1
- oscura/{extensibility → core/extensibility}/templates.py +73 -3
- oscura/{extensibility → core/extensibility}/validation.py +1 -1
- oscura/core/gpu_backend.py +11 -7
- oscura/core/log_query.py +101 -11
- oscura/core/logging.py +126 -54
- oscura/core/logging_advanced.py +5 -5
- oscura/core/memory_limits.py +108 -70
- oscura/core/memory_monitor.py +2 -2
- oscura/core/memory_progress.py +7 -7
- oscura/core/memory_warnings.py +1 -1
- oscura/core/numba_backend.py +13 -13
- oscura/{plugins → core/plugins}/__init__.py +9 -9
- oscura/{plugins → core/plugins}/base.py +7 -7
- oscura/{plugins → core/plugins}/cli.py +3 -3
- oscura/{plugins → core/plugins}/discovery.py +186 -106
- oscura/{plugins → core/plugins}/lifecycle.py +1 -1
- oscura/{plugins → core/plugins}/manager.py +7 -7
- oscura/{plugins → core/plugins}/registry.py +3 -3
- oscura/{plugins → core/plugins}/versioning.py +1 -1
- oscura/core/progress.py +16 -1
- oscura/core/provenance.py +8 -2
- oscura/{schemas → core/schemas}/__init__.py +2 -2
- oscura/{schemas → core/schemas}/device_mapping.json +2 -8
- oscura/{schemas → core/schemas}/packet_format.json +4 -24
- oscura/{schemas → core/schemas}/protocol_definition.json +2 -12
- oscura/core/types.py +4 -0
- oscura/core/uncertainty.py +3 -3
- oscura/correlation/__init__.py +52 -0
- oscura/correlation/multi_protocol.py +811 -0
- oscura/discovery/auto_decoder.py +117 -35
- oscura/discovery/comparison.py +191 -86
- oscura/discovery/quality_validator.py +155 -68
- oscura/discovery/signal_detector.py +196 -79
- oscura/export/__init__.py +18 -8
- oscura/export/kaitai_struct.py +513 -0
- oscura/export/scapy_layer.py +801 -0
- oscura/export/wireshark/generator.py +1 -1
- oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
- oscura/export/wireshark_dissector.py +746 -0
- oscura/guidance/wizard.py +207 -111
- oscura/hardware/__init__.py +19 -0
- oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
- oscura/{acquisition → hardware/acquisition}/file.py +2 -2
- oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
- oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
- oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
- oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
- oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
- oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
- oscura/hardware/firmware/__init__.py +29 -0
- oscura/hardware/firmware/pattern_recognition.py +874 -0
- oscura/hardware/hal_detector.py +736 -0
- oscura/hardware/security/__init__.py +37 -0
- oscura/hardware/security/side_channel_detector.py +1126 -0
- oscura/inference/__init__.py +4 -0
- oscura/inference/active_learning/observation_table.py +4 -1
- oscura/inference/alignment.py +216 -123
- oscura/inference/bayesian.py +113 -33
- oscura/inference/crc_reverse.py +101 -55
- oscura/inference/logic.py +6 -2
- oscura/inference/message_format.py +342 -183
- oscura/inference/protocol.py +95 -44
- oscura/inference/protocol_dsl.py +180 -82
- oscura/inference/signal_intelligence.py +1439 -706
- oscura/inference/spectral.py +99 -57
- oscura/inference/state_machine.py +810 -158
- oscura/inference/stream.py +270 -110
- oscura/iot/__init__.py +34 -0
- oscura/iot/coap/__init__.py +32 -0
- oscura/iot/coap/analyzer.py +668 -0
- oscura/iot/coap/options.py +212 -0
- oscura/iot/lorawan/__init__.py +21 -0
- oscura/iot/lorawan/crypto.py +206 -0
- oscura/iot/lorawan/decoder.py +801 -0
- oscura/iot/lorawan/mac_commands.py +341 -0
- oscura/iot/mqtt/__init__.py +27 -0
- oscura/iot/mqtt/analyzer.py +999 -0
- oscura/iot/mqtt/properties.py +315 -0
- oscura/iot/zigbee/__init__.py +31 -0
- oscura/iot/zigbee/analyzer.py +615 -0
- oscura/iot/zigbee/security.py +153 -0
- oscura/iot/zigbee/zcl.py +349 -0
- oscura/jupyter/display.py +125 -45
- oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
- oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
- oscura/jupyter/exploratory/fuzzy.py +746 -0
- oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
- oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
- oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
- oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
- oscura/jupyter/exploratory/sync.py +612 -0
- oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
- oscura/jupyter/magic.py +4 -4
- oscura/{ui → jupyter/ui}/__init__.py +2 -2
- oscura/{ui → jupyter/ui}/formatters.py +3 -3
- oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
- oscura/loaders/__init__.py +183 -67
- oscura/loaders/binary.py +88 -1
- oscura/loaders/chipwhisperer.py +153 -137
- oscura/loaders/configurable.py +208 -86
- oscura/loaders/csv_loader.py +458 -215
- oscura/loaders/hdf5_loader.py +278 -119
- oscura/loaders/lazy.py +87 -54
- oscura/loaders/mmap_loader.py +1 -1
- oscura/loaders/numpy_loader.py +253 -116
- oscura/loaders/pcap.py +226 -151
- oscura/loaders/rigol.py +110 -49
- oscura/loaders/sigrok.py +201 -78
- oscura/loaders/tdms.py +81 -58
- oscura/loaders/tektronix.py +291 -174
- oscura/loaders/touchstone.py +182 -87
- oscura/loaders/tss.py +456 -0
- oscura/loaders/vcd.py +215 -117
- oscura/loaders/wav.py +155 -68
- oscura/reporting/__init__.py +9 -0
- oscura/reporting/analyze.py +352 -146
- oscura/reporting/argument_preparer.py +69 -14
- oscura/reporting/auto_report.py +97 -61
- oscura/reporting/batch.py +131 -58
- oscura/reporting/chart_selection.py +57 -45
- oscura/reporting/comparison.py +63 -17
- oscura/reporting/content/executive.py +76 -24
- oscura/reporting/core_formats/multi_format.py +11 -8
- oscura/reporting/engine.py +312 -158
- oscura/reporting/enhanced_reports.py +949 -0
- oscura/reporting/export.py +86 -43
- oscura/reporting/formatting/numbers.py +69 -42
- oscura/reporting/html.py +139 -58
- oscura/reporting/index.py +137 -65
- oscura/reporting/output.py +158 -67
- oscura/reporting/pdf.py +67 -102
- oscura/reporting/plots.py +191 -112
- oscura/reporting/sections.py +88 -47
- oscura/reporting/standards.py +104 -61
- oscura/reporting/summary_generator.py +75 -55
- oscura/reporting/tables.py +138 -54
- oscura/reporting/templates/enhanced/protocol_re.html +525 -0
- oscura/sessions/__init__.py +14 -23
- oscura/sessions/base.py +3 -3
- oscura/sessions/blackbox.py +106 -10
- oscura/sessions/generic.py +2 -2
- oscura/sessions/legacy.py +783 -0
- oscura/side_channel/__init__.py +63 -0
- oscura/side_channel/dpa.py +1025 -0
- oscura/utils/__init__.py +15 -1
- oscura/utils/bitwise.py +118 -0
- oscura/{builders → utils/builders}/__init__.py +1 -1
- oscura/{comparison → utils/comparison}/__init__.py +6 -6
- oscura/{comparison → utils/comparison}/compare.py +202 -101
- oscura/{comparison → utils/comparison}/golden.py +83 -63
- oscura/{comparison → utils/comparison}/limits.py +313 -89
- oscura/{comparison → utils/comparison}/mask.py +151 -45
- oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
- oscura/{comparison → utils/comparison}/visualization.py +147 -89
- oscura/{component → utils/component}/__init__.py +3 -3
- oscura/{component → utils/component}/impedance.py +122 -58
- oscura/{component → utils/component}/reactive.py +165 -168
- oscura/{component → utils/component}/transmission_line.py +3 -3
- oscura/{filtering → utils/filtering}/__init__.py +6 -6
- oscura/{filtering → utils/filtering}/base.py +1 -1
- oscura/{filtering → utils/filtering}/convenience.py +2 -2
- oscura/{filtering → utils/filtering}/design.py +169 -93
- oscura/{filtering → utils/filtering}/filters.py +2 -2
- oscura/{filtering → utils/filtering}/introspection.py +2 -2
- oscura/utils/geometry.py +31 -0
- oscura/utils/imports.py +184 -0
- oscura/utils/lazy.py +1 -1
- oscura/{math → utils/math}/__init__.py +2 -2
- oscura/{math → utils/math}/arithmetic.py +114 -48
- oscura/{math → utils/math}/interpolation.py +139 -106
- oscura/utils/memory.py +129 -66
- oscura/utils/memory_advanced.py +92 -9
- oscura/utils/memory_extensions.py +10 -8
- oscura/{optimization → utils/optimization}/__init__.py +1 -1
- oscura/{optimization → utils/optimization}/search.py +2 -2
- oscura/utils/performance/__init__.py +58 -0
- oscura/utils/performance/caching.py +889 -0
- oscura/utils/performance/lsh_clustering.py +333 -0
- oscura/utils/performance/memory_optimizer.py +699 -0
- oscura/utils/performance/optimizations.py +675 -0
- oscura/utils/performance/parallel.py +654 -0
- oscura/utils/performance/profiling.py +661 -0
- oscura/{pipeline → utils/pipeline}/base.py +1 -1
- oscura/{pipeline → utils/pipeline}/composition.py +1 -1
- oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
- oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
- oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
- oscura/{search → utils/search}/__init__.py +3 -3
- oscura/{search → utils/search}/anomaly.py +188 -58
- oscura/utils/search/context.py +294 -0
- oscura/{search → utils/search}/pattern.py +138 -10
- oscura/utils/serial.py +51 -0
- oscura/utils/storage/__init__.py +61 -0
- oscura/utils/storage/database.py +1166 -0
- oscura/{streaming → utils/streaming}/chunked.py +302 -143
- oscura/{streaming → utils/streaming}/progressive.py +1 -1
- oscura/{streaming → utils/streaming}/realtime.py +3 -2
- oscura/{triggering → utils/triggering}/__init__.py +6 -6
- oscura/{triggering → utils/triggering}/base.py +6 -6
- oscura/{triggering → utils/triggering}/edge.py +2 -2
- oscura/{triggering → utils/triggering}/pattern.py +2 -2
- oscura/{triggering → utils/triggering}/pulse.py +115 -74
- oscura/{triggering → utils/triggering}/window.py +2 -2
- oscura/utils/validation.py +32 -0
- oscura/validation/__init__.py +121 -0
- oscura/{compliance → validation/compliance}/__init__.py +5 -5
- oscura/{compliance → validation/compliance}/advanced.py +5 -5
- oscura/{compliance → validation/compliance}/masks.py +1 -1
- oscura/{compliance → validation/compliance}/reporting.py +127 -53
- oscura/{compliance → validation/compliance}/testing.py +114 -52
- oscura/validation/compliance_tests.py +915 -0
- oscura/validation/fuzzer.py +990 -0
- oscura/validation/grammar_tests.py +596 -0
- oscura/validation/grammar_validator.py +904 -0
- oscura/validation/hil_testing.py +977 -0
- oscura/{quality → validation/quality}/__init__.py +4 -4
- oscura/{quality → validation/quality}/ensemble.py +251 -171
- oscura/{quality → validation/quality}/explainer.py +3 -3
- oscura/{quality → validation/quality}/scoring.py +1 -1
- oscura/{quality → validation/quality}/warnings.py +4 -4
- oscura/validation/regression_suite.py +808 -0
- oscura/validation/replay.py +788 -0
- oscura/{testing → validation/testing}/__init__.py +2 -2
- oscura/{testing → validation/testing}/synthetic.py +5 -5
- oscura/visualization/__init__.py +9 -0
- oscura/visualization/accessibility.py +1 -1
- oscura/visualization/annotations.py +64 -67
- oscura/visualization/colors.py +7 -7
- oscura/visualization/digital.py +180 -81
- oscura/visualization/eye.py +236 -85
- oscura/visualization/interactive.py +320 -143
- oscura/visualization/jitter.py +587 -247
- oscura/visualization/layout.py +169 -134
- oscura/visualization/optimization.py +103 -52
- oscura/visualization/palettes.py +1 -1
- oscura/visualization/power.py +427 -211
- oscura/visualization/power_extended.py +626 -297
- oscura/visualization/presets.py +2 -0
- oscura/visualization/protocols.py +495 -181
- oscura/visualization/render.py +79 -63
- oscura/visualization/reverse_engineering.py +171 -124
- oscura/visualization/signal_integrity.py +460 -279
- oscura/visualization/specialized.py +190 -100
- oscura/visualization/spectral.py +670 -255
- oscura/visualization/thumbnails.py +166 -137
- oscura/visualization/waveform.py +150 -63
- oscura/workflows/__init__.py +3 -0
- oscura/{batch → workflows/batch}/__init__.py +5 -5
- oscura/{batch → workflows/batch}/advanced.py +150 -75
- oscura/workflows/batch/aggregate.py +531 -0
- oscura/workflows/batch/analyze.py +236 -0
- oscura/{batch → workflows/batch}/logging.py +2 -2
- oscura/{batch → workflows/batch}/metrics.py +1 -1
- oscura/workflows/complete_re.py +1144 -0
- oscura/workflows/compliance.py +44 -54
- oscura/workflows/digital.py +197 -51
- oscura/workflows/legacy/__init__.py +12 -0
- oscura/{workflow → workflows/legacy}/dag.py +4 -1
- oscura/workflows/multi_trace.py +9 -9
- oscura/workflows/power.py +42 -62
- oscura/workflows/protocol.py +82 -49
- oscura/workflows/reverse_engineering.py +351 -150
- oscura/workflows/signal_integrity.py +157 -82
- oscura-0.7.0.dist-info/METADATA +661 -0
- oscura-0.7.0.dist-info/RECORD +591 -0
- oscura/batch/aggregate.py +0 -300
- oscura/batch/analyze.py +0 -139
- oscura/dsl/__init__.py +0 -73
- oscura/exceptions.py +0 -59
- oscura/exploratory/fuzzy.py +0 -513
- oscura/exploratory/sync.py +0 -384
- oscura/exporters/__init__.py +0 -94
- oscura/exporters/csv.py +0 -303
- oscura/exporters/exporters.py +0 -44
- oscura/exporters/hdf5.py +0 -217
- oscura/exporters/html_export.py +0 -701
- oscura/exporters/json_export.py +0 -291
- oscura/exporters/markdown_export.py +0 -367
- oscura/exporters/matlab_export.py +0 -354
- oscura/exporters/npz_export.py +0 -219
- oscura/exporters/spice_export.py +0 -210
- oscura/search/context.py +0 -149
- oscura/session/__init__.py +0 -34
- oscura/session/annotations.py +0 -289
- oscura/session/history.py +0 -313
- oscura/session/session.py +0 -520
- oscura/workflow/__init__.py +0 -13
- oscura-0.5.1.dist-info/METADATA +0 -583
- oscura-0.5.1.dist-info/RECORD +0 -481
- /oscura/core/{config.py → config/legacy.py} +0 -0
- /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
- /oscura/{extensibility → core/extensibility}/registry.py +0 -0
- /oscura/{plugins → core/plugins}/isolation.py +0 -0
- /oscura/{schemas → core/schemas}/bus_configuration.json +0 -0
- /oscura/{builders → utils/builders}/signal_builder.py +0 -0
- /oscura/{optimization → utils/optimization}/parallel.py +0 -0
- /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
- /oscura/{streaming → utils/streaming}/__init__.py +0 -0
- {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/WHEEL +0 -0
- {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/entry_points.txt +0 -0
- {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'
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
664
|
-
|
|
665
|
-
|
|
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
|
|
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
|
|
679
|
-
new_aligned
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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(
|