new fft
This commit is contained in:
@ -10,7 +10,13 @@ import numpy as np
|
|||||||
WF_WIDTH = 1000
|
WF_WIDTH = 1000
|
||||||
|
|
||||||
# Длина БПФ для спектра/водопада спектров
|
# Длина БПФ для спектра/водопада спектров
|
||||||
FFT_LEN = 1024
|
FFT_LEN = 2048
|
||||||
|
|
||||||
|
# Частотный диапазон для FFT (в ГГц)
|
||||||
|
FREQ_MIN_GHZ = -10.0 # Начало частотной оси
|
||||||
|
FREQ_MAX_GHZ = 10.0 # Конец частотной оси
|
||||||
|
DATA_FREQ_START_GHZ = 1.0 # Начало реальных данных
|
||||||
|
DATA_FREQ_END_GHZ = 10.0 # Конец реальных данных
|
||||||
|
|
||||||
# Порог для инверсии сырых данных: если среднее значение свипа ниже порога —
|
# Порог для инверсии сырых данных: если среднее значение свипа ниже порога —
|
||||||
# считаем, что сигнал «меньше нуля» и домножаем свип на -1
|
# считаем, что сигнал «меньше нуля» и домножаем свип на -1
|
||||||
|
|||||||
@ -18,7 +18,16 @@ try:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise RuntimeError(f"Нужны matplotlib и ее зависимости: {e}")
|
raise RuntimeError(f"Нужны matplotlib и ее зависимости: {e}")
|
||||||
|
|
||||||
from ..config import FFT_LEN, WF_WIDTH, SweepInfo, SweepPacket
|
from ..config import (
|
||||||
|
FFT_LEN,
|
||||||
|
WF_WIDTH,
|
||||||
|
SweepInfo,
|
||||||
|
SweepPacket,
|
||||||
|
FREQ_MIN_GHZ,
|
||||||
|
FREQ_MAX_GHZ,
|
||||||
|
DATA_FREQ_START_GHZ,
|
||||||
|
DATA_FREQ_END_GHZ,
|
||||||
|
)
|
||||||
from ..data_acquisition.sweep_reader import SweepReader
|
from ..data_acquisition.sweep_reader import SweepReader
|
||||||
from ..signal_processing.phase_analysis import apply_temporal_unwrap, phase_to_distance
|
from ..signal_processing.phase_analysis import apply_temporal_unwrap, phase_to_distance
|
||||||
from ..utils.formatting import format_status_kv, parse_spec_clip
|
from ..utils.formatting import format_status_kv, parse_spec_clip
|
||||||
@ -49,8 +58,8 @@ def run_matplotlib(args):
|
|||||||
ring_time = None # type: Optional[np.ndarray]
|
ring_time = None # type: Optional[np.ndarray]
|
||||||
head = 0
|
head = 0
|
||||||
# Авто-уровни цветовой шкалы водопада сырых данных пересчитываются по видимой области.
|
# Авто-уровни цветовой шкалы водопада сырых данных пересчитываются по видимой области.
|
||||||
# FFT состояние
|
# FFT состояние (полное FFT для отрицательных частот)
|
||||||
fft_bins = FFT_LEN // 2 + 1
|
fft_bins = FFT_LEN
|
||||||
ring_fft = None # type: Optional[np.ndarray]
|
ring_fft = None # type: Optional[np.ndarray]
|
||||||
y_min_fft, y_max_fft = None, None
|
y_min_fft, y_max_fft = None, None
|
||||||
freq_shared: Optional[np.ndarray] = None
|
freq_shared: Optional[np.ndarray] = None
|
||||||
@ -86,7 +95,7 @@ def run_matplotlib(args):
|
|||||||
# Линейный график спектра текущего свипа
|
# Линейный график спектра текущего свипа
|
||||||
fft_line_obj, = ax_fft.plot([], [], lw=1)
|
fft_line_obj, = ax_fft.plot([], [], lw=1)
|
||||||
ax_fft.set_title("FFT", pad=1)
|
ax_fft.set_title("FFT", pad=1)
|
||||||
ax_fft.set_xlabel("X")
|
ax_fft.set_xlabel("Частота, ГГц")
|
||||||
ax_fft.set_ylabel("Амплитуда, дБ")
|
ax_fft.set_ylabel("Амплитуда, дБ")
|
||||||
|
|
||||||
# Диапазон по Y для последнего свипа: авто по умолчанию (поддерживает отрицательные значения)
|
# Диапазон по Y для последнего свипа: авто по умолчанию (поддерживает отрицательные значения)
|
||||||
@ -128,7 +137,7 @@ def run_matplotlib(args):
|
|||||||
)
|
)
|
||||||
ax_spec.set_title("B-scan (дБ)", pad=12)
|
ax_spec.set_title("B-scan (дБ)", pad=12)
|
||||||
ax_spec.set_xlabel("")
|
ax_spec.set_xlabel("")
|
||||||
ax_spec.set_ylabel("расстояние")
|
ax_spec.set_ylabel("Частота, ГГц")
|
||||||
# Не показываем численные значения по времени на B-scan
|
# Не показываем численные значения по времени на B-scan
|
||||||
try:
|
try:
|
||||||
ax_spec.tick_params(axis="x", labelbottom=False)
|
ax_spec.tick_params(axis="x", labelbottom=False)
|
||||||
@ -138,7 +147,7 @@ def run_matplotlib(args):
|
|||||||
# График фазы текущего свипа
|
# График фазы текущего свипа
|
||||||
phase_line_obj, = ax_phase.plot([], [], lw=1)
|
phase_line_obj, = ax_phase.plot([], [], lw=1)
|
||||||
ax_phase.set_title("Фаза спектра (развернутая)", pad=1)
|
ax_phase.set_title("Фаза спектра (развернутая)", pad=1)
|
||||||
ax_phase.set_xlabel("Бин")
|
ax_phase.set_xlabel("Частота, ГГц")
|
||||||
ax_phase.set_ylabel("Фаза, радианы")
|
ax_phase.set_ylabel("Фаза, радианы")
|
||||||
|
|
||||||
# Добавим второй Y axis для расстояния
|
# Добавим второй Y axis для расстояния
|
||||||
@ -155,7 +164,7 @@ def run_matplotlib(args):
|
|||||||
)
|
)
|
||||||
ax_phase_wf.set_title("Водопад фазы", pad=12)
|
ax_phase_wf.set_title("Водопад фазы", pad=12)
|
||||||
ax_phase_wf.set_xlabel("")
|
ax_phase_wf.set_xlabel("")
|
||||||
ax_phase_wf.set_ylabel("Бин")
|
ax_phase_wf.set_ylabel("Частота, ГГц")
|
||||||
# Не показываем численные значения по времени
|
# Не показываем численные значения по времени
|
||||||
try:
|
try:
|
||||||
ax_phase_wf.tick_params(axis="x", labelbottom=False)
|
ax_phase_wf.tick_params(axis="x", labelbottom=False)
|
||||||
@ -166,14 +175,14 @@ def run_matplotlib(args):
|
|||||||
ax_smin = fig.add_axes([0.92, 0.55, 0.02, 0.35])
|
ax_smin = fig.add_axes([0.92, 0.55, 0.02, 0.35])
|
||||||
ax_smax = fig.add_axes([0.95, 0.55, 0.02, 0.35])
|
ax_smax = fig.add_axes([0.95, 0.55, 0.02, 0.35])
|
||||||
ax_sctr = fig.add_axes([0.98, 0.55, 0.02, 0.35])
|
ax_sctr = fig.add_axes([0.98, 0.55, 0.02, 0.35])
|
||||||
ymin_slider = Slider(ax_smin, "Y min", 0, max(1, fft_bins - 1), valinit=0, valstep=1, orientation="vertical")
|
ymin_slider = Slider(ax_smin, "Y min", FREQ_MIN_GHZ, FREQ_MAX_GHZ, valinit=FREQ_MIN_GHZ, valstep=0.1, orientation="vertical")
|
||||||
ymax_slider = Slider(ax_smax, "Y max", 0, max(1, fft_bins - 1), valinit=max(1, fft_bins - 1), valstep=1, orientation="vertical")
|
ymax_slider = Slider(ax_smax, "Y max", FREQ_MIN_GHZ, FREQ_MAX_GHZ, valinit=FREQ_MAX_GHZ, valstep=0.1, orientation="vertical")
|
||||||
contrast_slider = Slider(ax_sctr, "Int max", 0, 100, valinit=100, valstep=1, orientation="vertical")
|
contrast_slider = Slider(ax_sctr, "Int max", 0, 100, valinit=100, valstep=1, orientation="vertical")
|
||||||
|
|
||||||
def _on_ylim_change(_val):
|
def _on_ylim_change(_val):
|
||||||
try:
|
try:
|
||||||
y0 = int(min(ymin_slider.val, ymax_slider.val))
|
y0 = float(min(ymin_slider.val, ymax_slider.val))
|
||||||
y1 = int(max(ymin_slider.val, ymax_slider.val))
|
y1 = float(max(ymin_slider.val, ymax_slider.val))
|
||||||
ax_spec.set_ylim(y0, y1)
|
ax_spec.set_ylim(y0, y1)
|
||||||
fig.canvas.draw_idle()
|
fig.canvas.draw_idle()
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -209,18 +218,18 @@ def run_matplotlib(args):
|
|||||||
# FFT буферы: время по X, бин по Y
|
# FFT буферы: время по X, бин по Y
|
||||||
ring_fft = np.full((max_sweeps, fft_bins), np.nan, dtype=np.float32)
|
ring_fft = np.full((max_sweeps, fft_bins), np.nan, dtype=np.float32)
|
||||||
img_fft_obj.set_data(np.zeros((fft_bins, max_sweeps), dtype=np.float32))
|
img_fft_obj.set_data(np.zeros((fft_bins, max_sweeps), dtype=np.float32))
|
||||||
img_fft_obj.set_extent((0, max_sweeps - 1, 0, fft_bins - 1))
|
img_fft_obj.set_extent((0, max_sweeps - 1, FREQ_MIN_GHZ, FREQ_MAX_GHZ))
|
||||||
ax_spec.set_xlim(0, max_sweeps - 1)
|
ax_spec.set_xlim(0, max_sweeps - 1)
|
||||||
ax_spec.set_ylim(0, max(1, fft_bins - 1))
|
ax_spec.set_ylim(FREQ_MIN_GHZ, FREQ_MAX_GHZ)
|
||||||
freq_shared = np.arange(fft_bins, dtype=np.int32)
|
freq_shared = np.linspace(FREQ_MIN_GHZ, FREQ_MAX_GHZ, fft_bins, dtype=np.float32)
|
||||||
# Phase буферы: время по X, бин по Y
|
# Phase буферы: время по X, бин по Y
|
||||||
ring_phase = np.full((max_sweeps, fft_bins), np.nan, dtype=np.float32)
|
ring_phase = np.full((max_sweeps, fft_bins), np.nan, dtype=np.float32)
|
||||||
prev_phase_per_bin = np.zeros(fft_bins, dtype=np.float32)
|
prev_phase_per_bin = np.zeros(fft_bins, dtype=np.float32)
|
||||||
phase_offset_per_bin = np.zeros(fft_bins, dtype=np.float32)
|
phase_offset_per_bin = np.zeros(fft_bins, dtype=np.float32)
|
||||||
img_phase_obj.set_data(np.zeros((fft_bins, max_sweeps), dtype=np.float32))
|
img_phase_obj.set_data(np.zeros((fft_bins, max_sweeps), dtype=np.float32))
|
||||||
img_phase_obj.set_extent((0, max_sweeps - 1, 0, fft_bins - 1))
|
img_phase_obj.set_extent((0, max_sweeps - 1, FREQ_MIN_GHZ, FREQ_MAX_GHZ))
|
||||||
ax_phase_wf.set_xlim(0, max_sweeps - 1)
|
ax_phase_wf.set_xlim(0, max_sweeps - 1)
|
||||||
ax_phase_wf.set_ylim(0, max(1, fft_bins - 1))
|
ax_phase_wf.set_ylim(FREQ_MIN_GHZ, FREQ_MAX_GHZ)
|
||||||
|
|
||||||
def _visible_levels_matplotlib(data: np.ndarray, axis) -> Optional[Tuple[float, float]]:
|
def _visible_levels_matplotlib(data: np.ndarray, axis) -> Optional[Tuple[float, float]]:
|
||||||
"""(vmin, vmax) по текущей видимой области imshow (без накопления по времени)."""
|
"""(vmin, vmax) по текущей видимой области imshow (без накопления по времени)."""
|
||||||
@ -277,21 +286,41 @@ def run_matplotlib(args):
|
|||||||
fft_row = np.full((bins,), np.nan, dtype=np.float32)
|
fft_row = np.full((bins,), np.nan, dtype=np.float32)
|
||||||
phase_row = np.full((bins,), np.nan, dtype=np.float32)
|
phase_row = np.full((bins,), np.nan, dtype=np.float32)
|
||||||
else:
|
else:
|
||||||
|
# Создаем буфер для полного FFT (с отрицательными частотами)
|
||||||
fft_in = np.zeros((FFT_LEN,), dtype=np.float32)
|
fft_in = np.zeros((FFT_LEN,), dtype=np.float32)
|
||||||
seg = s[:take_fft]
|
|
||||||
|
# Вычисляем индексы для размещения данных (1-10 ГГц в диапазоне -10 до +10 ГГц)
|
||||||
|
freq_range_total = FREQ_MAX_GHZ - FREQ_MIN_GHZ # 20 ГГц
|
||||||
|
freq_range_data = DATA_FREQ_END_GHZ - DATA_FREQ_START_GHZ # 9 ГГц
|
||||||
|
|
||||||
|
# Начальный индекс для данных в FFT буфере
|
||||||
|
start_idx = int((DATA_FREQ_START_GHZ - FREQ_MIN_GHZ) / freq_range_total * FFT_LEN)
|
||||||
|
# Количество точек для данных
|
||||||
|
data_points = int(freq_range_data / freq_range_total * FFT_LEN)
|
||||||
|
data_points = min(data_points, take_fft, FFT_LEN - start_idx)
|
||||||
|
|
||||||
|
# Подготовка данных
|
||||||
|
seg = s[:data_points]
|
||||||
if isinstance(seg, np.ndarray):
|
if isinstance(seg, np.ndarray):
|
||||||
seg = np.nan_to_num(seg, nan=0.0).astype(np.float32, copy=False)
|
seg = np.nan_to_num(seg, nan=0.0).astype(np.float32, copy=False)
|
||||||
else:
|
else:
|
||||||
seg = np.asarray(seg, dtype=np.float32)
|
seg = np.asarray(seg, dtype=np.float32)
|
||||||
seg = np.nan_to_num(seg, nan=0.0)
|
seg = np.nan_to_num(seg, nan=0.0)
|
||||||
|
|
||||||
# Окно Хэннинга
|
# Окно Хэннинга
|
||||||
win = np.hanning(take_fft).astype(np.float32)
|
win = np.hanning(data_points).astype(np.float32)
|
||||||
fft_in[:take_fft] = seg * win
|
|
||||||
spec = np.fft.rfft(fft_in)
|
# Размещаем данные в правильной позиции
|
||||||
|
fft_in[start_idx:start_idx + data_points] = seg * win
|
||||||
|
|
||||||
|
# Полное FFT (включая отрицательные частоты)
|
||||||
|
spec = np.fft.fft(fft_in)
|
||||||
|
# Сдвигаем для центрирования нулевой частоты
|
||||||
|
spec = np.fft.fftshift(spec)
|
||||||
|
|
||||||
mag = np.abs(spec).astype(np.float32)
|
mag = np.abs(spec).astype(np.float32)
|
||||||
fft_row = 20.0 * np.log10(mag + 1e-9)
|
fft_row = 20.0 * np.log10(mag + 1e-9)
|
||||||
if fft_row.shape[0] != bins:
|
if fft_row.shape[0] != bins:
|
||||||
# rfft длиной FFT_LEN даёт bins == FFT_LEN//2+1
|
|
||||||
fft_row = fft_row[:bins]
|
fft_row = fft_row[:bins]
|
||||||
|
|
||||||
# Расчет фазы
|
# Расчет фазы
|
||||||
@ -398,11 +427,31 @@ def run_matplotlib(args):
|
|||||||
# Обновление спектра и фазы текущего свипа
|
# Обновление спектра и фазы текущего свипа
|
||||||
take_fft = min(int(current_sweep.size), FFT_LEN)
|
take_fft = min(int(current_sweep.size), FFT_LEN)
|
||||||
if take_fft > 0 and freq_shared is not None:
|
if take_fft > 0 and freq_shared is not None:
|
||||||
|
# Создаем буфер для полного FFT (с отрицательными частотами)
|
||||||
fft_in = np.zeros((FFT_LEN,), dtype=np.float32)
|
fft_in = np.zeros((FFT_LEN,), dtype=np.float32)
|
||||||
seg = np.nan_to_num(current_sweep[:take_fft], nan=0.0).astype(np.float32, copy=False)
|
|
||||||
win = np.hanning(take_fft).astype(np.float32)
|
# Вычисляем индексы для размещения данных (1-10 ГГц в диапазоне -10 до +10 ГГц)
|
||||||
fft_in[:take_fft] = seg * win
|
freq_range_total = FREQ_MAX_GHZ - FREQ_MIN_GHZ # 20 ГГц
|
||||||
spec = np.fft.rfft(fft_in)
|
freq_range_data = DATA_FREQ_END_GHZ - DATA_FREQ_START_GHZ # 9 ГГц
|
||||||
|
|
||||||
|
# Начальный индекс для данных в FFT буфере
|
||||||
|
start_idx = int((DATA_FREQ_START_GHZ - FREQ_MIN_GHZ) / freq_range_total * FFT_LEN)
|
||||||
|
# Количество точек для данных
|
||||||
|
data_points = int(freq_range_data / freq_range_total * FFT_LEN)
|
||||||
|
data_points = min(data_points, take_fft, FFT_LEN - start_idx)
|
||||||
|
|
||||||
|
# Подготовка данных с окном Хэннинга
|
||||||
|
seg = np.nan_to_num(current_sweep[:data_points], nan=0.0).astype(np.float32, copy=False)
|
||||||
|
win = np.hanning(data_points).astype(np.float32)
|
||||||
|
|
||||||
|
# Размещаем данные в правильной позиции
|
||||||
|
fft_in[start_idx:start_idx + data_points] = seg * win
|
||||||
|
|
||||||
|
# Полное FFT (включая отрицательные частоты)
|
||||||
|
spec = np.fft.fft(fft_in)
|
||||||
|
# Сдвигаем для центрирования нулевой частоты
|
||||||
|
spec = np.fft.fftshift(spec)
|
||||||
|
|
||||||
mag = np.abs(spec).astype(np.float32)
|
mag = np.abs(spec).astype(np.float32)
|
||||||
fft_vals = 20.0 * np.log10(mag + 1e-9)
|
fft_vals = 20.0 * np.log10(mag + 1e-9)
|
||||||
xs_fft = freq_shared
|
xs_fft = freq_shared
|
||||||
@ -411,7 +460,7 @@ def run_matplotlib(args):
|
|||||||
fft_line_obj.set_data(xs_fft[: fft_vals.size], fft_vals)
|
fft_line_obj.set_data(xs_fft[: fft_vals.size], fft_vals)
|
||||||
# Авто-диапазон по Y для спектра
|
# Авто-диапазон по Y для спектра
|
||||||
if np.isfinite(np.nanmin(fft_vals)) and np.isfinite(np.nanmax(fft_vals)):
|
if np.isfinite(np.nanmin(fft_vals)) and np.isfinite(np.nanmax(fft_vals)):
|
||||||
ax_fft.set_xlim(0, max(1, xs_fft.size - 1))
|
ax_fft.set_xlim(FREQ_MIN_GHZ, FREQ_MAX_GHZ)
|
||||||
ax_fft.set_ylim(float(np.nanmin(fft_vals)), float(np.nanmax(fft_vals)))
|
ax_fft.set_ylim(float(np.nanmin(fft_vals)), float(np.nanmax(fft_vals)))
|
||||||
|
|
||||||
# Расчет и отображение фазы текущего свипа
|
# Расчет и отображение фазы текущего свипа
|
||||||
@ -423,7 +472,7 @@ def run_matplotlib(args):
|
|||||||
phase_line_obj.set_data(xs_fft[: phase_unwrapped.size], phase_unwrapped)
|
phase_line_obj.set_data(xs_fft[: phase_unwrapped.size], phase_unwrapped)
|
||||||
# Авто-диапазон по Y для фазы
|
# Авто-диапазон по Y для фазы
|
||||||
if np.isfinite(np.nanmin(phase_unwrapped)) and np.isfinite(np.nanmax(phase_unwrapped)):
|
if np.isfinite(np.nanmin(phase_unwrapped)) and np.isfinite(np.nanmax(phase_unwrapped)):
|
||||||
ax_phase.set_xlim(0, max(1, xs_fft.size - 1))
|
ax_phase.set_xlim(FREQ_MIN_GHZ, FREQ_MAX_GHZ)
|
||||||
phase_min = float(np.nanmin(phase_unwrapped))
|
phase_min = float(np.nanmin(phase_unwrapped))
|
||||||
phase_max = float(np.nanmax(phase_unwrapped))
|
phase_max = float(np.nanmax(phase_unwrapped))
|
||||||
ax_phase.set_ylim(phase_min, phase_max)
|
ax_phase.set_ylim(phase_min, phase_max)
|
||||||
|
|||||||
@ -23,7 +23,16 @@ except Exception:
|
|||||||
"pyqtgraph/PyQt5(Pyside6) не найдены. Установите: pip install pyqtgraph PyQt5"
|
"pyqtgraph/PyQt5(Pyside6) не найдены. Установите: pip install pyqtgraph PyQt5"
|
||||||
) from e
|
) from e
|
||||||
|
|
||||||
from ..config import FFT_LEN, WF_WIDTH, SweepInfo, SweepPacket
|
from ..config import (
|
||||||
|
FFT_LEN,
|
||||||
|
WF_WIDTH,
|
||||||
|
SweepInfo,
|
||||||
|
SweepPacket,
|
||||||
|
FREQ_MIN_GHZ,
|
||||||
|
FREQ_MAX_GHZ,
|
||||||
|
DATA_FREQ_START_GHZ,
|
||||||
|
DATA_FREQ_END_GHZ,
|
||||||
|
)
|
||||||
from ..data_acquisition.sweep_reader import SweepReader
|
from ..data_acquisition.sweep_reader import SweepReader
|
||||||
from ..signal_processing.phase_analysis import apply_temporal_unwrap, phase_to_distance
|
from ..signal_processing.phase_analysis import apply_temporal_unwrap, phase_to_distance
|
||||||
from ..utils.formatting import format_status_kv, parse_spec_clip
|
from ..utils.formatting import format_status_kv, parse_spec_clip
|
||||||
@ -72,7 +81,7 @@ def run_pyqtgraph(args):
|
|||||||
p_fft = win.addPlot(row=1, col=0, title="FFT")
|
p_fft = win.addPlot(row=1, col=0, title="FFT")
|
||||||
p_fft.showGrid(x=True, y=True, alpha=0.3)
|
p_fft.showGrid(x=True, y=True, alpha=0.3)
|
||||||
curve_fft = p_fft.plot(pen=pg.mkPen((255, 120, 80), width=1))
|
curve_fft = p_fft.plot(pen=pg.mkPen((255, 120, 80), width=1))
|
||||||
p_fft.setLabel("bottom", "Бин")
|
p_fft.setLabel("bottom", "Частота, ГГц")
|
||||||
p_fft.setLabel("left", "Амплитуда, дБ")
|
p_fft.setLabel("left", "Амплитуда, дБ")
|
||||||
|
|
||||||
# Водопад спектров (справа-средний ряд)
|
# Водопад спектров (справа-средний ряд)
|
||||||
@ -84,7 +93,7 @@ def run_pyqtgraph(args):
|
|||||||
p_spec.getAxis("bottom").setStyle(showValues=False)
|
p_spec.getAxis("bottom").setStyle(showValues=False)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
p_spec.setLabel("left", "Бин (0 снизу)")
|
p_spec.setLabel("left", "Частота, ГГц (0 снизу)")
|
||||||
img_fft = pg.ImageItem()
|
img_fft = pg.ImageItem()
|
||||||
p_spec.addItem(img_fft)
|
p_spec.addItem(img_fft)
|
||||||
|
|
||||||
@ -92,7 +101,7 @@ def run_pyqtgraph(args):
|
|||||||
p_phase = win.addPlot(row=2, col=0, title="Фаза спектра (развернутая)")
|
p_phase = win.addPlot(row=2, col=0, title="Фаза спектра (развернутая)")
|
||||||
p_phase.showGrid(x=True, y=True, alpha=0.3)
|
p_phase.showGrid(x=True, y=True, alpha=0.3)
|
||||||
curve_phase = p_phase.plot(pen=pg.mkPen((120, 255, 80), width=1))
|
curve_phase = p_phase.plot(pen=pg.mkPen((120, 255, 80), width=1))
|
||||||
p_phase.setLabel("bottom", "Бин")
|
p_phase.setLabel("bottom", "Частота, ГГц")
|
||||||
p_phase.setLabel("left", "Фаза, радианы")
|
p_phase.setLabel("left", "Фаза, радианы")
|
||||||
# Добавим вторую ось Y для расстояния
|
# Добавим вторую ось Y для расстояния
|
||||||
p_phase_dist_axis = pg.ViewBox()
|
p_phase_dist_axis = pg.ViewBox()
|
||||||
@ -121,7 +130,7 @@ def run_pyqtgraph(args):
|
|||||||
p_phase_wf.getAxis("bottom").setStyle(showValues=False)
|
p_phase_wf.getAxis("bottom").setStyle(showValues=False)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
p_phase_wf.setLabel("left", "Бин (0 снизу)")
|
p_phase_wf.setLabel("left", "Частота, ГГц (0 снизу)")
|
||||||
img_phase = pg.ImageItem()
|
img_phase = pg.ImageItem()
|
||||||
p_phase_wf.addItem(img_phase)
|
p_phase_wf.addItem(img_phase)
|
||||||
|
|
||||||
@ -137,8 +146,8 @@ def run_pyqtgraph(args):
|
|||||||
current_sweep: Optional[np.ndarray] = None
|
current_sweep: Optional[np.ndarray] = None
|
||||||
current_info: Optional[SweepInfo] = None
|
current_info: Optional[SweepInfo] = None
|
||||||
# Авто-уровни цветовой шкалы водопада сырых данных пересчитываются по видимой области.
|
# Авто-уровни цветовой шкалы водопада сырых данных пересчитываются по видимой области.
|
||||||
# Для спектров
|
# Для спектров (полное FFT для отрицательных частот)
|
||||||
fft_bins = FFT_LEN // 2 + 1
|
fft_bins = FFT_LEN
|
||||||
ring_fft: Optional[np.ndarray] = None
|
ring_fft: Optional[np.ndarray] = None
|
||||||
freq_shared: Optional[np.ndarray] = None
|
freq_shared: Optional[np.ndarray] = None
|
||||||
y_min_fft, y_max_fft = None, None
|
y_min_fft, y_max_fft = None, None
|
||||||
@ -177,8 +186,8 @@ def run_pyqtgraph(args):
|
|||||||
ring_fft = np.full((max_sweeps, fft_bins), np.nan, dtype=np.float32)
|
ring_fft = np.full((max_sweeps, fft_bins), np.nan, dtype=np.float32)
|
||||||
img_fft.setImage(ring_fft.T, autoLevels=False)
|
img_fft.setImage(ring_fft.T, autoLevels=False)
|
||||||
p_spec.setRange(xRange=(0, max_sweeps - 1), yRange=(0, max(1, fft_bins - 1)), padding=0)
|
p_spec.setRange(xRange=(0, max_sweeps - 1), yRange=(0, max(1, fft_bins - 1)), padding=0)
|
||||||
p_fft.setXRange(0, max(1, fft_bins - 1), padding=0)
|
p_fft.setXRange(FREQ_MIN_GHZ, FREQ_MAX_GHZ, padding=0)
|
||||||
freq_shared = np.arange(fft_bins, dtype=np.int32)
|
freq_shared = np.linspace(FREQ_MIN_GHZ, FREQ_MAX_GHZ, fft_bins, dtype=np.float32)
|
||||||
# Phase: время по оси X, бин по оси Y
|
# Phase: время по оси X, бин по оси Y
|
||||||
ring_phase = np.full((max_sweeps, fft_bins), np.nan, dtype=np.float32)
|
ring_phase = np.full((max_sweeps, fft_bins), np.nan, dtype=np.float32)
|
||||||
prev_phase_per_bin = np.zeros(fft_bins, dtype=np.float32)
|
prev_phase_per_bin = np.zeros(fft_bins, dtype=np.float32)
|
||||||
@ -234,11 +243,33 @@ def run_pyqtgraph(args):
|
|||||||
bins = ring_fft.shape[1]
|
bins = ring_fft.shape[1]
|
||||||
take_fft = min(int(s.size), FFT_LEN)
|
take_fft = min(int(s.size), FFT_LEN)
|
||||||
if take_fft > 0:
|
if take_fft > 0:
|
||||||
|
# Создаем буфер для полного FFT (с отрицательными частотами)
|
||||||
fft_in = np.zeros((FFT_LEN,), dtype=np.float32)
|
fft_in = np.zeros((FFT_LEN,), dtype=np.float32)
|
||||||
seg = np.nan_to_num(s[:take_fft], nan=0.0).astype(np.float32, copy=False)
|
|
||||||
win = np.hanning(take_fft).astype(np.float32)
|
# Вычисляем индексы для размещения данных (1-10 ГГц в диапазоне -10 до +10 ГГц)
|
||||||
fft_in[:take_fft] = seg * win
|
# Диапазон данных: от DATA_FREQ_START_GHZ (1) до DATA_FREQ_END_GHZ (10)
|
||||||
spec = np.fft.rfft(fft_in)
|
# Полный диапазон: от FREQ_MIN_GHZ (-10) до FREQ_MAX_GHZ (10)
|
||||||
|
freq_range_total = FREQ_MAX_GHZ - FREQ_MIN_GHZ # 20 ГГц
|
||||||
|
freq_range_data = DATA_FREQ_END_GHZ - DATA_FREQ_START_GHZ # 9 ГГц
|
||||||
|
|
||||||
|
# Начальный индекс для данных в FFT буфере
|
||||||
|
start_idx = int((DATA_FREQ_START_GHZ - FREQ_MIN_GHZ) / freq_range_total * FFT_LEN)
|
||||||
|
# Количество точек для данных
|
||||||
|
data_points = int(freq_range_data / freq_range_total * FFT_LEN)
|
||||||
|
data_points = min(data_points, take_fft, FFT_LEN - start_idx)
|
||||||
|
|
||||||
|
# Подготовка данных с окном Хэннинга
|
||||||
|
seg = np.nan_to_num(s[:data_points], nan=0.0).astype(np.float32, copy=False)
|
||||||
|
win = np.hanning(data_points).astype(np.float32)
|
||||||
|
|
||||||
|
# Размещаем данные в правильной позиции (от -10 до 1 ГГц - нули, от 1 до 10 ГГц - данные)
|
||||||
|
fft_in[start_idx:start_idx + data_points] = seg * win
|
||||||
|
|
||||||
|
# Полное FFT (включая отрицательные частоты)
|
||||||
|
spec = np.fft.fft(fft_in)
|
||||||
|
# Сдвигаем для центрирования нулевой частоты
|
||||||
|
spec = np.fft.fftshift(spec)
|
||||||
|
|
||||||
mag = np.abs(spec).astype(np.float32)
|
mag = np.abs(spec).astype(np.float32)
|
||||||
fft_row = 20.0 * np.log10(mag + 1e-9)
|
fft_row = 20.0 * np.log10(mag + 1e-9)
|
||||||
if fft_row.shape[0] != bins:
|
if fft_row.shape[0] != bins:
|
||||||
@ -320,11 +351,31 @@ def run_pyqtgraph(args):
|
|||||||
# Обновим спектр и фазу
|
# Обновим спектр и фазу
|
||||||
take_fft = min(int(current_sweep.size), FFT_LEN)
|
take_fft = min(int(current_sweep.size), FFT_LEN)
|
||||||
if take_fft > 0 and freq_shared is not None:
|
if take_fft > 0 and freq_shared is not None:
|
||||||
|
# Создаем буфер для полного FFT (с отрицательными частотами)
|
||||||
fft_in = np.zeros((FFT_LEN,), dtype=np.float32)
|
fft_in = np.zeros((FFT_LEN,), dtype=np.float32)
|
||||||
seg = np.nan_to_num(current_sweep[:take_fft], nan=0.0).astype(np.float32, copy=False)
|
|
||||||
win = np.hanning(take_fft).astype(np.float32)
|
# Вычисляем индексы для размещения данных (1-10 ГГц в диапазоне -10 до +10 ГГц)
|
||||||
fft_in[:take_fft] = seg * win
|
freq_range_total = FREQ_MAX_GHZ - FREQ_MIN_GHZ # 20 ГГц
|
||||||
spec = np.fft.rfft(fft_in)
|
freq_range_data = DATA_FREQ_END_GHZ - DATA_FREQ_START_GHZ # 9 ГГц
|
||||||
|
|
||||||
|
# Начальный индекс для данных в FFT буфере
|
||||||
|
start_idx = int((DATA_FREQ_START_GHZ - FREQ_MIN_GHZ) / freq_range_total * FFT_LEN)
|
||||||
|
# Количество точек для данных
|
||||||
|
data_points = int(freq_range_data / freq_range_total * FFT_LEN)
|
||||||
|
data_points = min(data_points, take_fft, FFT_LEN - start_idx)
|
||||||
|
|
||||||
|
# Подготовка данных с окном Хэннинга
|
||||||
|
seg = np.nan_to_num(current_sweep[:data_points], nan=0.0).astype(np.float32, copy=False)
|
||||||
|
win = np.hanning(data_points).astype(np.float32)
|
||||||
|
|
||||||
|
# Размещаем данные в правильной позиции
|
||||||
|
fft_in[start_idx:start_idx + data_points] = seg * win
|
||||||
|
|
||||||
|
# Полное FFT (включая отрицательные частоты)
|
||||||
|
spec = np.fft.fft(fft_in)
|
||||||
|
# Сдвигаем для центрирования нулевой частоты
|
||||||
|
spec = np.fft.fftshift(spec)
|
||||||
|
|
||||||
mag = np.abs(spec).astype(np.float32)
|
mag = np.abs(spec).astype(np.float32)
|
||||||
fft_vals = 20.0 * np.log10(mag + 1e-9)
|
fft_vals = 20.0 * np.log10(mag + 1e-9)
|
||||||
xs_fft = freq_shared
|
xs_fft = freq_shared
|
||||||
|
|||||||
Reference in New Issue
Block a user