refactor(backend): clean up package re-exports, settings model properties, and unused signals

This commit is contained in:
jack
2026-07-09 12:49:33 -07:00
parent ca158a7946
commit ac4448ed44
34 changed files with 372 additions and 337 deletions
@@ -1,11 +0,0 @@
# ===== Visualization Sub-package =====
from pygui.backend.visualization.graph_quick_item import GraphQuickItem
from pygui.backend.visualization.graph_view import GraphView
from pygui.backend.visualization.rbf_heatmap import interpolate_field
from pygui.backend.visualization.trend_chart_item import TrendChartItem
from pygui.backend.visualization.wafer_map_item import WaferMapItem
__all__ = [
"GraphQuickItem", "GraphView", "TrendChartItem", "WaferMapItem",
"interpolate_field",
]
@@ -0,0 +1,33 @@
"""Pure coordinate-mapping math shared by the QQuickPaintedItem chart items
(GraphQuickItem, TrendChartItem). No QPainter or Qt-widget dependency, so
it's testable without a GUI — unlike the paint() methods that call it.
"""
from __future__ import annotations
def value_to_pixel(
value: float,
value_min: float,
value_max: float,
pixel_start: float,
pixel_span: float,
) -> float:
"""Map a value onto a pixel range, inverted so larger values sit at
smaller pixel coordinates (screen y grows downward).
Also used for evenly-spaced tick indices: pass the tick index as
`value` and `(tick_count - 1)` as `value_max` with `value_min=0`.
"""
value_range = value_max - value_min
if value_range < 1e-9:
return pixel_start + pixel_span / 2
fraction = 1.0 - (value - value_min) / value_range
return pixel_start + fraction * pixel_span
def index_to_pixel(index: float, count: int, pixel_start: float, pixel_span: float) -> float:
"""Map a 0-based sample index onto a pixel range, left to right."""
if count <= 1:
return pixel_start + pixel_span / 2
fraction = index / (count - 1)
return pixel_start + fraction * pixel_span
@@ -26,6 +26,8 @@ from PySide6.QtGui import QColor, QFont, QFontMetrics, QPainter, QPen
from PySide6.QtQml import QmlElement
from PySide6.QtQuick import QQuickPaintedItem
from pygui.backend.visualization.chart_geometry import index_to_pixel, value_to_pixel
QML_IMPORT_NAME = "ISC.Wafer"
QML_IMPORT_MAJOR_VERSION = 1
@@ -78,7 +80,7 @@ class GraphQuickItem(QQuickPaintedItem):
def seriesData(self) -> list:
return self._series_data
@seriesData.setter
@seriesData.setter # type: ignore[no-redef]
def seriesData(self, val: list) -> None:
self._series_data = [list(s) for s in (val or [])]
self._auto_range()
@@ -89,7 +91,7 @@ class GraphQuickItem(QQuickPaintedItem):
def sensorNames(self) -> list:
return self._sensor_names
@sensorNames.setter
@sensorNames.setter # type: ignore[no-redef]
def sensorNames(self, val: list) -> None:
self._sensor_names = list(val or [])
self.sensorNamesChanged.emit()
@@ -99,7 +101,7 @@ class GraphQuickItem(QQuickPaintedItem):
def title(self) -> str:
return self._title
@title.setter
@title.setter # type: ignore[no-redef]
def title(self, val: str) -> None:
self._title = str(val or "")
self.titleChanged.emit()
@@ -109,7 +111,7 @@ class GraphQuickItem(QQuickPaintedItem):
def xLabel(self) -> str:
return self._x_label
@xLabel.setter
@xLabel.setter # type: ignore[no-redef]
def xLabel(self, val: str) -> None:
self._x_label = str(val or "")
self.xLabelChanged.emit()
@@ -119,7 +121,7 @@ class GraphQuickItem(QQuickPaintedItem):
def yLabel(self) -> str:
return self._y_label
@yLabel.setter
@yLabel.setter # type: ignore[no-redef]
def yLabel(self, val: str) -> None:
self._y_label = str(val or "")
self.yLabelChanged.emit()
@@ -129,7 +131,7 @@ class GraphQuickItem(QQuickPaintedItem):
def showLegend(self) -> bool:
return self._show_legend
@showLegend.setter
@showLegend.setter # type: ignore[no-redef]
def showLegend(self, val: bool) -> None:
self._show_legend = bool(val)
self.colorsChanged.emit()
@@ -141,7 +143,7 @@ class GraphQuickItem(QQuickPaintedItem):
def backgroundColor(self) -> QColor:
return self._bg_color
@backgroundColor.setter
@backgroundColor.setter # type: ignore[no-redef]
def backgroundColor(self, c: QColor) -> None:
self._bg_color = c
self.update()
@@ -150,7 +152,7 @@ class GraphQuickItem(QQuickPaintedItem):
def gridColor(self) -> QColor:
return self._grid_color
@gridColor.setter
@gridColor.setter # type: ignore[no-redef]
def gridColor(self, c: QColor) -> None:
self._grid_color = c
self.update()
@@ -159,7 +161,7 @@ class GraphQuickItem(QQuickPaintedItem):
def axisColor(self) -> QColor:
return self._axis_color
@axisColor.setter
@axisColor.setter # type: ignore[no-redef]
def axisColor(self, c: QColor) -> None:
self._axis_color = c
self.update()
@@ -168,7 +170,7 @@ class GraphQuickItem(QQuickPaintedItem):
def textColor(self) -> QColor:
return self._text_color
@textColor.setter
@textColor.setter # type: ignore[no-redef]
def textColor(self, c: QColor) -> None:
self._text_color = c
self.update()
@@ -178,7 +180,7 @@ class GraphQuickItem(QQuickPaintedItem):
"""Return hex strings of the current series color palette."""
return [c.name() for c in self._series_colors]
@seriesColors.setter
@seriesColors.setter # type: ignore[no-redef]
def seriesColors(self, hex_list: list) -> None:
colors = []
for h in (hex_list or []):
@@ -275,8 +277,7 @@ class GraphQuickItem(QQuickPaintedItem):
for i in range(n_y_ticks):
y_val = self._min_y + i * y_step
y_frac = 1.0 - (i / (n_y_ticks - 1)) if n_y_ticks > 1 else 0.5
y_px = plot_top + y_frac * plot_h
y_px = value_to_pixel(i, 0, n_y_ticks - 1, plot_top, plot_h)
# Grid line
painter.setPen(grid_pen)
@@ -325,8 +326,7 @@ class GraphQuickItem(QQuickPaintedItem):
x_tick_step = (num_points - 1) / max(1, n_x_ticks - 1)
for i in range(n_x_ticks):
idx = int(round(i * x_tick_step))
x_frac = idx / (num_points - 1)
x_px = plot_left + x_frac * plot_w
x_px = index_to_pixel(idx, num_points, plot_left, plot_w)
painter.setPen(axis_pen)
painter.drawText(
int(x_px - 12), int(plot_top + plot_h + 14),
@@ -358,10 +358,8 @@ class GraphQuickItem(QQuickPaintedItem):
v = float(val)
except (ValueError, TypeError):
continue
x_frac = i / (max_len - 1) if max_len > 1 else 0.5
y_frac = 1.0 - ((v - self._min_y) / y_range) if y_range > 0 else 0.5
x_px = plot_left + x_frac * plot_w
y_px = plot_top + y_frac * plot_h
x_px = index_to_pixel(i, max_len, plot_left, plot_w)
y_px = value_to_pixel(v, self._min_y, self._max_y, plot_top, plot_h)
pts.append((x_px, y_px))
if len(pts) < 2:
@@ -23,6 +23,9 @@ class GraphView(QObject):
each sensor as a separate line series.
"""
# Set lazily in updateTrend (guarded by hasattr); declared for mypy.
_trend_line: Any
# ---- signals ----
dataReady = Signal(object) # {"success": bool, "sensory_names": list, "series": list}
@@ -227,7 +230,7 @@ class GraphView(QObject):
index_a, index_b = path[i]
if index_a < len(a) and index_b < len(b):
line = pg.InfiniteLine(pos=index_a, angle=90, pen=pg.mkPen("#666666", width=1,
style=Qt.DashLine))
style=Qt.PenStyle.DashLine))
self._plot_widget.addItem(line)
self._plot_widget.setTitle("DTW Comparison")
@@ -73,7 +73,8 @@ def interpolate_field(
dist = ((grid_x - cx) / rx) ** 2 + ((grid_y - cy) / ry) ** 2
field = np.where(dist <= 1.0, field, np.nan)
return field.astype(np.float64)
result: np.ndarray = field.astype(np.float64)
return result
def refine_field(field: np.ndarray, factor: int = REFINE_FACTOR) -> np.ndarray:
@@ -84,7 +85,8 @@ def refine_field(field: np.ndarray, factor: int = REFINE_FACTOR) -> np.ndarray:
resampling across a NaN/zero-filled hole rings at the boundary, and any
mask derived from the coarse grid keeps its staircase when upscaled.
"""
return _ndi_zoom(field, factor, order=3)
refined: np.ndarray = _ndi_zoom(field, factor, order=3)
return refined
def ellipse_alpha(height: int, width: int, feather_px: float = 1.2) -> np.ndarray:
@@ -20,6 +20,8 @@ from PySide6.QtGui import QColor, QFont, QPainter, QPen, QPolygon
from PySide6.QtQml import QmlElement
from PySide6.QtQuick import QQuickPaintedItem
from pygui.backend.visualization.chart_geometry import index_to_pixel, value_to_pixel
log = logging.getLogger(__name__)
QML_IMPORT_NAME = "ISC.Wafer"
@@ -60,7 +62,7 @@ class TrendChartItem(QQuickPaintedItem):
def data(self) -> list[float]:
return self._data
@data.setter
@data.setter # type: ignore[no-redef]
def data(self, val) -> None:
coerced = self._coerce_floats(val)
if coerced == self._data:
@@ -82,7 +84,7 @@ class TrendChartItem(QQuickPaintedItem):
def lineColor(self) -> QColor:
return self._line_color
@lineColor.setter
@lineColor.setter # type: ignore[no-redef]
def lineColor(self, c: QColor) -> None:
if c == self._line_color:
return
@@ -94,7 +96,7 @@ class TrendChartItem(QQuickPaintedItem):
def gridColor(self) -> QColor:
return self._grid_color
@gridColor.setter
@gridColor.setter # type: ignore[no-redef]
def gridColor(self, c: QColor) -> None:
if c == self._grid_color:
return
@@ -106,7 +108,7 @@ class TrendChartItem(QQuickPaintedItem):
def textColor(self) -> QColor:
return self._text_color
@textColor.setter
@textColor.setter # type: ignore[no-redef]
def textColor(self, c: QColor) -> None:
if c == self._text_color:
return
@@ -118,7 +120,7 @@ class TrendChartItem(QQuickPaintedItem):
def padding(self) -> int:
return self._padding
@padding.setter
@padding.setter # type: ignore[no-redef]
def padding(self, val: int) -> None:
v = max(0, int(val))
if v == self._padding:
@@ -201,15 +203,10 @@ class TrendChartItem(QQuickPaintedItem):
return math.floor(lo / step) * step, math.ceil(hi / step) * step
def _x_to_px(self, i: int, n: int, plot_rect: QRectF) -> float:
if n <= 1:
return plot_rect.left()
return plot_rect.left() + (i / (n - 1)) * plot_rect.width()
return index_to_pixel(i, n, plot_rect.left(), plot_rect.width())
def _y_to_px(self, v: float, y_min: float, y_max: float, plot_rect: QRectF) -> float:
if y_max - y_min < 1e-9:
return plot_rect.center().y()
t = (v - y_min) / (y_max - y_min)
return plot_rect.bottom() - t * plot_rect.height()
return value_to_pixel(v, y_min, y_max, plot_rect.top(), plot_rect.height())
def _draw_grid(self, painter: QPainter, plot_rect: QRectF) -> None:
pen = QPen(self._grid_color)
@@ -107,7 +107,7 @@ class WaferMapItem(QQuickPaintedItem):
def sensors(self) -> list:
return [{"label": s.label, "x": s.x, "y": s.y} for s in self._sensors]
@sensors.setter
@sensors.setter # type: ignore[no-redef]
def sensors(self, val: list) -> None:
self._sensors = [
Sensor(
@@ -127,7 +127,7 @@ class WaferMapItem(QQuickPaintedItem):
def values(self) -> list:
return self._values
@values.setter
@values.setter # type: ignore[no-redef]
def values(self, val: list) -> None:
self._values = list(val or [])
self._rebuild_heatmap()
@@ -138,7 +138,7 @@ class WaferMapItem(QQuickPaintedItem):
def bands(self) -> list:
return self._bands
@bands.setter
@bands.setter # type: ignore[no-redef]
def bands(self, val: list) -> None:
self._bands = list(val or [])
self.bandsChanged.emit()
@@ -148,7 +148,7 @@ class WaferMapItem(QQuickPaintedItem):
def target(self) -> float:
return self._target
@target.setter
@target.setter # type: ignore[no-redef]
def target(self, val: float) -> None:
self._target = float(val)
self._rebuild_heatmap()
@@ -159,7 +159,7 @@ class WaferMapItem(QQuickPaintedItem):
def margin(self) -> float:
return self._margin
@margin.setter
@margin.setter # type: ignore[no-redef]
def margin(self, val: float) -> None:
self._margin = float(val)
self._rebuild_heatmap()
@@ -170,7 +170,7 @@ class WaferMapItem(QQuickPaintedItem):
def blend(self) -> float:
return self._blend
@blend.setter
@blend.setter # type: ignore[no-redef]
def blend(self, val: float) -> None:
self._blend = max(0.0, min(1.0, float(val)))
if self._blend > 0 and self._heatmap is None:
@@ -182,7 +182,7 @@ class WaferMapItem(QQuickPaintedItem):
def showLabels(self) -> bool:
return self._show_labels
@showLabels.setter
@showLabels.setter # type: ignore[no-redef]
def showLabels(self, val: bool) -> None:
self._show_labels = bool(val)
self.showLabelsChanged.emit()
@@ -192,7 +192,7 @@ class WaferMapItem(QQuickPaintedItem):
def shape(self) -> str:
return self._shape
@shape.setter
@shape.setter # type: ignore[no-redef]
def shape(self, val: str) -> None:
self._shape = str(val).lower()
self._rebuild()
@@ -202,7 +202,7 @@ class WaferMapItem(QQuickPaintedItem):
def size(self) -> float:
return self._size
@size.setter
@size.setter # type: ignore[no-redef]
def size(self, val: float) -> None:
self._size = float(val)
self._rebuild()
@@ -212,7 +212,7 @@ class WaferMapItem(QQuickPaintedItem):
def thicknessData(self) -> list:
return self._thickness_data
@thicknessData.setter
@thicknessData.setter # type: ignore[no-redef]
def thicknessData(self, val:list) -> None:
self._thickness_data = list(val or [])
self._rebuild_thickness()
@@ -223,7 +223,7 @@ class WaferMapItem(QQuickPaintedItem):
def showThickness(self) -> bool:
return self._show_thickness
@showThickness.setter
@showThickness.setter # type: ignore[no-redef]
def showThickness(self, val: bool) -> None:
self._show_thickness = bool(val)
self.showThicknessChanged.emit()
@@ -233,7 +233,7 @@ class WaferMapItem(QQuickPaintedItem):
def hoveredIndex(self) -> int:
return self._hovered_index
@hoveredIndex.setter
@hoveredIndex.setter # type: ignore[no-redef]
def hoveredIndex(self, val: int) -> None:
val = int(val)
if val == self._hovered_index:
@@ -246,7 +246,7 @@ class WaferMapItem(QQuickPaintedItem):
def hoverScale(self) -> float:
return self._hover_scale
@hoverScale.setter
@hoverScale.setter # type: ignore[no-redef]
def hoverScale(self, val: float) -> None:
self._hover_scale = float(val)
self.hoverScaleChanged.emit()
@@ -255,32 +255,32 @@ class WaferMapItem(QQuickPaintedItem):
# Colour properties — QML can bind these to Theme tokens
@Property(QColor, notify=colorsChanged)
def ringColor(self) -> QColor: return self._ring_color
@ringColor.setter
@ringColor.setter # type: ignore[no-redef]
def ringColor(self, c: QColor) -> None: self._ring_color = c; self.update()
@Property(QColor, notify=colorsChanged)
def axisColor(self) -> QColor: return self._axis_color
@axisColor.setter
@axisColor.setter # type: ignore[no-redef]
def axisColor(self, c: QColor) -> None: self._axis_color = c; self.update()
@Property(QColor, notify=colorsChanged)
def lowColor(self) -> QColor: return self._low_color
@lowColor.setter
@lowColor.setter # type: ignore[no-redef]
def lowColor(self, c: QColor) -> None: self._low_color = c; self.update()
@Property(QColor, notify=colorsChanged)
def inRangeColor(self) -> QColor: return self._in_range_color
@inRangeColor.setter
@inRangeColor.setter # type: ignore[no-redef]
def inRangeColor(self, c: QColor) -> None: self._in_range_color = c; self.update()
@Property(QColor, notify=colorsChanged)
def highColor(self) -> QColor: return self._high_color
@highColor.setter
@highColor.setter # type: ignore[no-redef]
def highColor(self, c: QColor) -> None: self._high_color = c; self.update()
@Property(QColor, notify=colorsChanged)
def textColor(self) -> QColor: return self._text_color
@textColor.setter
@textColor.setter # type: ignore[no-redef]
def textColor(self, c: QColor) -> None: self._text_color = c; self.update()
# ── slots ─────────────────────────────────────────────────────────────
@@ -314,7 +314,7 @@ class WaferMapItem(QQuickPaintedItem):
return False
try:
result = self.grabToImage()
img = result.image()
img = result.image() # type: ignore[attr-defined]
img.save(file_path, "PNG")
return True
except Exception as e:
@@ -702,11 +702,11 @@ class WaferMapItem(QQuickPaintedItem):
painter.setPen(QPen(self._text_color))
y1 = ly + id_ascent
if side in ("top", "bottom"):
painter.drawText(lx + (text_w - id_w) // 2, y1, id_text)
painter.drawText(lx + (text_w - id_w) // 2, y1, id_text) # type: ignore[arg-type]
elif side == "left":
painter.drawText(lx + (text_w - id_w), y1, id_text)
painter.drawText(lx + (text_w - id_w), y1, id_text) # type: ignore[arg-type]
else:
painter.drawText(lx, y1, id_text)
painter.drawText(lx, y1, id_text) # type: ignore[arg-type]
# Draw Temperature (second line)
if has_temp:
@@ -714,8 +714,8 @@ class WaferMapItem(QQuickPaintedItem):
painter.setPen(QPen(color))
y2 = ly + id_line_h + temp_ascent
if side in ("top", "bottom"):
painter.drawText(lx + (text_w - temp_w) // 2, y2, temp_text)
painter.drawText(lx + (text_w - temp_w) // 2, y2, temp_text) # type: ignore[arg-type]
elif side == "left":
painter.drawText(lx + (text_w - temp_w), y2, temp_text)
painter.drawText(lx + (text_w - temp_w), y2, temp_text) # type: ignore[arg-type]
else:
painter.drawText(lx, y2, temp_text)
painter.drawText(lx, y2, temp_text) # type: ignore[arg-type]