normaliser #1
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,13 @@
|
||||
WF_WIDTH = 1000 # максимальное число точек в ряду водопада
|
||||
FFT_LEN = 1024 # длина БПФ для спектра/водопада спектров
|
||||
FFT_LEN = 2048 # длина БПФ для спектра/водопада спектров
|
||||
# Порог для инверсии сырых данных: если среднее значение свипа ниже порога —
|
||||
# считаем, что сигнал «меньше нуля» и домножаем свип на -1
|
||||
DATA_INVERSION_THRESHOLD = 10.0
|
||||
|
||||
# Параметры IFFT-спектра (временной профиль из спектра 3.2..14.3 ГГц)
|
||||
# Двусторонний спектр формируется как: [нули -14.3..-3.2 | нули -3.2..+3.2 | данные +3.2..+14.3]
|
||||
ZEROS_LOW = 758 # нули от -14.3 до -3.2 ГГц
|
||||
ZEROS_MID = 437 # нули от -3.2 до +3.2 ГГц
|
||||
SWEEP_LEN = 758 # ожидаемая длина свипа (3.2 → 14.3 ГГц)
|
||||
FREQ_SPAN_GHZ = 28.6 # полная двусторонняя полоса (-14.3 .. +14.3 ГГц)
|
||||
IFFT_LEN = ZEROS_LOW + ZEROS_MID + SWEEP_LEN # = 1953
|
||||
|
||||
@ -243,15 +243,14 @@ def run_matplotlib(args):
|
||||
ax_line.set_ylim(-1.05, 1.05)
|
||||
ax_line.set_ylabel("/ max")
|
||||
|
||||
# Спектр — используем уже вычисленный в ring FFT
|
||||
if ring.last_fft_vals is not None and ring.freq_shared is not None:
|
||||
# Спектр — используем уже вычисленный в ring IFFT (временной профиль)
|
||||
if ring.last_fft_vals is not None and ring.fft_time_axis is not None:
|
||||
fft_vals = ring.last_fft_vals
|
||||
xs_fft = ring.freq_shared
|
||||
if fft_vals.size > xs_fft.size:
|
||||
fft_vals = fft_vals[: xs_fft.size]
|
||||
fft_line_obj.set_data(xs_fft[: fft_vals.size], fft_vals)
|
||||
xs_fft = ring.fft_time_axis
|
||||
n = min(fft_vals.size, xs_fft.size)
|
||||
fft_line_obj.set_data(xs_fft[:n], fft_vals[:n])
|
||||
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(0, float(xs_fft[n - 1]))
|
||||
ax_fft.set_ylim(float(np.nanmin(fft_vals)), float(np.nanmax(fft_vals)))
|
||||
|
||||
# Водопад сырых данных
|
||||
|
||||
@ -7,12 +7,16 @@ from typing import Optional, Tuple
|
||||
|
||||
import numpy as np
|
||||
|
||||
from rfg_adc_plotter.constants import FREQ_SPAN_GHZ, IFFT_LEN
|
||||
from rfg_adc_plotter.io.sweep_reader import SweepReader
|
||||
from rfg_adc_plotter.processing.normalizer import build_calib_envelopes
|
||||
from rfg_adc_plotter.state.app_state import AppState, format_status
|
||||
from rfg_adc_plotter.state.ring_buffer import RingBuffer
|
||||
from rfg_adc_plotter.types import SweepPacket
|
||||
|
||||
# Максимальное значение временной оси IFFT в нс
|
||||
_IFFT_T_MAX_NS = float((IFFT_LEN - 1) / (FREQ_SPAN_GHZ * 1e9) * 1e9)
|
||||
|
||||
|
||||
def _parse_ylim(ylim_str: Optional[str]) -> Optional[Tuple[float, float]]:
|
||||
if not ylim_str:
|
||||
@ -164,8 +168,8 @@ def run_pyqtgraph(args):
|
||||
p_fft = win.addPlot(row=1, col=0, title="FFT")
|
||||
p_fft.showGrid(x=True, y=True, alpha=0.3)
|
||||
curve_fft = p_fft.plot(pen=pg.mkPen((255, 120, 80), width=1))
|
||||
p_fft.setLabel("bottom", "Бин")
|
||||
p_fft.setLabel("left", "Амплитуда, дБ")
|
||||
p_fft.setLabel("bottom", "Время, нс")
|
||||
p_fft.setLabel("left", "Мощность, дБ")
|
||||
|
||||
# Водопад спектров (справа-снизу)
|
||||
p_spec = win.addPlot(row=1, col=1, title="B-scan (дБ)")
|
||||
@ -176,7 +180,7 @@ def run_pyqtgraph(args):
|
||||
p_spec.getAxis("bottom").setStyle(showValues=False)
|
||||
except Exception:
|
||||
pass
|
||||
p_spec.setLabel("left", "Бин (0 снизу)")
|
||||
p_spec.setLabel("left", "Время, нс")
|
||||
img_fft = pg.ImageItem()
|
||||
p_spec.addItem(img_fft)
|
||||
|
||||
@ -197,7 +201,6 @@ def run_pyqtgraph(args):
|
||||
FREQ_MAX = 14.323
|
||||
|
||||
def _init_imshow_extents():
|
||||
w = ring.width
|
||||
ms = ring.max_sweeps
|
||||
fb = ring.fft_bins
|
||||
img.setImage(ring.ring.T, autoLevels=False)
|
||||
@ -205,8 +208,9 @@ def run_pyqtgraph(args):
|
||||
p_img.setRange(xRange=(0, ms - 1), yRange=(FREQ_MIN, FREQ_MAX), padding=0)
|
||||
p_line.setXRange(FREQ_MIN, FREQ_MAX, padding=0)
|
||||
img_fft.setImage(ring.ring_fft.T, autoLevels=False)
|
||||
p_spec.setRange(xRange=(0, ms - 1), yRange=(0, max(1, fb - 1)), padding=0)
|
||||
p_fft.setXRange(0, max(1, fb - 1), padding=0)
|
||||
img_fft.setRect(pg.QtCore.QRectF(0.0, 0.0, float(ms), _IFFT_T_MAX_NS))
|
||||
p_spec.setRange(xRange=(0, ms - 1), yRange=(0.0, _IFFT_T_MAX_NS), padding=0)
|
||||
p_fft.setXRange(0.0, _IFFT_T_MAX_NS, padding=0)
|
||||
|
||||
def _img_rect(ms: int) -> "pg.QtCore.QRectF":
|
||||
return pg.QtCore.QRectF(0.0, FREQ_MIN, float(ms), FREQ_MAX - FREQ_MIN)
|
||||
@ -245,13 +249,12 @@ def run_pyqtgraph(args):
|
||||
p_line.setYRange(-1.05, 1.05, padding=0)
|
||||
p_line.setLabel("left", "/ max")
|
||||
|
||||
# Спектр — используем уже вычисленный в ring FFT
|
||||
if ring.last_fft_vals is not None and ring.freq_shared is not None:
|
||||
# Спектр — используем уже вычисленный в ring IFFT (временной профиль)
|
||||
if ring.last_fft_vals is not None and ring.fft_time_axis is not None:
|
||||
fft_vals = ring.last_fft_vals
|
||||
xs_fft = ring.freq_shared
|
||||
if fft_vals.size > xs_fft.size:
|
||||
fft_vals = fft_vals[: xs_fft.size]
|
||||
curve_fft.setData(xs_fft[: fft_vals.size], fft_vals)
|
||||
xs_fft = ring.fft_time_axis
|
||||
n = min(fft_vals.size, xs_fft.size)
|
||||
curve_fft.setData(xs_fft[:n], fft_vals[:n])
|
||||
p_fft.setYRange(float(np.nanmin(fft_vals)), float(np.nanmax(fft_vals)), padding=0)
|
||||
|
||||
# Позиция подписи канала
|
||||
@ -290,6 +293,7 @@ def run_pyqtgraph(args):
|
||||
img_fft.setImage(disp_fft, autoLevels=False, levels=levels)
|
||||
else:
|
||||
img_fft.setImage(disp_fft, autoLevels=False)
|
||||
img_fft.setRect(pg.QtCore.QRectF(0.0, 0.0, float(ring.max_sweeps), _IFFT_T_MAX_NS))
|
||||
|
||||
timer = pg.QtCore.QTimer()
|
||||
timer.timeout.connect(update)
|
||||
|
||||
@ -5,7 +5,15 @@ from typing import Optional, Tuple
|
||||
|
||||
import numpy as np
|
||||
|
||||
from rfg_adc_plotter.constants import FFT_LEN, WF_WIDTH
|
||||
from rfg_adc_plotter.constants import (
|
||||
FFT_LEN,
|
||||
FREQ_SPAN_GHZ,
|
||||
IFFT_LEN,
|
||||
SWEEP_LEN,
|
||||
WF_WIDTH,
|
||||
ZEROS_LOW,
|
||||
ZEROS_MID,
|
||||
)
|
||||
|
||||
|
||||
class RingBuffer:
|
||||
@ -17,7 +25,7 @@ class RingBuffer:
|
||||
|
||||
def __init__(self, max_sweeps: int):
|
||||
self.max_sweeps = max_sweeps
|
||||
self.fft_bins = FFT_LEN // 2 + 1
|
||||
self.fft_bins = IFFT_LEN # = 1953 (полная длина IFFT-результата)
|
||||
|
||||
# Инициализируются при первом свипе (ensure_init)
|
||||
self.ring: Optional[np.ndarray] = None # (max_sweeps, WF_WIDTH)
|
||||
@ -26,7 +34,7 @@ class RingBuffer:
|
||||
self.head: int = 0
|
||||
self.width: Optional[int] = None
|
||||
self.x_shared: Optional[np.ndarray] = None
|
||||
self.freq_shared: Optional[np.ndarray] = None
|
||||
self.fft_time_axis: Optional[np.ndarray] = None # временная ось IFFT в нс
|
||||
self.y_min_fft: Optional[float] = None
|
||||
self.y_max_fft: Optional[float] = None
|
||||
# FFT последнего свипа (для отображения без повторного вычисления)
|
||||
@ -43,7 +51,10 @@ class RingBuffer:
|
||||
self.ring = np.full((self.max_sweeps, self.width), np.nan, dtype=np.float32)
|
||||
self.ring_time = np.full((self.max_sweeps,), np.nan, dtype=np.float64)
|
||||
self.ring_fft = np.full((self.max_sweeps, self.fft_bins), np.nan, dtype=np.float32)
|
||||
self.freq_shared = np.arange(self.fft_bins, dtype=np.int32)
|
||||
# Временная ось IFFT: шаг dt = 1/(FREQ_SPAN_GHZ*1e9), переведём в нс
|
||||
self.fft_time_axis = (
|
||||
np.arange(IFFT_LEN, dtype=np.float64) / (FREQ_SPAN_GHZ * 1e9) * 1e9
|
||||
).astype(np.float32)
|
||||
self.head = 0
|
||||
# Обновляем x_shared если пришёл свип большего размера
|
||||
if self.x_shared is None or sweep_width > self.x_shared.size:
|
||||
@ -64,20 +75,29 @@ class RingBuffer:
|
||||
self._push_fft(s)
|
||||
|
||||
def _push_fft(self, s: np.ndarray):
|
||||
bins = self.ring_fft.shape[1]
|
||||
take_fft = min(int(s.size), FFT_LEN)
|
||||
if take_fft <= 0:
|
||||
bins = self.ring_fft.shape[1] # = IFFT_LEN = 1953
|
||||
if s is None or s.size == 0:
|
||||
fft_row = np.full((bins,), np.nan, dtype=np.float32)
|
||||
else:
|
||||
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)
|
||||
fft_in[:take_fft] = seg * win
|
||||
spec = np.fft.rfft(fft_in)
|
||||
mag = np.abs(spec).astype(np.float32)
|
||||
# 1. Взять первые SWEEP_LEN отсчётов (остаток — нули если свип короче)
|
||||
sig = np.zeros(SWEEP_LEN, dtype=np.float32)
|
||||
take = min(int(s.size), SWEEP_LEN)
|
||||
seg = np.nan_to_num(s[:take], nan=0.0).astype(np.float32, copy=False)
|
||||
sig[:take] = seg
|
||||
|
||||
# 2. Собрать двусторонний спектр:
|
||||
# [ZEROS_LOW нулей | ZEROS_MID нулей | SWEEP_LEN данных]
|
||||
# = [-14.3..-3.2 ГГц | -3.2..+3.2 ГГц | +3.2..+14.3 ГГц]
|
||||
data = np.zeros(IFFT_LEN, dtype=np.complex64)
|
||||
data[ZEROS_LOW + ZEROS_MID:] = sig
|
||||
|
||||
# 3. ifftshift + ifft → временной профиль
|
||||
spec = np.fft.ifftshift(data)
|
||||
result = np.fft.ifft(spec)
|
||||
|
||||
# 4. Амплитуда в дБ
|
||||
mag = np.abs(result).astype(np.float32)
|
||||
fft_row = (20.0 * np.log10(mag + 1e-9)).astype(np.float32)
|
||||
if fft_row.shape[0] != bins:
|
||||
fft_row = fft_row[:bins]
|
||||
|
||||
prev_head = (self.head - 1) % self.ring_fft.shape[0]
|
||||
self.ring_fft[prev_head, :] = fft_row
|
||||
|
||||
Reference in New Issue
Block a user