added all the other shit

This commit is contained in:
Thomas Ales [M S E]
2026-06-14 18:54:03 -05:00
parent 002e9ace13
commit a465a3dd72
2 changed files with 142 additions and 25 deletions
-13
View File
@@ -118,19 +118,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item> <item>
<widget class="QPushButton" name="abort_btn"> <widget class="QPushButton" name="abort_btn">
<property name="font"> <property name="font">
+142 -12
View File
@@ -15,13 +15,17 @@ from pathlib import Path
import threading import threading
from threading import Thread from threading import Thread
import numpy as np
import serial import serial
from PyQt6 import uic from PyQt6 import uic
from PyQt6.QtCore import QObject, QThread, QTimer, Qt, pyqtSignal, pyqtSlot from PyQt6.QtCore import QObject, QThread, QTimer, Qt, pyqtSignal, pyqtSlot
from PyQt6.QtGui import QImage, QPixmap from PyQt6.QtGui import QImage, QPixmap, QPainter, QColor, QPen
from PyQt6.QtWidgets import ( from PyQt6.QtWidgets import (
QApplication, QFileDialog, QLabel, QMainWindow, QMessageBox, QWidget, QApplication, QFileDialog, QLabel, QMainWindow, QMessageBox, QSizePolicy,
QWidget,
) )
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
ROOT = Path(__file__).parent ROOT = Path(__file__).parent
sys.path.insert(0, str(ROOT)) sys.path.insert(0, str(ROOT))
@@ -95,6 +99,54 @@ T1T2T3_JOG_STEPS = 500 # steps per manual jog press for T1/T2/T3
_T3R_AXIS_NAMES = {1: "T1", 2: "T2", 3: "T3", 4: "GR"} _T3R_AXIS_NAMES = {1: "T1", 2: "T2", 3: "T3", 4: "GR"}
BBD_DEFAULT_JOG_MM = 0.5 # default jog step for BBD202 BBD_DEFAULT_JOG_MM = 0.5 # default jog step for BBD202
class DCBiasImageWidget(FigureCanvas):
"""Live 2D DC-bias image: rows × frames, matching sras_viewer's compute_dc_image view."""
def __init__(self, parent=None):
fig = Figure(tight_layout=True)
super().__init__(fig)
self.setParent(parent)
self.setMinimumSize(300, 150)
self.setSizePolicy(
QSizePolicy.Policy.Expanding,
QSizePolicy.Policy.Expanding,
)
self.ax = fig.add_subplot(111)
self._row_data: list[np.ndarray] = []
self._im = None
self.ax.set_title("DC Bias Preview (ADC counts)")
self.ax.set_xlabel("Frame")
self.ax.set_ylabel("Row")
self.ax.text(0.5, 0.5, "Waiting for data…",
transform=self.ax.transAxes, ha="center", va="center", color="gray")
self.draw()
def add_row(self, row_index: int, values):
"""Store per-frame DC mean values for row_index (1-based) and refresh the image."""
idx = row_index - 1
while len(self._row_data) <= idx:
self._row_data.append(np.empty(0, dtype=np.float32))
self._row_data[idx] = np.asarray(values, dtype=np.float32)
# Build rectangular image — pad shorter rows with NaN so they render distinctly
max_len = max(len(r) for r in self._row_data)
img = np.full((len(self._row_data), max_len), np.nan, dtype=np.float32)
for i, row in enumerate(self._row_data):
img[i, :len(row)] = row
self.ax.clear()
self.ax.set_title("DC Bias Preview (ADC counts)")
self.ax.set_xlabel("Frame")
self.ax.set_ylabel("Row")
self.ax.imshow(
img, aspect="auto", origin="upper",
cmap="viridis", interpolation="nearest",
)
self.draw_idle()
BBD_JOG_SPEED_MM_S = 10.0 BBD_JOG_SPEED_MM_S = 10.0
BBD_JOG_ACCEL_MM_S2 = 50.0 BBD_JOG_ACCEL_MM_S2 = 50.0
@@ -108,6 +160,17 @@ SCAN_CHANNELS = [1, 3, 4] # oscilloscope channels recorded, in order
BLOB_HDR_FMT = ">4sBHHffffIIdBB" BLOB_HDR_FMT = ">4sBHHffffIIdBB"
def _format_eta(secs: float) -> str:
secs = max(0.0, secs)
m, s = divmod(int(secs), 60)
h, m = divmod(m, 60)
if h > 0:
return f"{h}h {m:02d}m"
if m > 0:
return f"{m}m {s:02d}s"
return f"{s}s"
def _open_scan_file(path: Path, n_angles: int, n_rows: int, def _open_scan_file(path: Path, n_angles: int, n_rows: int,
x_start: float, x_delta: float, x_start: float, x_delta: float,
velocity: float, laser_freq: float, velocity: float, laser_freq: float,
@@ -994,15 +1057,63 @@ class ScanProgressWindow(QWidget):
uic.loadUi(ROOT / "sc3-aui-scanprogress.ui", self) uic.loadUi(ROOT / "sc3-aui-scanprogress.ui", self)
self.setWindowTitle("Scan Progress") self.setWindowTitle("Scan Progress")
self.abort_btn.clicked.connect(self.abort_requested.emit) self.abort_btn.clicked.connect(self.abort_requested.emit)
self.bias_image_widget = DCBiasImageWidget()
insert_pos = self.verticalLayout.count() - 1
self.verticalLayout.insertWidget(insert_pos, QLabel("DC Bias Preview"))
self.verticalLayout.insertWidget(insert_pos + 1, self.bias_image_widget)
self.verticalLayout.setStretch(insert_pos + 1, 1)
self._row_start_time: float | None = None
self._row_durations: list[float] = []
self._last_angle_idx: int = -1
def on_row_started(self, row: int, n_rows: int, angle_idx: int, n_angles: int):
self._row_start_time = time.monotonic()
def update_progress(self, row: int, n_rows: int, angle_idx: int, n_angles: int): def update_progress(self, row: int, n_rows: int, angle_idx: int, n_angles: int):
n_rows = max(1, n_rows)
n_angles = max(1, n_angles)
self.current_scan_current_row_indicator.setText(f"Row {row} of {n_rows}") self.current_scan_current_row_indicator.setText(f"Row {row} of {n_rows}")
pct_row = round(row / n_rows * 100) if n_rows > 0 else 0
self.current_scan_progbar.setValue(pct_row)
self.overall_scan_current_angle_indicator.setText(f"Angle {angle_idx} of {n_angles}") self.overall_scan_current_angle_indicator.setText(f"Angle {angle_idx} of {n_angles}")
pct_ang = round(angle_idx / n_angles * 100) if n_angles > 0 else 0
if row == 0:
self._row_durations = []
self._row_start_time = None
self._last_angle_idx = -1
self.current_scan_progbar.setMaximum(n_rows)
self.current_scan_progbar.setValue(0)
self.current_scan_progbar.setFormat("Row %v/%m")
else:
# Reset duration history when the angle changes
if angle_idx != self._last_angle_idx and self._last_angle_idx != -1:
self._row_durations = []
self._last_angle_idx = angle_idx
if self._row_start_time is not None:
self._row_durations.append(time.monotonic() - self._row_start_time)
self._row_start_time = None
self.current_scan_progbar.setMaximum(n_rows)
self.current_scan_progbar.setValue(row)
rows_left = n_rows - row
if self._row_durations and rows_left > 0:
recent = self._row_durations[-5:]
avg = sum(recent) / len(recent)
eta_str = _format_eta(avg * rows_left)
self.current_scan_progbar.setFormat(f"Row %v/%m — ETA: {eta_str}")
elif rows_left == 0:
self.current_scan_progbar.setFormat("Row %v/%m — Done")
else:
self.current_scan_progbar.setFormat("Row %v/%m")
pct_ang = round(angle_idx / n_angles * 100)
self.overall_scan_progbar.setValue(pct_ang) self.overall_scan_progbar.setValue(pct_ang)
def update_dc_bias_plot(self, row: int, values):
self.bias_image_widget.add_row(row, values)
# ── Scan worker ─────────────────────────────────────────────────────────────── # ── Scan worker ───────────────────────────────────────────────────────────────
@@ -1013,12 +1124,14 @@ class ScanWorker(QObject):
make blocking calls. BBD202Worker.scanning_active is set to True for the make blocking calls. BBD202Worker.scanning_active is set to True for the
duration to suppress position polling in the BBD worker thread. duration to suppress position polling in the BBD worker thread.
""" """
started = pyqtSignal() started = pyqtSignal()
completed = pyqtSignal() completed = pyqtSignal()
failed = pyqtSignal(str) dc_bias_updated = pyqtSignal(int, object) # row index, list[float] per-frame DC means
row_done = pyqtSignal(int, int, int, int) # row, n_rows, angle_idx, n_angles failed = pyqtSignal(str)
status_msg = pyqtSignal(str) row_started = pyqtSignal(int, int, int, int) # row, n_rows, angle_idx, n_angles
user_prompt = pyqtSignal(str, str) # title, message — ask user to acknowledge row_done = pyqtSignal(int, int, int, int) # row, n_rows, angle_idx, n_angles
status_msg = pyqtSignal(str)
user_prompt = pyqtSignal(str, str) # title, message
def __init__(self, bbd: BBD202Worker, t3r: T3RWorker, def __init__(self, bbd: BBD202Worker, t3r: T3RWorker,
oscope: OscopeWorker, params: dict): oscope: OscopeWorker, params: dict):
@@ -1268,6 +1381,7 @@ class ScanWorker(QObject):
if self._abort: if self._abort:
break break
self.row_started.emit(ri + 1, n_rows, ai + 1, n_angles)
self.status_msg.emit( self.status_msg.emit(
f"Angle {ai+1}/{n_angles} Row {ri+1}/{n_rows} " f"Angle {ai+1}/{n_angles} Row {ri+1}/{n_rows} "
f"(Y={y_pos:.3f} mm)" f"(Y={y_pos:.3f} mm)"
@@ -1302,6 +1416,20 @@ class ScanWorker(QObject):
n_frames = int(scope.query("ACQuire:NUMFRAMESACQuired?")) n_frames = int(scope.query("ACQuire:NUMFRAMESACQuired?"))
for _ in range(n_frames): for _ in range(n_frames):
scan_file.write(zero_frame) scan_file.write(zero_frame)
elif ch == 4:
self.status_msg.emit(f"Fetching CH{ch} data …")
scope.set_data_source(ch)
waveforms: list[bytes] = scope.transfer_fastframe(parse=False)
if waveforms:
frame_avgs = []
for w in waveforms:
n = len(w)
frame_avgs.append(
sum(struct.unpack(f"{n}b", w)) / n if n > 0 else 0.0
)
self.dc_bias_updated.emit(ri + 1, frame_avgs)
for w in waveforms:
scan_file.write(w)
else: else:
self.status_msg.emit(f"Fetching CH{ch} data …") self.status_msg.emit(f"Fetching CH{ch} data …")
scope.set_data_source(ch) scope.set_data_source(ch)
@@ -1729,7 +1857,9 @@ class MainWindow(QMainWindow):
) )
self._scan_worker.moveToThread(self._scan_thread) self._scan_worker.moveToThread(self._scan_thread)
self._scan_thread.started.connect(self._scan_worker.run) self._scan_thread.started.connect(self._scan_worker.run)
self._scan_worker.row_started.connect(self._scan_progress.on_row_started)
self._scan_worker.row_done.connect(self._on_row_done) self._scan_worker.row_done.connect(self._on_row_done)
self._scan_worker.dc_bias_updated.connect(self._scan_progress.update_dc_bias_plot)
self._scan_worker.completed.connect(self._on_scan_complete) self._scan_worker.completed.connect(self._on_scan_complete)
self._scan_worker.failed.connect(self._on_scan_failed) self._scan_worker.failed.connect(self._on_scan_failed)
self._scan_worker.status_msg.connect(lambda m: print(f"[SCAN] {m}")) self._scan_worker.status_msg.connect(lambda m: print(f"[SCAN] {m}"))
@@ -1757,7 +1887,7 @@ class MainWindow(QMainWindow):
x_delta = _f(self.x_delta_edit, "XD") x_delta = _f(self.x_delta_edit, "XD")
y_delta = _f(self.y_delta_edit, "YD") y_delta = _f(self.y_delta_edit, "YD")
num_angles = _i(self.num_angles_edit, "NumAngles") num_angles = _i(self.num_angles_edit, "NumAngles")
row_spacing = _f(self.row_spacing_edit,"RowSpacing") row_spacing = _f(self.row_spacing_edit, "RowSpacing")
prefix = self.scan_prefix_edit.text().strip() or "scan" prefix = self.scan_prefix_edit.text().strip() or "scan"
save_dir = self.scan_save_dir_edit.text().strip() or DEFAULT_SAVE_DIR save_dir = self.scan_save_dir_edit.text().strip() or DEFAULT_SAVE_DIR