little refactoring done
This commit is contained in:
@@ -59,6 +59,29 @@ class AppWindowPlotMixin:
|
||||
"""Clear pass-through magnitude and phase plots."""
|
||||
self._trace_magnitude_plot.clear()
|
||||
self._trace_phase_plot.clear()
|
||||
self._clear_trace_legends()
|
||||
self._trace_magnitude_curves.clear()
|
||||
self._trace_phase_curves.clear()
|
||||
|
||||
def _clear_trace_legends(self) -> None:
|
||||
"""Remove trace plot legends to avoid stale combo-color mappings."""
|
||||
mag_legend = self._trace_magnitude_legend
|
||||
if mag_legend is not None:
|
||||
try:
|
||||
self._trace_magnitude_plot.getPlotItem().removeItem(mag_legend)
|
||||
except Exception: # noqa: BLE001
|
||||
pass
|
||||
self._trace_magnitude_legend = None
|
||||
self._trace_magnitude_legend_combo_keys.clear()
|
||||
|
||||
phase_legend = self._trace_phase_legend
|
||||
if phase_legend is not None:
|
||||
try:
|
||||
self._trace_phase_plot.getPlotItem().removeItem(phase_legend)
|
||||
except Exception: # noqa: BLE001
|
||||
pass
|
||||
self._trace_phase_legend = None
|
||||
self._trace_phase_legend_combo_keys.clear()
|
||||
|
||||
def _draw_trace_lines(self, collection: ResultCollection) -> bool:
|
||||
"""Draw result payload traces as stacked magnitude/phase plots."""
|
||||
@@ -69,8 +92,8 @@ class AppWindowPlotMixin:
|
||||
|
||||
magnitude_plot.setVisible(show_magnitude)
|
||||
phase_plot.setVisible(show_phase)
|
||||
self._clear_trace_plots()
|
||||
if not show_magnitude and not show_phase:
|
||||
self._clear_trace_plots()
|
||||
return False
|
||||
|
||||
if show_magnitude:
|
||||
@@ -103,44 +126,90 @@ class AppWindowPlotMixin:
|
||||
"#1982c4",
|
||||
]
|
||||
|
||||
color_index = 0
|
||||
combo_colors: dict[tuple[int, int], str] = {}
|
||||
legend_source_magnitude: dict[tuple[int, int], pg.PlotCurveItem] = {}
|
||||
legend_source_phase: dict[tuple[int, int], pg.PlotCurveItem] = {}
|
||||
active_magnitude_keys: set[tuple[int, int, int, str]] = set()
|
||||
active_phase_keys: set[tuple[int, int, int, str]] = set()
|
||||
has_data = False
|
||||
x_min = np.inf
|
||||
x_max = -np.inf
|
||||
for block in collection.blocks:
|
||||
for payload in block.payloads:
|
||||
combo_key = (int(block.combo.input_pos), int(block.combo.output_pos))
|
||||
if combo_key not in combo_colors:
|
||||
combo_colors[combo_key] = palette[len(combo_colors) % len(palette)]
|
||||
color = combo_colors[combo_key]
|
||||
combo_label = f"in{combo_key[0]}/out{combo_key[1]}"
|
||||
|
||||
for payload_index, payload in enumerate(block.payloads):
|
||||
if payload.kind != 1 or payload.trace.size == 0:
|
||||
continue
|
||||
if payload.frequency_hz.size == 0 or payload.frequency_hz.size != payload.trace.size:
|
||||
continue
|
||||
curve_key = (
|
||||
combo_key[0],
|
||||
combo_key[1],
|
||||
int(payload_index),
|
||||
str(payload.processing_name),
|
||||
)
|
||||
|
||||
local_x_min = float(np.min(payload.frequency_hz))
|
||||
local_x_max = float(np.max(payload.frequency_hz))
|
||||
x_min = min(x_min, local_x_min)
|
||||
x_max = max(x_max, local_x_max)
|
||||
color = palette[color_index % len(palette)]
|
||||
|
||||
if show_magnitude:
|
||||
magnitude_values = 20.0 * np.log10(np.maximum(np.abs(payload.trace), 1e-12))
|
||||
magnitude_curve = pg.PlotCurveItem(
|
||||
payload.frequency_hz,
|
||||
magnitude_values,
|
||||
pen=pg.mkPen(color, width=1.4),
|
||||
)
|
||||
magnitude_plot.addItem(magnitude_curve)
|
||||
active_magnitude_keys.add(curve_key)
|
||||
magnitude_curve = self._trace_magnitude_curves.get(curve_key)
|
||||
if magnitude_curve is None:
|
||||
magnitude_curve = pg.PlotCurveItem(pen=pg.mkPen(color, width=1.4))
|
||||
self._trace_magnitude_curves[curve_key] = magnitude_curve
|
||||
magnitude_plot.addItem(magnitude_curve)
|
||||
else:
|
||||
magnitude_curve.setPen(pg.mkPen(color, width=1.4))
|
||||
magnitude_curve.setData(payload.frequency_hz, magnitude_values)
|
||||
legend_source_magnitude.setdefault(combo_key, magnitude_curve)
|
||||
has_data = True
|
||||
|
||||
if show_phase:
|
||||
phase_values = np.degrees(np.angle(payload.trace))
|
||||
phase_curve = pg.PlotCurveItem(
|
||||
payload.frequency_hz,
|
||||
phase_values,
|
||||
pen=pg.mkPen(color, width=1.2, style=Qt.PenStyle.DashLine),
|
||||
)
|
||||
phase_plot.addItem(phase_curve)
|
||||
active_phase_keys.add(curve_key)
|
||||
phase_curve = self._trace_phase_curves.get(curve_key)
|
||||
if phase_curve is None:
|
||||
phase_curve = pg.PlotCurveItem(pen=pg.mkPen(color, width=1.2))
|
||||
self._trace_phase_curves[curve_key] = phase_curve
|
||||
phase_plot.addItem(phase_curve)
|
||||
else:
|
||||
phase_curve.setPen(pg.mkPen(color, width=1.2))
|
||||
phase_x, phase_values = self._phase_display_arrays(payload.frequency_hz, payload.trace)
|
||||
phase_curve.setData(phase_x, phase_values)
|
||||
legend_source_phase.setdefault(combo_key, phase_curve)
|
||||
has_data = True
|
||||
|
||||
color_index += 1
|
||||
if show_magnitude:
|
||||
self._remove_inactive_trace_curves(
|
||||
plot=magnitude_plot,
|
||||
cache=self._trace_magnitude_curves,
|
||||
active_keys=active_magnitude_keys,
|
||||
)
|
||||
else:
|
||||
self._remove_all_trace_curves(plot=magnitude_plot, cache=self._trace_magnitude_curves)
|
||||
|
||||
if show_phase:
|
||||
self._remove_inactive_trace_curves(
|
||||
plot=phase_plot,
|
||||
cache=self._trace_phase_curves,
|
||||
active_keys=active_phase_keys,
|
||||
)
|
||||
else:
|
||||
self._remove_all_trace_curves(plot=phase_plot, cache=self._trace_phase_curves)
|
||||
|
||||
self._sync_trace_legends(
|
||||
show_magnitude=show_magnitude,
|
||||
show_phase=show_phase,
|
||||
magnitude_sources=legend_source_magnitude,
|
||||
phase_sources=legend_source_phase,
|
||||
)
|
||||
|
||||
if has_data:
|
||||
if np.isfinite(x_min) and np.isfinite(x_max):
|
||||
@@ -152,6 +221,105 @@ class AppWindowPlotMixin:
|
||||
phase_plot.setYRange(-180.0, 180.0, padding=0.02)
|
||||
return has_data
|
||||
|
||||
@staticmethod
|
||||
def _remove_inactive_trace_curves(
|
||||
*,
|
||||
plot: pg.PlotWidget,
|
||||
cache: dict[tuple[int, int, int, str], pg.PlotCurveItem],
|
||||
active_keys: set[tuple[int, int, int, str]],
|
||||
) -> None:
|
||||
"""Delete curve items no longer present in latest result collection."""
|
||||
for key in list(cache.keys()):
|
||||
if key in active_keys:
|
||||
continue
|
||||
curve = cache.pop(key)
|
||||
plot.removeItem(curve)
|
||||
|
||||
@staticmethod
|
||||
def _remove_all_trace_curves(
|
||||
*,
|
||||
plot: pg.PlotWidget,
|
||||
cache: dict[tuple[int, int, int, str], pg.PlotCurveItem],
|
||||
) -> None:
|
||||
"""Delete all cached curves from selected plot."""
|
||||
for curve in cache.values():
|
||||
plot.removeItem(curve)
|
||||
cache.clear()
|
||||
|
||||
def _phase_display_arrays(self, frequency_hz: np.ndarray, trace: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
|
||||
"""Return phase display arrays with decimation for faster rendering."""
|
||||
max_points = int(getattr(self, "_trace_phase_render_max_points", 1200))
|
||||
if max_points > 0 and trace.size > max_points:
|
||||
step = max(1, int(np.ceil(trace.size / max_points)))
|
||||
frequency_hz = frequency_hz[::step]
|
||||
trace = trace[::step]
|
||||
phase_values = np.arctan2(trace.imag, trace.real) * (180.0 / np.pi)
|
||||
return frequency_hz, phase_values
|
||||
|
||||
def _sync_trace_legends(
|
||||
self,
|
||||
*,
|
||||
show_magnitude: bool,
|
||||
show_phase: bool,
|
||||
magnitude_sources: dict[tuple[int, int], pg.PlotCurveItem],
|
||||
phase_sources: dict[tuple[int, int], pg.PlotCurveItem],
|
||||
) -> None:
|
||||
"""Rebuild legends only when active combo set changes."""
|
||||
self._sync_single_trace_legend(
|
||||
show=show_magnitude,
|
||||
plot=self._trace_magnitude_plot,
|
||||
legend_attr="_trace_magnitude_legend",
|
||||
legend_keys_attr="_trace_magnitude_legend_combo_keys",
|
||||
sources=magnitude_sources,
|
||||
)
|
||||
self._sync_single_trace_legend(
|
||||
show=show_phase,
|
||||
plot=self._trace_phase_plot,
|
||||
legend_attr="_trace_phase_legend",
|
||||
legend_keys_attr="_trace_phase_legend_combo_keys",
|
||||
sources=phase_sources,
|
||||
)
|
||||
|
||||
def _sync_single_trace_legend(
|
||||
self,
|
||||
*,
|
||||
show: bool,
|
||||
plot: pg.PlotWidget,
|
||||
legend_attr: str,
|
||||
legend_keys_attr: str,
|
||||
sources: dict[tuple[int, int], pg.PlotCurveItem],
|
||||
) -> None:
|
||||
"""Rebuild one legend from provided combo->curve mapping when needed."""
|
||||
legend = getattr(self, legend_attr)
|
||||
existing_keys = getattr(self, legend_keys_attr)
|
||||
active_keys = set(sources.keys())
|
||||
if not show or not active_keys:
|
||||
if legend is not None:
|
||||
try:
|
||||
plot.getPlotItem().removeItem(legend)
|
||||
except Exception: # noqa: BLE001
|
||||
pass
|
||||
setattr(self, legend_attr, None)
|
||||
existing_keys.clear()
|
||||
return
|
||||
|
||||
if legend is not None and existing_keys == active_keys:
|
||||
return
|
||||
|
||||
if legend is not None:
|
||||
try:
|
||||
plot.getPlotItem().removeItem(legend)
|
||||
except Exception: # noqa: BLE001
|
||||
pass
|
||||
|
||||
legend = plot.addLegend(offset=(8, 8))
|
||||
for combo_key in sorted(active_keys):
|
||||
curve = sources[combo_key]
|
||||
legend.addItem(curve, f"in{combo_key[0]}/out{combo_key[1]}")
|
||||
setattr(self, legend_attr, legend)
|
||||
existing_keys.clear()
|
||||
existing_keys.update(active_keys)
|
||||
|
||||
def _draw_bscan_heatmap(self, _collection: ResultCollection) -> bool:
|
||||
"""Draw B-scan image rebuilt from processed result history."""
|
||||
self._disable_phase_axis()
|
||||
@@ -189,18 +357,18 @@ class AppWindowPlotMixin:
|
||||
image_item.setLookupTable(self._bscan_lookup_table(axis_mode))
|
||||
image_item.setLevels(self._bscan_levels(sweeps, axis_mode))
|
||||
|
||||
self._plot.clear()
|
||||
view_box = self._plot.getViewBox()
|
||||
self._bscan_plot.clear()
|
||||
view_box = self._bscan_plot.getViewBox()
|
||||
view_box.invertY(True)
|
||||
view_box.enableAutoRange(x=False, y=False)
|
||||
self._plot.getPlotItem().showAxis("left", show=True)
|
||||
self._plot.getPlotItem().showAxis("bottom", show=True)
|
||||
self._plot.setLabel("bottom", "Sweep #")
|
||||
self._plot.setLabel("left", "Depth", units="m")
|
||||
self._plot.addItem(image_item)
|
||||
self._plot.setXRange(x_min, x_max, padding=0.02)
|
||||
self._plot.setYRange(depth_min, depth_max, padding=0.02)
|
||||
self._plot.setTitle(f"B-scan in{display_key[0]}/out{display_key[1]} | sweeps={sweep_count}")
|
||||
self._bscan_plot.getPlotItem().showAxis("left", show=True)
|
||||
self._bscan_plot.getPlotItem().showAxis("bottom", show=True)
|
||||
self._bscan_plot.setLabel("bottom", "Sweep #")
|
||||
self._bscan_plot.setLabel("left", "Depth", units="m")
|
||||
self._bscan_plot.addItem(image_item)
|
||||
self._bscan_plot.setXRange(x_min, x_max, padding=0.02)
|
||||
self._bscan_plot.setYRange(depth_min, depth_max, padding=0.02)
|
||||
self._bscan_plot.setTitle(f"B-scan in{display_key[0]}/out{display_key[1]} | sweeps={sweep_count}")
|
||||
return True
|
||||
|
||||
def _sync_bscan_history_from_results(self) -> None:
|
||||
@@ -284,7 +452,7 @@ class AppWindowPlotMixin:
|
||||
|
||||
def _ensure_phase_view_box(self) -> pg.ViewBox:
|
||||
"""Create or return secondary right-axis ViewBox for phase curves."""
|
||||
plot_item = self._plot.getPlotItem()
|
||||
plot_item = self._bscan_plot.getPlotItem()
|
||||
phase_view_box = self._phase_viewbox
|
||||
if phase_view_box is None:
|
||||
phase_view_box = pg.ViewBox()
|
||||
@@ -301,7 +469,7 @@ class AppWindowPlotMixin:
|
||||
phase_view_box = self._phase_viewbox
|
||||
if phase_view_box is None:
|
||||
return
|
||||
plot_item = self._plot.getPlotItem()
|
||||
plot_item = self._bscan_plot.getPlotItem()
|
||||
phase_view_box.setGeometry(plot_item.vb.sceneBoundingRect())
|
||||
phase_view_box.linkedViewChanged(plot_item.vb, phase_view_box.XAxis)
|
||||
|
||||
@@ -358,6 +526,9 @@ class AppWindowPlotMixin:
|
||||
pen=pg.mkPen("#ffd166", width=1.8),
|
||||
)
|
||||
magnitude_plot.addItem(magnitude_curve)
|
||||
self._trace_magnitude_curves[
|
||||
(int(trace.combo.input_pos), int(trace.combo.output_pos), 0, "__single_trace__")
|
||||
] = magnitude_curve
|
||||
|
||||
if show_phase:
|
||||
phase_deg = np.degrees(np.angle(trace.s21))
|
||||
@@ -367,6 +538,9 @@ class AppWindowPlotMixin:
|
||||
pen=pg.mkPen("#80ed99", width=1.4, style=Qt.PenStyle.DashLine),
|
||||
)
|
||||
phase_plot.addItem(phase_curve)
|
||||
self._trace_phase_curves[
|
||||
(int(trace.combo.input_pos), int(trace.combo.output_pos), 0, "__single_trace__")
|
||||
] = phase_curve
|
||||
phase_plot.setYRange(-180.0, 180.0, padding=0.02)
|
||||
|
||||
if np.size(trace.frequency_hz) > 1:
|
||||
|
||||
Reference in New Issue
Block a user