Skip to content

Raw IQ Processing (pynasonde.digisonde.raw)

This section documents the raw IQ processing pipeline for DPS4D Digisonde recordings.

Contents

  • raw_parse.mdprocess() function: full sounding pipeline (IQ → complementary-code correlation → ionogram NetCDF)
  • iq_reader.mdIQStream reader: time-partitioned binary file management
  • raw_plots.mdRawPlots / AFRLPlots: PSD and spectrogram visualisation helpers

Example quick start

from pynasonde.digisonde.raw.raw_parse import process
import datetime as dt

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",
}
process(program, dir_iq="/media/chakras4/69F9D939661D263B", out_dir="out/")

pynasonde.digisonde.raw.raw_parse

Python translation of the Julia Digisonde module.

The process function reproduces the DPS4D Digisonde ionogram pipeline: reading complex baseband IQ recordings, performing complementary-code correlation, assembling range–frequency power grids, and writing results to a NetCDF product. The logic mirrors the original Julia implementation as closely as possible while adopting Pythonic structure and naming.


Integration depth and the "how many .bin files?" question

Each sounding epoch requires reading a block of one-second IQ recordings. The total duration (and therefore the number of files) is determined by the program parameters::

n_coarse = (upper_freq - lower_freq) / coarse_step   # e.g. 435
pulses_per_freq = n_rep × 2 (comp. pair) × n_pol     # e.g. 32
total_pulses = n_coarse × pulses_per_freq              # e.g. 13 920
sounding_duration_s = total_pulses × IPP               # e.g. 139 s

For the default Kirtland schedule (IPP = 10 ms, nRep = 8, O+X, 2–15 MHz): each sounding reads ~139 one-second .bin files. The 12-minute cadence gives enough margin so that the next sounding starts only after all data for the current one has been collected.

Reducing Number of Integrated Repeats (nRep) trades SNR for cadence:

+------+-------------------+-------------------+------------+ | nRep | Sounding duration | Min. cadence | SNR loss | +======+===================+===================+============+ | 8 | ~139 s | ~3 min | baseline | | 4 | ~70 s | ~2 min | −1.5 dB | | 2 | ~35 s | ~1 min | −3.0 dB | | 1 | ~17 s | ~30 s | −4.5 dB | +------+-------------------+-------------------+------------+

SNR scales as √nRep because the correlation voltage averages coherently across repeats before the Doppler FFT. For strong daytime F-layer echoes nRep = 4 is usually sufficient; for weak or disturbed conditions keep nRep = 8.

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:

  1. Open the IQ stream via :class:~pynasonde.digisonde.raw.iq_reader.IQStream.
  2. 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.

  1. Vectorised Doppler FFT across all range gates → peak power and phase.
  2. Optionally write a compressed NetCDF product.
Parameters
program

Dictionary of sounding-program parameters. Required keys:

  • "Epoch" – :class:datetime.datetime or 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).