0
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2025-07-14 03:31: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 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. 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: Note:
We create data_plot_x and data_plot_y and modify them within this function 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. to avoid creating new arrays. This is important for performance.
We adjust the variables based on instruction type. Support update instructions are 'add', 'add_slice', and 'replace'.
- 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.
Args: Args:
msg(dict): Message with the async data. msg(dict): Message with the async data.
@ -1196,14 +1196,31 @@ class Waveform(PlotBase):
# Replace is trivial, no need to modify data_plot_y # Replace is trivial, no need to modify data_plot_y
# Get the x data if 'timestamp' is selected, otherwise compute it # Get x data for plotting
data_plot_x = async_data["timestamp"] if plot_mode in ["index", "auto", "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:
data_plot_x = np.linspace(0, len(data_plot_y) - 1, len(data_plot_y)) 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)) self._auto_adjust_async_curve_settings(curve, len(data_plot_y))
curve.setData(data_plot_x, 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 for the scan to finish and the data to be available in history
# Wait until scan_id is 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: if len(client.history) == 0:
return False return False
# Once items appear in storage, the last one hast to be the one we just scanned # 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 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] last_scan_data = client.history[-1]
# check plotted data # check plotted data
x_data, y_data = curve.get_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) wf = create_widget(qtbot, Waveform, client=mocked_client)
c = wf.plot(arg1="async_device", label="async_device-async_device") c = wf.plot(arg1="async_device", label="async_device-async_device")
wf._async_curves = [c] 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() dummy_scan = create_dummy_scan_item()
wf.scan_item = dummy_scan wf.scan_item = dummy_scan
@ -366,7 +366,7 @@ def test_update_async_curves(monkeypatch, qtbot, mocked_client):
monkeypatch.setattr(c, "setData", fake_setData) monkeypatch.setattr(c, "setData", fake_setData)
wf.update_async_curves() 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]) 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 assert "async_device" in endpoint_called
@pytest.mark.parametrize("x_mode", ("timestamp", "index")) def test_on_async_readback_add_update(qtbot, mocked_client):
def test_on_async_readback_add_update(qtbot, mocked_client, x_mode):
""" """
Test that on_async_readback extends or replaces async data depending on metadata instruction. Test that on_async_readback extends or replaces async data depending on metadata instruction.
For 'timestamp' mode, new timestamps are appended to x_data. 'Index' mode
For 'index' mode, x_data simply increases by integer index.
""" """
wf = create_widget(qtbot, Waveform, client=mocked_client) wf = create_widget(qtbot, Waveform, client=mocked_client)
wf.scan_item = create_dummy_scan_item() 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]) c.setData([0, 1, 2], [10, 11, 12])
# Set the x_axis_mode # Set the x_axis_mode
wf.x_axis_mode["name"] = x_mode wf.x_axis_mode["name"] = "index"
############# Test add ################ ############# 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() x_data, y_data = c.get_data()
assert len(x_data) == 5 assert len(x_data) == 5
# Check x_data based on x_mode # Check x_data based on x_mode
if x_mode == "timestamp": np.testing.assert_array_equal(x_data, [0, 1, 2, 3, 4])
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(y_data, [10, 11, 12, 100, 200]) 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"}} metadata2 = {"async_update": {"max_shape": [None], "type": "replace"}}
wf.on_async_readback(msg2, metadata2) wf.on_async_readback(msg2, metadata2)
x_data2, y_data2 = c.get_data() x_data2, y_data2 = c.get_data()
if x_mode == "timestamp": np.testing.assert_array_equal(x_data2, [0])
np.testing.assert_array_equal(x_data2, [555])
else:
np.testing.assert_array_equal(x_data2, [0])
np.testing.assert_array_equal(y_data2, [999]) np.testing.assert_array_equal(y_data2, [999])