feat(csaxs): csaxs.aerotech.plot() title shows the plotted scan number
The history plot uses the given scan number; the live plot the last scan at start and then the number of each followed aerotech_cont_grid scan. The title is set in the redraw thread, only when the scan changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017NrH3poJsYBCTVq2Y9fuHZ
This commit is contained in:
1 parent
12528a8f63
commit
663a6cf518
2 files changed
+46
-6
No files matched your search
@@ -53,8 +53,9 @@ class cSAXSAerotech:
|
||||
waveform, curves = self._setup_plot()
|
||||
if scan_number is not None:
|
||||
_draw(curves, self._history_lines(scan_number))
|
||||
waveform.title = _title(scan_number)
|
||||
return waveform
|
||||
self._live = _LivePositions(self, curves)
|
||||
self._live = _LivePositions(self, waveform, curves)
|
||||
self._live.start()
|
||||
return waveform
|
||||
|
||||
@@ -74,7 +75,7 @@ class cSAXSAerotech:
|
||||
if waveform is None:
|
||||
waveform = window.new("Waveform", object_name=self.PLOT, timeout=self.GUI_RPC_TIMEOUT)
|
||||
waveform.clear_all()
|
||||
waveform.title = "aerotech_cont_grid: exposure centres"
|
||||
waveform.title = _title(None)
|
||||
waveform.x_label = "X centre (aerox)"
|
||||
waveform.y_label = "Y centre (aeroy)"
|
||||
waveform.lock_aspect_ratio = True
|
||||
@@ -96,6 +97,13 @@ class cSAXSAerotech:
|
||||
return _lines_from_file(scan._msg.file_path)
|
||||
|
||||
|
||||
def _title(scan_number: int | None) -> str:
|
||||
"""Plot title with the number of the plotted scan."""
|
||||
if scan_number is None:
|
||||
return "aerotech_cont_grid: exposure centres"
|
||||
return f"aerotech_cont_grid scan {scan_number}: exposure centres"
|
||||
|
||||
|
||||
def _lines_from_file(file_path: str) -> list[tuple[np.ndarray, np.ndarray]]:
|
||||
"""Exposure centres per line from an aerotech_cont_grid data file."""
|
||||
with h5py.File(file_path, "r") as f:
|
||||
@@ -150,12 +158,15 @@ def _markers_only(x: np.ndarray, y: np.ndarray) -> tuple[np.ndarray, np.ndarray]
|
||||
class _LivePositions:
|
||||
"""Follows aerotech_cont_grid scans and redraws the exposure centres per line."""
|
||||
|
||||
def __init__(self, parent: cSAXSAerotech, curves: dict):
|
||||
def __init__(self, parent: cSAXSAerotech, waveform, curves: dict):
|
||||
self.parent = parent
|
||||
self.connector = parent.client.connector
|
||||
self.waveform = waveform
|
||||
self.curves = curves
|
||||
self._lock = threading.Lock()
|
||||
self._lines: list[tuple[np.ndarray, np.ndarray]] = []
|
||||
self._scan_number: int | None = None
|
||||
self._shown_scan_number: int | None = None
|
||||
self._scan_id = None
|
||||
self._stream_topic = None
|
||||
self._dirty = threading.Event()
|
||||
@@ -177,7 +188,8 @@ class _LivePositions:
|
||||
|
||||
def _show_last_scan(self) -> None:
|
||||
try:
|
||||
self._set_lines(_lines_from_file(self.parent.client.history[-1]._msg.file_path))
|
||||
msg = self.parent.client.history[-1]._msg
|
||||
self._set_lines(_lines_from_file(msg.file_path), scan_number=msg.scan_number)
|
||||
except Exception: # no scan yet, or the last one is not an aerotech_cont_grid
|
||||
pass
|
||||
|
||||
@@ -191,7 +203,7 @@ class _LivePositions:
|
||||
return
|
||||
self._unfollow()
|
||||
self._scan_id = status.scan_id
|
||||
self._set_lines([])
|
||||
self._set_lines([], scan_number=status.scan_number)
|
||||
self._stream_topic = MessageEndpoints.device_async_signal(
|
||||
scan_id=status.scan_id,
|
||||
device=self.parent.CAPTURE_DEVICE,
|
||||
@@ -216,9 +228,10 @@ class _LivePositions:
|
||||
self._lines.append((x, y))
|
||||
self._dirty.set()
|
||||
|
||||
def _set_lines(self, lines) -> None:
|
||||
def _set_lines(self, lines, scan_number: int | None = None) -> None:
|
||||
with self._lock:
|
||||
self._lines = list(lines)
|
||||
self._scan_number = scan_number
|
||||
self._dirty.set()
|
||||
|
||||
def _redraw_loop(self) -> None:
|
||||
@@ -229,8 +242,12 @@ class _LivePositions:
|
||||
self._dirty.clear()
|
||||
with self._lock:
|
||||
lines = list(self._lines)
|
||||
scan_number = self._scan_number
|
||||
try:
|
||||
_draw(self.curves, lines)
|
||||
if scan_number != self._shown_scan_number:
|
||||
self.waveform.title = _title(scan_number)
|
||||
self._shown_scan_number = scan_number
|
||||
except Exception as exc: # window or plot closed, GUI restarted
|
||||
# stop following instead of failing on every line of every scan
|
||||
logger.warning(
|
||||
|
||||
@@ -76,6 +76,7 @@ def test_plot_history_draws_forward_and_backward_curves():
|
||||
"Waveform", object_name="aerotech_positions", timeout=20
|
||||
)
|
||||
assert waveform.lock_aspect_ratio is True
|
||||
assert waveform.title == "aerotech_cont_grid scan 8494: exposure centres"
|
||||
for curve in (forward, backward):
|
||||
x, y = curve.set_data.call_args.args
|
||||
# markers only: a NaN after every point, X finite and increasing
|
||||
@@ -132,9 +133,31 @@ def test_live_follows_an_aerotech_scan_line_by_line():
|
||||
|
||||
x, _ = backward.set_data.call_args.args
|
||||
assert len(x) == 2 * 5
|
||||
assert waveform.title == "aerotech_cont_grid scan 1: exposure centres"
|
||||
client.connector.unregister.assert_called()
|
||||
|
||||
|
||||
def test_live_title_shows_the_last_scan():
|
||||
client = _client()
|
||||
waveform = _waveform(client)
|
||||
forward, backward = mock.MagicMock(), mock.MagicMock()
|
||||
waveform.plot.side_effect = [forward, backward]
|
||||
client.history.__getitem__.return_value._msg = SimpleNamespace(
|
||||
file_path="scan.h5", scan_number=4897
|
||||
)
|
||||
tool = cSAXSAerotech(client)
|
||||
with mock.patch(
|
||||
"csaxs_bec.bec_ipython_client.plugins.cSAXS.aerotech._lines_from_file",
|
||||
return_value=_snake(),
|
||||
):
|
||||
tool.plot()
|
||||
deadline = time.time() + 3
|
||||
while time.time() < deadline and "4897" not in str(waveform.title):
|
||||
time.sleep(0.05)
|
||||
tool.stop_plot()
|
||||
assert waveform.title == "aerotech_cont_grid scan 4897: exposure centres"
|
||||
|
||||
|
||||
def test_live_ignores_other_scans():
|
||||
client = _client()
|
||||
client.history.__getitem__.side_effect = IndexError
|
||||
|
||||
Reference in new issue
Block a user