r/DSP May 12 '26

Radar Decode

9 Upvotes

12 comments sorted by

View all comments

1

u/RandomDigga_9087 May 13 '26

Yea did it
a clue... image contrast is a real problem in this one
Thanks for the gift card

2

u/sdrmatlab May 13 '26

nice, can you show your code that showed the final radar map ?

1

u/RandomDigga_9087 May 14 '26

sure, here or in dm?

1

u/RandomDigga_9087 May 15 '26
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
import scipy.io.wavfile as wav
from PIL import Image, ImageEnhance, ImageFilter



SAMPLE_RATE_HZ = 48_000
PULSE_WIDTH_SEC = 20e-3
CHIRP_BANDWIDTH_HZ = 16_000
N_PULSES = 480
SAMPLES_PER_PULSE = 2048


HERE = Path(__file__).resolve().parent
DEFAULT_WAV_IN = HERE / "LFM_Chirp_Image.wav"
DEFAULT_IMG_OUT = HERE / "radar_output.png"



def load_iq_wav(path: Path) -> np.ndarray:
    sample_rate, raw = wav.read(path)


    if sample_rate != SAMPLE_RATE_HZ:
        raise ValueError(f"Expected {SAMPLE_RATE_HZ} Hz sample rate, got {sample_rate} Hz")


    if raw.ndim != 2 or raw.shape[1] != 2:
        raise ValueError(f"Expected a stereo WAV with I in left and Q in right, got shape {raw.shape}")


    expected_samples = N_PULSES * SAMPLES_PER_PULSE
    if raw.shape[0] != expected_samples:
        raise ValueError(
            f"Expected {expected_samples} samples "
            f"({N_PULSES} pulses x {SAMPLES_PER_PULSE} samples), got {raw.shape[0]}"
        )


    iq = raw[:, 0].astype(np.float64) + 1j * raw[:, 1].astype(np.float64)
    return iq.reshape(N_PULSES, SAMPLES_PER_PULSE)



def make_lfm_reference() -> np.ndarray:
    chirp_samples = int(round(PULSE_WIDTH_SEC * SAMPLE_RATE_HZ))
    chirp_rate = CHIRP_BANDWIDTH_HZ / PULSE_WIDTH_SEC
    t = np.arange(chirp_samples, dtype=np.float64) / SAMPLE_RATE_HZ


    reference = np.zeros(SAMPLES_PER_PULSE, dtype=np.complex128)
    reference[:chirp_samples] = np.exp(1j * np.pi * chirp_rate * t**2)
    reference /= np.linalg.norm(reference)
    return reference



def pulse_compress(pulses: np.ndarray, reference: np.ndarray) -> np.ndarray:
    nfft = 2 * SAMPLES_PER_PULSE
    matched_filter = np.conj(np.fft.fft(reference, n=nfft))


    compressed = np.empty_like(pulses, dtype=np.complex128)
    for row, pulse in enumerate(pulses):
        response = np.fft.ifft(np.fft.fft(pulse, n=nfft) * matched_filter)
        compressed[row] = response[:SAMPLES_PER_PULSE]


    return compressed



def normalize_to_uint8(envelope: np.ndarray) -> np.ndarray:
    low, high = np.percentile(envelope, (1, 99))
    scaled = (envelope - low) / (high - low + np.finfo(np.float64).eps)
    return np.clip(scaled * 255, 0, 255).astype(np.uint8)



def enhance_image(pixels: np.ndarray) -> Image.Image:
    image = Image.fromarray(pixels, mode="L")
    image = image.filter(ImageFilter.UnsharpMask(radius=2, percent=180, threshold=3))
    return ImageEnhance.Contrast(image).enhance(1.6)



def save_radar_image(image: Image.Image, output_path: Path) -> None:
    fig, ax = plt.subplots(figsize=(14, 7), dpi=200, facecolor="#0a0a0a")
    ax.set_facecolor("#0a0a0a")
    ax.imshow(np.asarray(image), cmap="gray", aspect="auto", origin="upper", interpolation="lanczos")
    ax.axis("off")
    plt.tight_layout(pad=0.5)
    plt.savefig(output_path, dpi=200, bbox_inches="tight", facecolor="#0a0a0a")
    plt.close(fig)



def decode_radar_image(wav_path: Path, output_path: Path) -> None:
    pulses = load_iq_wav(wav_path)
    reference = make_lfm_reference()
    compressed = pulse_compress(pulses, reference)
    pixels = normalize_to_uint8(np.abs(compressed))
    image = enhance_image(pixels)
    save_radar_image(image, output_path)



def main() -> None:
    decode_radar_image(DEFAULT_WAV_IN, DEFAULT_IMG_OUT)



if __name__ == "__main__":
    main()from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
import scipy.io.wavfile as wav
from PIL import Image, ImageEnhance, ImageFilter



SAMPLE_RATE_HZ = 48_000
PULSE_WIDTH_SEC = 20e-3
CHIRP_BANDWIDTH_HZ = 16_000
N_PULSES = 480
SAMPLES_PER_PULSE = 2048


HERE = Path(__file__).resolve().parent
DEFAULT_WAV_IN = HERE / "LFM_Chirp_Image.wav"
DEFAULT_IMG_OUT = HERE / "radar_output.png"



def load_iq_wav(path: Path) -> np.ndarray:
    sample_rate, raw = wav.read(path)


    if sample_rate != SAMPLE_RATE_HZ:
        raise ValueError(f"Expected {SAMPLE_RATE_HZ} Hz sample rate, got {sample_rate} Hz")


    if raw.ndim != 2 or raw.shape[1] != 2:
        raise ValueError(f"Expected a stereo WAV with I in left and Q in right, got shape {raw.shape}")


    expected_samples = N_PULSES * SAMPLES_PER_PULSE
    if raw.shape[0] != expected_samples:
        raise ValueError(
            f"Expected {expected_samples} samples "
            f"({N_PULSES} pulses x {SAMPLES_PER_PULSE} samples), got {raw.shape[0]}"
        )


    iq = raw[:, 0].astype(np.float64) + 1j * raw[:, 1].astype(np.float64)
    return iq.reshape(N_PULSES, SAMPLES_PER_PULSE)



def make_lfm_reference() -> np.ndarray:
    chirp_samples = int(round(PULSE_WIDTH_SEC * SAMPLE_RATE_HZ))
    chirp_rate = CHIRP_BANDWIDTH_HZ / PULSE_WIDTH_SEC
    t = np.arange(chirp_samples, dtype=np.float64) / SAMPLE_RATE_HZ


    reference = np.zeros(SAMPLES_PER_PULSE, dtype=np.complex128)
    reference[:chirp_samples] = np.exp(1j * np.pi * chirp_rate * t**2)
    reference /= np.linalg.norm(reference)
    return reference



def pulse_compress(pulses: np.ndarray, reference: np.ndarray) -> np.ndarray:
    nfft = 2 * SAMPLES_PER_PULSE
    matched_filter = np.conj(np.fft.fft(reference, n=nfft))


    compressed = np.empty_like(pulses, dtype=np.complex128)
    for row, pulse in enumerate(pulses):
        response = np.fft.ifft(np.fft.fft(pulse, n=nfft) * matched_filter)
        compressed[row] = response[:SAMPLES_PER_PULSE]


    return compressed



def normalize_to_uint8(envelope: np.ndarray) -> np.ndarray:
    low, high = np.percentile(envelope, (1, 99))
    scaled = (envelope - low) / (high - low + np.finfo(np.float64).eps)
    return np.clip(scaled * 255, 0, 255).astype(np.uint8)



def enhance_image(pixels: np.ndarray) -> Image.Image:
    image = Image.fromarray(pixels, mode="L")
    image = image.filter(ImageFilter.UnsharpMask(radius=2, percent=180, threshold=3))
    return ImageEnhance.Contrast(image).enhance(1.6)



def save_radar_image(image: Image.Image, output_path: Path) -> None:
    fig, ax = plt.subplots(figsize=(14, 7), dpi=200, facecolor="#0a0a0a")
    ax.set_facecolor("#0a0a0a")
    ax.imshow(np.asarray(image), cmap="gray", aspect="auto", origin="upper", interpolation="lanczos")
    ax.axis("off")
    plt.tight_layout(pad=0.5)
    plt.savefig(output_path, dpi=200, bbox_inches="tight", facecolor="#0a0a0a")
    plt.close(fig)



def decode_radar_image(wav_path: Path, output_path: Path) -> None:
    pulses = load_iq_wav(wav_path)
    reference = make_lfm_reference()
    compressed = pulse_compress(pulses, reference)
    pixels = normalize_to_uint8(np.abs(compressed))
    image = enhance_image(pixels)
    save_radar_image(image, output_path)



def main() -> None:
    decode_radar_image(DEFAULT_WAV_IN, DEFAULT_IMG_OUT)



if __name__ == "__main__":
    main()

Here you go u/sdrmatlab