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.
- oscura/__init__.py +169 -167
- oscura/analyzers/__init__.py +3 -0
- oscura/analyzers/classification.py +659 -0
- oscura/analyzers/digital/__init__.py +0 -48
- oscura/analyzers/digital/edges.py +325 -65
- oscura/analyzers/digital/extraction.py +0 -195
- 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/__init__.py +1 -22
- 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 +2763 -0
- 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/core/schemas/bus_configuration.json +322 -0
- oscura/core/schemas/device_mapping.json +182 -0
- oscura/core/schemas/packet_format.json +418 -0
- oscura/core/schemas/protocol_definition.json +363 -0
- 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 -20
- oscura/export/kaitai_struct.py +513 -0
- oscura/export/scapy_layer.py +801 -0
- oscura/export/wireshark/README.md +15 -15
- 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/README.md +7 -7
- 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 +171 -63
- 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/vcd.py +215 -117
- oscura/loaders/wav.py +155 -68
- oscura/reporting/__init__.py +9 -7
- 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/reporting/templates/index.md +13 -13
- 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/autodetect.py +1 -5
- 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 +11 -3
- 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.6.0.dist-info/METADATA +643 -0
- oscura-0.6.0.dist-info/RECORD +590 -0
- oscura/analyzers/digital/ic_database.py +0 -498
- oscura/analyzers/digital/timing_paths.py +0 -339
- oscura/analyzers/digital/vintage.py +0 -377
- oscura/analyzers/digital/vintage_result.py +0 -148
- oscura/analyzers/protocols/parallel_bus.py +0 -449
- 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/export/wavedrom.py +0 -430
- 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 -338
- 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/exporters/vintage_logic_csv.py +0 -247
- oscura/reporting/vintage_logic_report.py +0 -523
- 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/visualization/digital_advanced.py +0 -718
- oscura/visualization/figure_manager.py +0 -156
- oscura/workflow/__init__.py +0 -13
- oscura-0.5.0.dist-info/METADATA +0 -407
- oscura-0.5.0.dist-info/RECORD +0 -486
- /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/{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.0.dist-info → oscura-0.6.0.dist-info}/WHEEL +0 -0
- {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/entry_points.txt +0 -0
- {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -4,7 +4,7 @@ This module provides compliance report generation in multiple formats.
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
Example:
|
|
7
|
-
>>> from oscura.compliance import test_compliance, generate_compliance_report
|
|
7
|
+
>>> from oscura.validation.compliance import test_compliance, generate_compliance_report
|
|
8
8
|
>>> result = test_compliance(trace, mask)
|
|
9
9
|
>>> generate_compliance_report(result, 'report.html')
|
|
10
10
|
|
|
@@ -20,7 +20,7 @@ from pathlib import Path
|
|
|
20
20
|
from typing import TYPE_CHECKING
|
|
21
21
|
|
|
22
22
|
if TYPE_CHECKING:
|
|
23
|
-
from oscura.compliance.testing import ComplianceResult
|
|
23
|
+
from oscura.validation.compliance.testing import ComplianceResult
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
class ComplianceReportFormat(Enum):
|
|
@@ -110,37 +110,43 @@ def generate_compliance_report(
|
|
|
110
110
|
return output_path
|
|
111
111
|
|
|
112
112
|
|
|
113
|
-
def
|
|
114
|
-
|
|
115
|
-
output_path: Path,
|
|
116
|
-
*,
|
|
117
|
-
include_plot: bool = True,
|
|
118
|
-
title: str | None = None,
|
|
119
|
-
company_name: str | None = None,
|
|
120
|
-
dut_info: dict[str, str] | None = None,
|
|
121
|
-
) -> None:
|
|
122
|
-
"""Generate HTML compliance report."""
|
|
123
|
-
title = title or "EMC Compliance Report"
|
|
124
|
-
status_color = "#28a745" if result.passed else "#dc3545"
|
|
125
|
-
status_text = "PASS" if result.passed else "FAIL"
|
|
113
|
+
def _generate_dut_section_html(dut_info: dict[str, str] | None) -> str:
|
|
114
|
+
"""Generate DUT information section HTML.
|
|
126
115
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
116
|
+
Args:
|
|
117
|
+
dut_info: Device Under Test information dict.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
HTML string for DUT section.
|
|
121
|
+
"""
|
|
122
|
+
if not dut_info:
|
|
123
|
+
return ""
|
|
124
|
+
|
|
125
|
+
dut_rows = "".join(f"<tr><td>{k}</td><td>{v}</td></tr>" for k, v in dut_info.items())
|
|
126
|
+
return f"""
|
|
132
127
|
<h3>Device Under Test</h3>
|
|
133
128
|
<table class="info-table">
|
|
134
129
|
{dut_rows}
|
|
135
130
|
</table>
|
|
136
131
|
"""
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _generate_violations_section_html(result: ComplianceResult) -> str:
|
|
135
|
+
"""Generate violations table section HTML.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
result: ComplianceResult with violations list.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
HTML string for violations section.
|
|
142
|
+
"""
|
|
143
|
+
if not result.violations:
|
|
144
|
+
return ""
|
|
145
|
+
|
|
146
|
+
violation_rows = ""
|
|
147
|
+
for v in result.violations:
|
|
148
|
+
freq_mhz = v.frequency / 1e6
|
|
149
|
+
violation_rows += f"""
|
|
144
150
|
<tr>
|
|
145
151
|
<td>{freq_mhz:.3f}</td>
|
|
146
152
|
<td>{v.measured_level:.1f}</td>
|
|
@@ -148,7 +154,8 @@ def _generate_html_report(
|
|
|
148
154
|
<td style="color: red;">{v.excess_db:.1f}</td>
|
|
149
155
|
</tr>
|
|
150
156
|
"""
|
|
151
|
-
|
|
157
|
+
|
|
158
|
+
return f"""
|
|
152
159
|
<h3>Violations ({len(result.violations)})</h3>
|
|
153
160
|
<table class="data-table">
|
|
154
161
|
<thead>
|
|
@@ -164,22 +171,18 @@ def _generate_html_report(
|
|
|
164
171
|
</tbody>
|
|
165
172
|
</table>
|
|
166
173
|
"""
|
|
167
|
-
# Build plot section
|
|
168
|
-
plot_section = ""
|
|
169
|
-
if include_plot and len(result.spectrum_freq) > 0:
|
|
170
|
-
plot_section = _generate_plot_html(result)
|
|
171
174
|
|
|
172
|
-
# Company header
|
|
173
|
-
company_header = ""
|
|
174
|
-
if company_name:
|
|
175
|
-
company_header = f"<div class='company-name'>{company_name}</div>"
|
|
176
175
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
176
|
+
def _generate_report_css(status_color: str) -> str:
|
|
177
|
+
"""Generate CSS stylesheet for compliance report.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
status_color: Badge color for pass/fail status.
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
CSS stylesheet string.
|
|
184
|
+
"""
|
|
185
|
+
return f"""
|
|
183
186
|
body {{
|
|
184
187
|
font-family: 'Segoe UI', Arial, sans-serif;
|
|
185
188
|
max-width: 1200px;
|
|
@@ -258,14 +261,19 @@ def _generate_html_report(
|
|
|
258
261
|
color: #666;
|
|
259
262
|
font-size: 12px;
|
|
260
263
|
}}
|
|
261
|
-
|
|
262
|
-
</head>
|
|
263
|
-
<body>
|
|
264
|
-
{company_header}
|
|
265
|
-
<h1>{title}</h1>
|
|
264
|
+
"""
|
|
266
265
|
|
|
267
|
-
<div class="status-badge">{status_text}</div>
|
|
268
266
|
|
|
267
|
+
def _generate_summary_grid_html(result: ComplianceResult) -> str:
|
|
268
|
+
"""Generate summary grid HTML.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
result: ComplianceResult with summary metrics.
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
HTML string for summary grid.
|
|
275
|
+
"""
|
|
276
|
+
return f"""
|
|
269
277
|
<h2>Test Summary</h2>
|
|
270
278
|
<div class="summary-grid">
|
|
271
279
|
<div class="summary-card">
|
|
@@ -285,18 +293,84 @@ def _generate_html_report(
|
|
|
285
293
|
<div class="value">{len(result.violations)}</div>
|
|
286
294
|
</div>
|
|
287
295
|
</div>
|
|
296
|
+
"""
|
|
288
297
|
|
|
289
|
-
{dut_section}
|
|
290
298
|
|
|
291
|
-
|
|
299
|
+
def _generate_footer_html(result: ComplianceResult) -> str:
|
|
300
|
+
"""Generate footer HTML.
|
|
292
301
|
|
|
293
|
-
|
|
302
|
+
Args:
|
|
303
|
+
result: ComplianceResult with metadata.
|
|
294
304
|
|
|
305
|
+
Returns:
|
|
306
|
+
HTML string for footer.
|
|
307
|
+
"""
|
|
308
|
+
return f"""
|
|
295
309
|
<div class="footer">
|
|
296
310
|
<p>Report generated: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}</p>
|
|
297
311
|
<p>Detector: {result.detector} | Distance: {result.metadata.get("distance", "N/A")}m</p>
|
|
298
312
|
<p>Generated by Oscura EMC Compliance Module</p>
|
|
299
313
|
</div>
|
|
314
|
+
"""
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _generate_html_report(
|
|
318
|
+
result: ComplianceResult,
|
|
319
|
+
output_path: Path,
|
|
320
|
+
*,
|
|
321
|
+
include_plot: bool = True,
|
|
322
|
+
title: str | None = None,
|
|
323
|
+
company_name: str | None = None,
|
|
324
|
+
dut_info: dict[str, str] | None = None,
|
|
325
|
+
) -> None:
|
|
326
|
+
"""Generate HTML compliance report.
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
result: ComplianceResult from test_compliance().
|
|
330
|
+
output_path: Output file path.
|
|
331
|
+
include_plot: Include spectrum/limit plot in report.
|
|
332
|
+
title: Report title (default: "EMC Compliance Report").
|
|
333
|
+
company_name: Company name for header.
|
|
334
|
+
dut_info: Device Under Test information dict.
|
|
335
|
+
"""
|
|
336
|
+
title = title or "EMC Compliance Report"
|
|
337
|
+
status_color = "#28a745" if result.passed else "#dc3545"
|
|
338
|
+
status_text = "PASS" if result.passed else "FAIL"
|
|
339
|
+
|
|
340
|
+
# Build sections
|
|
341
|
+
dut_section = _generate_dut_section_html(dut_info)
|
|
342
|
+
violations_section = _generate_violations_section_html(result)
|
|
343
|
+
|
|
344
|
+
plot_section = ""
|
|
345
|
+
if include_plot and len(result.spectrum_freq) > 0:
|
|
346
|
+
plot_section = _generate_plot_html(result)
|
|
347
|
+
|
|
348
|
+
company_header = f"<div class='company-name'>{company_name}</div>" if company_name else ""
|
|
349
|
+
|
|
350
|
+
html = f"""<!DOCTYPE html>
|
|
351
|
+
<html>
|
|
352
|
+
<head>
|
|
353
|
+
<meta charset="UTF-8">
|
|
354
|
+
<title>{title}</title>
|
|
355
|
+
<style>
|
|
356
|
+
{_generate_report_css(status_color)}
|
|
357
|
+
</style>
|
|
358
|
+
</head>
|
|
359
|
+
<body>
|
|
360
|
+
{company_header}
|
|
361
|
+
<h1>{title}</h1>
|
|
362
|
+
|
|
363
|
+
<div class="status-badge">{status_text}</div>
|
|
364
|
+
|
|
365
|
+
{_generate_summary_grid_html(result)}
|
|
366
|
+
|
|
367
|
+
{dut_section}
|
|
368
|
+
|
|
369
|
+
{violations_section}
|
|
370
|
+
|
|
371
|
+
{plot_section}
|
|
372
|
+
|
|
373
|
+
{_generate_footer_html(result)}
|
|
300
374
|
</body>
|
|
301
375
|
</html>
|
|
302
376
|
"""
|
|
@@ -326,7 +400,7 @@ def _generate_plot_html(result: ComplianceResult) -> str:
|
|
|
326
400
|
width - 2 * padding
|
|
327
401
|
)
|
|
328
402
|
|
|
329
|
-
def y_scale(l: float) -> float:
|
|
403
|
+
def y_scale(l: float) -> float:
|
|
330
404
|
return height - padding - (l - level_min) / (level_max - level_min) * (height - 2 * padding) # type: ignore[no-any-return]
|
|
331
405
|
|
|
332
406
|
# Build spectrum path (downsample for SVG)
|
|
@@ -466,7 +540,7 @@ def _convert_html_to_pdf(html_path: Path, pdf_path: Path) -> None:
|
|
|
466
540
|
"""Convert HTML to PDF using available tools."""
|
|
467
541
|
try:
|
|
468
542
|
# Try weasyprint first
|
|
469
|
-
from weasyprint import HTML
|
|
543
|
+
from weasyprint import HTML
|
|
470
544
|
|
|
471
545
|
HTML(str(html_path)).write_pdf(str(pdf_path))
|
|
472
546
|
except ImportError:
|
|
@@ -4,7 +4,7 @@ This module provides compliance testing against regulatory limit masks.
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
Example:
|
|
7
|
-
>>> from oscura.compliance import load_limit_mask, test_compliance
|
|
7
|
+
>>> from oscura.validation.compliance import load_limit_mask, test_compliance
|
|
8
8
|
>>> mask = load_limit_mask('FCC_Part15_ClassB')
|
|
9
9
|
>>> result = test_compliance(trace, mask)
|
|
10
10
|
>>> print(f"Status: {result.status}")
|
|
@@ -25,8 +25,8 @@ import numpy as np
|
|
|
25
25
|
if TYPE_CHECKING:
|
|
26
26
|
from numpy.typing import NDArray
|
|
27
27
|
|
|
28
|
-
from oscura.compliance.masks import LimitMask
|
|
29
28
|
from oscura.core.types import WaveformTrace
|
|
29
|
+
from oscura.validation.compliance.masks import LimitMask
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
class DetectorType(Enum):
|
|
@@ -127,39 +127,23 @@ class ComplianceResult:
|
|
|
127
127
|
return "\n".join(lines)
|
|
128
128
|
|
|
129
129
|
|
|
130
|
-
def
|
|
130
|
+
def _prepare_spectrum(
|
|
131
131
|
trace_or_spectrum: WaveformTrace | tuple[NDArray[np.float64], NDArray[np.float64]],
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
unit_conversion: str | None = None,
|
|
137
|
-
) -> ComplianceResult:
|
|
138
|
-
"""Check signal against EMC limit mask.
|
|
132
|
+
detector: DetectorType,
|
|
133
|
+
unit_conversion: str | None,
|
|
134
|
+
) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
|
|
135
|
+
"""Prepare spectrum data from trace or pre-computed spectrum.
|
|
139
136
|
|
|
140
137
|
Args:
|
|
141
|
-
trace_or_spectrum: Either
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
detector: Detector type to use ('peak', 'quasi-peak', 'average', 'rms').
|
|
145
|
-
frequency_range: Optional (min, max) frequency range to test.
|
|
146
|
-
unit_conversion: Optional unit conversion ('V_to_dBuV', 'W_to_dBm', etc.)
|
|
138
|
+
trace_or_spectrum: Either WaveformTrace or (freq, mag) tuple.
|
|
139
|
+
detector: Detector type.
|
|
140
|
+
unit_conversion: Optional unit conversion.
|
|
147
141
|
|
|
148
142
|
Returns:
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
Example:
|
|
152
|
-
>>> mask = load_limit_mask('FCC_Part15_ClassB')
|
|
153
|
-
>>> result = check_compliance(trace, mask)
|
|
154
|
-
>>> print(result.summary())
|
|
143
|
+
Tuple of (frequency, spectrum_level_dB).
|
|
155
144
|
"""
|
|
156
145
|
from oscura.core.types import WaveformTrace
|
|
157
146
|
|
|
158
|
-
# Handle detector type
|
|
159
|
-
if isinstance(detector, str):
|
|
160
|
-
detector = DetectorType(detector.lower().replace("-", "_").replace(" ", "_"))
|
|
161
|
-
|
|
162
|
-
# Get spectrum
|
|
163
147
|
if isinstance(trace_or_spectrum, WaveformTrace):
|
|
164
148
|
freq, mag = _compute_spectrum(trace_or_spectrum, detector)
|
|
165
149
|
else:
|
|
@@ -167,19 +151,35 @@ def check_compliance(
|
|
|
167
151
|
|
|
168
152
|
# Convert to dB if needed
|
|
169
153
|
if unit_conversion == "V_to_dBuV":
|
|
170
|
-
# dBuV = 20*log10(V * 1e6)
|
|
171
154
|
spectrum_level = 20 * np.log10(np.abs(mag) * 1e6 + 1e-12)
|
|
172
155
|
elif unit_conversion == "W_to_dBm":
|
|
173
|
-
# dBm = 10*log10(W * 1000)
|
|
174
156
|
spectrum_level = 10 * np.log10(np.abs(mag) * 1000 + 1e-12)
|
|
175
157
|
elif mag.max() > 0 and mag.max() < 10:
|
|
176
|
-
# Assume linear voltage, convert to dBuV
|
|
177
158
|
spectrum_level = 20 * np.log10(np.abs(mag) * 1e6 + 1e-12)
|
|
178
159
|
else:
|
|
179
|
-
# Assume already in dB
|
|
180
160
|
spectrum_level = mag
|
|
181
161
|
|
|
182
|
-
|
|
162
|
+
return freq, spectrum_level
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _apply_frequency_filters(
|
|
166
|
+
freq: NDArray[np.float64],
|
|
167
|
+
spectrum_level: NDArray[np.float64],
|
|
168
|
+
mask: LimitMask,
|
|
169
|
+
frequency_range: tuple[float, float] | None,
|
|
170
|
+
) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
|
|
171
|
+
"""Apply frequency range filters to spectrum.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
freq: Frequency array.
|
|
175
|
+
spectrum_level: Spectrum level array.
|
|
176
|
+
mask: Limit mask.
|
|
177
|
+
frequency_range: Optional user-specified frequency range.
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
Filtered (freq, spectrum_level) tuple.
|
|
181
|
+
"""
|
|
182
|
+
# Apply user frequency range filter
|
|
183
183
|
if frequency_range is not None:
|
|
184
184
|
f_min, f_max = frequency_range
|
|
185
185
|
mask_filter = (freq >= f_min) & (freq <= f_max)
|
|
@@ -192,30 +192,31 @@ def check_compliance(
|
|
|
192
192
|
freq = freq[in_range]
|
|
193
193
|
spectrum_level = spectrum_level[in_range]
|
|
194
194
|
|
|
195
|
-
|
|
196
|
-
# No data in mask range
|
|
197
|
-
return ComplianceResult(
|
|
198
|
-
status="PASS",
|
|
199
|
-
mask_name=mask.name,
|
|
200
|
-
violations=[],
|
|
201
|
-
margin_to_limit=np.inf,
|
|
202
|
-
worst_frequency=0.0,
|
|
203
|
-
worst_margin=np.inf,
|
|
204
|
-
spectrum_freq=np.array([]),
|
|
205
|
-
spectrum_level=np.array([]),
|
|
206
|
-
limit_level=np.array([]),
|
|
207
|
-
detector=detector.value,
|
|
208
|
-
)
|
|
195
|
+
return freq, spectrum_level
|
|
209
196
|
|
|
210
|
-
# Interpolate limit to spectrum frequencies
|
|
211
|
-
limit_level = mask.interpolate(freq)
|
|
212
197
|
|
|
213
|
-
|
|
214
|
-
|
|
198
|
+
def _find_violations(
|
|
199
|
+
freq: NDArray[np.float64],
|
|
200
|
+
spectrum_level: NDArray[np.float64],
|
|
201
|
+
limit_level: NDArray[np.float64],
|
|
202
|
+
margin: NDArray[np.float64],
|
|
203
|
+
detector: DetectorType,
|
|
204
|
+
) -> list[ComplianceViolation]:
|
|
205
|
+
"""Find compliance violations.
|
|
215
206
|
|
|
216
|
-
|
|
207
|
+
Args:
|
|
208
|
+
freq: Frequency array.
|
|
209
|
+
spectrum_level: Measured spectrum level.
|
|
210
|
+
limit_level: Limit level.
|
|
211
|
+
margin: Margin to limit (positive = passing).
|
|
212
|
+
detector: Detector type.
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
List of violations.
|
|
216
|
+
"""
|
|
217
217
|
violations: list[ComplianceViolation] = []
|
|
218
218
|
violation_mask = margin < 0
|
|
219
|
+
|
|
219
220
|
if np.any(violation_mask):
|
|
220
221
|
violation_indices = np.where(violation_mask)[0]
|
|
221
222
|
for idx in violation_indices:
|
|
@@ -230,7 +231,68 @@ def check_compliance(
|
|
|
230
231
|
)
|
|
231
232
|
)
|
|
232
233
|
|
|
233
|
-
|
|
234
|
+
return violations
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def check_compliance(
|
|
238
|
+
trace_or_spectrum: WaveformTrace | tuple[NDArray[np.float64], NDArray[np.float64]],
|
|
239
|
+
mask: LimitMask,
|
|
240
|
+
*,
|
|
241
|
+
detector: DetectorType | str = DetectorType.PEAK,
|
|
242
|
+
frequency_range: tuple[float, float] | None = None,
|
|
243
|
+
unit_conversion: str | None = None,
|
|
244
|
+
) -> ComplianceResult:
|
|
245
|
+
"""Check signal against EMC limit mask.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
trace_or_spectrum: Either a WaveformTrace to analyze, or a tuple of
|
|
249
|
+
(frequency_array, magnitude_array) if spectrum already computed.
|
|
250
|
+
mask: LimitMask to test against.
|
|
251
|
+
detector: Detector type to use ('peak', 'quasi-peak', 'average', 'rms').
|
|
252
|
+
frequency_range: Optional (min, max) frequency range to test.
|
|
253
|
+
unit_conversion: Optional unit conversion ('V_to_dBuV', 'W_to_dBm', etc.)
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
ComplianceResult with pass/fail status and violation details.
|
|
257
|
+
|
|
258
|
+
Example:
|
|
259
|
+
>>> mask = load_limit_mask('FCC_Part15_ClassB')
|
|
260
|
+
>>> result = check_compliance(trace, mask)
|
|
261
|
+
>>> print(result.summary())
|
|
262
|
+
"""
|
|
263
|
+
# Handle detector type
|
|
264
|
+
if isinstance(detector, str):
|
|
265
|
+
detector = DetectorType(detector.lower().replace("-", "_").replace(" ", "_"))
|
|
266
|
+
|
|
267
|
+
# Prepare spectrum
|
|
268
|
+
freq, spectrum_level = _prepare_spectrum(trace_or_spectrum, detector, unit_conversion)
|
|
269
|
+
|
|
270
|
+
# Apply frequency filters
|
|
271
|
+
freq, spectrum_level = _apply_frequency_filters(freq, spectrum_level, mask, frequency_range)
|
|
272
|
+
|
|
273
|
+
# Handle empty frequency range
|
|
274
|
+
if len(freq) == 0:
|
|
275
|
+
return ComplianceResult(
|
|
276
|
+
status="PASS",
|
|
277
|
+
mask_name=mask.name,
|
|
278
|
+
violations=[],
|
|
279
|
+
margin_to_limit=np.inf,
|
|
280
|
+
worst_frequency=0.0,
|
|
281
|
+
worst_margin=np.inf,
|
|
282
|
+
spectrum_freq=np.array([]),
|
|
283
|
+
spectrum_level=np.array([]),
|
|
284
|
+
limit_level=np.array([]),
|
|
285
|
+
detector=detector.value,
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
# Interpolate limit and calculate margin
|
|
289
|
+
limit_level = mask.interpolate(freq)
|
|
290
|
+
margin = limit_level - spectrum_level
|
|
291
|
+
|
|
292
|
+
# Find violations
|
|
293
|
+
violations = _find_violations(freq, spectrum_level, limit_level, margin, detector)
|
|
294
|
+
|
|
295
|
+
# Compute overall results
|
|
234
296
|
status = "FAIL" if violations else "PASS"
|
|
235
297
|
margin_to_limit = float(np.min(margin))
|
|
236
298
|
worst_idx = int(np.argmin(margin))
|