Output & containers¶
The sample type (the datatype), the container (the file format), and the byte order are three orthogonal choices.
Output parameter reference¶
| Flag | Values | Default | Meaning |
|---|---|---|---|
--sample-type |
cf32 cf64 ci32 ci16 ci8 |
cf32 |
wire type; integers are full-scale ±1.0 |
--file-type |
raw csv blue sigmf |
raw |
container (below) |
--endian |
le be |
le |
byte order (raw/BLUE only; csv is text) |
--output / -o |
path (or zmq://…) |
stdout | sink |
--record |
path | — | write a JSON record of the resolved run (see Scenes) |
The integer sample types map ±1.0 → ±max-code (and can clip on PAPR > 0 dB content); see Levels & SNR → Scaling to the wire.
Containers¶
--file-type |
Output | Notes |
|---|---|---|
raw |
interleaved I/Q in the chosen --sample-type |
the SDR default; honors --endian |
csv |
one I,Q line per sample |
%0.9f cf32, %0.17g cf64, %d integer; text, no endian |
blue |
X-Midas / REDHAWK BLUE type-1000 | (wfmgen only) self-describing 512-byte header |
sigmf |
<base>.sigmf-data + <base>.sigmf-meta |
(wfmgen only) one annotation per segment |
BLUE type-1000 writes a complete 512-byte X-Midas/REDHAWK Header Control
Block so one file is fully self-describing: data_rep←--endian, format
(CB/CI/CL/CF/CD)←--sample-type, and xdelta = 1/fs. Add
--detached to split it into a header + data pair — <out>.hdr (the HCB,
with detached=1 and data_start=0) and <out>.det (the raw samples). Detached
output requires --output and a finite (non---continuous) run; attached mode
keeps whatever extension you give -o (.blue/.prm/.tmp).
SigMF writes the samples as raw into <base>.sigmf-data and a JSON sidecar
<base>.sigmf-meta with core:datatype/core:sample_rate, a capture at --fc,
and one annotation per composer segment (frequency edges, label, wfmgen:*
params).
# 16-bit big-endian into a self-describing BLUE file
wfmgen --type qpsk --count 200000 --sample-type ci16 --endian be \
--file-type blue -o capture.blue
# a SigMF pair (capture.sigmf-data + capture.sigmf-meta)
wfmgen --from-file scenario.json --sample-type ci16 --file-type sigmf -o capture
SigMF sidecar schema¶
The .sigmf-meta JSON is SigMF 1.0.0 with one annotation per source per
segment, so a multi-segment / multi-source scene becomes a self-labelling
ground-truth capture. The exact shape wfmgen (and Composer.to_sigmf) emit —
see native/src/wfm/wfm_writer.c:
{
"global": {
"core:datatype": "ci16_le",
"core:sample_rate": 1000000,
"core:version": "1.0.0",
"core:description": "doppler wfmgen",
"core:author": "doppler wfmgen"
},
"captures": [
{ "core:sample_start": 0, "core:frequency": 2400000000.0 }
],
"annotations": [
{
"core:sample_start": 0,
"core:sample_count": 4096,
"core:freq_lower_edge": -62500.0,
"core:freq_upper_edge": 62500.0,
"core:label": "qpsk",
"wfmgen:snr": 20.0,
"wfmgen:snr_mode": "esno",
"wfmgen:sps": 8,
"wfmgen:seed": 1,
"wfmgen:pn_length": 7,
"wfmgen:pn_poly": 0
}
]
}
core:datatypeis<sample_type>_<endian>(cf32_le,ci16_be, …).captures[0].core:frequencyis--fc(the RF centre); annotation frequency edges are baseband offsets from it — a chirp spansf_start..f_end, a modulated source is roughly±fs/(2·sps)wide, a tone is a point.core:labelis the source type; thewfmgen:*keys carry the generator parameters so the capture round-trips to the spec that made it.
Composer.to_sigmf(sample_type="cf32", endian="le", fs=1e6, fc=0.0) returns this
document as a string; pair it with a Writer(..., file_type="sigmf") data file.
Sinks¶
--output |
Result |
|---|---|
| (omitted) | binary stream to stdout (pipe it) |
file.iq |
write to a file |
zmq://tcp://*:5555 |
(wfmgen only) publish to a ZMQ PUB endpoint (SIGS wire format) |
wfmgen --type tone --count 1000000 | other-tool # pipe via stdout
wfmgen --type tone --continuous --output zmq://tcp://*:5555 # stream forever to ZMQ
A dp_sub_* subscriber (e.g. examples/c/spectrum_analyzer) reads the ZMQ
stream. For pacing a live stream to the true sample rate, see
Streaming.
Reading a capture back¶
The raw container is interleaved I/Q in the chosen --sample-type, so a
naive np.fromfile gets the layout (and, for integers, the scale) wrong.
read_iq does the right thing — a zero-copy complex view for the float types, a
SIMD rescale to ±1.0 for the integer types; the container-aware Reader also
auto-detects BLUE/SigMF/CSV/raw and recovers fs/fc/sample-type from metadata:
from doppler.wfm import read_iq, Reader
iq = read_iq("capture.iq", sample_type="ci16") # → complex64, ±1.0
with Reader("capture.blue") as r: # container auto-detected
print(r.file_type, r.fs, r.num_samples)
x = r.read(r.num_samples) # or block-wise: r.read(4096)
generate → read_iq is bit-faithful. See
Type System → Reading interleaved I/Q
and the Python API page.