mirror of
https://github.com/bec-project/bec_widgets.git
synced 2025-07-14 03:31:50 +02:00
Merge branch 'master' into frontend
This commit is contained in:
40
CHANGELOG.md
40
CHANGELOG.md
@ -2,6 +2,46 @@
|
|||||||
|
|
||||||
<!--next-version-placeholder-->
|
<!--next-version-placeholder-->
|
||||||
|
|
||||||
|
## v0.5.0 (2023-08-11)
|
||||||
|
|
||||||
|
### Feature
|
||||||
|
|
||||||
|
* Add generic connect function for slots ([`6a3df34`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/6a3df34cdfbec2434153362ded630305e5dc5e28))
|
||||||
|
* Add possibility to provide service config ([`8c9a9c9`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/8c9a9c93535ee77c0622b483a3157af367ebce1f))
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
|
||||||
|
* Dispatcher argparse and scan_plot tests ([`67f619e`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/67f619ee897e0040c6310e67d69fbb2e0685293d))
|
||||||
|
* Gui event removing bugs ([`a9dd191`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/a9dd191629295ca476e2f9a1b9944ff355216583))
|
||||||
|
|
||||||
|
## v0.4.0 (2023-08-11)
|
||||||
|
|
||||||
|
### Feature
|
||||||
|
|
||||||
|
* Cursor universal for 1D and 2D ([`f75554b`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/f75554bd7b072207847956a8720b9a62c20ba2c8))
|
||||||
|
* Added qt_utils package with general Crosshair function ([`5353fed`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/5353fed7bfe1819819fa3348ec93d2d0ba540628))
|
||||||
|
* 2D plot updating ([`d32088b`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/d32088b643a4d0613c32fb464a0a55a3b6b684d6))
|
||||||
|
* Metadata available on_dap_update ([`18b5d46`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/18b5d46678619a972815532629ce96c121f5fcc9))
|
||||||
|
* Plotting from streamer ([`bb806c1`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/bb806c149dee88023ecb647b523cbd5189ea9001))
|
||||||
|
* Added Legend to plot ([`0feca4b`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/0feca4b1578820ec1f5f3ead3073e4d45c23798b))
|
||||||
|
* Cursor coordinate as a QTable ([`a999f76`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/a999f7669a12910ad66e10a6d2e75197b2dce1c2))
|
||||||
|
* Changed from PlotItem to GraphicsLayoutWidget, added LabelItem ([`075cc79`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/075cc79d6fa011803cf4a06fbff8faa951c1b59f))
|
||||||
|
* Add display_ui_file.py ([`91d8ffa`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/91d8ffacffcbeebdf7623caf62e07244c4dcee16))
|
||||||
|
* Add disconnect_dap_slot ([`1325704`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/1325704750ebab897e3dcae80c9d455bfbbf886f))
|
||||||
|
* Inherit from GraphicsView for consistency with 2D plot ([`d8c101c`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/d8c101cdd7f960a152a1f318911cac6eecf6bad4))
|
||||||
|
* Add BECScanPlot2D ([`67905e8`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/67905e896c81383f57c268db544b3314104bda38))
|
||||||
|
* Emit the full bec message to slots ([`1bb3020`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/1bb30207038f3a54c0e96dbbbcd1ea7f6c70eca2))
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
|
||||||
|
* Q selection for gui_event signal ([`0bf452a`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/0bf452ad1b7d9ad941e2ef4b8d61ec4ed5266415))
|
||||||
|
* Fixed logic in data subscription ([`c2d469b`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/c2d469b4543fcf237b274399b83969cc2213b61b))
|
||||||
|
* Scan_plot to accept metadata from dap signal ([`7bec0b5`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/7bec0b5e6c1663670f8fcc2fc6aa6c8b6df28b61))
|
||||||
|
* Plotting latest 1d curves ([`378be81`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/378be81bf6dd5e9239f8f1fb908cafc97161c79d))
|
||||||
|
* Testing the data structure of plotting ([`4fb0a3b`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/4fb0a3b058957f5b37227ff7c8e9bdf5259a1cde))
|
||||||
|
* Fix examples when run directly as a script ([`cd11ee5`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/cd11ee51c1c725255e748a32b89a74487e84a631))
|
||||||
|
* Module paths ([`e7f644c`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/e7f644c5079a8665d7d872eb0b27ed7da6cbd078))
|
||||||
|
|
||||||
## v0.3.0 (2023-07-19)
|
## v0.3.0 (2023-07-19)
|
||||||
|
|
||||||
### Feature
|
### Feature
|
||||||
|
@ -1,20 +1,23 @@
|
|||||||
import os
|
import os
|
||||||
import warnings
|
|
||||||
import time
|
|
||||||
from typing import Any
|
|
||||||
import threading
|
import threading
|
||||||
|
import time
|
||||||
|
import warnings
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pyqtgraph
|
import pyqtgraph
|
||||||
import pyqtgraph as pg
|
import pyqtgraph as pg
|
||||||
|
from bec_lib.core import BECMessage
|
||||||
from PyQt5.QtCore import pyqtSlot
|
from PyQt5.QtCore import pyqtSlot
|
||||||
from PyQt5.QtWidgets import QTableWidgetItem, QCheckBox
|
from PyQt5.QtWidgets import QCheckBox, QTableWidgetItem
|
||||||
|
|
||||||
from bec_lib import BECClient
|
|
||||||
from pyqtgraph import mkBrush, mkColor, mkPen
|
from pyqtgraph import mkBrush, mkColor, mkPen
|
||||||
from pyqtgraph.Qt import QtCore, QtWidgets, uic
|
from pyqtgraph.Qt import QtCore, QtWidgets, uic
|
||||||
from pyqtgraph.Qt.QtCore import pyqtSignal
|
from pyqtgraph.Qt.QtCore import pyqtSignal
|
||||||
|
|
||||||
from bec_lib.core import BECMessage
|
from bec_widgets.bec_dispatcher import bec_dispatcher
|
||||||
|
from bec_lib.core.redis_connector import MessageObject, RedisConnector
|
||||||
|
|
||||||
|
client = bec_dispatcher.client
|
||||||
|
|
||||||
|
|
||||||
class BasicPlot(QtWidgets.QWidget):
|
class BasicPlot(QtWidgets.QWidget):
|
||||||
@ -35,7 +38,7 @@ class BasicPlot(QtWidgets.QWidget):
|
|||||||
pg.setConfigOption("background", "w")
|
pg.setConfigOption("background", "w")
|
||||||
pg.setConfigOption("foreground", "k")
|
pg.setConfigOption("foreground", "k")
|
||||||
current_path = os.path.dirname(__file__)
|
current_path = os.path.dirname(__file__)
|
||||||
uic.loadUi(os.path.join(current_path, "line_plot.ui"), self)
|
uic.loadUi(os.path.join(current_path, "basic_plot.ui"), self)
|
||||||
|
|
||||||
# Set splitter distribution of widgets
|
# Set splitter distribution of widgets
|
||||||
self.splitter.setSizes([3, 1])
|
self.splitter.setSizes([3, 1])
|
||||||
@ -44,6 +47,7 @@ class BasicPlot(QtWidgets.QWidget):
|
|||||||
self.title = ""
|
self.title = ""
|
||||||
self.label_bottom = ""
|
self.label_bottom = ""
|
||||||
self.label_left = ""
|
self.label_left = ""
|
||||||
|
self.producer = RedisConnector(["localhost:6379"]).producer()
|
||||||
|
|
||||||
self.scan_motors = []
|
self.scan_motors = []
|
||||||
self.y_value_list = y_value_list
|
self.y_value_list = y_value_list
|
||||||
@ -158,6 +162,14 @@ class BasicPlot(QtWidgets.QWidget):
|
|||||||
"""For testing purpose now, get roi region and print it to self.label as tuple"""
|
"""For testing purpose now, get roi region and print it to self.label as tuple"""
|
||||||
region = self.roi_selector.getRegion()
|
region = self.roi_selector.getRegion()
|
||||||
self.label.setText(f"x = {(10**region[0]):.4f}, y ={(10**region[1]):.4f}")
|
self.label.setText(f"x = {(10**region[0]):.4f}, y ={(10**region[1]):.4f}")
|
||||||
|
return_dict = {
|
||||||
|
"horiz_roi": [
|
||||||
|
np.where(self.plotter_data_x[0] > 10 ** region[0])[0][0],
|
||||||
|
np.where(self.plotter_data_x[0] < 10 ** region[1])[0][-1],
|
||||||
|
]
|
||||||
|
}
|
||||||
|
msg = BECMessage.DeviceMessage(signals=return_dict).dumps()
|
||||||
|
self.producer.set_and_publish("px_stream/gui_event", msg=msg)
|
||||||
self.roi_signal.emit(region)
|
self.roi_signal.emit(region)
|
||||||
|
|
||||||
def add_text_items(self): # TODO probably can be removed
|
def add_text_items(self): # TODO probably can be removed
|
||||||
@ -386,14 +398,14 @@ class BasicPlot(QtWidgets.QWidget):
|
|||||||
data = [BECMessage.DeviceMessage.loads(msg) for msg in msgs]
|
data = [BECMessage.DeviceMessage.loads(msg) for msg in msgs]
|
||||||
if not data:
|
if not data:
|
||||||
continue
|
continue
|
||||||
|
with np.errstate(divide="ignore", invalid="ignore"):
|
||||||
self.plotter_data_y = [
|
self.plotter_data_y = [
|
||||||
np.sum(
|
np.sum(
|
||||||
np.sum(data[-1].content["signals"]["data"] * self._current_norm, axis=1)
|
np.sum(data[-1].content["signals"]["data"] * self._current_norm, axis=1)
|
||||||
/ np.sum(self._current_norm, axis=0),
|
/ np.sum(self._current_norm, axis=0),
|
||||||
axis=0,
|
axis=0,
|
||||||
).squeeze()
|
).squeeze()
|
||||||
]
|
]
|
||||||
|
|
||||||
self.update_signal.emit()
|
self.update_signal.emit()
|
||||||
|
|
||||||
@ -418,9 +430,9 @@ class BasicPlot(QtWidgets.QWidget):
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import argparse
|
import argparse
|
||||||
from bec_widgets.bec_dispatcher import bec_dispatcher
|
|
||||||
|
|
||||||
from bec_widgets import ctrl_c
|
from bec_widgets import ctrl_c
|
||||||
|
from bec_widgets.bec_dispatcher import bec_dispatcher
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument(
|
parser.add_argument(
|
77
bec_widgets/basic_plot.ui
Normal file
77
bec_widgets/basic_plot.ui
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>Form</class>
|
||||||
|
<widget class="QWidget" name="Form">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>845</width>
|
||||||
|
<height>635</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Line Plot</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QSplitter" name="splitter">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="opaqueResize">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="verticalLayoutWidget">
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="pushButton_debug">
|
||||||
|
<property name="text">
|
||||||
|
<string>Debug</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="GraphicsLayoutWidget" name="glw"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<widget class="QTableWidget" name="mouse_table">
|
||||||
|
<property name="textElideMode">
|
||||||
|
<enum>Qt::ElideMiddle</enum>
|
||||||
|
</property>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Display</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Device</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>X</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Y</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<customwidgets>
|
||||||
|
<customwidget>
|
||||||
|
<class>GraphicsLayoutWidget</class>
|
||||||
|
<extends>QGraphicsView</extends>
|
||||||
|
<header>pyqtgraph.h</header>
|
||||||
|
</customwidget>
|
||||||
|
</customwidgets>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
@ -1,8 +1,11 @@
|
|||||||
|
import argparse
|
||||||
|
import itertools
|
||||||
|
import os
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from threading import RLock
|
from threading import RLock
|
||||||
|
|
||||||
from bec_lib import BECClient
|
from bec_lib import BECClient
|
||||||
from bec_lib.core import BECMessage, MessageEndpoints
|
from bec_lib.core import BECMessage, MessageEndpoints, ServiceConfig
|
||||||
from bec_lib.core.redis_connector import RedisConsumerThreaded
|
from bec_lib.core.redis_connector import RedisConsumerThreaded
|
||||||
from PyQt5.QtCore import QObject, pyqtSignal
|
from PyQt5.QtCore import QObject, pyqtSignal
|
||||||
|
|
||||||
@ -15,6 +18,27 @@ class _BECDap:
|
|||||||
slots = set()
|
slots = set()
|
||||||
|
|
||||||
|
|
||||||
|
# Adding a new pyqt signal requres a class factory, as they must be part of the class definition
|
||||||
|
# and cannot be dynamically added as class attributes after the class has been defined.
|
||||||
|
_signal_class_factory = (
|
||||||
|
type(f"Signal{i}", (QObject,), dict(signal=pyqtSignal("PyQt_PyObject")))
|
||||||
|
for i in itertools.count()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class _Connection:
|
||||||
|
"""Utility class to keep track of slots connected to a particular redis consumer"""
|
||||||
|
|
||||||
|
consumer: RedisConsumerThreaded
|
||||||
|
slots = set()
|
||||||
|
# keep a reference to a new signal class, so it is not gc'ed
|
||||||
|
_signal_container = next(_signal_class_factory)()
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
self.signal = self._signal_container.signal
|
||||||
|
|
||||||
|
|
||||||
class _BECDispatcher(QObject):
|
class _BECDispatcher(QObject):
|
||||||
new_scan = pyqtSignal(dict, dict)
|
new_scan = pyqtSignal(dict, dict)
|
||||||
scan_segment = pyqtSignal(dict, dict)
|
scan_segment = pyqtSignal(dict, dict)
|
||||||
@ -23,32 +47,45 @@ class _BECDispatcher(QObject):
|
|||||||
new_projection_id = pyqtSignal(dict)
|
new_projection_id = pyqtSignal(dict)
|
||||||
new_projection_data = pyqtSignal(dict)
|
new_projection_data = pyqtSignal(dict)
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, bec_config=None):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.client = BECClient()
|
self.client = BECClient()
|
||||||
self.client.start()
|
|
||||||
|
# TODO: this is a workaround for now to provide service config within qtdesigner, but is
|
||||||
|
# it possible to provide config via a cli arg?
|
||||||
|
if bec_config is None and os.path.isfile("bec_config.yaml"):
|
||||||
|
bec_config = "bec_config.yaml"
|
||||||
|
|
||||||
|
self.client.initialize(config=ServiceConfig(config_path=bec_config))
|
||||||
|
|
||||||
self._slot_signal_map = {
|
self._slot_signal_map = {
|
||||||
"on_scan_segment": self.scan_segment,
|
"on_scan_segment": self.scan_segment,
|
||||||
"on_new_scan": self.new_scan,
|
"on_new_scan": self.new_scan,
|
||||||
}
|
}
|
||||||
self._daps = {}
|
self._daps = {}
|
||||||
|
self._connections = {}
|
||||||
|
|
||||||
self._scan_id = None
|
self._scan_id = None
|
||||||
scan_lock = RLock()
|
scan_lock = RLock()
|
||||||
|
|
||||||
# self.new_projection_id.connect(self.new_projection_data)
|
# self.new_projection_id.connect(self.new_projection_data)
|
||||||
|
|
||||||
def _scan_segment_cb(scan_segment, metadata):
|
def _scan_segment_cb(msg):
|
||||||
|
msg = BECMessage.ScanMessage.loads(msg.value)[0]
|
||||||
with scan_lock:
|
with scan_lock:
|
||||||
# TODO: use ScanStatusMessage instead?
|
# TODO: use ScanStatusMessage instead?
|
||||||
scan_id = metadata["scanID"]
|
scan_id = msg.content["scanID"]
|
||||||
if self._scan_id != scan_id:
|
if self._scan_id != scan_id:
|
||||||
self._scan_id = scan_id
|
self._scan_id = scan_id
|
||||||
self.new_scan.emit(scan_segment, metadata)
|
self.new_scan.emit(msg.content, msg.metadata)
|
||||||
self.scan_segment.emit(scan_segment, metadata)
|
self.scan_segment.emit(msg.content, msg.metadata)
|
||||||
|
|
||||||
self.client.callbacks.register("scan_segment", _scan_segment_cb, sync=False)
|
scan_segment_topic = MessageEndpoints.scan_segment()
|
||||||
|
self._scan_segment_thread = self.client.connector.consumer(
|
||||||
|
topics=scan_segment_topic,
|
||||||
|
cb=_scan_segment_cb,
|
||||||
|
)
|
||||||
|
self._scan_segment_thread.start()
|
||||||
|
|
||||||
def connect(self, widget):
|
def connect(self, widget):
|
||||||
for slot_name, signal in self._slot_signal_map.items():
|
for slot_name, signal in self._slot_signal_map.items():
|
||||||
@ -56,6 +93,39 @@ class _BECDispatcher(QObject):
|
|||||||
if callable(slot):
|
if callable(slot):
|
||||||
signal.connect(slot)
|
signal.connect(slot)
|
||||||
|
|
||||||
|
def connect_slot(self, slot, topic):
|
||||||
|
# create new connection for topic if it doesn't exist
|
||||||
|
if topic not in self._connections:
|
||||||
|
|
||||||
|
def cb(msg):
|
||||||
|
msg = BECMessage.MessageReader.loads(msg.value)
|
||||||
|
self._connections[topic].signal.emit(msg)
|
||||||
|
|
||||||
|
consumer = self.client.connector.consumer(topics=topic, cb=cb)
|
||||||
|
consumer.start()
|
||||||
|
|
||||||
|
self._connections[topic] = _Connection(consumer)
|
||||||
|
|
||||||
|
# connect slot if it's not connected
|
||||||
|
if slot not in self._connections[topic].slots:
|
||||||
|
self._connections[topic].signal.connect(slot)
|
||||||
|
self._connections[topic].slots.add(slot)
|
||||||
|
|
||||||
|
def disconnect_slot(self, slot, topic):
|
||||||
|
if topic not in self._connections:
|
||||||
|
return
|
||||||
|
|
||||||
|
if slot not in self._connections[topic].slots:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._connections[topic].signal.disconnect(slot)
|
||||||
|
self._connections[topic].slots.remove(slot)
|
||||||
|
|
||||||
|
if not self._connections[topic].slots:
|
||||||
|
# shutdown consumer if there are no more connected slots
|
||||||
|
self._connections[topic].consumer.shutdown()
|
||||||
|
del self._connections[topic]
|
||||||
|
|
||||||
def connect_dap_slot(self, slot, dap_name):
|
def connect_dap_slot(self, slot, dap_name):
|
||||||
if dap_name not in self._daps:
|
if dap_name not in self._daps:
|
||||||
# create a new consumer and connect slot
|
# create a new consumer and connect slot
|
||||||
@ -150,4 +220,8 @@ class _BECDispatcher(QObject):
|
|||||||
del self._daps[data_ep]
|
del self._daps[data_ep]
|
||||||
|
|
||||||
|
|
||||||
bec_dispatcher = _BECDispatcher()
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--bec-config", default=None)
|
||||||
|
args, _ = parser.parse_known_args()
|
||||||
|
|
||||||
|
bec_dispatcher = _BECDispatcher(args.bec_config)
|
||||||
|
2
setup.py
2
setup.py
@ -1,6 +1,6 @@
|
|||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
|
|
||||||
__version__ = "0.3.0"
|
__version__ = "0.5.0"
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
setup(
|
setup(
|
||||||
|
152
tests/test_basic_plot.py
Normal file
152
tests/test_basic_plot.py
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from pytestqt import qtbot
|
||||||
|
|
||||||
|
from bec_widgets import basic_plot
|
||||||
|
|
||||||
|
|
||||||
|
def test_basic_plot_emits_no_signal(qtbot):
|
||||||
|
"""Test LinePlot emits no signal when only one data entry is present."""
|
||||||
|
|
||||||
|
y_value_list = ["y1", "y2"]
|
||||||
|
plot = basic_plot.BasicPlot(y_value_list=y_value_list)
|
||||||
|
data = {
|
||||||
|
"data": {
|
||||||
|
"x": {"x": {"value": 1}},
|
||||||
|
"y1": {"y1": {"value": 1}},
|
||||||
|
"y2": {"y2": {"value": 3}},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
metadata = {"scanID": "test", "scan_number": 1, "scan_report_devices": ["x"]}
|
||||||
|
with mock.patch("bec_widgets.basic_plot.client") as mock_client:
|
||||||
|
with mock.patch.object(plot, "update_signal") as mock_update_signal:
|
||||||
|
plot.on_scan_segment(data=data, metadata=metadata)
|
||||||
|
mock_update_signal.emit.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
|
def test_basic_plot_emits_signal(qtbot):
|
||||||
|
"""Test LinePlot emits signal."""
|
||||||
|
|
||||||
|
y_value_list = ["y1", "y2"]
|
||||||
|
plot = basic_plot.BasicPlot(y_value_list=y_value_list)
|
||||||
|
data = {
|
||||||
|
"data": {
|
||||||
|
"x": {"x": {"value": 1}},
|
||||||
|
"y1": {"y1": {"value": 1}},
|
||||||
|
"y2": {"y2": {"value": 3}},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
plotter_data_y = [[1, 1], [3, 3]]
|
||||||
|
metadata = {"scanID": "test", "scan_number": 1, "scan_report_devices": ["x"]}
|
||||||
|
with mock.patch("bec_widgets.basic_plot.client") as mock_client:
|
||||||
|
# mock_client.device_manager.devices.keys.return_value = ["y1"]
|
||||||
|
with mock.patch.object(plot, "update_signal") as mock_update_signal:
|
||||||
|
mock_update_signal.emit()
|
||||||
|
plot.on_scan_segment(data=data, metadata=metadata)
|
||||||
|
plot.on_scan_segment(data=data, metadata=metadata)
|
||||||
|
mock_update_signal.emit.assert_called()
|
||||||
|
# TODO allow mock_client to create return values for device_manager_devices
|
||||||
|
# assert plot.plotter_data_y == plotter_data_y
|
||||||
|
|
||||||
|
|
||||||
|
def test_basic_plot_raise_warning_wrong_signal_request(qtbot):
|
||||||
|
"""Test LinePlot raises warning and skips signal when entry not present in data."""
|
||||||
|
|
||||||
|
y_value_list = ["y1", "y22"]
|
||||||
|
plot = basic_plot.BasicPlot(y_value_list=y_value_list)
|
||||||
|
data = {
|
||||||
|
"data": {
|
||||||
|
"x": {"x": {"value": [1, 2, 3, 4, 5]}},
|
||||||
|
"y1": {"y1": {"value": [1, 2, 3, 4, 5]}},
|
||||||
|
"y2": {"y2": {"value": [1, 2, 3, 4, 5]}},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
metadata = {"scanID": "test", "scan_number": 1, "scan_report_devices": ["x"]}
|
||||||
|
with mock.patch("bec_widgets.basic_plot.client") as mock_client:
|
||||||
|
# TODO fix mock_client
|
||||||
|
mock_dict = {"y1": [1, 2]}
|
||||||
|
mock_client.device_manager.devices.__contains__.side_effect = mock_dict.__contains__
|
||||||
|
|
||||||
|
# = {"y1": [1, 2]}
|
||||||
|
with mock.patch.object(plot, "update_signal") as mock_update_signal:
|
||||||
|
mock_update_signal.emit()
|
||||||
|
plot.on_scan_segment(data=data, metadata=metadata)
|
||||||
|
assert plot.y_value_list == ["y1"]
|
||||||
|
|
||||||
|
|
||||||
|
# def test_basic_plot_update(qtbot):
|
||||||
|
# """Test LinePlot update."""
|
||||||
|
|
||||||
|
# y_value_list = ["y1", "y2"]
|
||||||
|
# plot = basic_plot.BasicPlot(y_value_list=y_value_list)
|
||||||
|
# plot.label_bottom = "x"
|
||||||
|
# plot.label_left = f"{', '.join(y_value_list)}"
|
||||||
|
# plot.plotter_data_x = [1, 2, 3, 4, 5]
|
||||||
|
# plot.plotter_data_y = [[1, 2, 3, 4, 5], [3, 4, 5, 6, 7]]
|
||||||
|
# plot.update()
|
||||||
|
|
||||||
|
# assert all(plot.curves[0].getData()[0] == np.array([1, 2, 3, 4, 5]))
|
||||||
|
# assert all(plot.curves[0].getData()[1] == np.array([1, 2, 3, 4, 5]))
|
||||||
|
# assert all(plot.curves[1].getData()[1] == np.array([3, 4, 5, 6, 7]))
|
||||||
|
|
||||||
|
|
||||||
|
# # TODO Outputting the wrong data, e.g. motor is not in list of devices
|
||||||
|
# def test_basic_plot_update(qtbot):
|
||||||
|
# """Test LinePlot update."""
|
||||||
|
|
||||||
|
# y_value_list = ["y1", "y2"]
|
||||||
|
# plot = basic_plot.BasicPlot(y_value_list=y_value_list)
|
||||||
|
# plot.label_bottom = "x"
|
||||||
|
# plot.label_left = f"{', '.join(y_value_list)}"
|
||||||
|
# plot.plotter_data_x = [1, 2, 3, 4, 5]
|
||||||
|
# plot.plotter_data_y = [[1, 2, 3, 4, 5], [3, 4, 5, 6, 7]]
|
||||||
|
# plot.update()
|
||||||
|
|
||||||
|
# assert all(plot.curves[0].getData()[0] == np.array([1, 2, 3, 4, 5]))
|
||||||
|
# assert all(plot.curves[0].getData()[1] == np.array([1, 2, 3, 4, 5]))
|
||||||
|
# assert all(plot.curves[1].getData()[1] == np.array([3, 4, 5, 6, 7]))
|
||||||
|
|
||||||
|
|
||||||
|
# def test_basic_plot_mouse_moved(qtbot):
|
||||||
|
# """Test LinePlot mouse_moved."""
|
||||||
|
|
||||||
|
# y_value_list = ["y1", "y2"]
|
||||||
|
# plot = basic_plot.BasicPlot(y_value_list=y_value_list)
|
||||||
|
# plot.plotter_data_x = [1, 2, 3, 4, 5]
|
||||||
|
# plot.plotter_data_y = [[1, 2, 3, 4, 5], [3, 4, 5, 6, 7]]
|
||||||
|
# plot.precision = 3
|
||||||
|
# string_cap = 10
|
||||||
|
# x_data = f"{3:.{plot.precision}f}"
|
||||||
|
# y_data = f"{3:.{plot.precision}f}"
|
||||||
|
# output_string = "".join(
|
||||||
|
# [
|
||||||
|
# "Mouse cursor",
|
||||||
|
# "\n",
|
||||||
|
# f"{y_value_list[0]}",
|
||||||
|
# "\n",
|
||||||
|
# f"X_data: {x_data:>{string_cap}}",
|
||||||
|
# "\n",
|
||||||
|
# f"Y_data: {y_data:>{string_cap}}",
|
||||||
|
# ]
|
||||||
|
# )
|
||||||
|
# x_data = f"{3:.{plot.precision}f}"
|
||||||
|
# y_data = f"{5:.{plot.precision}f}"
|
||||||
|
# output_string = "".join(
|
||||||
|
# [
|
||||||
|
# output_string,
|
||||||
|
# "\n",
|
||||||
|
# f"{y_value_list[1]}",
|
||||||
|
# "\n",
|
||||||
|
# f"X_data: {x_data:>{string_cap}}",
|
||||||
|
# "\n",
|
||||||
|
# f"Y_data: {y_data:>{string_cap}}",
|
||||||
|
# ]
|
||||||
|
# )
|
||||||
|
# with mock.patch.object(
|
||||||
|
# plot, "plot"
|
||||||
|
# ) as mock_plot: # TODO change test to simulate QTable instead of QLabel
|
||||||
|
# mock_plot.sceneBoundingRect.contains.return_value = True
|
||||||
|
# mock_plot.vb.mapSceneToView((20, 10)).x.return_value = 2.8
|
||||||
|
# plot.mouse_moved((20, 10))
|
||||||
|
# assert plot.mouse_box_data.text() == output_string
|
@ -1,152 +0,0 @@
|
|||||||
from unittest import mock
|
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
from pytestqt import qtbot
|
|
||||||
|
|
||||||
from bec_widgets import line_plot
|
|
||||||
|
|
||||||
|
|
||||||
def test_line_plot_emits_no_signal(qtbot):
|
|
||||||
"""Test LinePlot emits no signal when only one data entry is present."""
|
|
||||||
|
|
||||||
y_value_list = ["y1", "y2"]
|
|
||||||
plot = line_plot.BasicPlot(y_value_list=y_value_list)
|
|
||||||
data = {
|
|
||||||
"data": {
|
|
||||||
"x": {"x": {"value": 1}},
|
|
||||||
"y1": {"y1": {"value": 1}},
|
|
||||||
"y2": {"y2": {"value": 3}},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
metadata = {"scanID": "test", "scan_number": 1, "scan_report_devices": ["x"]}
|
|
||||||
with mock.patch("bec_widgets.line_plot.BECClient") as mock_client:
|
|
||||||
with mock.patch.object(plot, "update_signal") as mock_update_signal:
|
|
||||||
plot(data=data, metadata=metadata)
|
|
||||||
mock_update_signal.emit.assert_not_called()
|
|
||||||
|
|
||||||
|
|
||||||
def test_line_plot_emits_signal(qtbot):
|
|
||||||
"""Test LinePlot emits signal."""
|
|
||||||
|
|
||||||
y_value_list = ["y1", "y2"]
|
|
||||||
plot = line_plot.BasicPlot(y_value_list=y_value_list)
|
|
||||||
data = {
|
|
||||||
"data": {
|
|
||||||
"x": {"x": {"value": 1}},
|
|
||||||
"y1": {"y1": {"value": 1}},
|
|
||||||
"y2": {"y2": {"value": 3}},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
plotter_data_y = [[1, 1], [3, 3]]
|
|
||||||
metadata = {"scanID": "test", "scan_number": 1, "scan_report_devices": ["x"]}
|
|
||||||
with mock.patch("bec_widgets.line_plot.BECClient") as mock_client:
|
|
||||||
# mock_client.device_manager.devices.keys.return_value = ["y1"]
|
|
||||||
with mock.patch.object(plot, "update_signal") as mock_update_signal:
|
|
||||||
mock_update_signal.emit()
|
|
||||||
plot(data=data, metadata=metadata)
|
|
||||||
plot(data=data, metadata=metadata)
|
|
||||||
mock_update_signal.emit.assert_called()
|
|
||||||
# TODO allow mock_client to create return values for device_manager_devices
|
|
||||||
# assert plot.plotter_data_y == plotter_data_y
|
|
||||||
|
|
||||||
|
|
||||||
def test_line_plot_raise_warning_wrong_signal_request(qtbot):
|
|
||||||
"""Test LinePlot raises warning and skips signal when entry not present in data."""
|
|
||||||
|
|
||||||
y_value_list = ["y1", "y22"]
|
|
||||||
plot = line_plot.BasicPlot(y_value_list=y_value_list)
|
|
||||||
data = {
|
|
||||||
"data": {
|
|
||||||
"x": {"x": {"value": [1, 2, 3, 4, 5]}},
|
|
||||||
"y1": {"y1": {"value": [1, 2, 3, 4, 5]}},
|
|
||||||
"y2": {"y2": {"value": [1, 2, 3, 4, 5]}},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
metadata = {"scanID": "test", "scan_number": 1, "scan_report_devices": ["x"]}
|
|
||||||
with mock.patch("bec_widgets.line_plot.BECClient") as mock_client:
|
|
||||||
# TODO fix mock_client
|
|
||||||
mock_dict = {"y1": [1, 2]}
|
|
||||||
mock_client().device_manager.devices.__contains__.side_effect = mock_dict.__contains__
|
|
||||||
|
|
||||||
# = {"y1": [1, 2]}
|
|
||||||
with mock.patch.object(plot, "update_signal") as mock_update_signal:
|
|
||||||
mock_update_signal.emit()
|
|
||||||
plot(data=data, metadata=metadata)
|
|
||||||
assert plot.y_value_list == ["y1"]
|
|
||||||
|
|
||||||
|
|
||||||
def test_line_plot_update(qtbot):
|
|
||||||
"""Test LinePlot update."""
|
|
||||||
|
|
||||||
y_value_list = ["y1", "y2"]
|
|
||||||
plot = line_plot.BasicPlot(y_value_list=y_value_list)
|
|
||||||
plot.label_bottom = "x"
|
|
||||||
plot.label_left = f"{', '.join(y_value_list)}"
|
|
||||||
plot.plotter_data_x = [1, 2, 3, 4, 5]
|
|
||||||
plot.plotter_data_y = [[1, 2, 3, 4, 5], [3, 4, 5, 6, 7]]
|
|
||||||
plot.update()
|
|
||||||
|
|
||||||
assert all(plot.curves[0].getData()[0] == np.array([1, 2, 3, 4, 5]))
|
|
||||||
assert all(plot.curves[0].getData()[1] == np.array([1, 2, 3, 4, 5]))
|
|
||||||
assert all(plot.curves[1].getData()[1] == np.array([3, 4, 5, 6, 7]))
|
|
||||||
|
|
||||||
|
|
||||||
# TODO Outputting the wrong data, e.g. motor is not in list of devices
|
|
||||||
def test_line_plot_update(qtbot):
|
|
||||||
"""Test LinePlot update."""
|
|
||||||
|
|
||||||
y_value_list = ["y1", "y2"]
|
|
||||||
plot = line_plot.BasicPlot(y_value_list=y_value_list)
|
|
||||||
plot.label_bottom = "x"
|
|
||||||
plot.label_left = f"{', '.join(y_value_list)}"
|
|
||||||
plot.plotter_data_x = [1, 2, 3, 4, 5]
|
|
||||||
plot.plotter_data_y = [[1, 2, 3, 4, 5], [3, 4, 5, 6, 7]]
|
|
||||||
plot.update()
|
|
||||||
|
|
||||||
assert all(plot.curves[0].getData()[0] == np.array([1, 2, 3, 4, 5]))
|
|
||||||
assert all(plot.curves[0].getData()[1] == np.array([1, 2, 3, 4, 5]))
|
|
||||||
assert all(plot.curves[1].getData()[1] == np.array([3, 4, 5, 6, 7]))
|
|
||||||
|
|
||||||
|
|
||||||
def test_line_plot_mouse_moved(qtbot):
|
|
||||||
"""Test LinePlot mouse_moved."""
|
|
||||||
|
|
||||||
y_value_list = ["y1", "y2"]
|
|
||||||
plot = line_plot.BasicPlot(y_value_list=y_value_list)
|
|
||||||
plot.plotter_data_x = [1, 2, 3, 4, 5]
|
|
||||||
plot.plotter_data_y = [[1, 2, 3, 4, 5], [3, 4, 5, 6, 7]]
|
|
||||||
plot.precision = 3
|
|
||||||
string_cap = 10
|
|
||||||
x_data = f"{3:.{plot.precision}f}"
|
|
||||||
y_data = f"{3:.{plot.precision}f}"
|
|
||||||
output_string = "".join(
|
|
||||||
[
|
|
||||||
"Mouse cursor",
|
|
||||||
"\n",
|
|
||||||
f"{y_value_list[0]}",
|
|
||||||
"\n",
|
|
||||||
f"X_data: {x_data:>{string_cap}}",
|
|
||||||
"\n",
|
|
||||||
f"Y_data: {y_data:>{string_cap}}",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
x_data = f"{3:.{plot.precision}f}"
|
|
||||||
y_data = f"{5:.{plot.precision}f}"
|
|
||||||
output_string = "".join(
|
|
||||||
[
|
|
||||||
output_string,
|
|
||||||
"\n",
|
|
||||||
f"{y_value_list[1]}",
|
|
||||||
"\n",
|
|
||||||
f"X_data: {x_data:>{string_cap}}",
|
|
||||||
"\n",
|
|
||||||
f"Y_data: {y_data:>{string_cap}}",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
with mock.patch.object(
|
|
||||||
plot, "plot"
|
|
||||||
) as mock_plot: # TODO change test to simulate QTable instead of QLabel
|
|
||||||
mock_plot.sceneBoundingRect.contains.return_value = True
|
|
||||||
mock_plot.vb.mapSceneToView((20, 10)).x.return_value = 2.8
|
|
||||||
plot.mouse_moved((20, 10))
|
|
||||||
assert plot.mouse_box_data.text() == output_string
|
|
@ -13,12 +13,25 @@ def test_scan_plot(qtbot):
|
|||||||
plot.x_channel = "x"
|
plot.x_channel = "x"
|
||||||
plot.y_channel_list = ["y1", "y2"]
|
plot.y_channel_list = ["y1", "y2"]
|
||||||
|
|
||||||
plot.initialize()
|
plot.on_scan_segment(
|
||||||
plot.redraw_scan(
|
{
|
||||||
{"x": {"x": {"value": 1}}, "y1": {"y1": {"value": 1}}, "y2": {"y2": {"value": 3}}}
|
"data": {
|
||||||
|
"x": {"x": {"value": 1}},
|
||||||
|
"y1": {"y1": {"value": 1}},
|
||||||
|
"y2": {"y2": {"value": 3}},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{"scanID": "test", "scan_number": 1, "scan_report_devices": ["x"]},
|
||||||
)
|
)
|
||||||
plot.redraw_scan(
|
plot.on_scan_segment(
|
||||||
{"x": {"x": {"value": 2}}, "y1": {"y1": {"value": 2}}, "y2": {"y2": {"value": 4}}}
|
{
|
||||||
|
"data": {
|
||||||
|
"x": {"x": {"value": 2}},
|
||||||
|
"y1": {"y1": {"value": 2}},
|
||||||
|
"y2": {"y2": {"value": 4}},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{"scanID": "test", "scan_number": 1, "scan_report_devices": ["x"]},
|
||||||
)
|
)
|
||||||
|
|
||||||
assert all(plot.scan_curves["y1"].getData()[0] == [1, 2])
|
assert all(plot.scan_curves["y1"].getData()[0] == [1, 2])
|
||||||
@ -35,13 +48,26 @@ def test_scan_plot_clears_data(qtbot):
|
|||||||
plot.x_channel = "x"
|
plot.x_channel = "x"
|
||||||
plot.y_channel_list = ["y1", "y2"]
|
plot.y_channel_list = ["y1", "y2"]
|
||||||
|
|
||||||
plot.initialize()
|
plot.on_scan_segment(
|
||||||
plot.redraw_scan(
|
{
|
||||||
{"x": {"x": {"value": 1}}, "y1": {"y1": {"value": 1}}, "y2": {"y2": {"value": 3}}}
|
"data": {
|
||||||
|
"x": {"x": {"value": 1}},
|
||||||
|
"y1": {"y1": {"value": 1}},
|
||||||
|
"y2": {"y2": {"value": 3}},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{"scanID": "test", "scan_number": 1, "scan_report_devices": ["x"]},
|
||||||
)
|
)
|
||||||
plot.clearData()
|
plot.on_new_scan({}, {})
|
||||||
plot.redraw_scan(
|
plot.on_scan_segment(
|
||||||
{"x": {"x": {"value": 2}}, "y1": {"y1": {"value": 2}}, "y2": {"y2": {"value": 4}}}
|
{
|
||||||
|
"data": {
|
||||||
|
"x": {"x": {"value": 2}},
|
||||||
|
"y1": {"y1": {"value": 2}},
|
||||||
|
"y2": {"y2": {"value": 4}},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{"scanID": "test", "scan_number": 1, "scan_report_devices": ["x"]},
|
||||||
)
|
)
|
||||||
|
|
||||||
assert all(plot.scan_curves["y1"].getData()[0] == [2])
|
assert all(plot.scan_curves["y1"].getData()[0] == [2])
|
||||||
@ -57,8 +83,7 @@ def test_scan_plot_redraws_dap(qtbot):
|
|||||||
|
|
||||||
plot.y_channel_list = ["dap.y1", "dap.y2"]
|
plot.y_channel_list = ["dap.y1", "dap.y2"]
|
||||||
|
|
||||||
plot.initialize()
|
plot.redraw_dap({"y1": {"x": [1], "y": [1]}, "y2": {"x": [2], "y": [2]}}, {})
|
||||||
plot.redraw_dap({"y1": {"x": [1], "y": [1]}, "y2": {"x": [2], "y": [2]}})
|
|
||||||
|
|
||||||
assert all(plot.dap_curves["y1"].getData()[0] == [1])
|
assert all(plot.dap_curves["y1"].getData()[0] == [1])
|
||||||
assert all(plot.dap_curves["y2"].getData()[1] == [2])
|
assert all(plot.dap_curves["y2"].getData()[1] == [2])
|
||||||
|
Reference in New Issue
Block a user