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
oscura/reporting/index.py
CHANGED
|
@@ -312,26 +312,61 @@ class IndexGenerator:
|
|
|
312
312
|
|
|
313
313
|
Requirements:
|
|
314
314
|
"""
|
|
315
|
-
# Extract timestamp
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
315
|
+
# Extract timestamp and basic metadata
|
|
316
|
+
timestamp = self._extract_timestamp(result.output_dir.name)
|
|
317
|
+
context = self._build_basic_metadata(result, timestamp)
|
|
318
|
+
|
|
319
|
+
# Build domain information
|
|
320
|
+
domains = self._build_domains_info(result)
|
|
321
|
+
context["domains"] = domains
|
|
322
|
+
|
|
323
|
+
# Build error information
|
|
324
|
+
if result.errors:
|
|
325
|
+
context["errors"] = self._build_errors_info(result.errors)
|
|
326
|
+
|
|
327
|
+
return context
|
|
328
|
+
|
|
329
|
+
def _extract_timestamp(self, dir_name: str) -> str:
|
|
330
|
+
"""Extract formatted timestamp from directory name.
|
|
331
|
+
|
|
332
|
+
Args:
|
|
333
|
+
dir_name: Directory name in format YYYYMMDD_HHMMSS_name_analysis.
|
|
334
|
+
|
|
335
|
+
Returns:
|
|
336
|
+
Formatted timestamp string.
|
|
337
|
+
"""
|
|
338
|
+
if "_" not in dir_name:
|
|
339
|
+
return "N/A"
|
|
340
|
+
|
|
341
|
+
parts = dir_name.split("_")
|
|
342
|
+
if len(parts) < 2:
|
|
343
|
+
return "N/A"
|
|
344
|
+
|
|
345
|
+
date_part = parts[0] # YYYYMMDD
|
|
346
|
+
time_part = parts[1] # HHMMSS
|
|
347
|
+
|
|
348
|
+
if len(date_part) == 8 and len(time_part) == 6:
|
|
349
|
+
try:
|
|
350
|
+
return (
|
|
351
|
+
f"{date_part[:4]}-{date_part[4:6]}-{date_part[6:8]} "
|
|
352
|
+
f"{time_part[:2]}:{time_part[2:4]}:{time_part[4:6]}"
|
|
353
|
+
)
|
|
354
|
+
except (IndexError, ValueError):
|
|
355
|
+
return f"{date_part}_{time_part}"
|
|
356
|
+
|
|
357
|
+
return "N/A"
|
|
358
|
+
|
|
359
|
+
def _build_basic_metadata(self, result: AnalysisResult, timestamp: str) -> dict[str, Any]:
|
|
360
|
+
"""Build basic metadata context.
|
|
361
|
+
|
|
362
|
+
Args:
|
|
363
|
+
result: Analysis result.
|
|
364
|
+
timestamp: Formatted timestamp.
|
|
365
|
+
|
|
366
|
+
Returns:
|
|
367
|
+
Dictionary with basic metadata.
|
|
368
|
+
"""
|
|
369
|
+
return {
|
|
335
370
|
"title": "Analysis Report",
|
|
336
371
|
"input_name": result.input_file or "In-Memory Data",
|
|
337
372
|
"input_size": self._format_size(result.input_file),
|
|
@@ -345,44 +380,23 @@ class IndexGenerator:
|
|
|
345
380
|
"has_errors": len(result.errors) > 0,
|
|
346
381
|
}
|
|
347
382
|
|
|
348
|
-
|
|
349
|
-
|
|
383
|
+
def _build_domains_info(self, result: AnalysisResult) -> list[dict[str, Any]]:
|
|
384
|
+
"""Build domain information list.
|
|
385
|
+
|
|
386
|
+
Args:
|
|
387
|
+
result: Analysis result.
|
|
388
|
+
|
|
389
|
+
Returns:
|
|
390
|
+
List of domain dictionaries.
|
|
391
|
+
"""
|
|
350
392
|
domains: list[dict[str, Any]] = []
|
|
393
|
+
|
|
351
394
|
for domain, domain_results in result.domain_summaries.items():
|
|
352
|
-
# Count successful analyses in this domain
|
|
353
|
-
# domain_results is a dict of {function_name: result_value}
|
|
354
395
|
analyses_count = len(domain_results) if isinstance(domain_results, dict) else 0
|
|
355
396
|
|
|
356
|
-
# Find plots for this domain
|
|
357
|
-
domain_plots =
|
|
358
|
-
|
|
359
|
-
domain_id = domain.value
|
|
360
|
-
for plot_path in result.plot_paths:
|
|
361
|
-
# Check if plot belongs to this domain
|
|
362
|
-
plot_str = str(plot_path)
|
|
363
|
-
if f"/{domain_id}/" in plot_str or plot_str.startswith(domain_id):
|
|
364
|
-
domain_plots.append(
|
|
365
|
-
{
|
|
366
|
-
"title": plot_path.stem.replace("_", " ").title(),
|
|
367
|
-
"path": str(plot_path.name)
|
|
368
|
-
if plot_path.parent == result.output_dir
|
|
369
|
-
else str(plot_path.relative_to(result.output_dir)),
|
|
370
|
-
"filename": plot_path.name,
|
|
371
|
-
}
|
|
372
|
-
)
|
|
373
|
-
|
|
374
|
-
# Find data files for this domain
|
|
375
|
-
domain_data_files = []
|
|
376
|
-
domain_dir = result.domain_dirs.get(domain)
|
|
377
|
-
if domain_dir and domain_dir.exists():
|
|
378
|
-
for data_file in domain_dir.glob("*.json"):
|
|
379
|
-
domain_data_files.append(
|
|
380
|
-
{
|
|
381
|
-
"filename": data_file.name,
|
|
382
|
-
"path": str(data_file.relative_to(result.output_dir)),
|
|
383
|
-
"format": "JSON",
|
|
384
|
-
}
|
|
385
|
-
)
|
|
397
|
+
# Find plots and data files for this domain
|
|
398
|
+
domain_plots = self._find_domain_plots(result, domain)
|
|
399
|
+
domain_data_files = self._find_domain_data_files(result, domain)
|
|
386
400
|
|
|
387
401
|
# Build key findings from results
|
|
388
402
|
key_findings = self._extract_key_findings(domain_results)
|
|
@@ -399,22 +413,80 @@ class IndexGenerator:
|
|
|
399
413
|
}
|
|
400
414
|
domains.append(domain_data)
|
|
401
415
|
|
|
402
|
-
|
|
416
|
+
return domains
|
|
403
417
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
418
|
+
def _find_domain_plots(self, result: AnalysisResult, domain: Any) -> list[dict[str, Any]]:
|
|
419
|
+
"""Find plots for a specific domain.
|
|
420
|
+
|
|
421
|
+
Args:
|
|
422
|
+
result: Analysis result.
|
|
423
|
+
domain: Analysis domain.
|
|
424
|
+
|
|
425
|
+
Returns:
|
|
426
|
+
List of plot dictionaries.
|
|
427
|
+
"""
|
|
428
|
+
domain_plots: list[dict[str, Any]] = []
|
|
429
|
+
if not result.plot_paths:
|
|
430
|
+
return domain_plots
|
|
431
|
+
|
|
432
|
+
domain_id = domain.value
|
|
433
|
+
for plot_path in result.plot_paths:
|
|
434
|
+
plot_str = str(plot_path)
|
|
435
|
+
if f"/{domain_id}/" in plot_str or plot_str.startswith(domain_id):
|
|
436
|
+
domain_plots.append(
|
|
409
437
|
{
|
|
410
|
-
"
|
|
411
|
-
"
|
|
412
|
-
|
|
438
|
+
"title": plot_path.stem.replace("_", " ").title(),
|
|
439
|
+
"path": str(plot_path.name)
|
|
440
|
+
if plot_path.parent == result.output_dir
|
|
441
|
+
else str(plot_path.relative_to(result.output_dir)),
|
|
442
|
+
"filename": plot_path.name,
|
|
413
443
|
}
|
|
414
444
|
)
|
|
415
|
-
context["errors"] = errors
|
|
416
445
|
|
|
417
|
-
return
|
|
446
|
+
return domain_plots
|
|
447
|
+
|
|
448
|
+
def _find_domain_data_files(self, result: AnalysisResult, domain: Any) -> list[dict[str, Any]]:
|
|
449
|
+
"""Find data files for a specific domain.
|
|
450
|
+
|
|
451
|
+
Args:
|
|
452
|
+
result: Analysis result.
|
|
453
|
+
domain: Analysis domain.
|
|
454
|
+
|
|
455
|
+
Returns:
|
|
456
|
+
List of data file dictionaries.
|
|
457
|
+
"""
|
|
458
|
+
domain_data_files = []
|
|
459
|
+
domain_dir = result.domain_dirs.get(domain)
|
|
460
|
+
|
|
461
|
+
if domain_dir and domain_dir.exists():
|
|
462
|
+
for data_file in domain_dir.glob("*.json"):
|
|
463
|
+
domain_data_files.append(
|
|
464
|
+
{
|
|
465
|
+
"filename": data_file.name,
|
|
466
|
+
"path": str(data_file.relative_to(result.output_dir)),
|
|
467
|
+
"format": "JSON",
|
|
468
|
+
}
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
return domain_data_files
|
|
472
|
+
|
|
473
|
+
def _build_errors_info(self, errors: list[Any]) -> list[dict[str, Any]]:
|
|
474
|
+
"""Build error information list.
|
|
475
|
+
|
|
476
|
+
Args:
|
|
477
|
+
errors: List of error objects.
|
|
478
|
+
|
|
479
|
+
Returns:
|
|
480
|
+
List of error dictionaries.
|
|
481
|
+
"""
|
|
482
|
+
return [
|
|
483
|
+
{
|
|
484
|
+
"domain": error.domain.value,
|
|
485
|
+
"analysis_name": error.function,
|
|
486
|
+
"error_message": error.error_message,
|
|
487
|
+
}
|
|
488
|
+
for error in errors
|
|
489
|
+
]
|
|
418
490
|
|
|
419
491
|
def _extract_key_findings(self, domain_results: dict[str, Any]) -> list[str]:
|
|
420
492
|
"""Extract key findings from domain results for display.
|
oscura/reporting/output.py
CHANGED
|
@@ -11,6 +11,7 @@ from typing import Any
|
|
|
11
11
|
|
|
12
12
|
import numpy as np
|
|
13
13
|
import yaml
|
|
14
|
+
from numpy.typing import NDArray
|
|
14
15
|
|
|
15
16
|
from oscura.reporting.config import AnalysisDomain
|
|
16
17
|
|
|
@@ -39,88 +40,178 @@ def _sanitize_for_serialization(obj: Any, max_depth: int = 10) -> Any:
|
|
|
39
40
|
# Don't sanitize Oscura types - let the JSONEncoder handle them
|
|
40
41
|
if isinstance(obj, WaveformTrace | DigitalTrace | TraceMetadata):
|
|
41
42
|
return obj
|
|
43
|
+
|
|
44
|
+
# Dispatch to type-specific sanitizers
|
|
42
45
|
if isinstance(obj, dict):
|
|
43
|
-
|
|
44
|
-
sanitized = {}
|
|
45
|
-
for k, v in obj.items():
|
|
46
|
-
# Convert bytes keys to hex strings
|
|
47
|
-
if isinstance(k, bytes):
|
|
48
|
-
k = f"0x{k.hex()}"
|
|
49
|
-
# Convert other non-string keys to strings
|
|
50
|
-
elif not isinstance(k, str | int | float | bool | type(None)):
|
|
51
|
-
k = str(k)
|
|
52
|
-
sanitized[k] = _sanitize_for_serialization(v, max_depth - 1)
|
|
53
|
-
return sanitized
|
|
46
|
+
return _sanitize_dict(obj, max_depth)
|
|
54
47
|
elif isinstance(obj, list | tuple):
|
|
55
|
-
return
|
|
48
|
+
return _sanitize_sequence(obj, max_depth)
|
|
56
49
|
elif isinstance(obj, types.GeneratorType):
|
|
57
|
-
|
|
58
|
-
try:
|
|
59
|
-
items = list(obj)
|
|
60
|
-
return [_sanitize_for_serialization(item, max_depth - 1) for item in items]
|
|
61
|
-
except Exception:
|
|
62
|
-
# Return None for incompatible generators (cleaner than error string)
|
|
63
|
-
return None
|
|
50
|
+
return _sanitize_generator(obj, max_depth)
|
|
64
51
|
elif isinstance(obj, np.ndarray):
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
return obj.tolist()
|
|
69
|
-
elif isinstance(obj, np.generic):
|
|
70
|
-
# Catch all numpy scalar types (int, float, complex, bool, str, etc.)
|
|
71
|
-
# This includes np.integer, np.floating, np.bool_, np.complexfloating, etc.
|
|
72
|
-
return obj.item()
|
|
73
|
-
elif isinstance(obj, np.integer | np.floating):
|
|
74
|
-
# Redundant but kept for clarity
|
|
75
|
-
return obj.item()
|
|
76
|
-
elif isinstance(obj, np.bool_):
|
|
77
|
-
# Redundant but kept for clarity
|
|
78
|
-
return bool(obj)
|
|
52
|
+
return _sanitize_ndarray(obj)
|
|
53
|
+
elif isinstance(obj, np.generic | np.integer | np.floating | np.bool_):
|
|
54
|
+
return _sanitize_numpy_scalar(obj)
|
|
79
55
|
elif isinstance(obj, float):
|
|
80
|
-
|
|
81
|
-
import math
|
|
82
|
-
|
|
83
|
-
if math.isinf(obj) or math.isnan(obj):
|
|
84
|
-
return None
|
|
85
|
-
return obj
|
|
56
|
+
return _sanitize_float(obj)
|
|
86
57
|
elif isinstance(obj, complex):
|
|
87
|
-
|
|
88
|
-
import math
|
|
89
|
-
|
|
90
|
-
if (
|
|
91
|
-
math.isinf(obj.real)
|
|
92
|
-
or math.isnan(obj.real)
|
|
93
|
-
or math.isinf(obj.imag)
|
|
94
|
-
or math.isnan(obj.imag)
|
|
95
|
-
):
|
|
96
|
-
return None
|
|
97
|
-
return {"real": obj.real, "imag": obj.imag}
|
|
58
|
+
return _sanitize_complex(obj)
|
|
98
59
|
elif isinstance(obj, bytes):
|
|
99
|
-
|
|
100
|
-
if len(obj) > 1000:
|
|
101
|
-
return f"<bytes len={len(obj)}>"
|
|
102
|
-
return obj.hex()
|
|
60
|
+
return _sanitize_bytes(obj)
|
|
103
61
|
elif hasattr(obj, "__dict__") and not isinstance(obj, type):
|
|
104
|
-
|
|
105
|
-
try:
|
|
106
|
-
return {
|
|
107
|
-
k: _sanitize_for_serialization(v, max_depth - 1)
|
|
108
|
-
for k, v in obj.__dict__.items()
|
|
109
|
-
}
|
|
110
|
-
except Exception:
|
|
111
|
-
return str(obj)
|
|
62
|
+
return _sanitize_object(obj, max_depth)
|
|
112
63
|
elif callable(obj):
|
|
113
64
|
return f"<callable: {getattr(obj, '__name__', str(obj))}>"
|
|
114
65
|
else:
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
return obj
|
|
118
|
-
except Exception:
|
|
119
|
-
return str(obj)
|
|
66
|
+
return obj
|
|
67
|
+
|
|
120
68
|
except Exception as e:
|
|
121
69
|
return f"<error: {type(e).__name__}: {str(e)[:50]}>"
|
|
122
70
|
|
|
123
71
|
|
|
72
|
+
def _sanitize_dict(obj: dict[Any, Any], max_depth: int) -> dict[Any, Any]:
|
|
73
|
+
"""Sanitize dictionary for serialization.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
obj: Dictionary to sanitize.
|
|
77
|
+
max_depth: Remaining recursion depth.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Sanitized dictionary with string keys.
|
|
81
|
+
"""
|
|
82
|
+
sanitized = {}
|
|
83
|
+
for k, v in obj.items():
|
|
84
|
+
# Convert bytes keys to hex strings
|
|
85
|
+
if isinstance(k, bytes):
|
|
86
|
+
k = f"0x{k.hex()}"
|
|
87
|
+
# Convert other non-string keys to strings
|
|
88
|
+
elif not isinstance(k, str | int | float | bool | type(None)):
|
|
89
|
+
k = str(k)
|
|
90
|
+
sanitized[k] = _sanitize_for_serialization(v, max_depth - 1)
|
|
91
|
+
return sanitized
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _sanitize_sequence(obj: list[Any] | tuple[Any, ...], max_depth: int) -> list[Any]:
|
|
95
|
+
"""Sanitize list or tuple for serialization.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
obj: Sequence to sanitize.
|
|
99
|
+
max_depth: Remaining recursion depth.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Sanitized list.
|
|
103
|
+
"""
|
|
104
|
+
return [_sanitize_for_serialization(item, max_depth - 1) for item in obj]
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _sanitize_generator(obj: Any, max_depth: int) -> list[Any] | None:
|
|
108
|
+
"""Sanitize generator for serialization.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
obj: Generator to sanitize.
|
|
112
|
+
max_depth: Remaining recursion depth.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Sanitized list or None if generator fails.
|
|
116
|
+
"""
|
|
117
|
+
try:
|
|
118
|
+
items = list(obj)
|
|
119
|
+
return [_sanitize_for_serialization(item, max_depth - 1) for item in items]
|
|
120
|
+
except Exception:
|
|
121
|
+
return None
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _sanitize_ndarray(obj: NDArray[Any]) -> str | list[Any]:
|
|
125
|
+
"""Sanitize numpy array for serialization.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
obj: Numpy array to sanitize.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
String representation for large arrays, list for small arrays.
|
|
132
|
+
"""
|
|
133
|
+
if obj.size > 10000:
|
|
134
|
+
return f"<ndarray shape={obj.shape} dtype={obj.dtype}>"
|
|
135
|
+
result: list[Any] = obj.tolist()
|
|
136
|
+
return result
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _sanitize_numpy_scalar(obj: Any) -> Any:
|
|
140
|
+
"""Sanitize numpy scalar types for serialization.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
obj: Numpy scalar to sanitize.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
Python native type.
|
|
147
|
+
"""
|
|
148
|
+
if isinstance(obj, np.bool_):
|
|
149
|
+
return bool(obj)
|
|
150
|
+
return obj.item()
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _sanitize_float(obj: float) -> float | None:
|
|
154
|
+
"""Sanitize Python float for serialization.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
obj: Float to sanitize.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
Float or None if inf/nan.
|
|
161
|
+
"""
|
|
162
|
+
import math
|
|
163
|
+
|
|
164
|
+
if math.isinf(obj) or math.isnan(obj):
|
|
165
|
+
return None
|
|
166
|
+
return obj
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _sanitize_complex(obj: complex) -> dict[str, float] | None:
|
|
170
|
+
"""Sanitize complex number for serialization.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
obj: Complex number to sanitize.
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
Dictionary with real/imag or None if inf/nan components.
|
|
177
|
+
"""
|
|
178
|
+
import math
|
|
179
|
+
|
|
180
|
+
if math.isinf(obj.real) or math.isnan(obj.real) or math.isinf(obj.imag) or math.isnan(obj.imag):
|
|
181
|
+
return None
|
|
182
|
+
return {"real": obj.real, "imag": obj.imag}
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _sanitize_bytes(obj: bytes) -> str:
|
|
186
|
+
"""Sanitize bytes for serialization.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
obj: Bytes to sanitize.
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
Hex string or size placeholder for large sequences.
|
|
193
|
+
"""
|
|
194
|
+
if len(obj) > 1000:
|
|
195
|
+
return f"<bytes len={len(obj)}>"
|
|
196
|
+
return obj.hex()
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _sanitize_object(obj: Any, max_depth: int) -> dict[str, Any] | str:
|
|
200
|
+
"""Sanitize generic object for serialization.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
obj: Object to sanitize.
|
|
204
|
+
max_depth: Remaining recursion depth.
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
Sanitized dictionary or string representation.
|
|
208
|
+
"""
|
|
209
|
+
try:
|
|
210
|
+
return {k: _sanitize_for_serialization(v, max_depth - 1) for k, v in obj.__dict__.items()}
|
|
211
|
+
except Exception:
|
|
212
|
+
return str(obj)
|
|
213
|
+
|
|
214
|
+
|
|
124
215
|
class OutputManager:
|
|
125
216
|
"""Manages output directory structure and file operations for analysis reports.
|
|
126
217
|
|
oscura/reporting/pdf.py
CHANGED
|
@@ -132,108 +132,73 @@ def generate_pdf_report(
|
|
|
132
132
|
def _create_styles() -> dict[str, ParagraphStyle]:
|
|
133
133
|
"""Create PDF paragraph styles."""
|
|
134
134
|
base_styles = getSampleStyleSheet()
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
# TOC style
|
|
203
|
-
styles["TOC"] = ParagraphStyle(
|
|
204
|
-
name="TOC",
|
|
205
|
-
parent=base_styles["Normal"],
|
|
206
|
-
fontSize=10,
|
|
207
|
-
leftIndent=20,
|
|
208
|
-
spaceAfter=4,
|
|
209
|
-
)
|
|
210
|
-
|
|
211
|
-
# Pass/Fail styles with visual emphasis.
|
|
212
|
-
styles["Pass"] = ParagraphStyle(
|
|
213
|
-
name="Pass",
|
|
214
|
-
parent=base_styles["Normal"],
|
|
215
|
-
fontSize=10,
|
|
216
|
-
textColor=colors.HexColor("#27ae60"),
|
|
217
|
-
fontName="Helvetica-Bold",
|
|
218
|
-
)
|
|
219
|
-
|
|
220
|
-
styles["Fail"] = ParagraphStyle(
|
|
221
|
-
name="Fail",
|
|
222
|
-
parent=base_styles["Normal"],
|
|
223
|
-
fontSize=10,
|
|
224
|
-
textColor=colors.HexColor("#e74c3c"),
|
|
225
|
-
fontName="Helvetica-Bold",
|
|
226
|
-
)
|
|
227
|
-
|
|
228
|
-
styles["Warning"] = ParagraphStyle(
|
|
229
|
-
name="Warning",
|
|
230
|
-
parent=base_styles["Normal"],
|
|
231
|
-
fontSize=10,
|
|
232
|
-
textColor=colors.HexColor("#f39c12"),
|
|
233
|
-
fontName="Helvetica-Bold",
|
|
234
|
-
)
|
|
235
|
-
|
|
236
|
-
return styles
|
|
135
|
+
base = base_styles["Normal"]
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
"Normal": base,
|
|
139
|
+
"Title": ParagraphStyle(
|
|
140
|
+
"Title",
|
|
141
|
+
base,
|
|
142
|
+
fontSize=24,
|
|
143
|
+
textColor=colors.HexColor("#2c3e50"),
|
|
144
|
+
spaceAfter=12,
|
|
145
|
+
alignment=1,
|
|
146
|
+
fontName="Helvetica-Bold",
|
|
147
|
+
),
|
|
148
|
+
"Heading1": ParagraphStyle(
|
|
149
|
+
"Heading1",
|
|
150
|
+
base,
|
|
151
|
+
fontSize=18,
|
|
152
|
+
textColor=colors.HexColor("#2c3e50"),
|
|
153
|
+
spaceBefore=12,
|
|
154
|
+
spaceAfter=6,
|
|
155
|
+
fontName="Helvetica-Bold",
|
|
156
|
+
),
|
|
157
|
+
"Heading2": ParagraphStyle(
|
|
158
|
+
"Heading2",
|
|
159
|
+
base,
|
|
160
|
+
fontSize=14,
|
|
161
|
+
textColor=colors.HexColor("#34495e"),
|
|
162
|
+
spaceBefore=10,
|
|
163
|
+
spaceAfter=4,
|
|
164
|
+
fontName="Helvetica-Bold",
|
|
165
|
+
),
|
|
166
|
+
"Heading3": ParagraphStyle(
|
|
167
|
+
"Heading3",
|
|
168
|
+
base,
|
|
169
|
+
fontSize=12,
|
|
170
|
+
textColor=colors.HexColor("#34495e"),
|
|
171
|
+
spaceBefore=8,
|
|
172
|
+
spaceAfter=4,
|
|
173
|
+
fontName="Helvetica-Bold",
|
|
174
|
+
),
|
|
175
|
+
"Body": ParagraphStyle("Body", base, fontSize=10, leading=15, fontName="Times-Roman"),
|
|
176
|
+
"Metadata": ParagraphStyle(
|
|
177
|
+
"Metadata", base, fontSize=9, textColor=colors.HexColor("#555555"), fontName="Helvetica"
|
|
178
|
+
),
|
|
179
|
+
"TOC": ParagraphStyle("TOC", base, fontSize=10, leftIndent=20, spaceAfter=4),
|
|
180
|
+
"Pass": ParagraphStyle(
|
|
181
|
+
"Pass",
|
|
182
|
+
base,
|
|
183
|
+
fontSize=10,
|
|
184
|
+
textColor=colors.HexColor("#27ae60"),
|
|
185
|
+
fontName="Helvetica-Bold",
|
|
186
|
+
),
|
|
187
|
+
"Fail": ParagraphStyle(
|
|
188
|
+
"Fail",
|
|
189
|
+
base,
|
|
190
|
+
fontSize=10,
|
|
191
|
+
textColor=colors.HexColor("#e74c3c"),
|
|
192
|
+
fontName="Helvetica-Bold",
|
|
193
|
+
),
|
|
194
|
+
"Warning": ParagraphStyle(
|
|
195
|
+
"Warning",
|
|
196
|
+
base,
|
|
197
|
+
fontSize=10,
|
|
198
|
+
textColor=colors.HexColor("#f39c12"),
|
|
199
|
+
fontName="Helvetica-Bold",
|
|
200
|
+
),
|
|
201
|
+
}
|
|
237
202
|
|
|
238
203
|
|
|
239
204
|
def _format_metadata(report: Report) -> str:
|