Examples¶
Runnable snippets for the most common DAS workflows. All examples import:
1. Inspect a capture without reading the samples¶
Open, print the metadata, close — no sample bytes touched:
with File("capture.dat") as f:
print(f)
# File('capture.dat', mode=iq, shape=(10000, 974),
# sample_rate=250000000 Hz, trig_frequency=1000 Hz,
# duration=10.00s, distance=507.21m)
print(f"Mode: {f.mode.value}")
print(f"Pulses: {f.num_lines:,}")
print(f"Positions per pulse:{f.positions_per_line:,}")
print(f"Sample rate: {f.sample_rate / 1e6:.0f} MSps")
print(f"Trigger rate: {f.trig_frequency} Hz")
print(f"Duration: {f.duration:.2f} s")
print(f"Round-trip distance:{f.distance:.1f} m of fibre")
print(f"Range: ±{f.range:.2f} V")
print(f"Timestamp: {f.timestamp}")
Useful as a quick sanity check before launching a long batch processing job.
2. Plot the first pulse of an IQ capture¶
with File("iq.dat") as f:
assert f.mode is Mode.IQ
buf = f.read_lines(1) # first pulse only
i = f.get_i(buf)[0] # (positions_per_line,)
q = f.get_q(buf)[0]
position_m = np.arange(f.positions_per_line) * (f.distance / f.positions_per_line)
fig, (ax_iq, ax_iv) = plt.subplots(2, 1, figsize=(10, 6), sharex=True)
ax_iq.plot(position_m, i, label="I", lw=0.8)
ax_iq.plot(position_m, q, label="Q", lw=0.8)
ax_iq.set_ylabel("ADC code")
ax_iq.legend()
# Same data in volts using the physical-unit extractor.
iq_v = f.get_iq_volts(buf)[0]
ax_iv.plot(position_m, np.abs(iq_v) * 1000, label="|I+jQ| [mV]", color="C2")
ax_iv.set_ylabel("Envelope (mV)")
ax_iv.set_xlabel("Along-fibre distance (m)")
ax_iv.legend()
plt.tight_layout()
plt.show()
3. Wrapped phase from an IQ capture¶
with File("iq.dat") as f:
assert f.mode is Mode.IQ
# Stream 1 second at a time, accumulate the wrapped phase trace.
n_per_block = f.trig_frequency # 1 second of pulses
phase_blocks = []
while f.lines_left:
chunk = f.read_lines(n_per_block)
iq = f.get_iq_volts(chunk) # complex64, volts
wrapped = np.angle(iq).astype(np.float32)
phase_blocks.append(wrapped)
phase_rad = np.concatenate(phase_blocks) # (num_lines, positions)
print("Wrapped phase shape:", phase_rad.shape)
This gives you the wrapped (±π) phase computed in software from
I/Q. For unwrapped + fading-suppressed + gauge-differential phase,
acquire in Mode.PHASE directly instead — the FPGA does the heavy
lifting.
4. Magnitude waterfall from ArctanMagnitude¶
with File("arctan_mag.dat") as f:
assert f.mode is Mode.ARCTAN_MAGNITUDE
mag_v = f.get_magnitude_volts(n=f.num_lines) # (num_lines, positions), V
t = np.arange(mag_v.shape[0]) / f.trig_frequency
x_m = np.arange(mag_v.shape[1]) * (f.distance / mag_v.shape[1])
plt.figure(figsize=(10, 6))
plt.imshow(
mag_v * 1000, # mV
aspect="auto", origin="lower",
extent=[x_m[0], x_m[-1], t[0], t[-1]],
cmap="viridis",
)
plt.xlabel("Along-fibre distance (m)")
plt.ylabel("Time (s)")
plt.colorbar(label="|envelope| (mV)")
plt.title("Magnitude waterfall")
plt.show()
5. Process a multi-GB capture without OOMing¶
The naive read_all() would load 20 GB at once. Streaming
chunk-by-chunk keeps the peak memory bounded:
with File("multi_gb.dat") as f:
chunk_size = 5_000 # ~5s of pulses at 1 kHz triggers
running_var = np.zeros(f.positions_per_line, dtype=np.float64)
n_seen = 0
while f.lines_left:
chunk = f.read_lines(chunk_size)
# Spatial mean & variance, accumulated online.
for pulse in chunk:
n_seen += 1
delta = pulse - running_var
running_var += delta / n_seen
# `running_var` is the per-position mean across the whole capture
# — never held more than `chunk_size × positions_per_line × 2 B`
# of pulses in RAM at once.
Combine with multiprocessing for embarrassingly-parallel passes — open
one File per worker (each has its own internal cursor; sharing a
single File across processes is not supported).
6. Dispatch in a generic processing pipeline¶
When you write a function that should accept any DAS file regardless
of mode, dispatch on f.mode and return a uniform (rows,
positions) float32 array:
def physical_amplitude(f: File) -> np.ndarray:
"""Return the per-position amplitude (V) for any mode."""
match f.mode:
case Mode.RAW:
# ADC codes → volts via the range field.
return f.read_lines(f.lines_left).astype(np.float32) * (f.range / 32768)
case Mode.IQ:
return np.abs(f.get_iq_volts())
case Mode.ARCTAN_MAGNITUDE:
return f.get_magnitude_volts()
case Mode.PHASE:
# Phase has no amplitude — return zeros to keep the
# caller's shape contract.
return np.zeros(f.spatial_shape, dtype=np.float32)
case _:
raise ValueError(f"Unsupported mode: {f.mode}")
with File("any.dat") as f:
amp = physical_amplitude(f)
print(amp.shape, amp.dtype)
7. Read once, dispatch many times¶
For exploratory analysis you often want to look at multiple channels of the same buffer. Pass the buffer through to keep the read cursor intact:
with File("iq.dat") as f:
buf = f.read_lines(1000)
# All four lanes from the same bytes — no extra disk read.
i = f.get_i(buf)
q = f.get_q(buf)
iq = f.get_iq(buf)
iv = f.get_i_volts(buf)
iqv = f.get_iq_volts(buf)
Each call is a thin Rust loop over buf; the file handle is never
touched.
8. Convert between formats¶
The SDK can read every Audace format but only writes .dat. To
re-export an HDF5 capture as a DAT (e.g. for a downstream tool that
only accepts DAT), use export_dat:
from invisensing import File, export_dat, Mode
with File("acquisition.h5") as f:
if f.mode is Mode.PHASE:
samples = f.read_all()
export_dat(
"acquisition.dat",
samples,
sample_rate=f.sample_rate,
trig_frequency=f.trig_frequency,
range_v=f.range,
timestamp=f.timestamp,
flags=f.flags, # preserve PHASE / FLOAT / DEMODULATED
)
Round-trip is byte-perfect for the common cases — the 128-byte header is reconstructed and the samples are written verbatim.
9. Drop-in replacement for the legacy SDK¶
Old scripts using invisensing.File keep working without changes:
import invisensing.File as iFile
file = iFile.File("acquisition.dat")
print(file.get_line_size(), file.get_sample_rate())
while file.get_lines_left() > 0:
data = file.get_lines(5)
# … existing processing logic …
The legacy class is a thin wrapper around the modern File — same
Rust-backed implementation, same throughput, no migration needed.