fix upd speed waterflow

This commit is contained in:
awe
2026-04-27 19:07:01 +03:00
parent 75bc502fe1
commit ffb7dc3f25
2 changed files with 90 additions and 3 deletions

View File

@ -48,6 +48,7 @@ from rfg_adc_plotter.types import SweepAuxCurves, SweepInfo, SweepPacket
RAW_PLOT_MAX_POINTS = 4096
RAW_WATERFALL_MAX_POINTS = 2048
BSCAN_MAX_POINTS = 512
UI_MAX_PACKETS_PER_TICK = 8
DEBUG_FRAME_LOG_EVERY = 10
UI_BACKLOG_TAIL_THRESHOLD_MULTIPLIER = 1
@ -197,6 +198,19 @@ def resolve_heavy_refresh_stride(
return 1
def resolve_bscan_refresh_stride(
backlog_packets: int,
*,
max_packets: int = UI_MAX_PACKETS_PER_TICK,
) -> int:
"""Keep B-scan responsive by limiting suppression to every other frame."""
base = max(1, int(max_packets))
backlog = max(0, int(backlog_packets))
if backlog >= (base * UI_BACKLOG_LATEST_ONLY_THRESHOLD_MULTIPLIER):
return 2
return 1
def resolve_initial_window_size(available_width: int, available_height: int) -> Tuple[int, int]:
"""Fit the initial window to the current screen without assuming desktop-sized geometry."""
width_in = int(max(0, available_width))
@ -572,6 +586,38 @@ def decimate_curve_for_display(
return x_arr[display_idx], y_arr[display_idx]
def decimate_bscan_rows_for_display(
axis: Optional[np.ndarray],
data: np.ndarray,
*,
max_points: int = BSCAN_MAX_POINTS,
) -> Tuple[Optional[np.ndarray], np.ndarray]:
"""Reduce B-scan rows to keep waterfall rendering responsive."""
data_arr = np.asarray(data, dtype=np.float32)
if data_arr.ndim != 2:
return axis, data_arr
row_count = int(data_arr.shape[0])
limit = max(1, int(max_points))
if row_count <= limit:
return axis, data_arr
row_idx = np.linspace(0, row_count - 1, limit, dtype=np.int64)
row_idx = np.unique(row_idx)
decimated = data_arr[row_idx, :]
if axis is None:
return None, decimated
axis_arr = np.asarray(axis, dtype=np.float64).reshape(-1)
if axis_arr.size <= 0:
return None, decimated
take = min(axis_arr.size, row_count)
axis_arr = axis_arr[:take]
valid_idx = row_idx[row_idx < axis_arr.size]
if valid_idx.size != row_idx.size:
decimated = data_arr[valid_idx, :]
return axis_arr[valid_idx], decimated
def coalesce_packets_for_ui(
packets: Sequence[SweepPacket],
*,
@ -1103,6 +1149,7 @@ def run_pyqtgraph(args) -> None:
last_queue_backlog = 0
last_backlog_skipped = 0
last_heavy_refresh_stride = 1
last_bscan_refresh_stride = 1
expected_sweep_width = 0
base_freqs_cache: Dict[int, np.ndarray] = {}
last_packet_processed_at: Optional[float] = None
@ -2110,7 +2157,8 @@ def run_pyqtgraph(args) -> None:
runtime.current_fft_db = fft_mag_to_db(runtime.current_fft_mag)
def drain_queue() -> int:
nonlocal processed_frames, ui_frames_skipped, last_queue_backlog, last_backlog_skipped, last_heavy_refresh_stride
nonlocal processed_frames, ui_frames_skipped, last_queue_backlog, last_backlog_skipped
nonlocal last_heavy_refresh_stride, last_bscan_refresh_stride
nonlocal expected_sweep_width, base_freqs_cache, last_packet_processed_at
pending_packets: List[SweepPacket] = []
while True:
@ -2123,6 +2171,7 @@ def run_pyqtgraph(args) -> None:
if drained <= 0:
last_backlog_skipped = 0
last_heavy_refresh_stride = 1
last_bscan_refresh_stride = 1
return 0
pending_packets, skipped_packets = coalesce_packets_for_ui(
@ -2131,6 +2180,7 @@ def run_pyqtgraph(args) -> None:
)
last_backlog_skipped = skipped_packets
last_heavy_refresh_stride = resolve_heavy_refresh_stride(drained)
last_bscan_refresh_stride = resolve_bscan_refresh_stride(drained)
ui_frames_skipped += skipped_packets
if skipped_packets > 0:
log_debug_event(
@ -2272,6 +2322,11 @@ def run_pyqtgraph(args) -> None:
or last_heavy_refresh_stride <= 1
or (update_ticks % last_heavy_refresh_stride) == 0
)
refresh_bscan_views = (
runtime.plot_dirty
or last_bscan_refresh_stride <= 1
or (update_ticks % last_bscan_refresh_stride) == 0
)
if redraw_needed:
refresh_signal_mode_labels()
@ -2717,10 +2772,10 @@ def run_pyqtgraph(args) -> None:
pass
if redraw_needed and runtime.ring.ring_fft is not None:
if not refresh_heavy_views:
if not refresh_bscan_views:
log_debug_event(
"suppressed_fft_image_refresh",
f"ui FFT waterfall refresh suppressed stride:{last_heavy_refresh_stride}",
f"ui FFT waterfall refresh suppressed stride:{last_bscan_refresh_stride}",
)
else:
disp_fft_lin = runtime.ring.get_display_fft_linear()
@ -2735,6 +2790,11 @@ def run_pyqtgraph(args) -> None:
if keep_mask.size > 0:
disp_fft_lin = disp_fft_lin[keep_mask, :]
disp_fft_axis = axis_arr
disp_fft_axis, disp_fft_lin = decimate_bscan_rows_for_display(
disp_fft_axis,
disp_fft_lin,
max_points=BSCAN_MAX_POINTS,
)
if spec_mean_sec > 0.0:
disp_times = runtime.ring.get_display_times()
if disp_times is not None:

View File

@ -16,9 +16,11 @@ from rfg_adc_plotter.gui.pyqtgraph_backend import (
compute_background_subtracted_bscan_levels,
compute_aux_phase_curve,
convert_tty_i16_to_voltage,
decimate_bscan_rows_for_display,
decimate_curve_for_display,
is_short_sweep,
resolve_axis_bounds,
resolve_bscan_refresh_stride,
resolve_heavy_refresh_stride,
resolve_initial_window_size,
resolve_distance_cut_start,
@ -377,6 +379,31 @@ class ProcessingTests(unittest.TestCase):
self.assertEqual(resolve_heavy_refresh_stride(8, max_packets=8), 2)
self.assertEqual(resolve_heavy_refresh_stride(16, max_packets=8), 4)
def test_resolve_bscan_refresh_stride_limits_suppression(self):
self.assertEqual(resolve_bscan_refresh_stride(0, max_packets=8), 1)
self.assertEqual(resolve_bscan_refresh_stride(8, max_packets=8), 1)
self.assertEqual(resolve_bscan_refresh_stride(16, max_packets=8), 2)
def test_decimate_bscan_rows_for_display_keeps_shape_consistent(self):
axis = np.linspace(0.0, 1.0, 10, dtype=np.float64)
data = np.arange(50, dtype=np.float32).reshape(10, 5)
dec_axis, dec_data = decimate_bscan_rows_for_display(axis, data, max_points=4)
self.assertEqual(dec_data.shape, (4, 5))
self.assertIsNotNone(dec_axis)
self.assertEqual(dec_axis.shape, (4,))
self.assertAlmostEqual(float(dec_axis[0]), 0.0, places=12)
self.assertAlmostEqual(float(dec_axis[-1]), 1.0, places=12)
def test_decimate_bscan_rows_for_display_handles_missing_axis(self):
data = np.arange(32, dtype=np.float32).reshape(8, 4)
dec_axis, dec_data = decimate_bscan_rows_for_display(None, data, max_points=3)
self.assertIsNone(dec_axis)
self.assertEqual(dec_data.shape, (3, 4))
def test_update_expected_sweep_width_initializes_from_first_valid_sweep(self):
self.assertEqual(update_expected_sweep_width(0, 411), 411)