0
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2025-07-13 19:21:50 +02:00

refactor: add support to plot against x_data

This commit is contained in:
2025-04-08 11:07:19 +02:00
committed by wyzula_j
parent ed2d958de6
commit 0e276d4c09
3 changed files with 37 additions and 29 deletions

View File

@ -1144,13 +1144,13 @@ class Waveform(PlotBase):
to reduce the number of copies in between cycles. Be careful when refactoring
this part as it will affect the performance of the async readback.
Async curves support plotting against 'index' or other 'device_signal'. No 'auto' or 'timestamp'.
The fallback mechanism for 'auto' and 'timestamp' is to use the 'index'.
Note:
We create data_plot_x and data_plot_y and modify them within this function
to avoid creating new arrays. This is important for performance.
We adjust the variables based on instruction type.
- add: Add the new data to the existing data.
- add_slice: Add the new data to the existing data and set the slice index.
- replace: Replace the existing data with the new data.
Support update instructions are 'add', 'add_slice', and 'replace'.
Args:
msg(dict): Message with the async data.
@ -1196,14 +1196,31 @@ class Waveform(PlotBase):
# Replace is trivial, no need to modify data_plot_y
# Get the x data if 'timestamp' is selected, otherwise compute it
data_plot_x = async_data["timestamp"]
if plot_mode == "timestamp" and instruction != "add_slice":
if data_plot_x is not None and x_data is not None:
data_plot_x = np.hstack((x_data, data_plot_x))
else:
# Get x data for plotting
if plot_mode in ["index", "auto", "timestamp"]:
data_plot_x = np.linspace(0, len(data_plot_y) - 1, len(data_plot_y))
# Set the x data
self._auto_adjust_async_curve_settings(curve, len(data_plot_y))
curve.setData(data_plot_x, data_plot_y)
# Move on in the loop
continue
# Only consider device signals that are async for now, fallback is index
x_device_entry = self.x_axis_mode["entry"]
async_data = msg["signals"].get(x_device_entry, None)
# Make sure the signal exists, otherwise fall back to index
if async_data is None:
# Try to grab the data from device signals
data_plot_x = self._get_x_data(plot_mode, x_device_entry)
if len(data_plot_x) != len(data_plot_y):
# If the data is not the same length, fall back to index
logger.warning(
f"Async data for curve {curve.name()} is None. Falling back to index."
)
data_plot_x = np.linspace(0, len(data_plot_y) - 1, len(data_plot_y))
elif x_data is not None:
data_plot_x = np.hstack((x_data, async_data["value"]))
else:
data_plot_x = async_data["value"]
# Plot the data
self._auto_adjust_async_curve_settings(curve, len(data_plot_y))
curve.setData(data_plot_x, data_plot_y)

View File

@ -135,13 +135,13 @@ def test_async_plotting(qtbot, bec_client_lib, connected_client_gui_obj):
# Wait for the scan to finish and the data to be available in history
# Wait until scan_id is in history
def _wait_for_scan_in_hisotry():
def _wait_for_scan_in_history():
if len(client.history) == 0:
return False
# Once items appear in storage, the last one hast to be the one we just scanned
return client.history[-1].metadata.bec["scan_id"] == status.scan.scan_id
qtbot.waitUntil(_wait_for_scan_in_hisotry, timeout=10000)
qtbot.waitUntil(_wait_for_scan_in_history, timeout=10000)
last_scan_data = client.history[-1]
# check plotted data
x_data, y_data = curve.get_data()

View File

@ -353,7 +353,7 @@ def test_update_async_curves(monkeypatch, qtbot, mocked_client):
wf = create_widget(qtbot, Waveform, client=mocked_client)
c = wf.plot(arg1="async_device", label="async_device-async_device")
wf._async_curves = [c]
wf.x_mode = "timestamp"
wf.x_mode = "timestamp" # Timestamp is not supported, fallback to index.
dummy_scan = create_dummy_scan_item()
wf.scan_item = dummy_scan
@ -366,7 +366,7 @@ def test_update_async_curves(monkeypatch, qtbot, mocked_client):
monkeypatch.setattr(c, "setData", fake_setData)
wf.update_async_curves()
np.testing.assert_array_equal(recorded.get("x"), [11, 21, 31])
np.testing.assert_array_equal(recorded.get("x"), [0, 1, 2])
np.testing.assert_array_equal(recorded.get("y"), [1, 2, 3])
@ -528,12 +528,10 @@ def test_setup_async_curve(qtbot, mocked_client, monkeypatch):
assert "async_device" in endpoint_called
@pytest.mark.parametrize("x_mode", ("timestamp", "index"))
def test_on_async_readback_add_update(qtbot, mocked_client, x_mode):
def test_on_async_readback_add_update(qtbot, mocked_client):
"""
Test that on_async_readback extends or replaces async data depending on metadata instruction.
For 'timestamp' mode, new timestamps are appended to x_data.
For 'index' mode, x_data simply increases by integer index.
'Index' mode
"""
wf = create_widget(qtbot, Waveform, client=mocked_client)
wf.scan_item = create_dummy_scan_item()
@ -543,7 +541,7 @@ def test_on_async_readback_add_update(qtbot, mocked_client, x_mode):
c.setData([0, 1, 2], [10, 11, 12])
# Set the x_axis_mode
wf.x_axis_mode["name"] = x_mode
wf.x_axis_mode["name"] = "index"
############# Test add ################
@ -554,10 +552,7 @@ def test_on_async_readback_add_update(qtbot, mocked_client, x_mode):
x_data, y_data = c.get_data()
assert len(x_data) == 5
# Check x_data based on x_mode
if x_mode == "timestamp":
np.testing.assert_array_equal(x_data, [0, 1, 2, 1001, 1002])
else: # x_mode == "index"
np.testing.assert_array_equal(x_data, [0, 1, 2, 3, 4])
np.testing.assert_array_equal(x_data, [0, 1, 2, 3, 4])
np.testing.assert_array_equal(y_data, [10, 11, 12, 100, 200])
@ -566,11 +561,7 @@ def test_on_async_readback_add_update(qtbot, mocked_client, x_mode):
metadata2 = {"async_update": {"max_shape": [None], "type": "replace"}}
wf.on_async_readback(msg2, metadata2)
x_data2, y_data2 = c.get_data()
if x_mode == "timestamp":
np.testing.assert_array_equal(x_data2, [555])
else:
np.testing.assert_array_equal(x_data2, [0])
np.testing.assert_array_equal(x_data2, [0])
np.testing.assert_array_equal(y_data2, [999])