oscura 0.0.1__py3-none-any.whl → 0.1.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 +813 -8
- oscura/__main__.py +392 -0
- oscura/analyzers/__init__.py +37 -0
- oscura/analyzers/digital/__init__.py +177 -0
- oscura/analyzers/digital/bus.py +691 -0
- oscura/analyzers/digital/clock.py +805 -0
- oscura/analyzers/digital/correlation.py +720 -0
- oscura/analyzers/digital/edges.py +632 -0
- oscura/analyzers/digital/extraction.py +413 -0
- oscura/analyzers/digital/quality.py +878 -0
- oscura/analyzers/digital/signal_quality.py +877 -0
- oscura/analyzers/digital/thresholds.py +708 -0
- oscura/analyzers/digital/timing.py +1104 -0
- oscura/analyzers/eye/__init__.py +46 -0
- oscura/analyzers/eye/diagram.py +434 -0
- oscura/analyzers/eye/metrics.py +555 -0
- oscura/analyzers/jitter/__init__.py +83 -0
- oscura/analyzers/jitter/ber.py +333 -0
- oscura/analyzers/jitter/decomposition.py +759 -0
- oscura/analyzers/jitter/measurements.py +413 -0
- oscura/analyzers/jitter/spectrum.py +220 -0
- oscura/analyzers/measurements.py +40 -0
- oscura/analyzers/packet/__init__.py +171 -0
- oscura/analyzers/packet/daq.py +1077 -0
- oscura/analyzers/packet/metrics.py +437 -0
- oscura/analyzers/packet/parser.py +327 -0
- oscura/analyzers/packet/payload.py +2156 -0
- oscura/analyzers/packet/payload_analysis.py +1312 -0
- oscura/analyzers/packet/payload_extraction.py +236 -0
- oscura/analyzers/packet/payload_patterns.py +670 -0
- oscura/analyzers/packet/stream.py +359 -0
- oscura/analyzers/patterns/__init__.py +266 -0
- oscura/analyzers/patterns/clustering.py +1036 -0
- oscura/analyzers/patterns/discovery.py +539 -0
- oscura/analyzers/patterns/learning.py +797 -0
- oscura/analyzers/patterns/matching.py +1091 -0
- oscura/analyzers/patterns/periodic.py +650 -0
- oscura/analyzers/patterns/sequences.py +767 -0
- oscura/analyzers/power/__init__.py +116 -0
- oscura/analyzers/power/ac_power.py +391 -0
- oscura/analyzers/power/basic.py +383 -0
- oscura/analyzers/power/conduction.py +314 -0
- oscura/analyzers/power/efficiency.py +297 -0
- oscura/analyzers/power/ripple.py +356 -0
- oscura/analyzers/power/soa.py +372 -0
- oscura/analyzers/power/switching.py +479 -0
- oscura/analyzers/protocol/__init__.py +150 -0
- oscura/analyzers/protocols/__init__.py +150 -0
- oscura/analyzers/protocols/base.py +500 -0
- oscura/analyzers/protocols/can.py +620 -0
- oscura/analyzers/protocols/can_fd.py +448 -0
- oscura/analyzers/protocols/flexray.py +405 -0
- oscura/analyzers/protocols/hdlc.py +399 -0
- oscura/analyzers/protocols/i2c.py +368 -0
- oscura/analyzers/protocols/i2s.py +296 -0
- oscura/analyzers/protocols/jtag.py +393 -0
- oscura/analyzers/protocols/lin.py +445 -0
- oscura/analyzers/protocols/manchester.py +333 -0
- oscura/analyzers/protocols/onewire.py +501 -0
- oscura/analyzers/protocols/spi.py +334 -0
- oscura/analyzers/protocols/swd.py +325 -0
- oscura/analyzers/protocols/uart.py +393 -0
- oscura/analyzers/protocols/usb.py +495 -0
- oscura/analyzers/signal_integrity/__init__.py +63 -0
- oscura/analyzers/signal_integrity/embedding.py +294 -0
- oscura/analyzers/signal_integrity/equalization.py +370 -0
- oscura/analyzers/signal_integrity/sparams.py +484 -0
- oscura/analyzers/spectral/__init__.py +53 -0
- oscura/analyzers/spectral/chunked.py +273 -0
- oscura/analyzers/spectral/chunked_fft.py +571 -0
- oscura/analyzers/spectral/chunked_wavelet.py +391 -0
- oscura/analyzers/spectral/fft.py +92 -0
- oscura/analyzers/statistical/__init__.py +250 -0
- oscura/analyzers/statistical/checksum.py +923 -0
- oscura/analyzers/statistical/chunked_corr.py +228 -0
- oscura/analyzers/statistical/classification.py +778 -0
- oscura/analyzers/statistical/entropy.py +1113 -0
- oscura/analyzers/statistical/ngrams.py +614 -0
- oscura/analyzers/statistics/__init__.py +119 -0
- oscura/analyzers/statistics/advanced.py +885 -0
- oscura/analyzers/statistics/basic.py +263 -0
- oscura/analyzers/statistics/correlation.py +630 -0
- oscura/analyzers/statistics/distribution.py +298 -0
- oscura/analyzers/statistics/outliers.py +463 -0
- oscura/analyzers/statistics/streaming.py +93 -0
- oscura/analyzers/statistics/trend.py +520 -0
- oscura/analyzers/validation.py +598 -0
- oscura/analyzers/waveform/__init__.py +36 -0
- oscura/analyzers/waveform/measurements.py +943 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
- oscura/analyzers/waveform/spectral.py +1689 -0
- oscura/analyzers/waveform/wavelets.py +298 -0
- oscura/api/__init__.py +62 -0
- oscura/api/dsl.py +538 -0
- oscura/api/fluent.py +571 -0
- oscura/api/operators.py +498 -0
- oscura/api/optimization.py +392 -0
- oscura/api/profiling.py +396 -0
- oscura/automotive/__init__.py +73 -0
- oscura/automotive/can/__init__.py +52 -0
- oscura/automotive/can/analysis.py +356 -0
- oscura/automotive/can/checksum.py +250 -0
- oscura/automotive/can/correlation.py +212 -0
- oscura/automotive/can/discovery.py +355 -0
- oscura/automotive/can/message_wrapper.py +375 -0
- oscura/automotive/can/models.py +385 -0
- oscura/automotive/can/patterns.py +381 -0
- oscura/automotive/can/session.py +452 -0
- oscura/automotive/can/state_machine.py +300 -0
- oscura/automotive/can/stimulus_response.py +461 -0
- oscura/automotive/dbc/__init__.py +15 -0
- oscura/automotive/dbc/generator.py +156 -0
- oscura/automotive/dbc/parser.py +146 -0
- oscura/automotive/dtc/__init__.py +30 -0
- oscura/automotive/dtc/database.py +3036 -0
- oscura/automotive/j1939/__init__.py +14 -0
- oscura/automotive/j1939/decoder.py +745 -0
- oscura/automotive/loaders/__init__.py +35 -0
- oscura/automotive/loaders/asc.py +98 -0
- oscura/automotive/loaders/blf.py +77 -0
- oscura/automotive/loaders/csv_can.py +136 -0
- oscura/automotive/loaders/dispatcher.py +136 -0
- oscura/automotive/loaders/mdf.py +331 -0
- oscura/automotive/loaders/pcap.py +132 -0
- oscura/automotive/obd/__init__.py +14 -0
- oscura/automotive/obd/decoder.py +707 -0
- oscura/automotive/uds/__init__.py +48 -0
- oscura/automotive/uds/decoder.py +265 -0
- oscura/automotive/uds/models.py +64 -0
- oscura/automotive/visualization.py +369 -0
- oscura/batch/__init__.py +55 -0
- oscura/batch/advanced.py +627 -0
- oscura/batch/aggregate.py +300 -0
- oscura/batch/analyze.py +139 -0
- oscura/batch/logging.py +487 -0
- oscura/batch/metrics.py +556 -0
- oscura/builders/__init__.py +41 -0
- oscura/builders/signal_builder.py +1131 -0
- oscura/cli/__init__.py +14 -0
- oscura/cli/batch.py +339 -0
- oscura/cli/characterize.py +273 -0
- oscura/cli/compare.py +775 -0
- oscura/cli/decode.py +551 -0
- oscura/cli/main.py +247 -0
- oscura/cli/shell.py +350 -0
- oscura/comparison/__init__.py +66 -0
- oscura/comparison/compare.py +397 -0
- oscura/comparison/golden.py +487 -0
- oscura/comparison/limits.py +391 -0
- oscura/comparison/mask.py +434 -0
- oscura/comparison/trace_diff.py +30 -0
- oscura/comparison/visualization.py +481 -0
- oscura/compliance/__init__.py +70 -0
- oscura/compliance/advanced.py +756 -0
- oscura/compliance/masks.py +363 -0
- oscura/compliance/reporting.py +483 -0
- oscura/compliance/testing.py +298 -0
- oscura/component/__init__.py +38 -0
- oscura/component/impedance.py +365 -0
- oscura/component/reactive.py +598 -0
- oscura/component/transmission_line.py +312 -0
- oscura/config/__init__.py +191 -0
- oscura/config/defaults.py +254 -0
- oscura/config/loader.py +348 -0
- oscura/config/memory.py +271 -0
- oscura/config/migration.py +458 -0
- oscura/config/pipeline.py +1077 -0
- oscura/config/preferences.py +530 -0
- oscura/config/protocol.py +875 -0
- oscura/config/schema.py +713 -0
- oscura/config/settings.py +420 -0
- oscura/config/thresholds.py +599 -0
- oscura/convenience.py +457 -0
- oscura/core/__init__.py +299 -0
- oscura/core/audit.py +457 -0
- oscura/core/backend_selector.py +405 -0
- oscura/core/cache.py +590 -0
- oscura/core/cancellation.py +439 -0
- oscura/core/confidence.py +225 -0
- oscura/core/config.py +506 -0
- oscura/core/correlation.py +216 -0
- oscura/core/cross_domain.py +422 -0
- oscura/core/debug.py +301 -0
- oscura/core/edge_cases.py +541 -0
- oscura/core/exceptions.py +535 -0
- oscura/core/gpu_backend.py +523 -0
- oscura/core/lazy.py +832 -0
- oscura/core/log_query.py +540 -0
- oscura/core/logging.py +931 -0
- oscura/core/logging_advanced.py +952 -0
- oscura/core/memoize.py +171 -0
- oscura/core/memory_check.py +274 -0
- oscura/core/memory_guard.py +290 -0
- oscura/core/memory_limits.py +336 -0
- oscura/core/memory_monitor.py +453 -0
- oscura/core/memory_progress.py +465 -0
- oscura/core/memory_warnings.py +315 -0
- oscura/core/numba_backend.py +362 -0
- oscura/core/performance.py +352 -0
- oscura/core/progress.py +524 -0
- oscura/core/provenance.py +358 -0
- oscura/core/results.py +331 -0
- oscura/core/types.py +504 -0
- oscura/core/uncertainty.py +383 -0
- oscura/discovery/__init__.py +52 -0
- oscura/discovery/anomaly_detector.py +672 -0
- oscura/discovery/auto_decoder.py +415 -0
- oscura/discovery/comparison.py +497 -0
- oscura/discovery/quality_validator.py +528 -0
- oscura/discovery/signal_detector.py +769 -0
- oscura/dsl/__init__.py +73 -0
- oscura/dsl/commands.py +246 -0
- oscura/dsl/interpreter.py +455 -0
- oscura/dsl/parser.py +689 -0
- oscura/dsl/repl.py +172 -0
- oscura/exceptions.py +59 -0
- oscura/exploratory/__init__.py +111 -0
- oscura/exploratory/error_recovery.py +642 -0
- oscura/exploratory/fuzzy.py +513 -0
- oscura/exploratory/fuzzy_advanced.py +786 -0
- oscura/exploratory/legacy.py +831 -0
- oscura/exploratory/parse.py +358 -0
- oscura/exploratory/recovery.py +275 -0
- oscura/exploratory/sync.py +382 -0
- oscura/exploratory/unknown.py +707 -0
- oscura/export/__init__.py +25 -0
- oscura/export/wireshark/README.md +265 -0
- oscura/export/wireshark/__init__.py +47 -0
- oscura/export/wireshark/generator.py +312 -0
- oscura/export/wireshark/lua_builder.py +159 -0
- oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
- oscura/export/wireshark/type_mapping.py +165 -0
- oscura/export/wireshark/validator.py +105 -0
- oscura/exporters/__init__.py +94 -0
- oscura/exporters/csv.py +303 -0
- oscura/exporters/exporters.py +44 -0
- oscura/exporters/hdf5.py +219 -0
- oscura/exporters/html_export.py +701 -0
- oscura/exporters/json_export.py +291 -0
- oscura/exporters/markdown_export.py +367 -0
- oscura/exporters/matlab_export.py +354 -0
- oscura/exporters/npz_export.py +219 -0
- oscura/exporters/spice_export.py +210 -0
- oscura/extensibility/__init__.py +131 -0
- oscura/extensibility/docs.py +752 -0
- oscura/extensibility/extensions.py +1125 -0
- oscura/extensibility/logging.py +259 -0
- oscura/extensibility/measurements.py +485 -0
- oscura/extensibility/plugins.py +414 -0
- oscura/extensibility/registry.py +346 -0
- oscura/extensibility/templates.py +913 -0
- oscura/extensibility/validation.py +651 -0
- oscura/filtering/__init__.py +89 -0
- oscura/filtering/base.py +563 -0
- oscura/filtering/convenience.py +564 -0
- oscura/filtering/design.py +725 -0
- oscura/filtering/filters.py +32 -0
- oscura/filtering/introspection.py +605 -0
- oscura/guidance/__init__.py +24 -0
- oscura/guidance/recommender.py +429 -0
- oscura/guidance/wizard.py +518 -0
- oscura/inference/__init__.py +251 -0
- oscura/inference/active_learning/README.md +153 -0
- oscura/inference/active_learning/__init__.py +38 -0
- oscura/inference/active_learning/lstar.py +257 -0
- oscura/inference/active_learning/observation_table.py +230 -0
- oscura/inference/active_learning/oracle.py +78 -0
- oscura/inference/active_learning/teachers/__init__.py +15 -0
- oscura/inference/active_learning/teachers/simulator.py +192 -0
- oscura/inference/adaptive_tuning.py +453 -0
- oscura/inference/alignment.py +653 -0
- oscura/inference/bayesian.py +943 -0
- oscura/inference/binary.py +1016 -0
- oscura/inference/crc_reverse.py +711 -0
- oscura/inference/logic.py +288 -0
- oscura/inference/message_format.py +1305 -0
- oscura/inference/protocol.py +417 -0
- oscura/inference/protocol_dsl.py +1084 -0
- oscura/inference/protocol_library.py +1230 -0
- oscura/inference/sequences.py +809 -0
- oscura/inference/signal_intelligence.py +1509 -0
- oscura/inference/spectral.py +215 -0
- oscura/inference/state_machine.py +634 -0
- oscura/inference/stream.py +918 -0
- oscura/integrations/__init__.py +59 -0
- oscura/integrations/llm.py +1827 -0
- oscura/jupyter/__init__.py +32 -0
- oscura/jupyter/display.py +268 -0
- oscura/jupyter/magic.py +334 -0
- oscura/loaders/__init__.py +526 -0
- oscura/loaders/binary.py +69 -0
- oscura/loaders/configurable.py +1255 -0
- oscura/loaders/csv.py +26 -0
- oscura/loaders/csv_loader.py +473 -0
- oscura/loaders/hdf5.py +9 -0
- oscura/loaders/hdf5_loader.py +510 -0
- oscura/loaders/lazy.py +370 -0
- oscura/loaders/mmap_loader.py +583 -0
- oscura/loaders/numpy_loader.py +436 -0
- oscura/loaders/pcap.py +432 -0
- oscura/loaders/preprocessing.py +368 -0
- oscura/loaders/rigol.py +287 -0
- oscura/loaders/sigrok.py +321 -0
- oscura/loaders/tdms.py +367 -0
- oscura/loaders/tektronix.py +711 -0
- oscura/loaders/validation.py +584 -0
- oscura/loaders/vcd.py +464 -0
- oscura/loaders/wav.py +233 -0
- oscura/math/__init__.py +45 -0
- oscura/math/arithmetic.py +824 -0
- oscura/math/interpolation.py +413 -0
- oscura/onboarding/__init__.py +39 -0
- oscura/onboarding/help.py +498 -0
- oscura/onboarding/tutorials.py +405 -0
- oscura/onboarding/wizard.py +466 -0
- oscura/optimization/__init__.py +19 -0
- oscura/optimization/parallel.py +440 -0
- oscura/optimization/search.py +532 -0
- oscura/pipeline/__init__.py +43 -0
- oscura/pipeline/base.py +338 -0
- oscura/pipeline/composition.py +242 -0
- oscura/pipeline/parallel.py +448 -0
- oscura/pipeline/pipeline.py +375 -0
- oscura/pipeline/reverse_engineering.py +1119 -0
- oscura/plugins/__init__.py +122 -0
- oscura/plugins/base.py +272 -0
- oscura/plugins/cli.py +497 -0
- oscura/plugins/discovery.py +411 -0
- oscura/plugins/isolation.py +418 -0
- oscura/plugins/lifecycle.py +959 -0
- oscura/plugins/manager.py +493 -0
- oscura/plugins/registry.py +421 -0
- oscura/plugins/versioning.py +372 -0
- oscura/py.typed +0 -0
- oscura/quality/__init__.py +65 -0
- oscura/quality/ensemble.py +740 -0
- oscura/quality/explainer.py +338 -0
- oscura/quality/scoring.py +616 -0
- oscura/quality/warnings.py +456 -0
- oscura/reporting/__init__.py +248 -0
- oscura/reporting/advanced.py +1234 -0
- oscura/reporting/analyze.py +448 -0
- oscura/reporting/argument_preparer.py +596 -0
- oscura/reporting/auto_report.py +507 -0
- oscura/reporting/batch.py +615 -0
- oscura/reporting/chart_selection.py +223 -0
- oscura/reporting/comparison.py +330 -0
- oscura/reporting/config.py +615 -0
- oscura/reporting/content/__init__.py +39 -0
- oscura/reporting/content/executive.py +127 -0
- oscura/reporting/content/filtering.py +191 -0
- oscura/reporting/content/minimal.py +257 -0
- oscura/reporting/content/verbosity.py +162 -0
- oscura/reporting/core.py +508 -0
- oscura/reporting/core_formats/__init__.py +17 -0
- oscura/reporting/core_formats/multi_format.py +210 -0
- oscura/reporting/engine.py +836 -0
- oscura/reporting/export.py +366 -0
- oscura/reporting/formatting/__init__.py +129 -0
- oscura/reporting/formatting/emphasis.py +81 -0
- oscura/reporting/formatting/numbers.py +403 -0
- oscura/reporting/formatting/standards.py +55 -0
- oscura/reporting/formatting.py +466 -0
- oscura/reporting/html.py +578 -0
- oscura/reporting/index.py +590 -0
- oscura/reporting/multichannel.py +296 -0
- oscura/reporting/output.py +379 -0
- oscura/reporting/pdf.py +373 -0
- oscura/reporting/plots.py +731 -0
- oscura/reporting/pptx_export.py +360 -0
- oscura/reporting/renderers/__init__.py +11 -0
- oscura/reporting/renderers/pdf.py +94 -0
- oscura/reporting/sections.py +471 -0
- oscura/reporting/standards.py +680 -0
- oscura/reporting/summary_generator.py +368 -0
- oscura/reporting/tables.py +397 -0
- oscura/reporting/template_system.py +724 -0
- oscura/reporting/templates/__init__.py +15 -0
- oscura/reporting/templates/definition.py +205 -0
- oscura/reporting/templates/index.html +649 -0
- oscura/reporting/templates/index.md +173 -0
- oscura/schemas/__init__.py +158 -0
- oscura/schemas/bus_configuration.json +322 -0
- oscura/schemas/device_mapping.json +182 -0
- oscura/schemas/packet_format.json +418 -0
- oscura/schemas/protocol_definition.json +363 -0
- oscura/search/__init__.py +16 -0
- oscura/search/anomaly.py +292 -0
- oscura/search/context.py +149 -0
- oscura/search/pattern.py +160 -0
- oscura/session/__init__.py +34 -0
- oscura/session/annotations.py +289 -0
- oscura/session/history.py +313 -0
- oscura/session/session.py +445 -0
- oscura/streaming/__init__.py +43 -0
- oscura/streaming/chunked.py +611 -0
- oscura/streaming/progressive.py +393 -0
- oscura/streaming/realtime.py +622 -0
- oscura/testing/__init__.py +54 -0
- oscura/testing/synthetic.py +808 -0
- oscura/triggering/__init__.py +68 -0
- oscura/triggering/base.py +229 -0
- oscura/triggering/edge.py +353 -0
- oscura/triggering/pattern.py +344 -0
- oscura/triggering/pulse.py +581 -0
- oscura/triggering/window.py +453 -0
- oscura/ui/__init__.py +48 -0
- oscura/ui/formatters.py +526 -0
- oscura/ui/progressive_display.py +340 -0
- oscura/utils/__init__.py +99 -0
- oscura/utils/autodetect.py +338 -0
- oscura/utils/buffer.py +389 -0
- oscura/utils/lazy.py +407 -0
- oscura/utils/lazy_imports.py +147 -0
- oscura/utils/memory.py +836 -0
- oscura/utils/memory_advanced.py +1326 -0
- oscura/utils/memory_extensions.py +465 -0
- oscura/utils/progressive.py +352 -0
- oscura/utils/windowing.py +362 -0
- oscura/visualization/__init__.py +321 -0
- oscura/visualization/accessibility.py +526 -0
- oscura/visualization/annotations.py +374 -0
- oscura/visualization/axis_scaling.py +305 -0
- oscura/visualization/colors.py +453 -0
- oscura/visualization/digital.py +337 -0
- oscura/visualization/eye.py +420 -0
- oscura/visualization/histogram.py +281 -0
- oscura/visualization/interactive.py +858 -0
- oscura/visualization/jitter.py +702 -0
- oscura/visualization/keyboard.py +394 -0
- oscura/visualization/layout.py +365 -0
- oscura/visualization/optimization.py +1028 -0
- oscura/visualization/palettes.py +446 -0
- oscura/visualization/plot.py +92 -0
- oscura/visualization/power.py +290 -0
- oscura/visualization/power_extended.py +626 -0
- oscura/visualization/presets.py +467 -0
- oscura/visualization/protocols.py +932 -0
- oscura/visualization/render.py +207 -0
- oscura/visualization/rendering.py +444 -0
- oscura/visualization/reverse_engineering.py +791 -0
- oscura/visualization/signal_integrity.py +808 -0
- oscura/visualization/specialized.py +553 -0
- oscura/visualization/spectral.py +811 -0
- oscura/visualization/styles.py +381 -0
- oscura/visualization/thumbnails.py +311 -0
- oscura/visualization/time_axis.py +351 -0
- oscura/visualization/waveform.py +367 -0
- oscura/workflow/__init__.py +13 -0
- oscura/workflow/dag.py +377 -0
- oscura/workflows/__init__.py +58 -0
- oscura/workflows/compliance.py +280 -0
- oscura/workflows/digital.py +272 -0
- oscura/workflows/multi_trace.py +502 -0
- oscura/workflows/power.py +178 -0
- oscura/workflows/protocol.py +492 -0
- oscura/workflows/reverse_engineering.py +639 -0
- oscura/workflows/signal_integrity.py +227 -0
- oscura-0.1.0.dist-info/METADATA +300 -0
- oscura-0.1.0.dist-info/RECORD +463 -0
- oscura-0.1.0.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.0.dist-info}/licenses/LICENSE +1 -1
- oscura-0.0.1.dist-info/METADATA +0 -63
- oscura-0.0.1.dist-info/RECORD +0 -5
- {oscura-0.0.1.dist-info → oscura-0.1.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
"""NumPy NPZ file loader.
|
|
2
|
+
|
|
3
|
+
This module provides loading of waveform data from NumPy .npz archive files.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
>>> from oscura.loaders.numpy_loader import load_npz
|
|
8
|
+
>>> trace = load_npz("waveform.npz")
|
|
9
|
+
>>> print(f"Sample rate: {trace.metadata.sample_rate} Hz")
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import TYPE_CHECKING, Any
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
from numpy.typing import NDArray
|
|
19
|
+
|
|
20
|
+
from oscura.core.exceptions import FormatError, LoaderError
|
|
21
|
+
from oscura.core.types import TraceMetadata, WaveformTrace
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from os import PathLike
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# Common array names for waveform data
|
|
28
|
+
DATA_ARRAY_NAMES = ["data", "waveform", "signal", "samples", "y", "voltage"]
|
|
29
|
+
|
|
30
|
+
# Common metadata keys
|
|
31
|
+
SAMPLE_RATE_KEYS = ["sample_rate", "samplerate", "fs", "sampling_rate", "rate"]
|
|
32
|
+
VERTICAL_SCALE_KEYS = ["vertical_scale", "v_scale", "scale", "volts_per_div"]
|
|
33
|
+
VERTICAL_OFFSET_KEYS = ["vertical_offset", "v_offset", "offset"]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def load_npz(
|
|
37
|
+
path: str | PathLike[str],
|
|
38
|
+
*,
|
|
39
|
+
channel: str | int | None = None,
|
|
40
|
+
sample_rate: float | None = None,
|
|
41
|
+
mmap: bool = False,
|
|
42
|
+
) -> WaveformTrace:
|
|
43
|
+
"""Load waveform data from a NumPy NPZ archive.
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
Extracts waveform array and metadata from an NPZ file. The function
|
|
47
|
+
looks for common array names like 'data', 'waveform', 'signal', etc.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
path: Path to the .npz file.
|
|
51
|
+
channel: Specific array name or index to load. If None, auto-detects.
|
|
52
|
+
sample_rate: Override sample rate (if not found in file metadata).
|
|
53
|
+
mmap: If True, use memory mapping to avoid loading entire file into RAM.
|
|
54
|
+
Data stays on disk until accessed. Useful for very large files.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
WaveformTrace containing the waveform data and metadata.
|
|
58
|
+
|
|
59
|
+
Raises:
|
|
60
|
+
LoaderError: If the file cannot be loaded.
|
|
61
|
+
FormatError: If no valid waveform data is found.
|
|
62
|
+
|
|
63
|
+
Example:
|
|
64
|
+
>>> trace = load_npz("waveform.npz")
|
|
65
|
+
>>> print(f"Sample rate: {trace.metadata.sample_rate} Hz")
|
|
66
|
+
|
|
67
|
+
>>> # Load specific channel from multi-channel file
|
|
68
|
+
>>> trace = load_npz("multi.npz", channel="ch1")
|
|
69
|
+
|
|
70
|
+
>>> # Memory-map large file to avoid loading all into RAM
|
|
71
|
+
>>> trace = load_npz("large.npz", mmap=True)
|
|
72
|
+
>>> # Access only what you need: trace.data[1000:2000]
|
|
73
|
+
|
|
74
|
+
Security Warning:
|
|
75
|
+
NPZ files may contain pickled Python objects. Only load NPZ files from
|
|
76
|
+
trusted sources. Loading malicious NPZ files could execute arbitrary
|
|
77
|
+
code. For untrusted data, prefer formats like plain NumPy arrays (.npy),
|
|
78
|
+
CSV, or HDF5.
|
|
79
|
+
"""
|
|
80
|
+
path = Path(path)
|
|
81
|
+
|
|
82
|
+
if not path.exists():
|
|
83
|
+
raise LoaderError(
|
|
84
|
+
"File not found",
|
|
85
|
+
file_path=str(path),
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
npz = np.load(path, allow_pickle=True, mmap_mode="r" if mmap else None)
|
|
90
|
+
except Exception as e:
|
|
91
|
+
raise LoaderError(
|
|
92
|
+
"Failed to load NPZ file",
|
|
93
|
+
file_path=str(path),
|
|
94
|
+
details=str(e),
|
|
95
|
+
) from e
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
# Find waveform data array
|
|
99
|
+
data_array = _find_data_array(npz, channel)
|
|
100
|
+
|
|
101
|
+
if data_array is None:
|
|
102
|
+
available = list(npz.keys())
|
|
103
|
+
raise FormatError(
|
|
104
|
+
"No waveform data found in NPZ file",
|
|
105
|
+
file_path=str(path),
|
|
106
|
+
expected=f"Array named: {', '.join(DATA_ARRAY_NAMES)}",
|
|
107
|
+
got=f"Arrays: {', '.join(available)}",
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Convert to float64 (keep mmap if enabled)
|
|
111
|
+
if mmap and isinstance(data_array, np.memmap):
|
|
112
|
+
# Keep as memmap, just ensure float64 dtype
|
|
113
|
+
if data_array.dtype != np.float64:
|
|
114
|
+
# For memmap, we need to copy to convert dtype
|
|
115
|
+
try:
|
|
116
|
+
data = data_array.astype(np.float64)
|
|
117
|
+
except (ValueError, TypeError) as e:
|
|
118
|
+
raise FormatError(
|
|
119
|
+
"Data array is not numeric",
|
|
120
|
+
file_path=str(path),
|
|
121
|
+
expected="Numeric dtype (int, float)",
|
|
122
|
+
got=f"{data_array.dtype}",
|
|
123
|
+
) from e
|
|
124
|
+
else:
|
|
125
|
+
data = data_array
|
|
126
|
+
else:
|
|
127
|
+
try:
|
|
128
|
+
data = data_array.astype(np.float64)
|
|
129
|
+
except (ValueError, TypeError) as e:
|
|
130
|
+
raise FormatError(
|
|
131
|
+
"Data array is not numeric",
|
|
132
|
+
file_path=str(path),
|
|
133
|
+
expected="Numeric dtype (int, float)",
|
|
134
|
+
got=f"{data_array.dtype}",
|
|
135
|
+
) from e
|
|
136
|
+
|
|
137
|
+
# Extract metadata
|
|
138
|
+
detected_sample_rate = _find_metadata_value(npz, SAMPLE_RATE_KEYS)
|
|
139
|
+
detected_vertical_scale = _find_metadata_value(npz, VERTICAL_SCALE_KEYS)
|
|
140
|
+
detected_vertical_offset = _find_metadata_value(npz, VERTICAL_OFFSET_KEYS)
|
|
141
|
+
|
|
142
|
+
# Use provided sample_rate if specified
|
|
143
|
+
if sample_rate is not None:
|
|
144
|
+
detected_sample_rate = sample_rate
|
|
145
|
+
elif detected_sample_rate is None:
|
|
146
|
+
detected_sample_rate = 1e6 # Default to 1 MSa/s
|
|
147
|
+
|
|
148
|
+
# Build metadata
|
|
149
|
+
metadata = TraceMetadata(
|
|
150
|
+
sample_rate=float(detected_sample_rate),
|
|
151
|
+
vertical_scale=float(detected_vertical_scale)
|
|
152
|
+
if detected_vertical_scale is not None
|
|
153
|
+
else None,
|
|
154
|
+
vertical_offset=float(detected_vertical_offset)
|
|
155
|
+
if detected_vertical_offset is not None
|
|
156
|
+
else None,
|
|
157
|
+
source_file=str(path),
|
|
158
|
+
channel_name=_get_channel_name(npz, channel),
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
return WaveformTrace(data=data, metadata=metadata)
|
|
162
|
+
|
|
163
|
+
finally:
|
|
164
|
+
npz.close()
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _find_data_array(
|
|
168
|
+
npz: np.lib.npyio.NpzFile,
|
|
169
|
+
channel: str | int | None,
|
|
170
|
+
) -> NDArray[np.float64] | None:
|
|
171
|
+
"""Find the waveform data array in the NPZ file.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
npz: Loaded NPZ file.
|
|
175
|
+
channel: Specific channel name or index.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
Waveform data array or None if not found.
|
|
179
|
+
"""
|
|
180
|
+
keys = list(npz.keys())
|
|
181
|
+
|
|
182
|
+
# If channel specified by name
|
|
183
|
+
if isinstance(channel, str):
|
|
184
|
+
if channel in keys:
|
|
185
|
+
return npz[channel]
|
|
186
|
+
# Try case-insensitive match
|
|
187
|
+
channel_lower = channel.lower()
|
|
188
|
+
for key in keys:
|
|
189
|
+
if key.lower() == channel_lower:
|
|
190
|
+
return npz[key]
|
|
191
|
+
return None
|
|
192
|
+
|
|
193
|
+
# If channel specified by index
|
|
194
|
+
if isinstance(channel, int):
|
|
195
|
+
# Find numeric-suffixed arrays (ch1, ch2, etc.)
|
|
196
|
+
channel_arrays = [k for k in keys if _is_channel_array(k)]
|
|
197
|
+
if channel_arrays and channel < len(channel_arrays):
|
|
198
|
+
return npz[sorted(channel_arrays)[channel]]
|
|
199
|
+
# Or use nth array
|
|
200
|
+
data_arrays = [k for k in keys if _is_data_array(k)]
|
|
201
|
+
if data_arrays and channel < len(data_arrays):
|
|
202
|
+
return npz[data_arrays[channel]]
|
|
203
|
+
return None
|
|
204
|
+
|
|
205
|
+
# Auto-detect: look for common data array names
|
|
206
|
+
for name in DATA_ARRAY_NAMES:
|
|
207
|
+
if name in keys:
|
|
208
|
+
return npz[name]
|
|
209
|
+
# Try case-insensitive match
|
|
210
|
+
name_lower = name.lower()
|
|
211
|
+
for key in keys:
|
|
212
|
+
if key.lower() == name_lower:
|
|
213
|
+
return npz[key]
|
|
214
|
+
|
|
215
|
+
# Fall back to first 1D or 2D array
|
|
216
|
+
for key in keys:
|
|
217
|
+
arr = npz[key]
|
|
218
|
+
if isinstance(arr, np.ndarray) and arr.ndim in (1, 2):
|
|
219
|
+
# Skip metadata scalars
|
|
220
|
+
if arr.size > 10: # Arbitrary threshold
|
|
221
|
+
return arr.ravel() if arr.ndim == 2 else arr
|
|
222
|
+
|
|
223
|
+
return None
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _is_channel_array(name: str) -> bool:
|
|
227
|
+
"""Check if array name looks like a channel (ch1, channel1, etc.)."""
|
|
228
|
+
name_lower = name.lower()
|
|
229
|
+
return (
|
|
230
|
+
name_lower.startswith("ch")
|
|
231
|
+
or name_lower.startswith("channel")
|
|
232
|
+
or name_lower.startswith("analog")
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _is_data_array(name: str) -> bool:
|
|
237
|
+
"""Check if array name looks like waveform data."""
|
|
238
|
+
name_lower = name.lower()
|
|
239
|
+
return any(data_name in name_lower for data_name in DATA_ARRAY_NAMES)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _find_metadata_value(
|
|
243
|
+
npz: np.lib.npyio.NpzFile,
|
|
244
|
+
key_names: list[str],
|
|
245
|
+
) -> float | None:
|
|
246
|
+
"""Find a metadata value by trying multiple key names.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
npz: Loaded NPZ file.
|
|
250
|
+
key_names: List of possible key names to try.
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
Metadata value or None if not found.
|
|
254
|
+
"""
|
|
255
|
+
keys = list(npz.keys())
|
|
256
|
+
|
|
257
|
+
for name in key_names:
|
|
258
|
+
# Exact match
|
|
259
|
+
if name in keys:
|
|
260
|
+
value = npz[name]
|
|
261
|
+
if np.isscalar(value):
|
|
262
|
+
return float(value) # type: ignore[arg-type]
|
|
263
|
+
elif isinstance(value, np.ndarray) and value.size == 1:
|
|
264
|
+
return float(value.item()) # type: ignore[arg-type]
|
|
265
|
+
|
|
266
|
+
# Case-insensitive match
|
|
267
|
+
name_lower = name.lower()
|
|
268
|
+
for key in keys:
|
|
269
|
+
if key.lower() == name_lower:
|
|
270
|
+
value = npz[key]
|
|
271
|
+
if np.isscalar(value):
|
|
272
|
+
return float(value) # type: ignore[arg-type]
|
|
273
|
+
elif isinstance(value, np.ndarray) and value.size == 1:
|
|
274
|
+
return float(value.item()) # type: ignore[arg-type]
|
|
275
|
+
|
|
276
|
+
# Check for metadata dict
|
|
277
|
+
if "metadata" in keys:
|
|
278
|
+
metadata = npz["metadata"]
|
|
279
|
+
if isinstance(metadata, np.ndarray):
|
|
280
|
+
try:
|
|
281
|
+
meta_dict = metadata.item()
|
|
282
|
+
if isinstance(meta_dict, dict):
|
|
283
|
+
for name in key_names:
|
|
284
|
+
if name in meta_dict:
|
|
285
|
+
return float(meta_dict[name])
|
|
286
|
+
except (ValueError, TypeError):
|
|
287
|
+
pass
|
|
288
|
+
|
|
289
|
+
return None
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def _get_channel_name(
|
|
293
|
+
npz: np.lib.npyio.NpzFile,
|
|
294
|
+
channel: str | int | None,
|
|
295
|
+
) -> str:
|
|
296
|
+
"""Get a channel name for the loaded data.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
npz: Loaded NPZ file.
|
|
300
|
+
channel: Channel specification.
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
Channel name string.
|
|
304
|
+
"""
|
|
305
|
+
if isinstance(channel, str):
|
|
306
|
+
return channel
|
|
307
|
+
elif isinstance(channel, int):
|
|
308
|
+
return f"CH{channel + 1}"
|
|
309
|
+
|
|
310
|
+
# Try to find channel name in metadata
|
|
311
|
+
keys = list(npz.keys())
|
|
312
|
+
if "channel_name" in keys:
|
|
313
|
+
value = npz["channel_name"]
|
|
314
|
+
# NPZ values are always ndarrays
|
|
315
|
+
return str(value.item())
|
|
316
|
+
|
|
317
|
+
return "CH1"
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def list_arrays(path: str | PathLike[str]) -> list[str]:
|
|
321
|
+
"""List all arrays in an NPZ file.
|
|
322
|
+
|
|
323
|
+
Args:
|
|
324
|
+
path: Path to the NPZ file.
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
List of array names.
|
|
328
|
+
|
|
329
|
+
Raises:
|
|
330
|
+
LoaderError: If file not found or cannot be read.
|
|
331
|
+
|
|
332
|
+
Example:
|
|
333
|
+
>>> arrays = list_arrays("multi.npz")
|
|
334
|
+
>>> print(arrays)
|
|
335
|
+
['ch1', 'ch2', 'sample_rate']
|
|
336
|
+
"""
|
|
337
|
+
path = Path(path)
|
|
338
|
+
if not path.exists():
|
|
339
|
+
raise LoaderError("File not found", file_path=str(path))
|
|
340
|
+
|
|
341
|
+
try:
|
|
342
|
+
with np.load(path, allow_pickle=True) as npz:
|
|
343
|
+
return list(npz.keys())
|
|
344
|
+
except Exception as e:
|
|
345
|
+
raise LoaderError(
|
|
346
|
+
"Failed to read NPZ file",
|
|
347
|
+
file_path=str(path),
|
|
348
|
+
details=str(e),
|
|
349
|
+
) from e
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def load_raw_binary(
|
|
353
|
+
path: str | PathLike[str],
|
|
354
|
+
*,
|
|
355
|
+
dtype: str = "float32",
|
|
356
|
+
sample_rate: float = 1e6,
|
|
357
|
+
mmap: bool = False,
|
|
358
|
+
offset: int = 0,
|
|
359
|
+
count: int = -1,
|
|
360
|
+
) -> WaveformTrace:
|
|
361
|
+
"""Load waveform data from a raw binary file.
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
Loads raw binary waveform data with optional memory mapping for
|
|
365
|
+
files larger than available RAM.
|
|
366
|
+
|
|
367
|
+
Args:
|
|
368
|
+
path: Path to the raw binary file.
|
|
369
|
+
dtype: Data type of samples (float32, float64, int16, etc.).
|
|
370
|
+
sample_rate: Sample rate in Hz.
|
|
371
|
+
mmap: If True, use memory mapping to avoid loading entire file.
|
|
372
|
+
offset: Number of elements to skip at start of file.
|
|
373
|
+
count: Number of elements to read (-1 = all).
|
|
374
|
+
|
|
375
|
+
Returns:
|
|
376
|
+
WaveformTrace containing the waveform data and metadata.
|
|
377
|
+
|
|
378
|
+
Raises:
|
|
379
|
+
LoaderError: If the file cannot be loaded.
|
|
380
|
+
|
|
381
|
+
Example:
|
|
382
|
+
>>> # Load entire file into memory
|
|
383
|
+
>>> trace = load_raw_binary("signal.bin", dtype="float32", sample_rate=1e9)
|
|
384
|
+
|
|
385
|
+
>>> # Memory-map large file
|
|
386
|
+
>>> trace = load_raw_binary("large.bin", dtype="float32", sample_rate=1e9, mmap=True)
|
|
387
|
+
>>> # Access subset: trace.data[1000:2000]
|
|
388
|
+
|
|
389
|
+
>>> # Load only portion of file
|
|
390
|
+
>>> trace = load_raw_binary("signal.bin", dtype="int16", offset=1000, count=10000)
|
|
391
|
+
"""
|
|
392
|
+
path = Path(path)
|
|
393
|
+
|
|
394
|
+
if not path.exists():
|
|
395
|
+
raise LoaderError("File not found", file_path=str(path))
|
|
396
|
+
|
|
397
|
+
try:
|
|
398
|
+
data: NDArray[np.float64] | np.memmap[Any, np.dtype[Any]]
|
|
399
|
+
if mmap:
|
|
400
|
+
# Memory-mapped array (stays on disk)
|
|
401
|
+
data = np.memmap(
|
|
402
|
+
path,
|
|
403
|
+
dtype=dtype,
|
|
404
|
+
mode="r",
|
|
405
|
+
offset=offset * np.dtype(dtype).itemsize,
|
|
406
|
+
shape=(count,) if count > 0 else None,
|
|
407
|
+
)
|
|
408
|
+
# Convert to float64 if needed (may copy)
|
|
409
|
+
if data.dtype != np.float64:
|
|
410
|
+
# For large files, user should slice before converting
|
|
411
|
+
# data = data.astype(np.float64) # This would load entire file!
|
|
412
|
+
# Instead, keep original dtype and convert in WaveformTrace
|
|
413
|
+
pass
|
|
414
|
+
else:
|
|
415
|
+
# Load into memory
|
|
416
|
+
data_raw = np.fromfile(path, dtype=dtype, count=count, offset=offset)
|
|
417
|
+
# Convert to float64
|
|
418
|
+
data = data_raw.astype(np.float64)
|
|
419
|
+
|
|
420
|
+
metadata = TraceMetadata(
|
|
421
|
+
sample_rate=sample_rate,
|
|
422
|
+
source_file=str(path),
|
|
423
|
+
channel_name="RAW",
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
return WaveformTrace(data=data, metadata=metadata)
|
|
427
|
+
|
|
428
|
+
except Exception as e:
|
|
429
|
+
raise LoaderError(
|
|
430
|
+
"Failed to load raw binary file",
|
|
431
|
+
file_path=str(path),
|
|
432
|
+
details=str(e),
|
|
433
|
+
) from e
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
__all__ = ["list_arrays", "load_npz", "load_raw_binary"]
|