Claude Code for Radar Signal Processing (2026)
Why Claude Code for Radar Signal Processing
Radar signal processing chains multiple mathematically dense stages: pulse compression via matched filtering, moving target indication (MTI) for clutter rejection, Doppler FFT for velocity estimation, and CFAR (Constant False Alarm Rate) detection to set adaptive thresholds. Each stage has parameters tied to the radar’s physical characteristics – PRF, bandwidth, pulse width, antenna beamwidth – and getting one wrong cascades errors through the entire chain.
Claude Code handles the range-Doppler math, generates matched filter coefficients from your waveform parameters, and builds CFAR detectors that adapt to your clutter environment. It bridges the gap between radar textbook equations and working numpy code that processes actual I/Q data.
The Workflow
Step 1: Radar Processing Setup
pip install numpy scipy matplotlib
pip install pyfftw # FFTW3 bindings for fast FFT
pip install h5py # for radar data files
mkdir -p src/radar src/waveforms src/detection tests/
Step 2: Build a Pulse Compression Pipeline
# src/radar/pulse_compression.py
"""Linear FM (chirp) pulse compression for pulsed Doppler radar.
Implements range processing via matched filtering.
"""
import numpy as np
from scipy import signal
from dataclasses import dataclass
@dataclass
class RadarParams:
fc_ghz: float # Carrier frequency
bandwidth_mhz: float # Chirp bandwidth
pulse_width_us: float # Pulse duration
prf_hz: float # Pulse repetition frequency
fs_mhz: float # ADC sample rate
num_pulses: int # Pulses per CPI (Coherent Processing Interval)
@property
def range_resolution_m(self) -> float:
"""Range resolution = c / (2 * BW)."""
return 299.792458 / (2.0 * self.bandwidth_mhz)
@property
def max_unambiguous_range_m(self) -> float:
"""Rmax = c / (2 * PRF)."""
return 299792458.0 / (2.0 * self.prf_hz)
@property
def max_unambiguous_velocity_mps(self) -> float:
"""Vmax = lambda * PRF / 4."""
wavelength_m = 0.299792458 / self.fc_ghz
return wavelength_m * self.prf_hz / 4.0
def generate_lfm_chirp(params: RadarParams) -> np.ndarray:
"""Generate Linear Frequency Modulated (LFM) chirp waveform."""
num_samples = int(params.pulse_width_us * params.fs_mhz)
t = np.arange(num_samples) / (params.fs_mhz * 1e6)
chirp_rate = (params.bandwidth_mhz * 1e6) / (params.pulse_width_us * 1e-6)
# Complex baseband chirp
phase = np.pi * chirp_rate * t**2
waveform = np.exp(1j * phase)
assert len(waveform) == num_samples
return waveform
def matched_filter(rx_signal: np.ndarray,
reference: np.ndarray) -> np.ndarray:
"""Apply matched filter (pulse compression) via frequency domain.
Gain = time-bandwidth product (pulse compression ratio).
"""
n_fft = len(rx_signal) + len(reference) - 1
# Zero-pad to next power of 2 for efficient FFT
n_fft = int(2**np.ceil(np.log2(n_fft)))
RX = np.fft.fft(rx_signal, n=n_fft)
REF = np.fft.fft(reference, n=n_fft)
# Matched filter = correlation = conj(REF) * RX
compressed = np.fft.ifft(RX * np.conj(REF))
return compressed[:len(rx_signal)]
def range_doppler_map(iq_data: np.ndarray,
reference: np.ndarray,
params: RadarParams) -> np.ndarray:
"""Build range-Doppler map from CPI of pulses.
iq_data: shape (num_pulses, samples_per_pulse)
"""
assert iq_data.ndim == 2
num_pulses, num_range_bins = iq_data.shape
# Step 1: Range compression (matched filter per pulse)
rd_map = np.zeros_like(iq_data, dtype=complex)
for pulse_idx in range(num_pulses):
rd_map[pulse_idx, :] = matched_filter(
iq_data[pulse_idx, :], reference
)
# Step 2: Doppler FFT along pulse dimension (slow-time)
window = np.hanning(num_pulses)
for rng_bin in range(num_range_bins):
rd_map[:, rng_bin] = np.fft.fftshift(
np.fft.fft(rd_map[:, rng_bin] * window)
)
return rd_map
Step 3: CFAR Detection
# src/detection/cfar.py
"""Cell-Averaging CFAR detector for adaptive threshold."""
import numpy as np
def ca_cfar_1d(signal_db: np.ndarray,
guard_cells: int = 4,
training_cells: int = 16,
pfa: float = 1e-6) -> np.ndarray:
"""Cell-Averaging CFAR detector.
Returns boolean detection mask.
"""
n = len(signal_db)
assert guard_cells > 0, "Need at least 1 guard cell"
assert training_cells > guard_cells, "Training cells must exceed guard cells"
# CFAR threshold multiplier from desired Pfa
# For CA-CFAR: alpha = N * (Pfa^(-1/N) - 1)
N = 2 * training_cells
alpha_linear = N * (pfa**(-1.0/N) - 1.0)
alpha_db = 10.0 * np.log10(alpha_linear)
detections = np.zeros(n, dtype=bool)
half_window = guard_cells + training_cells
for i in range(half_window, n - half_window):
# Leading training cells
lead_start = i - half_window
lead_end = i - guard_cells
leading = signal_db[lead_start:lead_end]
# Lagging training cells
lag_start = i + guard_cells + 1
lag_end = i + half_window + 1
lagging = signal_db[lag_start:lag_end]
noise_estimate = np.mean(np.concatenate([leading, lagging]))
threshold = noise_estimate + alpha_db
if signal_db[i] > threshold:
detections[i] = True
return detections
Step 4: Verify with Synthetic Target
python3 -c "
import numpy as np
from src.radar.pulse_compression import RadarParams, generate_lfm_chirp, range_doppler_map
from src.detection.cfar import ca_cfar_1d
params = RadarParams(fc_ghz=10.0, bandwidth_mhz=50.0, pulse_width_us=10.0,
prf_hz=1000.0, fs_mhz=100.0, num_pulses=64)
print(f'Range resolution: {params.range_resolution_m:.2f} m')
print(f'Max range: {params.max_unambiguous_range_m:.0f} m')
ref = generate_lfm_chirp(params)
samples_per_pulse = int(params.pulse_width_us * params.fs_mhz) * 4 # 4x overrange
# Inject synthetic target at range bin 300, Doppler bin 10
iq = np.random.randn(params.num_pulses, samples_per_pulse) * 0.01 + \
1j * np.random.randn(params.num_pulses, samples_per_pulse) * 0.01
target_range = 300
target_doppler = 10
for p in range(params.num_pulses):
phase = 2*np.pi*target_doppler*p/params.num_pulses
iq[p, target_range:target_range+len(ref)] += 10.0 * ref * np.exp(1j*phase)
rd = range_doppler_map(iq, ref, params)
rd_db = 20*np.log10(np.abs(rd) + 1e-12)
peak = np.unravel_index(np.argmax(np.abs(rd)), rd.shape)
print(f'Peak at Doppler bin {peak[0]}, Range bin {peak[1]}')
assert abs(peak[1] - target_range) < 5, 'Range bin mismatch'
print('Radar processing: PASS')
"
CLAUDE.md for Radar Signal Processing
# Radar Signal Processing Standards
## Domain Rules
- Always apply windowing before Doppler FFT (Hann or Taylor)
- Matched filter length must equal transmitted pulse length
- CFAR guard cells must exceed mainlobe width of compressed pulse
- Range-Doppler coupling in LFM requires correction for moving targets
- Use fftshift for zero-Doppler centered display
## Libraries
- numpy 1.26+ (array math, FFT)
- scipy.signal (filter design, windowing)
- pyfftw 0.14+ (FFTW3 for large FFTs)
- h5py (HDF5 radar data format)
- matplotlib (range-Doppler plots)
## File Patterns
- .h5, .hdf5 — raw I/Q radar data
- .npy — processed range-Doppler maps
- .json — radar parameter configuration
## Common Commands
- python3 src/radar/pulse_compression.py — run range processing
- python3 src/detection/cfar.py — run CFAR detection
- ffmpeg -f rawvideo ... — convert raw I/Q to viewable format
- gnuradio-companion — SDR-based radar receive chain
Common Pitfalls
- Missing windowing before Doppler FFT: Rectangular window creates -13 dB sidelobes that mask weak targets near strong ones. Claude Code inserts Hann or Taylor windowing by default and warns if you remove it.
- CFAR guard cells too narrow: If guard cells are narrower than the compressed pulse mainlobe, target energy leaks into training cells and raises the threshold. Claude Code calculates minimum guard cell count from your time-bandwidth product.
- Range-Doppler coupling: LFM waveforms shift the apparent range of moving targets proportional to their Doppler. Claude Code generates the keystone transform correction for high-speed target scenarios.
Related
- Claude Code for DSP Pipeline Development
- Claude Code for Sonar Array Processing
- CLAUDE.md File Guide
- Claude Code for Satellite Telemetry (2026)
Frequently Asked Questions
Do I need a paid Anthropic plan to use this?
Claude Code works with any Anthropic API plan, including the free tier. However, the free tier has lower rate limits (requests per minute and tokens per minute) that may slow down multi-step workflows. For professional use, the Build or Scale plan provides higher limits and priority access during peak hours.
How does this affect token usage and cost?
The token cost depends on the size of your prompts and Claude’s responses. Typical development tasks consume 10K-50K tokens per interaction. Using a CLAUDE.md file and skills reduces exploration tokens by 50-80%, which directly lowers costs. Monitor your usage at console.anthropic.com/settings/billing.
Can I customize this for my specific project?
Yes. All Claude Code behavior can be customized through CLAUDE.md (project rules), .claude/settings.json (permissions), and .claude/skills/ (domain knowledge). The most impactful customization is adding your project’s specific patterns, conventions, and common commands to CLAUDE.md so Claude Code follows your standards from the start.
What happens when Claude Code makes a mistake?
Claude Code creates files and edits through standard filesystem operations, so all changes are visible in git diff. If a change is wrong, revert it with git checkout -- <file> for a single file or git stash for all changes. Claude Code does not make irreversible changes unless you explicitly allow destructive commands in settings.json.
Practical Details
When working with Claude Code on this topic, keep these implementation details in mind:
Project Configuration. Your CLAUDE.md should include specific references to how your project handles this area. Include file paths, naming conventions, and any project-specific patterns that differ from defaults. Claude Code reads this file at session start and uses it to guide all operations.
Integration with Existing Tools. Claude Code works alongside your existing development tools rather than replacing them. It respects .gitignore for file visibility, uses your project’s installed dependencies, and follows the build/test scripts defined in package.json (or equivalent). Ensure your toolchain is working correctly before involving Claude Code.
Performance Considerations. For large codebases (10,000+ files), Claude Code’s file scanning can be slow if not properly scoped. Use .claudeignore to exclude generated directories (dist, build, .next, coverage) and dependency directories (node_modules, vendor). This typically reduces scan time by 80-90%.
Version Control Integration. All changes Claude Code makes are regular filesystem operations visible to git. Use git diff after each significant change to review what was modified. For experimental changes, create a branch first with git checkout -b experiment/topic so you can easily discard or keep the results.
Build yours → Create a custom CLAUDE.md with our Generator Tool.
Related Guides
Estimate tokens → Calculate your usage with our Token Estimator.
Try it: Estimate your monthly spend with our Cost Calculator.
- Claude Batch Processing 100K Requests
- Claude Code for Processing and p5.js
- Async Claude Processing
- Claude Code Batch File Processing
Implementation Details
When working with this in Claude Code, pay attention to these practical details:
Project configuration. Add specific instructions to your CLAUDE.md file describing how your project handles this area. Include file paths, naming conventions, and any patterns that differ from common defaults. Claude Code reads CLAUDE.md at the start of every session and uses it to guide all operations.
Testing the setup. After configuration, verify everything works by running a simple test task. Ask Claude Code to perform a read-only operation first (like listing files or reading a config) before moving to write operations. This confirms that permissions, paths, and tools are all correctly configured.
Monitoring and iteration. Track your results over several sessions. If Claude Code consistently makes the same mistake, the fix is usually a more specific CLAUDE.md instruction. If it makes different mistakes each time, the issue is likely in the project setup or toolchain configuration.
Troubleshooting Checklist
When something does not work as expected, check these items in order:
- CLAUDE.md exists at the project root — run
ls -la CLAUDE.mdto verify - Node.js version is 18+ — run
node --versionto check - API key is set — run
echo $ANTHROPIC_API_KEY | head -c 10to verify (shows first 10 characters only) - Disk space is available — run
df -h .to check - Network can reach the API — run
curl -s -o /dev/null -w "%{http_code}" https://api.anthropic.com(should return 401 without auth, meaning the server is reachable) - No conflicting processes — run
ps aux | grep claude | grep -v grepto check for stale sessions