📡 GNU Radio Dedispersion Flowgraph (ASCII)
+----------------------+
| Signal/File Source |
| (SDR or IQ file) |
+----------+-----------+
|
v
+------------------------------+
| Frequency Xlating FIR Filter |
| (tune + decimate) |
+--------------+---------------+
|
v
+------------------------------+
| PFB Channelizer |
| (split into N channels) |
+--------------+---------------+
|
v
+------------------------------+
| Stream to Vector |
| (length = N channels) |
+--------------+---------------+
|
v
+------------------------------+
| Dedispersion Block |
| (Python: per-channel delay) |
+--------------+---------------+
|
v
+------------------------------+
| Vector to Stream |
+--------------+---------------+
|
v
+------------------------------+
| Channel Sum (Adder) |
| (collapse to time series) |
+--------------+---------------+
|
+------+-+
| |
v v
+-----------+ +----------------+
| QT Scope | | File Sink |
| (visual) | | (save output) |
+-----------+ +----------------+
🧠 Dedispersion Core Idea (ASCII)
Dispersion delay:
delta_t = 4.15e3 * DM * (f^-2 - f_ref^-2) [ms]
Convert to samples:
delay_samples = delta_t * sample_rate
Each channel is delayed differently, then aligned and summed.
🧩 Key Block Notes
Signal Source
- SDR (Osmocom / SDRplay)
OR
- File Source (complex64)
Frequency Xlating FIR
Purpose:
- Shift desired band to baseband
- Reduce sample rate
Typical:
- Decimation: 2–10
- LPF cutoff: ~bandwidth/2
PFB Channelizer
Block: pfb.channelizer_ccf
Splits wideband signal into:
[ f1 ][ f2 ][ f3 ] ... [ fN ]
Typical N:
- 64
- 128
- 256
Stream to Vector
Input: scalar stream
Output: vectors of length N
Each vector = one time sample across all frequencies
Dedispersion Block (core logic)
Pseudo-ASCII logic:
for each time sample:
for each channel:
delayed_sample = buffer[channel][delay[channel]]
output aligned vector
Channel Sum
sum = ch1 + ch2 + ch3 + ... + chN
Result:
- Dedispersed time series
⚙️ Parameters
Channels (N): 64–256
Bandwidth: 1–10 MHz
Sample rate: depends on SDR
DM range: 0–500 pc/cm^3
Time resolution: ms scale
🧪 Usage Notes
1. Frequency Array
Must match channelizer:
freqs = linspace(f_low, f_high, N)
2. Tradeoffs
More channels:
+ Better dedispersion
- More CPU load
3. Real-Time vs Offline
Real-time:
- Fewer channels
- Smaller bandwidth
Offline:
+ More accurate
+ Easier debugging
4. DM Search
for DM in range(0, 500):
run dedispersion
check for peaks
5. Performance Tips
- Precompute delays
- Use NumPy arrays
- Keep buffers fixed size
6. With Interferometry (your setup)
Two approaches:
Option A:
correlate -> dedisperse
Option B (advanced):
dedisperse per channel -> correlate
🚀 Minimal Working Order
1. Record IQ data
2. Channelize
3. Apply dedispersion
4. Sum channels
5. Look for pulses
🧩 1. GNU Radio Companion (.grc) Flowgraph (text you can import)
Save this as dedispersion_flowgraph.grc and open in GNU Radio Companion.
<?xml version="1.0"?>
<flow_graph>
<block>
<key>options</key>
<param><key>id</key><value>dedispersion_flowgraph</value></param>
</block>
<!-- VARIABLES -->
<block>
<key>variable</key>
<param><key>id</key><value>samp_rate</value></param>
<param><key>value</key><value>2e6</value></param>
</block>
<block>
<key>variable</key>
<param><key>id</key><value>nchan</value></param>
<param><key>value</key><value>128</value></param>
</block>
<!-- FILE SOURCE -->
<block>
<key>blocks_file_source</key>
<param><key>file</key><value>/path/to/iq_data.c64</value></param>
<param><key>type</key><value>complex</value></param>
<param><key>repeat</key><value>False</value></param>
</block>
<!-- THROTTLE (for file playback) -->
<block>
<key>blocks_throttle</key>
<param><key>sample_rate</key><value>samp_rate</value></param>
</block>
<!-- CHANNELIZER -->
<block>
<key>pfb_channelizer_ccf</key>
<param><key>numchans</key><value>nchan</value></param>
</block>
<!-- STREAM TO VECTOR -->
<block>
<key>blocks_stream_to_vector</key>
<param><key>num_items</key><value>nchan</value></param>
<param><key>type</key><value>float</value></param>
</block>
<!-- PYTHON DEDISPERSION BLOCK -->
<block>
<key>embedded_python_block</key>
<param>
<key>source_code</key>
<value>
import numpy as np
from gnuradio import gr
class blk(gr.sync_block):
def __init__(self, dm=50.0, nchan=128, samp_rate=2e6):
gr.sync_block.__init__(self,
name="dedisperse",
in_sig=[(np.float32, nchan)],
out_sig=[(np.float32, nchan)])
freqs = np.linspace(1400e6, 1420e6, nchan)
f_ref = np.max(freqs)
k = 4.15e3
delays = k * dm * (freqs**-2 - f_ref**-2)
self.delays = np.round(delays * 1e-3 * samp_rate).astype(int)
self.buffers = [np.zeros(abs(d)+1) for d in self.delays]
def work(self, input_items, output_items):
inp = input_items[0]
out = output_items[0]
for i in range(len(inp)):
vec = inp[i]
aligned = np.zeros_like(vec)
for ch in range(len(vec)):
buf = self.buffers[ch]
buf = np.append(buf, vec[ch])
aligned[ch] = buf[-self.delays[ch]]
self.buffers[ch] = buf[-len(buf):]
out[i] = aligned
return len(out)
</value>
</param>
</block>
<!-- VECTOR TO STREAM -->
<block>
<key>blocks_vector_to_stream</key>
<param><key>num_items</key><value>nchan</value></param>
<param><key>type</key><value>float</value></param>
</block>
<!-- ADDER -->
<block>
<key>blocks_add_xx</key>
<param><key>type</key><value>float</value></param>
<param><key>num_inputs</key><value>nchan</value></param>
</block>
<!-- QT TIME SINK -->
<block>
<key>qtgui_time_sink_f</key>
<param><key>size</key><value>1024</value></param>
<param><key>samp_rate</key><value>samp_rate</value></param>
</block>
</flow_graph>
🚀 2. Extended FRB / Pulse Detection Pipeline
Here’s a more complete search pipeline built on top of that flowgraph.
📡 Full Signal Chain (ASCII)
[ IQ Source ]
|
v
[ Channelizer ]
|
v
[ Dedispersion ]
|
v
[ Channel Sum ]
|
v
[ Moving Average ]
|
v
[ Threshold Detector ]
|
+------> [ Trigger Log ]
|
v
[ Waterfall Display ]
🧠 Additional Blocks to Add
1. Moving Average (Noise Reduction)
Block: blocks.moving_average_ff
Length: 10–100 samples
Purpose:
- Smooth noise
- Improve SNR
2. Power Detection
If needed:
complex → magnitude^2
Block:
blocks.complex_to_mag_squared
3. Threshold Detector
Simple approach:
if signal > mean + N*sigma → trigger
GNU Radio option:
blocks.threshold_ff
Typical:
low = 0.5
high = 2.0 (tune experimentally)
4. Event Logger (Python Block)
if sample > threshold:
print("Pulse detected at time:", n)
Better:
- Write timestamps to file
- Save short data snippet
5. Waterfall (Frequency-Time)
Add:
QT GUI Waterfall Sink
Before dedispersion:
- See dispersion sweep
After:
- Verify alignment
🔍 DM Search Extension
To detect unknown sources:
Option A: Manual Sweep
Run flowgraph multiple times with different DM
Option B: Parallel Dedispersion
[Channelizer]
|
+--> [DM=10] \
+--> [DM=20] >--> compare outputs
+--> [DM=30] /
⚡ Performance Scaling
For your kind of setup:
Small test
Channels: 64
BW: 1 MHz
Serious FRB hunting
Channels: 256–1024
BW: 5–20 MHz
GPU recommended
🧪 Practical Workflow
1. Record IQ (wideband)
2. Visualise waterfall (confirm dispersion)
3. Tune DM manually
4. Enable detection threshold
5. Log candidates
🛰️ Nice Upgrade Paths
If you want to push this further:
- ✅ Coherent dedispersion (phase correction)
- ✅ GPU dedispersion (CuPy / CUDA)
- ✅ Real-time candidate clustering
- ✅ Multi-DM heatmap display
- ✅ Integration with your interferometer baseline