oscura 0.0.1__py3-none-any.whl → 0.1.1__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.1.dist-info/METADATA +300 -0
- oscura-0.1.1.dist-info/RECORD +463 -0
- oscura-0.1.1.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.1.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.1.dist-info}/WHEEL +0 -0
oscura/loaders/lazy.py
ADDED
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
"""Lazy loading for huge waveform files.
|
|
2
|
+
|
|
3
|
+
This module provides memory-mapped file loading where metadata is loaded
|
|
4
|
+
immediately but data is deferred until first access. Useful for multi-GB files.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import TYPE_CHECKING, Any
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
from oscura.core.exceptions import LoaderError
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from numpy.typing import DTypeLike, NDArray
|
|
18
|
+
|
|
19
|
+
from oscura.core.types import WaveformTrace
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class LazyWaveformTrace:
|
|
23
|
+
"""Lazy-loading wrapper for WaveformTrace.
|
|
24
|
+
|
|
25
|
+
Loads metadata immediately but defers data loading until first access.
|
|
26
|
+
Uses numpy.memmap for efficient memory-mapped file access.
|
|
27
|
+
|
|
28
|
+
Example:
|
|
29
|
+
>>> from oscura.loaders.lazy import load_trace_lazy
|
|
30
|
+
>>> trace = load_trace_lazy('huge_trace.npy', lazy=True)
|
|
31
|
+
>>> # Metadata available immediately
|
|
32
|
+
>>> print(f"Length: {trace.length}, Sample rate: {trace.sample_rate}")
|
|
33
|
+
>>> # Data loaded on first access
|
|
34
|
+
>>> data = trace.data # Loads data now
|
|
35
|
+
>>> subset = trace[1000:2000] # Only loads requested slice
|
|
36
|
+
|
|
37
|
+
References:
|
|
38
|
+
API-017: Lazy Loading for Huge Files
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
file_path: str | Path,
|
|
44
|
+
sample_rate: float,
|
|
45
|
+
length: int,
|
|
46
|
+
*,
|
|
47
|
+
dtype: DTypeLike = np.float64,
|
|
48
|
+
offset: int = 0,
|
|
49
|
+
metadata: dict[str, Any] | None = None,
|
|
50
|
+
) -> None:
|
|
51
|
+
"""Initialize lazy trace.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
file_path: Path to binary data file.
|
|
55
|
+
sample_rate: Sample rate in Hz.
|
|
56
|
+
length: Number of samples.
|
|
57
|
+
dtype: Data type of samples.
|
|
58
|
+
offset: Byte offset to start of data in file.
|
|
59
|
+
metadata: Additional metadata.
|
|
60
|
+
|
|
61
|
+
Raises:
|
|
62
|
+
LoaderError: If file not found.
|
|
63
|
+
|
|
64
|
+
Example:
|
|
65
|
+
>>> trace = LazyWaveformTrace(
|
|
66
|
+
... file_path='trace.npy',
|
|
67
|
+
... sample_rate=1e9,
|
|
68
|
+
... length=10_000_000
|
|
69
|
+
... )
|
|
70
|
+
"""
|
|
71
|
+
self._file_path = Path(file_path)
|
|
72
|
+
self._sample_rate = sample_rate
|
|
73
|
+
self._length = length
|
|
74
|
+
self._dtype = np.dtype(dtype)
|
|
75
|
+
self._offset = offset
|
|
76
|
+
self._metadata = metadata or {}
|
|
77
|
+
|
|
78
|
+
# Deferred data - loaded on first access
|
|
79
|
+
self._data: NDArray[np.float64] | None = None
|
|
80
|
+
self._memmap: np.memmap[Any, np.dtype[Any]] | None = None
|
|
81
|
+
|
|
82
|
+
# Verify file exists
|
|
83
|
+
if not self._file_path.exists():
|
|
84
|
+
raise LoaderError(f"File not found: {self._file_path}")
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def sample_rate(self) -> float:
|
|
88
|
+
"""Sample rate in Hz."""
|
|
89
|
+
return self._sample_rate
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def length(self) -> int:
|
|
93
|
+
"""Number of samples."""
|
|
94
|
+
return self._length
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def duration(self) -> float:
|
|
98
|
+
"""Duration in seconds."""
|
|
99
|
+
return self._length / self._sample_rate
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def metadata(self) -> dict[str, Any]:
|
|
103
|
+
"""Metadata dictionary."""
|
|
104
|
+
return self._metadata
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def data(self) -> NDArray[np.float64]:
|
|
108
|
+
"""Waveform data array.
|
|
109
|
+
|
|
110
|
+
Loads data on first access. Subsequent accesses return cached data.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
Numpy array of waveform samples.
|
|
114
|
+
"""
|
|
115
|
+
if self._data is None:
|
|
116
|
+
self._load_data()
|
|
117
|
+
return self._data # type: ignore[return-value]
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def time_vector(self) -> NDArray[np.float64]:
|
|
121
|
+
"""Time vector in seconds.
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
Array of time values corresponding to samples.
|
|
125
|
+
"""
|
|
126
|
+
return np.arange(self._length) / self._sample_rate
|
|
127
|
+
|
|
128
|
+
def _load_data(self) -> None:
|
|
129
|
+
"""Load data from file using memory mapping."""
|
|
130
|
+
try:
|
|
131
|
+
# Use memmap for efficient access
|
|
132
|
+
self._memmap = np.memmap(
|
|
133
|
+
self._file_path,
|
|
134
|
+
dtype=self._dtype,
|
|
135
|
+
mode="r",
|
|
136
|
+
offset=self._offset,
|
|
137
|
+
shape=(self._length,),
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Convert to regular array (copies data into memory)
|
|
141
|
+
self._data = np.array(self._memmap, dtype=np.float64)
|
|
142
|
+
|
|
143
|
+
except Exception as e:
|
|
144
|
+
raise LoaderError(f"Failed to load data from {self._file_path}: {e}") from e
|
|
145
|
+
|
|
146
|
+
def __getitem__(self, key: int | slice) -> LazyWaveformTrace | float:
|
|
147
|
+
"""Slice the trace.
|
|
148
|
+
|
|
149
|
+
Slicing remains lazy - only loads requested portion.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
key: Index or slice.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
LazyWaveformTrace for slice, float for single index.
|
|
156
|
+
|
|
157
|
+
Raises:
|
|
158
|
+
TypeError: If key is not int or slice.
|
|
159
|
+
|
|
160
|
+
Example:
|
|
161
|
+
>>> subset = trace[1000:2000] # Lazy - doesn't load full data
|
|
162
|
+
>>> sample = trace[500] # Loads single sample
|
|
163
|
+
"""
|
|
164
|
+
if isinstance(key, int):
|
|
165
|
+
# Load single sample
|
|
166
|
+
if self._memmap is None:
|
|
167
|
+
self._memmap = np.memmap(
|
|
168
|
+
self._file_path,
|
|
169
|
+
dtype=self._dtype,
|
|
170
|
+
mode="r",
|
|
171
|
+
offset=self._offset,
|
|
172
|
+
shape=(self._length,),
|
|
173
|
+
)
|
|
174
|
+
return float(self._memmap[key])
|
|
175
|
+
|
|
176
|
+
elif isinstance(key, slice):
|
|
177
|
+
# Create new lazy trace for slice
|
|
178
|
+
start, stop, step = key.indices(self._length)
|
|
179
|
+
|
|
180
|
+
if step != 1:
|
|
181
|
+
# Non-unit step requires loading data
|
|
182
|
+
if self._data is None:
|
|
183
|
+
self._load_data()
|
|
184
|
+
sliced_data = self._data[key] # type: ignore[index]
|
|
185
|
+
|
|
186
|
+
# Return eager trace
|
|
187
|
+
from oscura.core.types import TraceMetadata, WaveformTrace
|
|
188
|
+
|
|
189
|
+
metadata = TraceMetadata(
|
|
190
|
+
sample_rate=self._sample_rate,
|
|
191
|
+
**self._metadata, # type: ignore[arg-type]
|
|
192
|
+
)
|
|
193
|
+
return WaveformTrace(data=sliced_data, metadata=metadata) # type: ignore[return-value]
|
|
194
|
+
|
|
195
|
+
# Create lazy slice
|
|
196
|
+
length = stop - start
|
|
197
|
+
offset = self._offset + start * self._dtype.itemsize
|
|
198
|
+
|
|
199
|
+
return LazyWaveformTrace(
|
|
200
|
+
file_path=self._file_path,
|
|
201
|
+
sample_rate=self._sample_rate,
|
|
202
|
+
length=length,
|
|
203
|
+
dtype=self._dtype,
|
|
204
|
+
offset=offset,
|
|
205
|
+
metadata=self._metadata.copy(),
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
else:
|
|
209
|
+
raise TypeError(f"Indices must be int or slice, not {type(key)}")
|
|
210
|
+
|
|
211
|
+
def to_eager(self) -> WaveformTrace:
|
|
212
|
+
"""Convert to regular WaveformTrace by loading all data.
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
WaveformTrace with data loaded in memory.
|
|
216
|
+
|
|
217
|
+
Example:
|
|
218
|
+
>>> eager_trace = lazy_trace.to_eager()
|
|
219
|
+
"""
|
|
220
|
+
from oscura.core.types import TraceMetadata, WaveformTrace
|
|
221
|
+
|
|
222
|
+
metadata = TraceMetadata(
|
|
223
|
+
sample_rate=self._sample_rate,
|
|
224
|
+
**self._metadata, # type: ignore[arg-type]
|
|
225
|
+
)
|
|
226
|
+
return WaveformTrace(data=self.data, metadata=metadata)
|
|
227
|
+
|
|
228
|
+
def close(self) -> None:
|
|
229
|
+
"""Close memory-mapped file handle.
|
|
230
|
+
|
|
231
|
+
Should be called when done with the trace to free resources.
|
|
232
|
+
|
|
233
|
+
Example:
|
|
234
|
+
>>> trace = load_trace_lazy('huge_file.npy', lazy=True)
|
|
235
|
+
>>> # ... use trace ...
|
|
236
|
+
>>> trace.close()
|
|
237
|
+
"""
|
|
238
|
+
if self._memmap is not None:
|
|
239
|
+
del self._memmap
|
|
240
|
+
self._memmap = None
|
|
241
|
+
|
|
242
|
+
def __del__(self) -> None:
|
|
243
|
+
"""Cleanup memory map on deletion."""
|
|
244
|
+
self.close()
|
|
245
|
+
|
|
246
|
+
def __repr__(self) -> str:
|
|
247
|
+
"""String representation."""
|
|
248
|
+
return (
|
|
249
|
+
f"LazyWaveformTrace(file={self._file_path.name}, "
|
|
250
|
+
f"sample_rate={self._sample_rate:.2e}, "
|
|
251
|
+
f"length={self._length}, "
|
|
252
|
+
f"loaded={self._data is not None})"
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
def __len__(self) -> int:
|
|
256
|
+
"""Number of samples."""
|
|
257
|
+
return self._length
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def load_trace_lazy(
|
|
261
|
+
file_path: str | Path,
|
|
262
|
+
sample_rate: float | None = None,
|
|
263
|
+
*,
|
|
264
|
+
lazy: bool = True,
|
|
265
|
+
**kwargs: Any,
|
|
266
|
+
) -> LazyWaveformTrace | WaveformTrace:
|
|
267
|
+
"""Load trace with optional lazy loading.
|
|
268
|
+
|
|
269
|
+
Loads metadata immediately but defers data loading until first access
|
|
270
|
+
if lazy=True.
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
file_path: Path to trace file (must be .npy or raw binary).
|
|
274
|
+
sample_rate: Sample rate in Hz (required for raw files).
|
|
275
|
+
lazy: If True, defer data loading. If False, load immediately.
|
|
276
|
+
**kwargs: Additional arguments (dtype, offset, etc.).
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
LazyWaveformTrace if lazy=True, otherwise WaveformTrace.
|
|
280
|
+
|
|
281
|
+
Raises:
|
|
282
|
+
LoaderError: If file not found or has invalid format.
|
|
283
|
+
|
|
284
|
+
Example:
|
|
285
|
+
>>> # Lazy loading for huge files
|
|
286
|
+
>>> trace = load_trace_lazy('10GB_trace.npy', lazy=True)
|
|
287
|
+
>>> print(f"Duration: {trace.duration} seconds") # No data loaded yet
|
|
288
|
+
>>> data_subset = trace[1000:2000].data # Only loads this slice
|
|
289
|
+
>>>
|
|
290
|
+
>>> # Eager loading
|
|
291
|
+
>>> trace = load_trace_lazy('small_trace.npy', lazy=False)
|
|
292
|
+
>>> data = trace.data # Already loaded
|
|
293
|
+
|
|
294
|
+
References:
|
|
295
|
+
API-017: Lazy Loading for Huge Files
|
|
296
|
+
"""
|
|
297
|
+
file_path = Path(file_path)
|
|
298
|
+
|
|
299
|
+
if not file_path.exists():
|
|
300
|
+
raise LoaderError(f"File not found: {file_path}")
|
|
301
|
+
|
|
302
|
+
# Determine format
|
|
303
|
+
suffix = file_path.suffix.lower()
|
|
304
|
+
|
|
305
|
+
if suffix == ".npy":
|
|
306
|
+
# NumPy format - can read shape and dtype without loading data
|
|
307
|
+
with open(file_path, "rb") as f:
|
|
308
|
+
# Read NumPy header
|
|
309
|
+
import numpy.lib.format as npf
|
|
310
|
+
|
|
311
|
+
npf.read_magic(f) # type: ignore[no-untyped-call]
|
|
312
|
+
shape, _fortran_order, dtype = npf.read_array_header_1_0(f) # type: ignore[no-untyped-call]
|
|
313
|
+
offset = f.tell()
|
|
314
|
+
|
|
315
|
+
if not isinstance(shape, tuple) or len(shape) != 1:
|
|
316
|
+
raise LoaderError(f"Expected 1D array, got shape {shape}")
|
|
317
|
+
|
|
318
|
+
length = shape[0]
|
|
319
|
+
|
|
320
|
+
# Get sample rate from metadata or argument
|
|
321
|
+
if sample_rate is None:
|
|
322
|
+
raise LoaderError("sample_rate is required for .npy files")
|
|
323
|
+
|
|
324
|
+
if lazy:
|
|
325
|
+
return LazyWaveformTrace(
|
|
326
|
+
file_path=file_path,
|
|
327
|
+
sample_rate=sample_rate,
|
|
328
|
+
length=length,
|
|
329
|
+
dtype=dtype,
|
|
330
|
+
offset=offset,
|
|
331
|
+
)
|
|
332
|
+
else:
|
|
333
|
+
# Load eagerly
|
|
334
|
+
from oscura.core.types import TraceMetadata, WaveformTrace
|
|
335
|
+
|
|
336
|
+
data = np.load(file_path).astype(np.float64)
|
|
337
|
+
metadata = TraceMetadata(sample_rate=sample_rate)
|
|
338
|
+
return WaveformTrace(data=data, metadata=metadata)
|
|
339
|
+
|
|
340
|
+
else:
|
|
341
|
+
# Raw binary - need sample rate and dtype
|
|
342
|
+
if sample_rate is None:
|
|
343
|
+
raise LoaderError("sample_rate is required for raw binary files")
|
|
344
|
+
|
|
345
|
+
dtype = kwargs.get("dtype", np.float64)
|
|
346
|
+
offset = kwargs.get("offset", 0)
|
|
347
|
+
|
|
348
|
+
# Compute length from file size
|
|
349
|
+
file_size = file_path.stat().st_size - offset
|
|
350
|
+
dtype_size = np.dtype(dtype).itemsize
|
|
351
|
+
length = file_size // dtype_size
|
|
352
|
+
|
|
353
|
+
if lazy:
|
|
354
|
+
return LazyWaveformTrace(
|
|
355
|
+
file_path=file_path,
|
|
356
|
+
sample_rate=sample_rate,
|
|
357
|
+
length=length,
|
|
358
|
+
dtype=dtype,
|
|
359
|
+
offset=offset,
|
|
360
|
+
)
|
|
361
|
+
else:
|
|
362
|
+
# Load eagerly
|
|
363
|
+
from oscura.core.types import TraceMetadata, WaveformTrace
|
|
364
|
+
|
|
365
|
+
data = np.fromfile(file_path, dtype=dtype, count=length, offset=offset)
|
|
366
|
+
metadata = TraceMetadata(sample_rate=sample_rate)
|
|
367
|
+
return WaveformTrace(data=data.astype(np.float64), metadata=metadata)
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
__all__ = ["LazyWaveformTrace", "load_trace_lazy"]
|