decode-wfs — GitHub
Decode binary .WFS (Waveform Stream) files produced by Physical Acoustics
AEwin64 / EasyAE acoustic emission software into numpy arrays.
The AEwin64 manual describes a format using message ID 173 (0xAD), but real
streaming captures use ID 174 (0xAE) with a slightly different layout. This
library handles the empirically observed streaming format and has been verified
against real captures. Voltage values are recovered with the calibration factor
used by AEwin64's own ASCII waveform exporter (raw_counts / 16384).
Installation
pip install decode-wfs
Requires Python 3.10+ and numpy.
Quick start
decode_wfs() — full access to every record
from decode_wfs import decode_wfs
wfs = decode_wfs("capture.wfs")
print(f"Sample rate : {wfs.sample_rate_hz:,} Hz")
print(f"Channels : {wfs.channels()}")
print(f"Records : {len(wfs.waveforms)}")
# Iterate individual waveform records
for rec in wfs.waveforms:
print(rec.channel, rec.samples.shape, rec.samples.max())
# Time axis for each record (includes pretrigger offset when present)
t = wfs.waveforms[0].time_axis_s # 1-D float64 array in seconds
# Stack all records from channel 1 into a 2-D array (records × samples)
arr = wfs.to_array(channel=1) # shape (n_records, n_samples), float64 volts
wfs_to_numpy() — one-liner for a 2-D array
from decode_wfs import wfs_to_numpy
# shape: (n_waveforms, n_samples_per_waveform), dtype float64, units volts
arr = wfs_to_numpy("capture.wfs", channel=1)
# Optionally return the per-waveform time axis as well
arr, t = wfs_to_numpy("capture.wfs", channel=1, return_time_axis=True)
load_continuous() — single concatenated time series
Reconstructs the continuous stream from all records, preserving absolute timing when the file contains per-record position pointers.
from decode_wfs import load_continuous
samples, time, sr = load_continuous("capture.wfs", channel=1)
# samples: 1-D float64 array, volts
# time : 1-D float64 array, seconds (negative at pretrigger)
# sr : sample rate in Hz
# Example: compute a power spectrum
import numpy as np
freqs = np.fft.rfftfreq(len(samples), d=1 / sr)
psd = np.abs(np.fft.rfft(samples)) ** 2
API summary
| Function / class | Returns | When to use |
|---|---|---|
decode_wfs(path) |
WFSFile |
Need per-record metadata or multi-channel data |
wfs_to_numpy(path) |
ndarray (n×m) |
Just need the voltage array |
load_continuous(path) |
(samples, time, sr) |
Spectral analysis on a single channel |
WFSFile.to_array(channel) |
ndarray (n×m) |
After decode_wfs, stack records into an array |
WFSFile.waveform_time_axis_s() |
ndarray (m,) |
Shared time axis for all waveform records |
WFSFile.channels() |
list[int] |
Which AE channel numbers are in the file |
WaveformRecord.time_axis_s |
ndarray (m,) |
Per-record time axis including pretrigger |
Logging
decode_wfs uses the standard logging module under the logger name
decode_wfs. To see progress messages:
import logging
logging.basicConfig(level=logging.INFO)
CLI
decode-wfs <file.wfs> [--channel N] [--max-records N]
Example:
$ decode-wfs capture.wfs --channel 1
File : capture.wfs
Sample rate : 1,000,000 Hz (1.000 MSPS)
Sample format : ADT=2 (16-bit signed samples)
Setup blocks : 2
Pretrigger : -256 samples
Total records : 512
Channels : [1, 2]
[0] ch=1 n=4096 samples min=-0.03125000 V max=0.04394531 V std=0.00201416 V
[1] ch=1 n=4096 samples min=-0.02832031 V max=0.03955078 V std=0.00198364 V
...
numpy array shape : (256, 4096) dtype=float64
Done.
Notes
- Voltage calibration — ADC counts are divided by 16384 to match the voltages in AEwin64's ASCII waveform exports. This factor was determined empirically and may not hold for all hardware configurations.
- Pretrigger — when a pretrigger window is configured,
time_axis_sand thetimeaxis fromload_continuousstart at a negative time offset so that t=0 corresponds to the trigger point. - Multi-channel files — records from all channels are interleaved in the
file. Use the
channelparameter to filter, or iterateWFSFile.waveformsand checkrec.channel. - Large files — pass
max_records=Nto any function to stop after reading N waveform records, useful during development.
Metadata
Release files for decode-wfs 0.1.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| decode_wfs-0.1.3.tar.gz | 12.4 kB | Without provenance | Details for decode_wfs-0.1.3.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| decode_wfs-0.1.3-py3-none-any.whl | Python 3 | none | any | Details for decode_wfs-0.1.3-py3-none-any.whl |
Total release size: 24.7 kB
Release files / decode_wfs-0.1.3.tar.gz
| Download URL | decode_wfs-0.1.3.tar.gz |
|---|---|
| Size | 12.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9926620a4f3f00ef370846f33ed2b9afc661a5a5eb18d3a95d3feeb92f544e16
|
|
BLAKE2b-256 checksum How to use checksums |
523f3bafaad0e102b8912f24b5981adb717f0de0100ab1c872a1c8fd2fc66d22
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.9
|
Release files / decode_wfs-0.1.3-py3-none-any.whl
| Download URL | decode_wfs-0.1.3-py3-none-any.whl |
|---|---|
| Size | 12.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8f6a19250a9f74c9967f61d570090136cfd0d386767d8548bb9b74aa8bc3d4d6
|
|
BLAKE2b-256 checksum How to use checksums |
14521847c0af94f2d0f6774d85a7b3c866dbee1b2f00b24e73fffb62072e7978
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.9
|
Security for decode-wfs 0.1.3
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.