加入 langgraph-vivado-install, langgraph-plan-view, 更新 fft-analyse
This commit is contained in:
@@ -234,7 +234,10 @@ def analyse(
|
||||
v = r.get(col, "").strip()
|
||||
if v and v.lower() not in ("x", "z", "nan"):
|
||||
# If it looks like hex (has a-f OR is exactly 4 hex digits)
|
||||
if re.match(r"^[0-9a-fA-F]+$", v):
|
||||
if v.lower().startswith("0x") or (
|
||||
re.match(r"^[0-9a-fA-F]+$", v)
|
||||
and any(c in v.lower() for c in "abcdef")
|
||||
):
|
||||
return True
|
||||
break
|
||||
return False
|
||||
@@ -259,10 +262,14 @@ def analyse(
|
||||
i_mean, q_mean = float(np.mean(i_data)), float(np.mean(q_data))
|
||||
iq = (i_data[:fft_n] - i_mean) + 1j * (q_data[:fft_n] - q_mean)
|
||||
win = np.hanning(fft_n)
|
||||
cg = float(np.sum(win))
|
||||
cg = float(np.sum(win)) # coherent gain (for peak amplitude)
|
||||
win_power = float(np.sum(win ** 2)) # for PSD normalisation
|
||||
fft_data = np.fft.fftshift(np.fft.fft(iq * win, n=fft_n))
|
||||
amp = np.abs(fft_data) / cg
|
||||
amp = np.abs(fft_data) / cg # peak amplitude (linear, for OBW / peaks)
|
||||
dbfs = 20.0 * np.log10((amp + np.finfo(np.float64).eps) / full_scale)
|
||||
# Power spectral density: dBFS/Hz (normalised to full-scale power)
|
||||
psd = (np.abs(fft_data) ** 2) / (full_scale ** 2 * win_power * fs_hz)
|
||||
psd_dbfs_hz = 10.0 * np.log10(psd + np.finfo(np.float64).eps)
|
||||
freq_hz = (np.arange(fft_n) - fft_n / 2.0) * fs_hz / fft_n
|
||||
freq_mhz = freq_hz / 1e6
|
||||
|
||||
@@ -279,6 +286,7 @@ def analyse(
|
||||
result = dict(
|
||||
i_data=i_data, q_data=q_data, mag_time=mag_time, i_mean=i_mean, q_mean=q_mean,
|
||||
fft_n=fft_n, freq_hz=freq_hz, freq_mhz=freq_mhz, amp=amp, dbfs=dbfs,
|
||||
psd_dbfs_hz=psd_dbfs_hz,
|
||||
obw90=obw90, obw99=obw99, frac90=frac90, frac99=frac99, peaks=peaks,
|
||||
fs_hz=fs_hz, full_scale=full_scale, i_col=i_col, q_col=q_col,
|
||||
input_name=input_path.name, skip=s,
|
||||
@@ -366,8 +374,9 @@ def _plot(result: dict, xlim_mhz, out_dir: Path, prefix: str, title: str | None)
|
||||
axes[1].grid(True, alpha=0.3)
|
||||
axes[1].legend(loc="upper right")
|
||||
|
||||
# panel 3 — spectrum
|
||||
axes[2].plot(freq_mhz, dbfs, color="#1f5a95", linewidth=1.0)
|
||||
# panel 3 — spectrum (PSD)
|
||||
psd_dbfs_hz = result["psd_dbfs_hz"]
|
||||
axes[2].plot(freq_mhz, psd_dbfs_hz, color="#1f5a95", linewidth=1.0)
|
||||
obw90, obw99 = result["obw90"], result["obw99"]
|
||||
if obw90 > 0:
|
||||
axes[2].axvline(-obw90 / 2, color="#ff7f0e", ls="--", lw=1.3,
|
||||
@@ -377,23 +386,26 @@ def _plot(result: dict, xlim_mhz, out_dir: Path, prefix: str, title: str | None)
|
||||
axes[2].axvline(-obw99 / 2, color="#9467bd", ls="--", lw=1.3,
|
||||
label=f"99% OBW: {obw99:.2f} MHz")
|
||||
axes[2].axvline( obw99 / 2, color="#9467bd", ls="--", lw=1.3)
|
||||
# annotate top peaks using PSD value at peak bin
|
||||
for p in result["peaks"][:5]:
|
||||
if xlim_mhz[0] <= p["freq_mhz"] <= xlim_mhz[1]:
|
||||
axes[2].plot(p["freq_mhz"], p["dbfs"], "o", color="#c23b22", ms=6)
|
||||
axes[2].annotate(f"{p['freq_mhz']:.2f} MHz", (p["freq_mhz"], p["dbfs"]),
|
||||
psd_val = float(psd_dbfs_hz[p["bin"]])
|
||||
axes[2].plot(p["freq_mhz"], psd_val, "o", color="#c23b22", ms=6)
|
||||
axes[2].annotate(f"{p['freq_mhz']:.2f} MHz\n{psd_val:.1f} dBFS/Hz",
|
||||
(p["freq_mhz"], psd_val),
|
||||
xytext=(0, 8), textcoords="offset points",
|
||||
ha="center", fontsize=11)
|
||||
ha="center", fontsize=10)
|
||||
axes[2].set_title(f"{fft_n}-pt complex FFT, Hann window, DC removed, "
|
||||
f"fs = {fs_hz/1e6:.2f} MHz, FS ref = {result['full_scale']:.0f}")
|
||||
axes[2].set_xlabel("Frequency (MHz)")
|
||||
axes[2].set_ylabel("Magnitude (dBFS)")
|
||||
axes[2].set_ylabel("PSD (dBFS/Hz)")
|
||||
axes[2].set_xlim(xlim_mhz)
|
||||
in_view = (freq_mhz >= xlim_mhz[0]) & (freq_mhz <= xlim_mhz[1])
|
||||
if np.any(in_view):
|
||||
y_top = max(-20.0, float(np.ceil(np.max(dbfs[in_view]) / 10) * 10 + 10))
|
||||
y_top = max(-60.0, float(np.ceil(np.max(psd_dbfs_hz[in_view]) / 10) * 10 + 10))
|
||||
else:
|
||||
y_top = 10.0
|
||||
axes[2].set_ylim(-140, y_top)
|
||||
y_top = -60.0
|
||||
axes[2].set_ylim(y_top - 120, y_top)
|
||||
axes[2].grid(True, alpha=0.3)
|
||||
axes[2].legend(loc="lower right")
|
||||
|
||||
@@ -406,8 +418,8 @@ def _plot(result: dict, xlim_mhz, out_dir: Path, prefix: str, title: str | None)
|
||||
|
||||
def _auto_fs(path: Path) -> float:
|
||||
name = path.stem.lower()
|
||||
for pat, hz in [("491", 491.52e6), ("245", 245.76e6), ("240m", 245.76e6),
|
||||
("122", 122.88e6), ("120m", 122.88e6), ("61", 61.44e6),
|
||||
for pat, hz in [("491", 491.52e6), ("245", 245.76e6), ("240m", 240e6),
|
||||
("122", 122.88e6), ("120m", 120e6), ("61", 61.44e6),
|
||||
("250m", 250e6), ("125m", 125e6)]:
|
||||
if pat in name:
|
||||
return hz
|
||||
|
||||
Reference in New Issue
Block a user