mirror of
https://github.com/bec-project/bec_widgets.git
synced 2026-03-30 05:18:36 +02:00
fix(image): disconnecting of 2d monitor
This commit is contained in:
@@ -270,6 +270,16 @@ class Image(ImageBase):
|
||||
return
|
||||
|
||||
old_device = self._config.device
|
||||
old_signal = self._config.signal
|
||||
old_config = self.subscriptions["main"]
|
||||
if old_device and old_signal and old_device != value:
|
||||
self._disconnect_monitor_subscription(
|
||||
device=old_device,
|
||||
signal=old_signal,
|
||||
source=old_config.source,
|
||||
async_update=self.async_update,
|
||||
async_signal_name=old_config.async_signal_name,
|
||||
)
|
||||
self._config.device = value
|
||||
|
||||
# If we have a signal, reconnect with the new device
|
||||
@@ -325,6 +335,16 @@ class Image(ImageBase):
|
||||
self._set_connection_status("disconnected")
|
||||
return
|
||||
|
||||
old_signal = self._config.signal
|
||||
old_config = self.subscriptions["main"]
|
||||
if self._config.device and old_signal and old_signal != value:
|
||||
self._disconnect_monitor_subscription(
|
||||
device=self._config.device,
|
||||
signal=old_signal,
|
||||
source=old_config.source,
|
||||
async_update=self.async_update,
|
||||
async_signal_name=old_config.async_signal_name,
|
||||
)
|
||||
self._config.signal = value
|
||||
|
||||
# If we have a device, try to connect
|
||||
@@ -447,6 +467,61 @@ class Image(ImageBase):
|
||||
)
|
||||
self._autorange_on_next_update = True
|
||||
|
||||
def _disconnect_monitor_subscription(
|
||||
self,
|
||||
*,
|
||||
device: str,
|
||||
signal: str,
|
||||
source: Literal["device_monitor_1d", "device_monitor_2d"] | None,
|
||||
async_update: bool,
|
||||
async_signal_name: str | None,
|
||||
) -> None:
|
||||
if not device or not signal:
|
||||
return
|
||||
|
||||
if async_update:
|
||||
async_signal_name = async_signal_name or signal
|
||||
ids_to_check = [self.scan_id, self.old_scan_id]
|
||||
|
||||
if source == "device_monitor_1d":
|
||||
for scan_id in ids_to_check:
|
||||
if scan_id is None:
|
||||
continue
|
||||
self.bec_dispatcher.disconnect_slot(
|
||||
self.on_image_update_1d,
|
||||
MessageEndpoints.device_async_signal(scan_id, device, async_signal_name),
|
||||
)
|
||||
logger.info(
|
||||
f"Disconnecting 1d update ScanID:{scan_id}, Device Name:{device},Device Entry:{async_signal_name}"
|
||||
)
|
||||
elif source == "device_monitor_2d":
|
||||
for scan_id in ids_to_check:
|
||||
if scan_id is None:
|
||||
continue
|
||||
self.bec_dispatcher.disconnect_slot(
|
||||
self.on_image_update_2d,
|
||||
MessageEndpoints.device_async_signal(scan_id, device, async_signal_name),
|
||||
)
|
||||
logger.info(
|
||||
f"Disconnecting 2d update ScanID:{scan_id}, Device Name:{device},Device Entry:{async_signal_name}"
|
||||
)
|
||||
return
|
||||
|
||||
if source == "device_monitor_1d":
|
||||
self.bec_dispatcher.disconnect_slot(
|
||||
self.on_image_update_1d, MessageEndpoints.device_preview(device, signal)
|
||||
)
|
||||
logger.info(
|
||||
f"Disconnecting preview 1d update Device Name:{device}, Device Entry:{signal}"
|
||||
)
|
||||
elif source == "device_monitor_2d":
|
||||
self.bec_dispatcher.disconnect_slot(
|
||||
self.on_image_update_2d, MessageEndpoints.device_preview(device, signal)
|
||||
)
|
||||
logger.info(
|
||||
f"Disconnecting preview 2d update Device Name:{device}, Device Entry:{signal}"
|
||||
)
|
||||
|
||||
def _disconnect_current_monitor(self):
|
||||
"""
|
||||
Internal method to disconnect the current monitor subscriptions.
|
||||
@@ -455,55 +530,13 @@ class Image(ImageBase):
|
||||
return
|
||||
|
||||
config = self.subscriptions["main"]
|
||||
|
||||
if self.async_update:
|
||||
async_signal_name = config.async_signal_name or self._config.signal
|
||||
ids_to_check = [self.scan_id, self.old_scan_id]
|
||||
|
||||
if config.source == "device_monitor_1d":
|
||||
for scan_id in ids_to_check:
|
||||
if scan_id is None:
|
||||
continue
|
||||
self.bec_dispatcher.disconnect_slot(
|
||||
self.on_image_update_1d,
|
||||
MessageEndpoints.device_async_signal(
|
||||
scan_id, self._config.device, async_signal_name
|
||||
),
|
||||
)
|
||||
logger.info(
|
||||
f"Disconnecting 1d update ScanID:{scan_id}, Device Name:{self._config.device},Device Entry:{async_signal_name}"
|
||||
)
|
||||
elif config.source == "device_monitor_2d":
|
||||
for scan_id in ids_to_check:
|
||||
if scan_id is None:
|
||||
continue
|
||||
self.bec_dispatcher.disconnect_slot(
|
||||
self.on_image_update_2d,
|
||||
MessageEndpoints.device_async_signal(
|
||||
scan_id, self._config.device, async_signal_name
|
||||
),
|
||||
)
|
||||
logger.info(
|
||||
f"Disconnecting 2d update ScanID:{scan_id}, Device Name:{self._config.device},Device Entry:{async_signal_name}"
|
||||
)
|
||||
|
||||
else:
|
||||
if config.source == "device_monitor_1d":
|
||||
self.bec_dispatcher.disconnect_slot(
|
||||
self.on_image_update_1d,
|
||||
MessageEndpoints.device_preview(self._config.device, self._config.signal),
|
||||
)
|
||||
logger.info(
|
||||
f"Disconnecting preview 1d update Device Name:{self._config.device}, Device Entry:{self._config.signal}"
|
||||
)
|
||||
elif config.source == "device_monitor_2d":
|
||||
self.bec_dispatcher.disconnect_slot(
|
||||
self.on_image_update_2d,
|
||||
MessageEndpoints.device_preview(self._config.device, self._config.signal),
|
||||
)
|
||||
logger.info(
|
||||
f"Disconnecting preview 2d update Device Name:{self._config.device}, Device Entry:{self._config.signal}"
|
||||
)
|
||||
self._disconnect_monitor_subscription(
|
||||
device=self._config.device,
|
||||
signal=self._config.signal,
|
||||
source=config.source,
|
||||
async_update=self.async_update,
|
||||
async_signal_name=config.async_signal_name,
|
||||
)
|
||||
|
||||
# Reset async state
|
||||
self.async_update = False
|
||||
@@ -860,45 +893,19 @@ class Image(ImageBase):
|
||||
logger.warning("Cannot disconnect monitor without both device and signal")
|
||||
return
|
||||
|
||||
if self.async_update:
|
||||
async_signal_name = config.async_signal_name or target_entry
|
||||
ids_to_check = [self.scan_id, self.old_scan_id]
|
||||
if config.source == "device_monitor_1d":
|
||||
for scan_id in ids_to_check:
|
||||
if scan_id is None:
|
||||
continue
|
||||
self.bec_dispatcher.disconnect_slot(
|
||||
self.on_image_update_1d,
|
||||
MessageEndpoints.device_async_signal(
|
||||
scan_id, target_device, async_signal_name
|
||||
),
|
||||
)
|
||||
elif config.source == "device_monitor_2d":
|
||||
for scan_id in ids_to_check:
|
||||
if scan_id is None:
|
||||
continue
|
||||
self.bec_dispatcher.disconnect_slot(
|
||||
self.on_image_update_2d,
|
||||
MessageEndpoints.device_async_signal(
|
||||
scan_id, target_device, async_signal_name
|
||||
),
|
||||
)
|
||||
else:
|
||||
if config.source == "device_monitor_1d":
|
||||
self.bec_dispatcher.disconnect_slot(
|
||||
self.on_image_update_1d,
|
||||
MessageEndpoints.device_preview(target_device, target_entry),
|
||||
)
|
||||
elif config.source == "device_monitor_2d":
|
||||
self.bec_dispatcher.disconnect_slot(
|
||||
self.on_image_update_2d,
|
||||
MessageEndpoints.device_preview(target_device, target_entry),
|
||||
)
|
||||
else:
|
||||
logger.warning(
|
||||
f"Cannot disconnect monitor {target_device}.{target_entry} with source {self.subscriptions['main'].source}"
|
||||
)
|
||||
return
|
||||
if config.source not in {"device_monitor_1d", "device_monitor_2d"}:
|
||||
logger.warning(
|
||||
f"Cannot disconnect monitor {target_device}.{target_entry} with source {self.subscriptions['main'].source}"
|
||||
)
|
||||
return
|
||||
|
||||
self._disconnect_monitor_subscription(
|
||||
device=target_device,
|
||||
signal=target_entry,
|
||||
source=config.source,
|
||||
async_update=self.async_update,
|
||||
async_signal_name=config.async_signal_name,
|
||||
)
|
||||
|
||||
self.subscriptions["main"].async_signal_name = None
|
||||
self.async_update = False
|
||||
|
||||
@@ -197,6 +197,163 @@ def test_image_setup_preview_signal_2d(qtbot, mocked_client):
|
||||
np.testing.assert_array_equal(view.main_image.image, test_data)
|
||||
|
||||
|
||||
def test_switching_device_disconnects_previous_preview_endpoint(qtbot, mocked_client, monkeypatch):
|
||||
view = create_widget(qtbot, Image, client=mocked_client)
|
||||
_set_signal_config(mocked_client, "eiger", "img", signal_class="PreviewSignal", ndim=2)
|
||||
_set_signal_config(mocked_client, "waveform1d", "img", signal_class="PreviewSignal", ndim=2)
|
||||
|
||||
connected = []
|
||||
disconnected = []
|
||||
monkeypatch.setattr(
|
||||
view.bec_dispatcher,
|
||||
"connect_slot",
|
||||
lambda slot, endpoint, *args, **kwargs: connected.append(endpoint),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
view.bec_dispatcher,
|
||||
"disconnect_slot",
|
||||
lambda slot, endpoint, *args, **kwargs: disconnected.append(endpoint),
|
||||
)
|
||||
|
||||
view.image(device="eiger", signal="img")
|
||||
connected.clear()
|
||||
disconnected.clear()
|
||||
|
||||
view.device = "waveform1d"
|
||||
|
||||
assert MessageEndpoints.device_preview("eiger", "img") in disconnected
|
||||
assert MessageEndpoints.device_preview("waveform1d", "img") in connected
|
||||
|
||||
|
||||
def test_switching_device_disconnects_previous_async_endpoint(qtbot, mocked_client, monkeypatch):
|
||||
"""
|
||||
Verify that switching device while async_update=True disconnects device_async_signal
|
||||
endpoints for both scan_id and old_scan_id on the old device before reconnecting to
|
||||
the new device.
|
||||
"""
|
||||
view = create_widget(qtbot, Image, client=mocked_client)
|
||||
_set_signal_config(
|
||||
mocked_client, "eiger", "img", signal_class="AsyncSignal", ndim=2, obj_name="async_obj"
|
||||
)
|
||||
_set_signal_config(
|
||||
mocked_client, "waveform1d", "img", signal_class="AsyncSignal", ndim=2, obj_name="async_obj"
|
||||
)
|
||||
|
||||
connected = []
|
||||
disconnected = []
|
||||
monkeypatch.setattr(
|
||||
view.bec_dispatcher,
|
||||
"connect_slot",
|
||||
lambda slot, endpoint, *args, **kwargs: connected.append(endpoint),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
view.bec_dispatcher,
|
||||
"disconnect_slot",
|
||||
lambda slot, endpoint, *args, **kwargs: disconnected.append(endpoint),
|
||||
)
|
||||
|
||||
view.image(device="eiger", signal="img")
|
||||
assert view.async_update is True
|
||||
assert view.subscriptions["main"].async_signal_name == "async_obj"
|
||||
|
||||
view.scan_id = "scan_current"
|
||||
view.old_scan_id = "scan_previous"
|
||||
connected.clear()
|
||||
disconnected.clear()
|
||||
|
||||
view.device = "waveform1d"
|
||||
|
||||
# Both scan_id and old_scan_id endpoints for the old device must be disconnected
|
||||
assert (
|
||||
MessageEndpoints.device_async_signal("scan_current", "eiger", "async_obj") in disconnected
|
||||
)
|
||||
assert (
|
||||
MessageEndpoints.device_async_signal("scan_previous", "eiger", "async_obj") in disconnected
|
||||
)
|
||||
# The new device's async endpoint for the current scan must be connected
|
||||
assert (
|
||||
MessageEndpoints.device_async_signal("scan_current", "waveform1d", "async_obj") in connected
|
||||
)
|
||||
|
||||
|
||||
def test_switching_signal_disconnects_previous_preview_endpoint(qtbot, mocked_client, monkeypatch):
|
||||
view = create_widget(qtbot, Image, client=mocked_client)
|
||||
_set_signal_config(mocked_client, "eiger", "img_a", signal_class="PreviewSignal", ndim=2)
|
||||
_set_signal_config(mocked_client, "eiger", "img_b", signal_class="PreviewSignal", ndim=2)
|
||||
|
||||
connected = []
|
||||
disconnected = []
|
||||
monkeypatch.setattr(
|
||||
view.bec_dispatcher,
|
||||
"connect_slot",
|
||||
lambda slot, endpoint, *args, **kwargs: connected.append(endpoint),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
view.bec_dispatcher,
|
||||
"disconnect_slot",
|
||||
lambda slot, endpoint, *args, **kwargs: disconnected.append(endpoint),
|
||||
)
|
||||
|
||||
view.image(device="eiger", signal="img_a")
|
||||
connected.clear()
|
||||
disconnected.clear()
|
||||
|
||||
view.signal = "img_b"
|
||||
|
||||
assert MessageEndpoints.device_preview("eiger", "img_a") in disconnected
|
||||
assert MessageEndpoints.device_preview("eiger", "img_b") in connected
|
||||
|
||||
|
||||
def test_switching_signal_disconnects_previous_async_endpoint(qtbot, mocked_client, monkeypatch):
|
||||
"""
|
||||
When the current monitor is an async signal, switching to a different signal must
|
||||
disconnect the previous async endpoint (based on scan_id/async_signal_name) before
|
||||
reconnecting with the new signal's async endpoint.
|
||||
"""
|
||||
view = create_widget(qtbot, Image, client=mocked_client)
|
||||
_set_signal_config(
|
||||
mocked_client, "eiger", "img_a", signal_class="AsyncSignal", ndim=2, obj_name="async_obj_a"
|
||||
)
|
||||
_set_signal_config(
|
||||
mocked_client, "eiger", "img_b", signal_class="AsyncSignal", ndim=2, obj_name="async_obj_b"
|
||||
)
|
||||
|
||||
connected = []
|
||||
disconnected = []
|
||||
monkeypatch.setattr(
|
||||
view.bec_dispatcher,
|
||||
"connect_slot",
|
||||
lambda slot, endpoint, *args, **kwargs: connected.append(endpoint),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
view.bec_dispatcher,
|
||||
"disconnect_slot",
|
||||
lambda slot, endpoint, *args, **kwargs: disconnected.append(endpoint),
|
||||
)
|
||||
|
||||
# Connect to img_a as an async signal; scan_id is None so no actual subscription is made
|
||||
view.image(device="eiger", signal="img_a")
|
||||
assert view.async_update is True
|
||||
assert view.subscriptions["main"].async_signal_name == "async_obj_a"
|
||||
assert view.subscriptions["main"].source == "device_monitor_2d"
|
||||
|
||||
# Simulate an active scan so that the async endpoint is real
|
||||
view.scan_id = "scan_123"
|
||||
connected.clear()
|
||||
disconnected.clear()
|
||||
|
||||
# Switch to a different signal
|
||||
view.signal = "img_b"
|
||||
|
||||
# The previous async endpoint for img_a must have been disconnected
|
||||
expected_disconnect = MessageEndpoints.device_async_signal("scan_123", "eiger", "async_obj_a")
|
||||
assert expected_disconnect in disconnected
|
||||
|
||||
# The new async endpoint for img_b must have been connected
|
||||
expected_connect = MessageEndpoints.device_async_signal("scan_123", "eiger", "async_obj_b")
|
||||
assert expected_connect in connected
|
||||
|
||||
|
||||
def test_preview_signals_skip_0d_entries(qtbot, mocked_client, monkeypatch):
|
||||
"""
|
||||
Preview/async combobox should omit 0‑D signals.
|
||||
|
||||
Reference in New Issue
Block a user