Raw IQ Pipeline — raw_parse¶
P
pynasonde.digisonde.raw.raw_parse — full sounding pipeline (IQ → correlation → ionogram) and the IonogramResult container.
IonogramResult¶
C Dataclass that holds all arrays produced by a single Digisonde sounding.
pynasonde.digisonde.raw.raw_parse.IonogramResult
dataclass
¶
Container for a single processed Digisonde sounding.
All arrays are in physical units. Use :meth:to_netcdf to write the
standard compressed NetCDF product, or :meth:to_xarray to get an
in-memory xarray.Dataset for interactive analysis.
Attributes¶
frequency_hz
RF frequencies at which the sounding was taken, shape (n_freqs,),
in Hz.
range_km
Virtual heights (group range) corresponding to each range gate, shape
(n_ranges,), in km. The 220 µs Digisonde hardware delay has
already been subtracted.
time_unix
Unix timestamp of the first pulse for each sounding frequency, shape
(n_freqs,).
power_o
Ordinary-mode receive power (linear, arbitrary units), shape
(n_freqs, n_ranges).
power_x
Extraordinary-mode receive power (linear, arbitrary units), same shape.
All zeros when Polarization != "O and X".
phase_o
Ordinary-mode phase at the Doppler peak (radians), or None when
Save Phase was not requested.
phase_x
Extraordinary-mode phase at the Doppler peak (radians), or None.
program_id
Identifier string from the program dictionary (e.g. "DPS4D_Kirtland0").
epoch
UTC epoch of the sounding.
frequency_mhz: np.ndarray
property
¶
Frequency axis in MHz.
power_total: np.ndarray
property
¶
Sum of O- and X-mode power (linear).
power_db(mode='total')
¶
Functions¶
M
process — run the full sounding pipeline and return an IonogramResult.
pynasonde.digisonde.raw.raw_parse.process(program, dir_iq='/mnt/Data/', out_dir='out/', min_range=-math.inf, max_range=math.inf, nc_flag=True, verbose=True)
¶
Run a single Digisonde sounding program and return an :class:IonogramResult.
The pipeline mirrors the Julia Digisonde.process function:
- Open the IQ stream via :class:
~pynasonde.digisonde.raw.iq_reader.IQStream. - For every (frequency, polarisation, repeat, complementary-code) tuple:
a. Read n_samples raw IQ samples for the pulse's sub-time.
b. FFT → frequency-shift to baseband + decimate to chip bandwidth.
c. IFFT → optional fine-frequency residual mix.
d. Interpolate onto the 30 kHz chip grid.
e. Correlate with the reversed phase code via FFT convolution.
f. Accumulate into the CIT voltage array.
- Vectorised Doppler FFT across all range gates → peak power and phase.
- Optionally write a compressed NetCDF product.
Parameters¶
program
Dictionary of sounding-program parameters. Required keys:
"Epoch"– :class:datetime.datetimeor Unix timestamp"ID"– string identifier written into the NetCDF"Freq Stepping Law"– must be"linear""Wave Form"– must be"16-chip complementary""Lower Freq Limit"/"Upper Freq Limit"– Hz"Coarse Freq Step"/"Fine Freq step"– Hz"Number of Fine Steps"– int"Fine Multiplexing"– bool"Inter-Pulse Period"– seconds (IPP)"Number of Integrated Repeats"– int (nRep)"Interpulse Phase Switching"– bool"Polarization"–"O and X"or"O"
Optional keys: "rxTag" (default "ch0"),
"Save Phase" (default False), "FFTMode" (default False).
dir_iq
Root directory containing the time-partitioned IQ recordings.
out_dir
Root directory for NetCDF output. A sub-path
<hostname>/<ID>/<YYYY-mm-dd>/ is created automatically.
min_range, max_range: Reserved for future range-gate masking (not yet applied).
nc_flag
Write the NetCDF product when True (default).
verbose
Emit progress messages via loguru.
Returns¶
IonogramResult or None
None only if the output file already existed and was skipped.
Notes¶
Integration depth and file count — see module docstring for the full
table. For the default Kirtland schedule (IPP = 10 ms, nRep = 8, O+X,
2–15 MHz at 30 kHz steps) each call reads ≈139 one-second .bin files
and takes about 139 s of wall-clock IQ data. The 12-minute cadence in
:func:main leaves margin before the next sounding. Reducing nRep
proportionally shortens the sounding and trades SNR (scales as √nRep).
M
read_pulse — read raw IQ for a single pulse without running the full pipeline.
pynasonde.digisonde.raw.raw_parse.read_pulse(program, dir_iq, coarse_index, fine_index=0, pol_index=0, rep_index=0, comp_index=0)
¶
Read the raw complex IQ samples for one specific pulse.
This is the entry point for inspecting individual pulses without running
the full sounding pipeline. The returned samples are in the ADC's native
baseband (full receiver bandwidth); use the FFT-domain mixing logic in
:func:process to tune to a narrower band around a specific frequency.
Parameters¶
program
Same program dictionary passed to :func:process.
dir_iq
Root directory of the IQ recording tree.
coarse_index
Which coarse-frequency step to read (0-based).
fine_index
Fine-frequency sub-step within the coarse step (0 for most programs).
pol_index
Polarisation index: 0 = O-mode, 1 = X-mode.
rep_index
Repeat (integration) index within the CIT, 0 … nRep-1.
comp_index
Complementary-code index: 0 = code A, 1 = code B.
Returns¶
np.ndarray
Complex64 array of length n_samples (next power-of-two of
IPP × f_sample).
Examples¶
samples = read_pulse(program, "/media/data", coarse_index=0) import matplotlib.pyplot as plt plt.plot(samples.real[:500])
Quick start¶
import datetime as dt
from pynasonde.digisonde.raw.raw_parse import process
program = {
"Epoch": dt.datetime(2023, 10, 14, 16, 0, tzinfo=dt.timezone.utc),
"ID": "DPS4D_Kirtland0",
"FFTMode": False,
"rxTag": "ch0",
"Save Phase": False,
"Freq Stepping Law": "linear",
"Lower Freq Limit": 2e6,
"Upper Freq Limit": 15e6,
"Coarse Freq Step": 30e3,
"Number of Fine Steps": 1,
"Fine Freq step": 5e3,
"Fine Multiplexing": False,
"Inter-Pulse Period": 10e-3,
"Number of Integrated Repeats": 8,
"Interpulse Phase Switching": False,
"Wave Form": "16-chip complementary",
"Polarization": "O and X",
}
result = process(program, dir_iq="/media/chakras4/69F9D939661D263B", out_dir="out/")
if result is not None:
print(result.frequency_mhz) # (n_freqs,) array in MHz
ds = result.to_xarray() # xarray.Dataset
result.to_netcdf("out/ionogram.nc") # write to disk
Accessing raw IQ for a single pulse¶
from pynasonde.digisonde.raw.raw_parse import read_pulse
iq = read_pulse(
program,
dir_iq="/media/chakras4/69F9D939661D263B",
coarse_index=10, # 10th coarse frequency step
pol_index=0, # O-mode
rep_index=0, # first repeat
comp_index=0, # complementary code A
)
# iq is a np.ndarray of np.complex64, length = file_size (≈ sample rate)