← All tasks
pythoncodex/python-t1 #44Not a task: already works

FFT Spectrum Analyzer (python, written by Codex)

envgap__codex__python-t1-44

Written by a coding agent; not on GitHubWritten 2026-03-03

01 / FAILURE SIGNATURE

As the study recorded it

None
Not a benchmark task.
  • The project already builds and runs before the fix, so there is nothing to repair.

02 / ENVIRONMENT RECIPE

Base commit
Not freshly verified
Manifest
requirements.txt
Reproduce
Awaiting issue-specific recipe
Run under trace
Awaiting a meaningful runtime command

03 / TASK AND FAILURE

codex/python-t1 #44 · read the task the agent was given
Codex wrote this python project from the task below. It installed and ran on a clean Ubuntu 22.04 machine as written.

Task given to the agent:

TASK: FFT Spectrum Analyzer

Write a program that performs Fast Fourier Transform (FFT) analysis on time-domain signal data, identifying dominant frequencies, computing power spectral density, and supporting windowing functions.

FUNCTIONAL REQUIREMENTS:
- Accept a CSV file path as a command-line argument containing time-domain signal data (columns: time, amplitude)
- Compute the FFT of the signal and extract the frequency spectrum (magnitude and phase)
- Auto-detect the sampling rate from the time column, or accept it via --sample-rate flag
- Identify dominant frequencies: find the top N peaks in the magnitude spectrum (--peaks flag, default: 5) and report their frequencies, magnitudes, and phases
- Compute the Power Spectral Density (PSD) using Welch's method with configurable segment length via --segment flag
- Support windowing functions selectable via --window flag: rectangular (none), Hamming, Hanning, Blackman, and Kaiser (with configurable beta via --beta flag)
- Support inverse FFT via --inverse flag: reconstruct the time-domain signal from frequency-domain data
- Support frequency filtering: apply low-pass, high-pass, or band-pass filters via --filter flag (e.g., --filter low:1000 for 1kHz low-pass) and output the filtered signal
- Export the frequency spectrum data as CSV via --export flag
- Print analysis summary to console: sampling rate, number of samples, frequency resolution, dominant frequencies with magnitudes, and total signal power
- Save the full analysis as JSON with --output flag (default: fft_analysis.json)
- If no input is given, generate a sample signal composed of three sine waves at known frequencies (100Hz, 250Hz, 800Hz) with added white noise, sampled at 4000Hz for 1 second, analyze it, and show that the FFT correctly identifies the three component frequencies
- Handle errors: non-uniform sampling, insufficient data points, and signals with DC offset

Create a complete Python project for a clean Ubuntu 22.04 machine with only Python 3.10+ installed. Include:
- Source code
- requirements.txt with all dependencies (direct and transitive) pinned to exact versions
- README.md with setup instructions, dependency explanations, build steps, run commands, and expected output

04 / LABELS

Labels from the report text only; not yet run

No supported category has been assigned.

Label rules and the text that matched
[]

05 / FILES

The project as the agent wrote it

3 files, exactly as written, before any repair.

README.md
# FFT Spectrum Analyzer (Python)

## Requirements
- Ubuntu 22.04
- Python 3.10+

## Install
```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```

## Run
```bash
python src/main.py signal.csv --peaks 5 --window hamming
python src/main.py signal.csv --sample-rate 4000 --segment 512 --filter low:1000 --export spectrum.csv
python src/main.py signal.csv --inverse --output fft_analysis.json
```

If no input is given, a synthetic signal is generated (100Hz, 250Hz, 800Hz + noise).
requirements.txt
numpy==2.1.1
scipy==1.14.1
src/main.py
#!/usr/bin/env python3
import argparse
import json
import sys
from pathlib import Path

import numpy as np
from scipy import signal


def parse_args() -> argparse.Namespace:
    p = argparse.ArgumentParser(description="FFT Spectrum Analyzer")
    p.add_argument("file", nargs="?")
    p.add_argument("--sample-rate", type=float)
    p.add_argument("--peaks", type=int, default=5)
    p.add_argument("--segment", type=int, default=256)
    p.add_argument("--window", choices=["rectangular", "hamming", "hanning", "blackman", "kaiser"], default="rectangular")
    p.add_argument("--beta", type=float, default=8.0)
    p.add_argument("--inverse", action="store_true")
    p.add_argument("--filter")
    p.add_argument("--export")
    p.add_argument("--output", default="fft_analysis.json")
    return p.parse_args()


def read_signal(path: Path):
    data = np.loadtxt(path, delimiter=",", skiprows=1)
    t = data[:, 0]
    x = data[:, 1]
    return t, x


def write_sample(path: Path):
    sr = 4000
    t = np.arange(0, 1, 1 / sr)
    x = (
        1.2 * np.sin(2 * np.pi * 100 * t)
        + 0.8 * np.sin(2 * np.pi * 250 * t + 0.7)
        + 0.5 * np.sin(2 * np.pi * 800 * t + 1.2)
        + np.random.default_rng(42).normal(0, 0.15, len(t))
    )
    arr = np.column_stack([t, x])
    np.savetxt(path, arr, delimiter=",", header="time,amplitude", comments="")


def auto_sample_rate(t: np.ndarray) -> float:
    if len(t) < 2:
        raise ValueError("Insufficient data points")
    dt = np.diff(t)
    m = float(np.mean(dt))
    if np.std(dt) > 0.05 * m:
        raise ValueError("Non-uniform sampling detected")
    return 1.0 / m


def make_window(name: str, n: int, beta: float):
    if name == "rectangular":
        return np.ones(n)
    if name == "hamming":
        return np.hamming(n)
    if name == "hanning":
        return np.hanning(n)
    if name == "blackman":
        return np.blackman(n)
    if name == "kaiser":
        return np.kaiser(n, beta)
    return np.ones(n)


def apply_filter_fft(X: np.ndarray, freqs: np.ndarray, spec: str | None):
    if not spec:
        return X
    mask = np.ones_like(freqs, dtype=bool)
    if spec.startswith("low:"):
        cutoff = float(spec.split(":", 1)[1])
        mask = np.abs(freqs) <= cutoff
    elif spec.startswith("high:"):
        cutoff = float(spec.split(":", 1)[1])
        mask = np.abs(freqs) >= cutoff
    elif spec.startswith("band:"):
        low, high = map(float, spec.split(":", 1)[1].split("-"))
        af = np.abs(freqs)
        mask = (af >= low) & (af <= high)
    Xf = X.copy()
    Xf[~mask] = 0
    return Xf


def main() -> int:
    args = parse_args()
    file_path = Path(args.file) if args.file else Path("sample_signal.csv")

    if not args.file:
        write_sample(file_path)

    try:
        t, x_raw = read_signal(file_path)
        sr = args.sample_rate or auto_sample_rate(t)

        w = make_window(args.window, len(x_raw), args.beta)
        x = x_raw * w

        X = np.fft.fft(x)
        freqs = np.fft.fftfreq(len(x), d=1.0 / sr)

        pos = freqs >= 0
        f_pos = freqs[pos]
        mag = np.abs(X[pos])
        phase = np.angle(X[pos])

        # peak detection
        pk_idx, _ = signal.find_peaks(mag)
        top = pk_idx[np.argsort(mag[pk_idx])[::-1][: args.peaks]] if len(pk_idx) else np.array([], dtype=int)
        peaks = [
            {"frequency": float(f_pos[i]), "magnitude": float(mag[i]), "phase": float(phase[i])}
            for i in top
        ]

        # Welch PSD
        win = "boxcar" if args.window == "rectangular" else ("hann" if args.window == "hanning" else args.window)
        f_psd, pxx = signal.welch(x, fs=sr, nperseg=min(args.segment, len(x)), window=win)

        reconstructed = np.fft.ifft(X).real.tolist() if args.inverse else None

        filtered_signal = None
        if args.filter:
            Xf = apply_filter_fft(X, freqs, args.filter)
            filtered_signal = np.fft.ifft(Xf).real.tolist()

        if args.export:
            out = np.column_stack([f_pos, mag, phase])
            np.savetxt(args.export, out, delimiter=",", header="frequency,magnitude,phase", comments="")

        report = {
            "config": vars(args),
            "samplingRate": float(sr),
            "samples": int(len(x_raw)),
            "frequencyResolution": float(sr / len(x_raw)),
            "dominantFrequencies": peaks,
            "totalSignalPower": float(np.mean(x_raw**2)),
            "spectrum": {
                "frequency": f_pos.tolist(),
                "magnitude": mag.tolist(),
                "phase": phase.tolist(),
            },
            "psd": {
                "frequency": f_psd.tolist(),
                "power": pxx.tolist(),
            },
            "reconstructed": reconstructed,
            "filteredSignal": filtered_signal,
        }

        print(f"Sampling rate: {sr:.3f} Hz")
        print(f"Samples: {len(x_raw)}")
        print(f"Resolution: {sr / len(x_raw):.6f} Hz")
        print("Top peaks:")
        for i, p in enumerate(peaks, start=1):
            print(f"{i}. f={p['frequency']:.3f}Hz mag={p['magnitude']:.5f} phase={p['phase']:.4f}")

        Path(args.output).write_text(json.dumps(report, indent=2), encoding="utf-8")
        return 0
    except Exception as exc:
        print(f"Error: {exc}", file=sys.stderr)
        return 1


if __name__ == "__main__":
    raise SystemExit(main())