0
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2025-07-14 03:31:50 +02:00

fix: compatibility adjustment to .ui loading and tests for PySide6

This commit is contained in:
2024-04-23 23:21:08 +02:00
parent 0fea8d6065
commit 07b99d91a5
19 changed files with 219 additions and 1131 deletions

View File

@ -1,307 +0,0 @@
import json
import os
import threading
import h5py
import numpy as np
import pyqtgraph as pg
import zmq
from pyqtgraph.Qt import uic
from qtpy.QtCore import Signal as pyqtSignal
from qtpy.QtCore import Slot as pyqtSlot
from qtpy.QtGui import QKeySequence
from qtpy.QtWidgets import QDialog, QFileDialog, QFrame, QLabel, QShortcut, QVBoxLayout, QWidget
# from scipy.stats import multivariate_normal
class EigerPlot(QWidget):
update_signal = pyqtSignal()
def __init__(self, parent=None):
super().__init__(parent)
# pg.setConfigOptions(background="w", foreground="k", antialias=True)
current_path = os.path.dirname(__file__)
uic.loadUi(os.path.join(current_path, "eiger_plot.ui"), self)
# Set widow name
self.setWindowTitle("Eiger Plot")
self.hist_lims = None
self.mask = None
self.image = None
# UI
self.init_ui()
self.hook_signals()
self.key_bindings()
# ZMQ Consumer
self._zmq_consumer_exit_event = threading.Event()
self._zmq_consumer_thread = self.start_zmq_consumer()
def close(self):
super().close()
self._zmq_consumer_exit_event.set()
self._zmq_consumer_thread.join()
def init_ui(self):
# Create Plot and add ImageItem
self.plot_item = pg.PlotItem()
self.plot_item.setAspectLocked(True)
self.imageItem = pg.ImageItem()
self.plot_item.addItem(self.imageItem)
# Setting up histogram
self.hist = pg.HistogramLUTItem()
self.hist.setImageItem(self.imageItem)
self.hist.gradient.loadPreset("magma")
self.update_hist()
# Adding Items to Graphical Layout
self.glw.addItem(self.plot_item)
self.glw.addItem(self.hist)
def hook_signals(self):
# Buttons
# self.pushButton_test.clicked.connect(self.start_sim_stream)
self.pushButton_mask.clicked.connect(self.load_mask_dialog)
self.pushButton_delete_mask.clicked.connect(self.delete_mask)
self.pushButton_help.clicked.connect(self.show_help_dialog)
# SpinBoxes
self.doubleSpinBox_hist_min.valueChanged.connect(self.update_hist)
self.doubleSpinBox_hist_max.valueChanged.connect(self.update_hist)
# Signal/Slots
self.update_signal.connect(self.on_image_update)
def key_bindings(self):
# Key bindings for rotation
rotate_plus = QShortcut(QKeySequence("Ctrl+A"), self)
rotate_minus = QShortcut(QKeySequence("Ctrl+Z"), self)
self.comboBox_rotation.setToolTip("Increase rotation: Ctrl+A\nDecrease rotation: Ctrl+Z")
self.checkBox_transpose.setToolTip("Toggle transpose: Ctrl+T")
max_index = self.comboBox_rotation.count() - 1 # Maximum valid index
rotate_plus.activated.connect(
lambda: self.comboBox_rotation.setCurrentIndex(
min(self.comboBox_rotation.currentIndex() + 1, max_index)
)
)
rotate_minus.activated.connect(
lambda: self.comboBox_rotation.setCurrentIndex(
max(self.comboBox_rotation.currentIndex() - 1, 0)
)
)
# Key bindings for transpose
transpose = QShortcut(QKeySequence("Ctrl+T"), self)
transpose.activated.connect(self.checkBox_transpose.toggle)
FFT = QShortcut(QKeySequence("Ctrl+F"), self)
FFT.activated.connect(self.checkBox_FFT.toggle)
self.checkBox_FFT.setToolTip("Toggle FFT: Ctrl+F")
log = QShortcut(QKeySequence("Ctrl+L"), self)
log.activated.connect(self.checkBox_log.toggle)
self.checkBox_log.setToolTip("Toggle log: Ctrl+L")
mask = QShortcut(QKeySequence("Ctrl+M"), self)
mask.activated.connect(self.pushButton_mask.click)
self.pushButton_mask.setToolTip("Load mask: Ctrl+M")
delete_mask = QShortcut(QKeySequence("Ctrl+D"), self)
delete_mask.activated.connect(self.pushButton_delete_mask.click)
self.pushButton_delete_mask.setToolTip("Delete mask: Ctrl+D")
def update_hist(self):
self.hist_levels = [
self.doubleSpinBox_hist_min.value(),
self.doubleSpinBox_hist_max.value(),
]
self.hist.setLevels(min=self.hist_levels[0], max=self.hist_levels[1])
self.hist.setHistogramRange(
self.hist_levels[0] - 0.1 * self.hist_levels[0],
self.hist_levels[1] + 0.1 * self.hist_levels[1],
)
def load_mask_dialog(self):
options = QFileDialog.Options()
options |= QFileDialog.ReadOnly
file_name, _ = QFileDialog.getOpenFileName(
self, "Select Mask File", "", "H5 Files (*.h5);;All Files (*)", options=options
)
if file_name:
self.load_mask(file_name)
def load_mask(self, path):
try:
with h5py.File(path, "r") as f:
self.mask = f["data"][...]
if self.mask is not None:
# Set label to mask name without path
self.label_mask.setText(os.path.basename(path))
except KeyError as e:
# Update GUI with the error message
print(f"Error: {str(e)}")
def delete_mask(self):
self.mask = None
self.label_mask.setText("No Mask")
@pyqtSlot()
def on_image_update(self):
# TODO first rotate then transpose
if self.mask is not None:
# self.image = np.ma.masked_array(self.image, mask=self.mask) #TODO test if np works
self.image = self.image * (1 - self.mask) + 1
if self.checkBox_FFT.isChecked():
self.image = np.abs(np.fft.fftshift(np.fft.fft2(self.image)))
if self.comboBox_rotation.currentIndex() > 0: # rotate
self.image = np.rot90(self.image, k=self.comboBox_rotation.currentIndex(), axes=(0, 1))
if self.checkBox_transpose.isChecked(): # transpose
self.image = np.transpose(self.image)
if self.checkBox_log.isChecked():
self.image = np.log10(self.image)
self.imageItem.setImage(self.image, autoLevels=False)
###############################
# ZMQ Consumer
###############################
def start_zmq_consumer(self):
consumer_thread = threading.Thread(
target=self.zmq_consumer, args=(self._zmq_consumer_exit_event,), daemon=True
)
consumer_thread.start()
return consumer_thread
def zmq_consumer(self, exit_event):
print("starting consumer")
live_stream_url = "tcp://129.129.95.38:20000"
receiver = zmq.Context().socket(zmq.SUB)
receiver.connect(live_stream_url)
receiver.setsockopt_string(zmq.SUBSCRIBE, "")
poller = zmq.Poller()
poller.register(receiver, zmq.POLLIN)
# code could be a bit simpler here, testing exit_event in
# 'while' condition, but like this it is easier for the
# 'test_zmq_consumer' test
while True:
if poller.poll(1000): # 1s timeout
raw_meta, raw_data = receiver.recv_multipart(zmq.NOBLOCK)
meta = json.loads(raw_meta.decode("utf-8"))
self.image = np.frombuffer(raw_data, dtype=meta["type"]).reshape(meta["shape"])
self.update_signal.emit()
if exit_event.is_set():
break
receiver.disconnect(live_stream_url)
###############################
# just simulations from here
###############################
def show_help_dialog(self):
dialog = QDialog(self)
dialog.setWindowTitle("Help")
layout = QVBoxLayout()
# Key bindings section
layout.addWidget(QLabel("Keyboard Shortcuts:"))
key_bindings = [
("Ctrl+A", "Increase rotation"),
("Ctrl+Z", "Decrease rotation"),
("Ctrl+T", "Toggle transpose"),
("Ctrl+F", "Toggle FFT"),
("Ctrl+L", "Toggle log scale"),
("Ctrl+M", "Load mask"),
("Ctrl+D", "Delete mask"),
]
for keys, action in key_bindings:
layout.addWidget(QLabel(f"{keys} - {action}"))
# Separator
separator = QFrame()
separator.setFrameShape(QFrame.HLine)
separator.setFrameShadow(QFrame.Sunken)
layout.addWidget(separator)
# Histogram section
layout.addWidget(QLabel("Histogram:"))
layout.addWidget(
QLabel(
"Use the Double Spin Boxes to adjust the minimum and maximum values of the histogram."
)
)
# Another Separator
another_separator = QFrame()
another_separator.setFrameShape(QFrame.HLine)
another_separator.setFrameShadow(QFrame.Sunken)
layout.addWidget(another_separator)
# Mask section
layout.addWidget(QLabel("Mask:"))
layout.addWidget(
QLabel(
"Use 'Load Mask' to load a mask from an H5 file. 'Delete Mask' removes the current mask."
)
)
dialog.setLayout(layout)
dialog.exec()
###############################
# just simulations from here
###############################
# def start_sim_stream(self):
# sim_stream_thread = threading.Thread(target=self.sim_stream, daemon=True)
# sim_stream_thread.start()
#
# def sim_stream(self):
# for i in range(100):
# # Generate 100x100 image of random noise
# self.image = np.random.rand(100, 100) * 0.2
#
# # Define Gaussian parameters
# x, y = np.mgrid[0:50, 0:50]
# pos = np.dstack((x, y))
#
# # Center at (25, 25) longer along y-axis
# rv = multivariate_normal(mean=[25, 25], cov=[[25, 0], [0, 80]])
#
# # Generate Gaussian in the first quadrant
# gaussian_quadrant = rv.pdf(pos) * 40
#
# # Place Gaussian in the first quadrant
# self.image[0:50, 0:50] += gaussian_quadrant * 10
#
# self.update_signal.emit()
# time.sleep(0.1)
if __name__ == "__main__":
import sys
from qtpy.QtWidgets import QApplication
app = QApplication(sys.argv)
plot = EigerPlot()
plot.show()
sys.exit(app.exec())

View File

@ -1,207 +0,0 @@
<?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>874</width>
<height>762</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Plot Control</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Histogram MIN</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QDoubleSpinBox" name="doubleSpinBox_hist_min">
<property name="minimum">
<double>-100000.000000000000000</double>
</property>
<property name="maximum">
<double>100000.000000000000000</double>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Histogram MAX</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QDoubleSpinBox" name="doubleSpinBox_hist_max">
<property name="minimum">
<double>-100000.000000000000000</double>
</property>
<property name="maximum">
<double>100000.000000000000000</double>
</property>
<property name="value">
<double>2.000000000000000</double>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Data Control</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QCheckBox" name="checkBox_FFT">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>FFT</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBox_log">
<property name="text">
<string>log</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_mask">
<property name="text">
<string>Load Mask</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_delete_mask">
<property name="text">
<string>Delete Mask</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Orientation</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="2" column="1">
<widget class="QComboBox" name="comboBox_rotation">
<item>
<property name="text">
<string>0</string>
</property>
</item>
<item>
<property name="text">
<string>90</string>
</property>
</item>
<item>
<property name="text">
<string>180</string>
</property>
</item>
<item>
<property name="text">
<string>270</string>
</property>
</item>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Rotation</string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QCheckBox" name="checkBox_transpose">
<property name="text">
<string>Transpose</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_4">
<property name="title">
<string>Help</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="label_mask">
<property name="text">
<string>No Mask</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_help">
<property name="text">
<string>Help</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="GraphicsLayoutWidget" name="glw"/>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>GraphicsLayoutWidget</class>
<extends>QGraphicsView</extends>
<header>pyqtgraph.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -2,7 +2,7 @@ import os
import numpy as np
import pyqtgraph as pg
from pyqtgraph.Qt import QtWidgets, uic
from pyqtgraph.Qt import QtWidgets
from qtconsole.inprocess import QtInProcessKernelManager
from qtconsole.rich_jupyter_widget import RichJupyterWidget
from qtpy.QtCore import QSize
@ -10,7 +10,7 @@ from qtpy.QtGui import QIcon
from qtpy.QtWidgets import QApplication, QVBoxLayout, QWidget
from bec_widgets.cli.rpc_register import RPCRegister
from bec_widgets.utils import BECDispatcher
from bec_widgets.utils import BECDispatcher, UILoader
from bec_widgets.widgets import BECFigure
from bec_widgets.widgets.dock.dock_area import BECDockArea
from bec_widgets.widgets.spiral_progress_bar.spiral_progress_bar import SpiralProgressBar
@ -40,11 +40,11 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
super().__init__(parent)
current_path = os.path.dirname(__file__)
uic.loadUi(os.path.join(current_path, "jupyter_console_window.ui"), self)
self.ui = UILoader().load_ui(os.path.join(current_path, "jupyter_console_window.ui"), self)
self._init_ui()
self.splitter.setSizes([200, 100])
self.ui.splitter.setSizes([200, 100])
self.safe_close = False
# self.figure.clean_signal.connect(self.confirm_close)
@ -75,11 +75,11 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
def _init_ui(self):
# Plotting window
self.glw_1_layout = QVBoxLayout(self.glw) # Create a new QVBoxLayout
self.glw_1_layout = QVBoxLayout(self.ui.glw) # Create a new QVBoxLayout
self.figure = BECFigure(parent=self, gui_id="remote") # Create a new BECDeviceMonitor
self.glw_1_layout.addWidget(self.figure) # Add BECDeviceMonitor to the layout
self.dock_layout = QVBoxLayout(self.dock_placeholder)
self.dock_layout = QVBoxLayout(self.ui.dock_placeholder)
self.dock = BECDockArea(gui_id="remote")
self.dock_layout.addWidget(self.dock)
@ -89,7 +89,7 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
# init dock for testing
self._init_dock()
self.console_layout = QVBoxLayout(self.widget_console)
self.console_layout = QVBoxLayout(self.ui.widget_console)
self.console = JupyterConsoleWidget()
self.console_layout.addWidget(self.console)
self.console.set_default_style("linux")

View File

@ -1,92 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1433</width>
<height>689</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="2">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Plot Config 2</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="BECMonitor" name="plot_1"/>
</item>
<item row="1" column="3">
<widget class="QPushButton" name="pushButton_setting_2">
<property name="text">
<string>Setting Plot 2</string>
</property>
</widget>
</item>
<item row="3" column="2" colspan="2">
<widget class="BECMonitor" name="plot_2"/>
</item>
<item row="1" column="4">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Plot Scan Types = True</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="pushButton_setting_1">
<property name="text">
<string>Setting Plot 1</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Plot Config 1</string>
</property>
</widget>
</item>
<item row="1" column="5">
<widget class="QPushButton" name="pushButton_setting_3">
<property name="text">
<string>Setting Plot 3</string>
</property>
</widget>
</item>
<item row="3" column="4" colspan="2">
<widget class="BECMonitor" name="plot_3"/>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1433</width>
<height>37</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<customwidgets>
<customwidget>
<class>BECMonitor</class>
<extends>QGraphicsView</extends>
<header location="global">bec_widgets.widgets.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -1,197 +0,0 @@
import os
from qtpy import uic
from qtpy.QtWidgets import QApplication, QMainWindow
from bec_widgets.utils.bec_dispatcher import BECDispatcher
from bec_widgets.widgets import BECMonitor
# some default configs for demonstration purposes
CONFIG_SIMPLE = {
"plot_settings": {
"background_color": "black",
"num_columns": 2,
"colormap": "plasma",
"scan_types": False,
},
"plot_data": [
{
"plot_name": "BPM4i plots vs samx",
"x_label": "Motor X",
"y_label": "bpm4i",
"sources": [
{
"type": "scan_segment",
"signals": {
"x": [{"name": "samx"}],
"y": [{"name": "bpm4i", "entry": "bpm4i"}],
},
},
# {
# "type": "history",
# "signals": {
# "x": [{"name": "samx"}],
# "y": [{"name": "bpm4i", "entry": "bpm4i"}],
# },
# },
# {
# "type": "dap",
# 'worker':'some_worker',
# "signals": {
# "x": [{"name": "samx"}],
# "y": [{"name": "bpm4i", "entry": "bpm4i"}],
# },
# },
],
},
{
"plot_name": "Gauss plots vs samx",
"x_label": "Motor X",
"y_label": "Gauss",
"sources": [
{
"type": "scan_segment",
"signals": {
"x": [{"name": "samx", "entry": "samx"}],
"y": [{"name": "gauss_bpm"}, {"name": "gauss_adc1"}],
},
}
],
},
],
}
CONFIG_SCAN_MODE = {
"plot_settings": {
"background_color": "white",
"num_columns": 3,
"colormap": "plasma",
"scan_types": True,
},
"plot_data": {
"grid_scan": [
{
"plot_name": "Grid plot 1",
"x_label": "Motor X",
"y_label": "BPM",
"sources": [
{
"type": "scan_segment",
"signals": {
"x": [{"name": "samx", "entry": "samx"}],
"y": [{"name": "gauss_bpm"}],
},
}
],
},
{
"plot_name": "Grid plot 2",
"x_label": "Motor X",
"y_label": "BPM",
"sources": [
{
"type": "scan_segment",
"signals": {
"x": [{"name": "samx", "entry": "samx"}],
"y": [{"name": "gauss_adc1"}],
},
}
],
},
{
"plot_name": "Grid plot 3",
"x_label": "Motor X",
"y_label": "BPM",
"sources": [
{
"type": "scan_segment",
"signals": {"x": [{"name": "samy"}], "y": [{"name": "gauss_adc2"}]},
}
],
},
{
"plot_name": "Grid plot 4",
"x_label": "Motor X",
"y_label": "BPM",
"sources": [
{
"type": "scan_segment",
"signals": {
"x": [{"name": "samy", "entry": "samy"}],
"y": [{"name": "gauss_adc3"}],
},
}
],
},
],
"line_scan": [
{
"plot_name": "BPM plots vs samx",
"x_label": "Motor X",
"y_label": "Gauss",
"sources": [
{
"type": "scan_segment",
"signals": {
"x": [{"name": "samx", "entry": "samx"}],
"y": [{"name": "bpm4i"}],
},
}
],
},
{
"plot_name": "Gauss plots vs samx",
"x_label": "Motor X",
"y_label": "Gauss",
"sources": [
{
"type": "scan_segment",
"signals": {
"x": [{"name": "samx", "entry": "samx"}],
"y": [{"name": "gauss_bpm"}, {"name": "gauss_adc1"}],
},
}
],
},
],
},
}
class ModularApp(QMainWindow):
def __init__(self, client=None, parent=None):
super(ModularApp, self).__init__(parent)
# Client and device manager from BEC
self.client = BECDispatcher().client if client is None else client
# Loading UI
current_path = os.path.dirname(__file__)
uic.loadUi(os.path.join(current_path, "modular.ui"), self)
self._init_plots()
def _init_plots(self):
"""Initialize plots and connect the buttons to the config dialogs"""
plots = [self.plot_1, self.plot_2, self.plot_3]
configs = [CONFIG_SIMPLE, CONFIG_SCAN_MODE, CONFIG_SCAN_MODE]
buttons = [self.pushButton_setting_1, self.pushButton_setting_2, self.pushButton_setting_3]
# hook plots, configs and buttons together
for plot, config, button in zip(plots, configs, buttons):
plot.on_config_update(config)
button.clicked.connect(plot.show_config_dialog)
if __name__ == "__main__":
# BECclient global variables
client = BECDispatcher().client
client.start()
app = QApplication([])
modularApp = ModularApp(client=client)
window = modularApp
window.show()
app.exec()

View File

@ -151,7 +151,7 @@ class MotorControlPanel(QWidget):
self.selection_widget.selected_motors_signal.connect(self.absolute_widget.change_motors)
# Set the window to a fixed size based on its contents
self.layout().setSizeConstraint(layout.SetFixedSize)
# self.layout().setSizeConstraint(layout.SetFixedSize)
class MotorControlPanelAbsolute(QWidget):
@ -178,9 +178,6 @@ class MotorControlPanelAbsolute(QWidget):
# Connecting signals and slots
self.selection_widget.selected_motors_signal.connect(self.absolute_widget.change_motors)
# Set the window to a fixed size based on its contents
self.layout().setSizeConstraint(layout.SetFixedSize)
class MotorControlPanelRelative(QWidget):
def __init__(self, parent=None, client=None, config=None):
@ -206,9 +203,6 @@ class MotorControlPanelRelative(QWidget):
# Connecting signals and slots
self.selection_widget.selected_motors_signal.connect(self.relative_widget.change_motors)
# Set the window to a fixed size based on its contents
self.layout().setSizeConstraint(layout.SetFixedSize)
if __name__ == "__main__": # pragma: no cover
import argparse

View File

@ -29,10 +29,10 @@
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="GraphicsLayoutWidget" name="glw_plot"/>
<widget class="GraphicsLayoutWidget" name="glw_image"/>
<widget class="QWidget" name="glw_plot_placeholder" native="true"/>
<widget class="QWidget" name="glw_image_placeholder" native="true"/>
</widget>
<widget class="QWidget" name="">
<widget class="QWidget" name="layoutWidget">
<layout class="QVBoxLayout" name="verticalLayout" stretch="1,1,1,15">
<item>
<widget class="QPushButton" name="pushButton_generate">
@ -143,13 +143,6 @@
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>GraphicsLayoutWidget</class>
<extends>QGraphicsView</extends>
<header>pyqtgraph.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -9,18 +9,17 @@ from bec_lib import messages
from bec_lib.endpoints import MessageEndpoints
from bec_lib.redis_connector import RedisConnector
from pyqtgraph import mkBrush, mkPen
from pyqtgraph.Qt import QtCore, QtWidgets, uic
from pyqtgraph.Qt.QtCore import pyqtSignal
from qtpy.QtCore import Slot as pyqtSlot
from qtpy.QtWidgets import QTableWidgetItem
from pyqtgraph.Qt import QtCore, QtWidgets
from qtpy.QtCore import Signal, Slot
from qtpy.QtWidgets import QTableWidgetItem, QVBoxLayout
from bec_widgets.utils import Colors, Crosshair
from bec_widgets.utils import Colors, Crosshair, UILoader
from bec_widgets.utils.bec_dispatcher import BECDispatcher
class StreamPlot(QtWidgets.QWidget):
update_signal = pyqtSignal()
roi_signal = pyqtSignal(tuple)
update_signal = Signal()
roi_signal = Signal(tuple)
def __init__(self, name="", y_value_list=["gauss_bpm"], client=None, parent=None) -> None:
"""
@ -39,7 +38,7 @@ class StreamPlot(QtWidgets.QWidget):
pg.setConfigOption("background", "w")
pg.setConfigOption("foreground", "k")
current_path = os.path.dirname(__file__)
uic.loadUi(os.path.join(current_path, "line_plot.ui"), self)
self.ui = UILoader().load_ui(os.path.join(current_path, "line_plot.ui"), self)
self._idle_time = 100
self.connector = RedisConnector(["localhost:6379"])
@ -82,6 +81,9 @@ class StreamPlot(QtWidgets.QWidget):
# LabelItem for ROI
self.label_plot = pg.LabelItem(justify="center")
self.glw_plot_layout = QVBoxLayout(self.ui.glw_plot_placeholder)
self.glw_plot = pg.GraphicsLayoutWidget()
self.glw_plot_layout.addWidget(self.glw_plot)
self.glw_plot.addItem(self.label_plot)
self.label_plot.setText("ROI region")
@ -112,6 +114,9 @@ class StreamPlot(QtWidgets.QWidget):
# Label for coordinates moved
self.label_image_moved = pg.LabelItem(justify="center")
self.glw_image_layout = QVBoxLayout(self.ui.glw_image_placeholder)
self.glw_image = pg.GraphicsLayoutWidget()
self.glw_plot_layout.addWidget(self.glw_image)
self.glw_image.addItem(self.label_image_moved)
self.label_image_moved.setText("Actual coordinates (X, Y)")
@ -221,10 +226,10 @@ class StreamPlot(QtWidgets.QWidget):
def init_table(self):
# Init number of rows in table according to n of devices
self.cursor_table.setRowCount(len(self.y_value_list))
self.ui.cursor_table.setRowCount(len(self.y_value_list))
# self.table.setHorizontalHeaderLabels(["(X, Y) - Moved", "(X, Y) - Clicked"]) #TODO can be dynamic
self.cursor_table.setVerticalHeaderLabels(self.y_value_list)
self.cursor_table.resizeColumnsToContents()
self.ui.cursor_table.setVerticalHeaderLabels(self.y_value_list)
self.ui.cursor_table.resizeColumnsToContents()
def update_table(self, table_widget, x, y_values):
for i, y in enumerate(y_values):
@ -287,13 +292,13 @@ class StreamPlot(QtWidgets.QWidget):
self.update_signal.emit()
@pyqtSlot(dict, dict)
@Slot(dict, dict)
def on_dap_update(self, data: dict, metadata: dict):
flipped_data = self.flip_even_rows(data["data"]["z"])
self.img.setImage(flipped_data)
@pyqtSlot(dict, dict)
@Slot(dict, dict)
def new_proj(self, content: dict, _metadata: dict):
proj_nr = content["signals"]["proj_nr"]
endpoint = f"px_stream/projection_{proj_nr}/metadata"

View File

@ -8,13 +8,13 @@ from qtpy.QtCore import Signal as pyqtSignal
class Crosshair(QObject):
# Signal for 1D plot
coordinatesChanged1D = pyqtSignal(float, list)
coordinatesClicked1D = pyqtSignal(float, list)
coordinatesChanged1D = pyqtSignal(tuple)
coordinatesClicked1D = pyqtSignal(tuple)
# Signal for 2D plot
coordinatesChanged2D = pyqtSignal(float, float)
coordinatesClicked2D = pyqtSignal(float, float)
coordinatesChanged2D = pyqtSignal(tuple)
coordinatesClicked2D = pyqtSignal(tuple)
def __init__(self, plot_item: pg.PlotItem, precision: int = None, parent=None):
def __init__(self, plot_item: pg.PlotItem, precision: int = 3, parent=None):
"""
Crosshair for 1D and 2D plots.
@ -174,10 +174,11 @@ class Crosshair(QObject):
if isinstance(item, pg.PlotDataItem):
if x is None or all(v is None for v in y_values):
return
self.coordinatesChanged1D.emit(
coordinate_to_emit = (
round(x, self.precision),
[round(y_val, self.precision) for y_val in y_values],
)
self.coordinatesChanged1D.emit(coordinate_to_emit)
for i, y_val in enumerate(y_values):
self.marker_moved_1d[i].setData(
[x if not self.is_log_x else np.log10(x)],
@ -186,7 +187,8 @@ class Crosshair(QObject):
elif isinstance(item, pg.ImageItem):
if x is None or y_values is None:
return
self.coordinatesChanged2D.emit(x, y_values)
coordinate_to_emit = (x, y_values)
self.coordinatesChanged2D.emit(coordinate_to_emit)
def mouse_clicked(self, event):
"""Handles the mouse clicked event, updating the crosshair position and emitting signals.
@ -209,10 +211,11 @@ class Crosshair(QObject):
if isinstance(item, pg.PlotDataItem):
if x is None or all(v is None for v in y_values):
return
self.coordinatesClicked1D.emit(
coordinate_to_emit = (
round(x, self.precision),
[round(y_val, self.precision) for y_val in y_values],
)
self.coordinatesClicked1D.emit(coordinate_to_emit)
for i, y_val in enumerate(y_values):
for marker in self.marker_clicked_1d[i]:
marker.setData(
@ -222,7 +225,8 @@ class Crosshair(QObject):
elif isinstance(item, pg.ImageItem):
if x is None or y_values is None:
return
self.coordinatesClicked2D.emit(x, y_values)
coordinate_to_emit = (x, y_values)
self.coordinatesClicked2D.emit(coordinate_to_emit)
self.marker_2d.setPos([x, y_values])
def check_log(self):

View File

@ -16,6 +16,7 @@ from qtpy.QtWidgets import (
QTableWidgetItem,
)
from bec_widgets.utils import UILoader
from bec_widgets.widgets.motor_control.motor_control import MotorControlWidget
@ -37,25 +38,25 @@ class MotorCoordinateTable(MotorControlWidget):
def _load_ui(self):
"""Load the UI for the coordinate table."""
current_path = os.path.dirname(__file__)
uic.loadUi(os.path.join(current_path, "motor_table.ui"), self)
self.ui = UILoader().load_ui(os.path.join(current_path, "motor_table.ui"), self)
def _init_ui(self):
"""Initialize the UI"""
# Setup table behaviour
self._setup_table()
self.table.setSelectionBehavior(QTableWidget.SelectRows)
self.ui.table.setSelectionBehavior(QTableWidget.SelectRows)
# for tag columns default tag
self.tag_counter = 1
# Connect signals and slots
self.checkBox_resize_auto.stateChanged.connect(self.resize_table_auto)
self.comboBox_mode.currentIndexChanged.connect(self.mode_switch)
self.ui.checkBox_resize_auto.stateChanged.connect(self.resize_table_auto)
self.ui.comboBox_mode.currentIndexChanged.connect(self.mode_switch)
# Keyboard shortcuts for deleting a row
self.delete_shortcut = QShortcut(QKeySequence(Qt.Key_Delete), self.table)
self.delete_shortcut = QShortcut(QKeySequence(Qt.Key_Delete), self.ui.table)
self.delete_shortcut.activated.connect(self.delete_selected_row)
self.backspace_shortcut = QShortcut(QKeySequence(Qt.Key_Backspace), self.table)
self.backspace_shortcut = QShortcut(QKeySequence(Qt.Key_Backspace), self.ui.table)
self.backspace_shortcut.activated.connect(self.delete_selected_row)
# Warning message for mode switch enable/disable
@ -83,13 +84,13 @@ class MotorCoordinateTable(MotorControlWidget):
self.mode = self.config["motor_control"].get("mode", "Individual")
# Set combobox to default mode
self.comboBox_mode.setCurrentText(self.mode)
self.ui.comboBox_mode.setCurrentText(self.mode)
self._init_ui()
def _setup_table(self):
"""Setup the table with appropriate headers and configurations."""
mode = self.comboBox_mode.currentText()
mode = self.ui.comboBox_mode.currentText()
if mode == "Individual":
self._setup_individual_mode()
@ -101,14 +102,14 @@ class MotorCoordinateTable(MotorControlWidget):
def _setup_individual_mode(self):
"""Setup the table for individual mode."""
self.table.setColumnCount(5)
self.table.setHorizontalHeaderLabels(["Show", "Move", "Tag", "X", "Y"])
self.table.verticalHeader().setVisible(False)
self.ui.table.setColumnCount(5)
self.ui.table.setHorizontalHeaderLabels(["Show", "Move", "Tag", "X", "Y"])
self.ui.table.verticalHeader().setVisible(False)
def _setup_start_stop_mode(self):
"""Setup the table for start/stop mode."""
self.table.setColumnCount(8)
self.table.setHorizontalHeaderLabels(
self.ui.table.setColumnCount(8)
self.ui.table.setHorizontalHeaderLabels(
[
"Show",
"Move [start]",
@ -120,15 +121,15 @@ class MotorCoordinateTable(MotorControlWidget):
"Y [end]",
]
)
self.table.verticalHeader().setVisible(False)
self.ui.table.verticalHeader().setVisible(False)
# Set flag to track if the coordinate is stat or the end of the entry
self.is_next_entry_end = False
def mode_switch(self):
"""Switch between individual and start/stop mode."""
last_selected_index = self.comboBox_mode.currentIndex()
last_selected_index = self.ui.comboBox_mode.currentIndex()
if self.table.rowCount() > 0 and self.warning_message is True:
if self.ui.table.rowCount() > 0 and self.warning_message is True:
msgBox = QMessageBox()
msgBox.setIcon(QMessageBox.Critical)
msgBox.setText(
@ -138,9 +139,9 @@ class MotorCoordinateTable(MotorControlWidget):
returnValue = msgBox.exec()
if returnValue is QMessageBox.Cancel:
self.comboBox_mode.blockSignals(True) # Block signals
self.comboBox_mode.setCurrentIndex(last_selected_index)
self.comboBox_mode.blockSignals(False) # Unblock signals
self.ui.comboBox_mode.blockSignals(True) # Block signals
self.ui.comboBox_mode.setCurrentIndex(last_selected_index)
self.ui.comboBox_mode.blockSignals(False) # Unblock signals
return
# Wipe table
@ -170,7 +171,7 @@ class MotorCoordinateTable(MotorControlWidget):
y(float): Y coordinate.
"""
mode = self.comboBox_mode.currentText()
mode = self.ui.comboBox_mode.currentText()
if mode == "Individual":
checkbox_pos = 0
button_pos = 1
@ -181,8 +182,8 @@ class MotorCoordinateTable(MotorControlWidget):
color = "green"
# Add new row -> new entry
row_count = self.table.rowCount()
self.table.insertRow(row_count)
row_count = self.ui.table.rowCount()
self.ui.table.insertRow(row_count)
# Add Widgets
self._add_widgets(
@ -213,8 +214,8 @@ class MotorCoordinateTable(MotorControlWidget):
color = "blue"
# Add new row -> new entry
row_count = self.table.rowCount()
self.table.insertRow(row_count)
row_count = self.ui.table.rowCount()
self.ui.table.insertRow(row_count)
# Add Widgets
self._add_widgets(
@ -236,7 +237,7 @@ class MotorCoordinateTable(MotorControlWidget):
elif self.is_next_entry_end is True: # It is the end position of the entry
print("End position")
row_count = self.table.rowCount() - 1 # Current row
row_count = self.ui.table.rowCount() - 1 # Current row
button_pos = 2
x_pos = 6
y_pos = 7
@ -294,7 +295,7 @@ class MotorCoordinateTable(MotorControlWidget):
# Add widgets
self._add_checkbox(row, checkBox_pos, x_pos, y_pos)
self._add_move_button(row, button_pos, x_pos, y_pos)
self.table.setItem(row, tag_pos, QTableWidgetItem(tag))
self.ui.table.setItem(row, tag_pos, QTableWidgetItem(tag))
self._add_line_edit(x, row, x_pos, x_pos, y_pos, coordinate_reference, color)
self._add_line_edit(y, row, y_pos, x_pos, y_pos, coordinate_reference, color)
@ -302,10 +303,10 @@ class MotorCoordinateTable(MotorControlWidget):
self.emit_plot_coordinates(x_pos, y_pos, coordinate_reference, color)
# Connect item edit to emit coordinates
self.table.itemChanged.connect(
self.ui.table.itemChanged.connect(
lambda: print(f"item changed from {coordinate_reference} slot \n {x}-{y}-{color}")
)
self.table.itemChanged.connect(
self.ui.table.itemChanged.connect(
lambda: self.emit_plot_coordinates(x_pos, y_pos, coordinate_reference, color)
)
@ -321,7 +322,7 @@ class MotorCoordinateTable(MotorControlWidget):
show_checkbox = QCheckBox()
show_checkbox.setChecked(True)
show_checkbox.stateChanged.connect(lambda: self.emit_plot_coordinates(x_pos, y_pos))
self.table.setCellWidget(row, checkBox_pos, show_checkbox)
self.ui.table.setCellWidget(row, checkBox_pos, show_checkbox)
def _add_move_button(self, row: int, button_pos: int, x_pos: int, y_pos: int) -> None:
"""
@ -334,7 +335,7 @@ class MotorCoordinateTable(MotorControlWidget):
"""
move_button = QPushButton("Move")
move_button.clicked.connect(lambda: self.handle_move_button_click(x_pos, y_pos))
self.table.setCellWidget(row, button_pos, move_button)
self.ui.table.setCellWidget(row, button_pos, move_button)
def _add_line_edit(
self,
@ -367,7 +368,7 @@ class MotorCoordinateTable(MotorControlWidget):
edit.setAlignment(Qt.AlignmentFlag.AlignCenter)
# Add line edit to the table
self.table.setCellWidget(row, line_pos, edit)
self.ui.table.setCellWidget(row, line_pos, edit)
edit.textChanged.connect(
lambda: self.emit_plot_coordinates(x_pos, y_pos, coordinate_reference, color)
)
@ -375,10 +376,10 @@ class MotorCoordinateTable(MotorControlWidget):
def wipe_motor_map_coordinates(self):
"""Wipe the motor map coordinates."""
try:
self.table.itemChanged.disconnect() # Disconnect all previous connections
self.ui.table.itemChanged.disconnect() # Disconnect all previous connections
except TypeError:
print("No previous connections to disconnect")
self.table.setRowCount(0)
self.ui.table.setRowCount(0)
reference_tags = ["Individual", "Start", "Stop"]
for reference_tag in reference_tags:
self.plot_coordinates_signal.emit([], reference_tag, "green")
@ -391,7 +392,7 @@ class MotorCoordinateTable(MotorControlWidget):
y_pos(int): Y position of the coordinate.
"""
button = self.sender()
row = self.table.indexAt(button.pos()).row()
row = self.ui.table.indexAt(button.pos()).row()
x = self.get_coordinate(row, x_pos)
y = self.get_coordinate(row, y_pos)
@ -410,8 +411,8 @@ class MotorCoordinateTable(MotorControlWidget):
f"Emitting plot coordinates: x_pos={x_pos}, y_pos={y_pos}, reference_tag={reference_tag}, color={color}"
)
coordinates = []
for row in range(self.table.rowCount()):
show = self.table.cellWidget(row, 0).isChecked()
for row in range(self.ui.table.rowCount()):
show = self.ui.table.cellWidget(row, 0).isChecked()
x = self.get_coordinate(row, x_pos)
y = self.get_coordinate(row, y_pos)
@ -427,27 +428,27 @@ class MotorCoordinateTable(MotorControlWidget):
Returns:
float: Value of the coordinate.
"""
edit = self.table.cellWidget(row, column)
edit = self.ui.table.cellWidget(row, column)
value = float(edit.text()) if edit and edit.text() != "" else None
if value:
return value
def delete_selected_row(self):
"""Delete the selected row from the table."""
selected_rows = self.table.selectionModel().selectedRows()
selected_rows = self.ui.table.selectionModel().selectedRows()
for row in selected_rows:
self.table.removeRow(row.row())
if self.comboBox_mode.currentText() == "Start/Stop":
self.ui.table.removeRow(row.row())
if self.ui.comboBox_mode.currentText() == "Start/Stop":
self.emit_plot_coordinates(x_pos=4, y_pos=5, reference_tag="Start", color="blue")
self.emit_plot_coordinates(x_pos=6, y_pos=7, reference_tag="Stop", color="red")
self.is_next_entry_end = False
elif self.comboBox_mode.currentText() == "Individual":
elif self.ui.comboBox_mode.currentText() == "Individual":
self.emit_plot_coordinates(x_pos=3, y_pos=4, reference_tag="Individual", color="green")
def resize_table_auto(self):
"""Resize the table to fit the contents."""
if self.checkBox_resize_auto.isChecked():
self.table.resizeColumnsToContents()
if self.ui.checkBox_resize_auto.isChecked():
self.ui.table.resizeColumnsToContents()
def move_motor(self, x: float, y: float) -> None:
"""

View File

@ -3,8 +3,10 @@ import os
from qtpy import uic
from qtpy.QtCore import Signal as pyqtSignal
from qtpy.QtCore import Slot as pyqtSlot
from qtpy.QtWidgets import QWidget
from bec_widgets.widgets.motor_control.motor_control import MotorControlWidget
from bec_widgets.utils import UILoader
from bec_widgets.widgets.motor_control.motor_control import MotorControlErrors, MotorControlWidget
class MotorControlAbsolute(MotorControlWidget):
@ -23,26 +25,26 @@ class MotorControlAbsolute(MotorControlWidget):
def _load_ui(self):
"""Load the UI from the .ui file."""
current_path = os.path.dirname(__file__)
uic.loadUi(os.path.join(current_path, "movement_absolute.ui"), self)
self.ui = UILoader().load_ui(os.path.join(current_path, "movement_absolute.ui"), self)
def _init_ui(self):
"""Initialize the UI."""
# Check if there are any motors connected
if self.motor_x is None or self.motor_y is None:
self.motorControl_absolute.setEnabled(False)
self.ui.motorControl_absolute.setEnabled(False)
return
# Move to absolute coordinates
self.pushButton_go_absolute.clicked.connect(
self.ui.pushButton_go_absolute.clicked.connect(
lambda: self.move_motor_absolute(
self.spinBox_absolute_x.value(), self.spinBox_absolute_y.value()
self.ui.spinBox_absolute_x.value(), self.ui.spinBox_absolute_y.value()
)
)
self.pushButton_set.clicked.connect(self.save_absolute_coordinates)
self.pushButton_save.clicked.connect(self.save_current_coordinates)
self.pushButton_stop.clicked.connect(self.motor_thread.stop_movement)
self.ui.pushButton_set.clicked.connect(self.save_absolute_coordinates)
self.ui.pushButton_save.clicked.connect(self.save_current_coordinates)
self.ui.pushButton_stop.clicked.connect(self.motor_thread.stop_movement)
# Enable/Disable GUI
self.motor_thread.lock_gui.connect(self.enable_motor_controls)
@ -80,11 +82,11 @@ class MotorControlAbsolute(MotorControlWidget):
"""
# Disable or enable all controls within the motorControl_absolute group box
for widget in self.motorControl_absolute.findChildren(QWidget):
for widget in self.ui.motorControl_absolute.findChildren(QWidget):
widget.setEnabled(enable)
# Enable the pushButton_stop if the motor is moving
self.pushButton_stop.setEnabled(True)
self.ui.pushButton_stop.setEnabled(True)
@pyqtSlot(str, str)
def change_motors(self, motor_x: str, motor_y: str):
@ -109,8 +111,8 @@ class MotorControlAbsolute(MotorControlWidget):
"""
self.precision = precision
self.config["motor_control"]["precision"] = precision
self.spinBox_absolute_x.setDecimals(precision)
self.spinBox_absolute_y.setDecimals(precision)
self.ui.spinBox_absolute_x.setDecimals(precision)
self.ui.spinBox_absolute_y.setDecimals(precision)
def move_motor_absolute(self, x: float, y: float) -> None:
"""
@ -122,32 +124,32 @@ class MotorControlAbsolute(MotorControlWidget):
# self._enable_motor_controls(False)
target_coordinates = (x, y)
self.motor_thread.move_absolute(self.motor_x, self.motor_y, target_coordinates)
if self.checkBox_save_with_go.isChecked():
if self.ui.checkBox_save_with_go.isChecked():
self.save_absolute_coordinates()
def _init_keyboard_shortcuts(self):
"""Initialize the keyboard shortcuts."""
# Go absolute button
self.pushButton_go_absolute.setShortcut("Ctrl+G")
self.pushButton_go_absolute.setToolTip("Ctrl+G")
self.ui.pushButton_go_absolute.setShortcut("Ctrl+G")
self.ui.pushButton_go_absolute.setToolTip("Ctrl+G")
# Set absolute coordinates
self.pushButton_set.setShortcut("Ctrl+D")
self.pushButton_set.setToolTip("Ctrl+D")
self.ui.pushButton_set.setShortcut("Ctrl+D")
self.ui.pushButton_set.setToolTip("Ctrl+D")
# Save Current coordinates
self.pushButton_save.setShortcut("Ctrl+S")
self.pushButton_save.setToolTip("Ctrl+S")
self.ui.pushButton_save.setShortcut("Ctrl+S")
self.ui.pushButton_save.setToolTip("Ctrl+S")
# Stop Button
self.pushButton_stop.setShortcut("Ctrl+X")
self.pushButton_stop.setToolTip("Ctrl+X")
self.ui.pushButton_stop.setShortcut("Ctrl+X")
self.ui.pushButton_stop.setToolTip("Ctrl+X")
def save_absolute_coordinates(self):
"""Emit the setup coordinates from the spinboxes"""
x, y = round(self.spinBox_absolute_x.value(), self.precision), round(
self.spinBox_absolute_y.value(), self.precision
x, y = round(self.ui.spinBox_absolute_x.value(), self.precision), round(
self.ui.spinBox_absolute_y.value(), self.precision
)
self.coordinates_signal.emit((x, y))

View File

@ -7,6 +7,7 @@ from qtpy.QtCore import Slot as pyqtSlot
from qtpy.QtGui import QKeySequence
from qtpy.QtWidgets import QDoubleSpinBox, QShortcut, QWidget
from bec_widgets.utils import UILoader
from bec_widgets.widgets.motor_control.motor_control import MotorControlWidget
@ -27,7 +28,7 @@ class MotorControlRelative(MotorControlWidget):
"""Load the UI from the .ui file."""
# Loading UI
current_path = os.path.dirname(__file__)
uic.loadUi(os.path.join(current_path, "movement_relative.ui"), self)
self.ui = UILoader().load_ui(os.path.join(current_path, "movement_relative.ui"), self)
def _init_ui(self):
"""Initialize the UI."""
@ -51,15 +52,15 @@ class MotorControlRelative(MotorControlWidget):
# Update step precision
self.precision = self.config["motor_control"]["precision"]
self.spinBox_precision.setValue(self.precision)
self.ui.spinBox_precision.setValue(self.precision)
# Update step sizes
self.spinBox_step_x.setValue(self.config["motor_control"]["step_size_x"])
self.spinBox_step_y.setValue(self.config["motor_control"]["step_size_y"])
self.ui.spinBox_step_x.setValue(self.config["motor_control"]["step_size_x"])
self.ui.spinBox_step_y.setValue(self.config["motor_control"]["step_size_y"])
# Checkboxes for keyboard shortcuts and x/y step size link
self.checkBox_same_xy.setChecked(self.config["motor_control"]["step_x_y_same"])
self.checkBox_enableArrows.setChecked(self.config["motor_control"]["move_with_arrows"])
self.ui.checkBox_same_xy.setChecked(self.config["motor_control"]["step_x_y_same"])
self.ui.checkBox_enableArrows.setChecked(self.config["motor_control"]["move_with_arrows"])
self._init_ui()
@ -67,30 +68,32 @@ class MotorControlRelative(MotorControlWidget):
"""Initialize the motor control elements"""
# Connect checkbox and spinBoxes
self.checkBox_same_xy.stateChanged.connect(self._sync_step_sizes)
self.spinBox_step_x.valueChanged.connect(self._update_step_size_x)
self.spinBox_step_y.valueChanged.connect(self._update_step_size_y)
self.ui.checkBox_same_xy.stateChanged.connect(self._sync_step_sizes)
self.ui.spinBox_step_x.valueChanged.connect(self._update_step_size_x)
self.ui.spinBox_step_y.valueChanged.connect(self._update_step_size_y)
self.toolButton_right.clicked.connect(
self.ui.toolButton_right.clicked.connect(
lambda: self.move_motor_relative(self.motor_x, "x", 1)
)
self.toolButton_left.clicked.connect(
self.ui.toolButton_left.clicked.connect(
lambda: self.move_motor_relative(self.motor_x, "x", -1)
)
self.toolButton_up.clicked.connect(lambda: self.move_motor_relative(self.motor_y, "y", 1))
self.toolButton_down.clicked.connect(
self.ui.toolButton_up.clicked.connect(
lambda: self.move_motor_relative(self.motor_y, "y", 1)
)
self.ui.toolButton_down.clicked.connect(
lambda: self.move_motor_relative(self.motor_y, "y", -1)
)
# Switch between key shortcuts active
self.checkBox_enableArrows.stateChanged.connect(self._update_arrow_key_shortcuts)
self.ui.checkBox_enableArrows.stateChanged.connect(self._update_arrow_key_shortcuts)
self._update_arrow_key_shortcuts()
# Enable/Disable GUI
self.motor_thread.lock_gui.connect(self.enable_motor_controls)
# Precision update
self.spinBox_precision.valueChanged.connect(lambda x: self._update_precision(x))
self.ui.spinBox_precision.valueChanged.connect(lambda x: self._update_precision(x))
# Error messages
self.motor_thread.motor_error.connect(
@ -98,7 +101,7 @@ class MotorControlRelative(MotorControlWidget):
)
# Stop Button
self.pushButton_stop.clicked.connect(self.motor_thread.stop_movement)
self.ui.pushButton_stop.clicked.connect(self.motor_thread.stop_movement)
def _init_keyboard_shortcuts(self) -> None:
"""Initialize the keyboard shortcuts"""
@ -107,42 +110,42 @@ class MotorControlRelative(MotorControlWidget):
increase_x_shortcut = QShortcut(QKeySequence("Ctrl+A"), self)
decrease_x_shortcut = QShortcut(QKeySequence("Ctrl+Z"), self)
increase_x_shortcut.activated.connect(
lambda: self._change_step_size(self.spinBox_step_x, 2)
lambda: self._change_step_size(self.ui.spinBox_step_x, 2)
)
decrease_x_shortcut.activated.connect(
lambda: self._change_step_size(self.spinBox_step_x, 0.5)
lambda: self._change_step_size(self.ui.spinBox_step_x, 0.5)
)
self.spinBox_step_x.setToolTip("Increase step size: Ctrl+A\nDecrease step size: Ctrl+Z")
self.ui.spinBox_step_x.setToolTip("Increase step size: Ctrl+A\nDecrease step size: Ctrl+Z")
# Increase/decrease step size for Y motor
increase_y_shortcut = QShortcut(QKeySequence("Alt+A"), self)
decrease_y_shortcut = QShortcut(QKeySequence("Alt+Z"), self)
increase_y_shortcut.activated.connect(
lambda: self._change_step_size(self.spinBox_step_y, 2)
lambda: self._change_step_size(self.ui.spinBox_step_y, 2)
)
decrease_y_shortcut.activated.connect(
lambda: self._change_step_size(self.spinBox_step_y, 0.5)
lambda: self._change_step_size(self.ui.spinBox_step_y, 0.5)
)
self.spinBox_step_y.setToolTip("Increase step size: Alt+A\nDecrease step size: Alt+Z")
self.ui.spinBox_step_y.setToolTip("Increase step size: Alt+A\nDecrease step size: Alt+Z")
# Stop Button
self.pushButton_stop.setShortcut("Ctrl+X")
self.pushButton_stop.setToolTip("Ctrl+X")
self.ui.pushButton_stop.setShortcut("Ctrl+X")
self.ui.pushButton_stop.setToolTip("Ctrl+X")
def _update_arrow_key_shortcuts(self) -> None:
"""Update the arrow key shortcuts based on the checkbox state."""
if self.checkBox_enableArrows.isChecked():
if self.ui.checkBox_enableArrows.isChecked():
# Set the arrow key shortcuts for motor movement
self.toolButton_right.setShortcut(Qt.Key_Right)
self.toolButton_left.setShortcut(Qt.Key_Left)
self.toolButton_up.setShortcut(Qt.Key_Up)
self.toolButton_down.setShortcut(Qt.Key_Down)
self.ui.toolButton_right.setShortcut(Qt.Key_Right)
self.ui.toolButton_left.setShortcut(Qt.Key_Left)
self.ui.toolButton_up.setShortcut(Qt.Key_Up)
self.ui.toolButton_down.setShortcut(Qt.Key_Down)
else:
# Clear the shortcuts
self.toolButton_right.setShortcut("")
self.toolButton_left.setShortcut("")
self.toolButton_up.setShortcut("")
self.toolButton_down.setShortcut("")
self.ui.toolButton_right.setShortcut("")
self.ui.toolButton_left.setShortcut("")
self.ui.toolButton_up.setShortcut("")
self.ui.toolButton_down.setShortcut("")
def _update_precision(self, precision: int) -> None:
"""
@ -150,8 +153,8 @@ class MotorControlRelative(MotorControlWidget):
Args:
precision(int): Precision of the coordinates.
"""
self.spinBox_step_x.setDecimals(precision)
self.spinBox_step_y.setDecimals(precision)
self.ui.spinBox_step_x.setDecimals(precision)
self.ui.spinBox_step_y.setDecimals(precision)
self.precision_signal.emit(precision)
def _change_step_size(self, spinBox: QDoubleSpinBox, factor: float) -> None:
@ -167,21 +170,21 @@ class MotorControlRelative(MotorControlWidget):
def _sync_step_sizes(self):
"""Sync step sizes based on checkbox state."""
if self.checkBox_same_xy.isChecked():
value = self.spinBox_step_x.value()
self.spinBox_step_y.setValue(value)
if self.ui.checkBox_same_xy.isChecked():
value = self.ui.spinBox_step_x.value()
self.ui.spinBox_step_y.setValue(value)
def _update_step_size_x(self):
"""Update step size for x if checkbox is checked."""
if self.checkBox_same_xy.isChecked():
value = self.spinBox_step_x.value()
self.spinBox_step_y.setValue(value)
if self.ui.checkBox_same_xy.isChecked():
value = self.ui.spinBox_step_x.value()
self.ui.spinBox_step_y.setValue(value)
def _update_step_size_y(self):
"""Update step size for y if checkbox is checked."""
if self.checkBox_same_xy.isChecked():
value = self.spinBox_step_y.value()
self.spinBox_step_x.setValue(value)
if self.ui.checkBox_same_xy.isChecked():
value = self.ui.spinBox_step_y.value()
self.ui.spinBox_step_x.setValue(value)
@pyqtSlot(str, str)
def change_motors(self, motor_x: str, motor_y: str):
@ -206,11 +209,11 @@ class MotorControlRelative(MotorControlWidget):
"""
# Disable or enable all controls within the motorControl_absolute group box
for widget in self.motorControl.findChildren(QWidget):
for widget in self.ui.motorControl.findChildren(QWidget):
widget.setEnabled(disable)
# Enable the pushButton_stop if the motor is moving
self.pushButton_stop.setEnabled(True)
self.ui.pushButton_stop.setEnabled(True)
def move_motor_relative(self, motor, axis: str, direction: int) -> None:
"""
@ -221,7 +224,7 @@ class MotorControlRelative(MotorControlWidget):
direction(int): Direction to move. 1 for positive, -1 for negative.
"""
if axis == "x":
step = direction * self.spinBox_step_x.value()
step = direction * self.ui.spinBox_step_x.value()
elif axis == "y":
step = direction * self.spinBox_step_y.value()
step = direction * self.ui.spinBox_step_y.value()
self.motor_thread.move_relative(motor, step)

View File

@ -150,7 +150,7 @@ def test_spiral_bar(rpc_server_dock):
dock = BECDockArea(rpc_server_dock.gui_id)
dock_server = rpc_server_dock.gui
d0 = dock.add_dock("dock_0")
d0 = dock.add_dock(name="dock_0")
bar = d0.add_widget_bec("SpiralProgressBar")
assert bar.__class__.__name__ == "SpiralProgressBar"

View File

@ -17,8 +17,8 @@ def cli_figure():
def test_rpc_call_plot(cli_figure):
fig, mock_rpc_call = cli_figure
fig.plot("samx", "bpm4i")
mock_rpc_call.assert_called_with("plot", "samx", "bpm4i")
fig.plot(x_name="samx", y_name="bpm4i")
mock_rpc_call.assert_called_with("plot", x_name="samx", y_name="bpm4i")
def test_rpc_call_accepts_device_as_input(cli_figure):

View File

@ -44,8 +44,8 @@ def test_mouse_moved_signals(qtbot):
# Create a slot that will store the emitted values as tuples
emitted_values_1D = []
def slot(x, y_values):
emitted_values_1D.append((x, y_values))
def slot(coordinates):
emitted_values_1D.append(coordinates)
# Connect the signal to the custom slot
crosshair.coordinatesChanged1D.connect(slot)
@ -59,7 +59,7 @@ def test_mouse_moved_signals(qtbot):
crosshair.mouse_moved(event_mock)
# Assert the expected behavior
assert emitted_values_1D == [(2.0, [5.0])]
assert emitted_values_1D == [(2, [5])]
def test_mouse_moved_signals_outside(qtbot):
@ -106,8 +106,8 @@ def test_mouse_moved_signals_2D(qtbot):
# Create a slot that will store the emitted values as tuples
emitted_values_2D = []
def slot(x, y):
emitted_values_2D.append((x, y))
def slot(coordinates):
emitted_values_2D.append(coordinates)
# Connect the signal to the custom slot
crosshair.coordinatesChanged2D.connect(slot)

View File

@ -1,115 +0,0 @@
# pylint: disable = no-name-in-module,missing-class-docstring, missing-module-docstring
import json
from unittest.mock import MagicMock, patch
import numpy as np
import pyqtgraph as pg
import pytest
import zmq
from bec_widgets.examples.eiger_plot.eiger_plot import EigerPlot
# Common fixture for all tests
@pytest.fixture
def eiger_plot_instance(qtbot):
widget = EigerPlot()
qtbot.addWidget(widget)
qtbot.waitExposed(widget)
yield widget
widget.close()
@pytest.mark.parametrize(
"fft_checked, rotation_index, transpose_checked, log_checked, expected_image",
[
(False, 0, False, False, np.array([[2, 1], [1, 5]], dtype=float)), # just mask
(False, 1, False, False, np.array([[1, 5], [2, 1]], dtype=float)), # 90 deg rotation
(False, 2, False, False, np.array([[5, 1], [1, 2]], dtype=float)), # 180 deg rotation
(False, 0, True, False, np.array([[2, 1], [1, 5]], dtype=float)), # transposed
(False, 0, False, True, np.array([[0.30103, 0.0], [0.0, 0.69897]], dtype=float)), # log
(True, 0, False, False, np.array([[5.0, 3.0], [3.0, 9.0]], dtype=float)), # FFT
],
)
def test_on_image_update(
qtbot,
eiger_plot_instance,
fft_checked,
rotation_index,
transpose_checked,
log_checked,
expected_image,
):
# Initialize image and mask
eiger_plot_instance.image = np.array([[1, 2], [3, 4]], dtype=float)
eiger_plot_instance.mask = np.array([[0, 1], [1, 0]], dtype=float)
# Mock UI elements
eiger_plot_instance.checkBox_FFT = MagicMock()
eiger_plot_instance.checkBox_FFT.isChecked.return_value = fft_checked
eiger_plot_instance.comboBox_rotation = MagicMock()
eiger_plot_instance.comboBox_rotation.currentIndex.return_value = rotation_index
eiger_plot_instance.checkBox_transpose = MagicMock()
eiger_plot_instance.checkBox_transpose.isChecked.return_value = transpose_checked
eiger_plot_instance.checkBox_log = MagicMock()
eiger_plot_instance.checkBox_log.isChecked.return_value = log_checked
eiger_plot_instance.imageItem = MagicMock()
# Call the method
eiger_plot_instance.on_image_update()
# Validate the transformations
np.testing.assert_array_almost_equal(eiger_plot_instance.image, expected_image, decimal=5)
# Validate that setImage was called
eiger_plot_instance.imageItem.setImage.assert_called_with(
eiger_plot_instance.image, autoLevels=False
)
def test_init_ui(eiger_plot_instance):
assert isinstance(eiger_plot_instance.plot_item, pg.PlotItem)
assert isinstance(eiger_plot_instance.imageItem, pg.ImageItem)
assert isinstance(eiger_plot_instance.hist, pg.HistogramLUTItem)
def test_start_zmq_consumer(eiger_plot_instance):
with patch("threading.Thread") as MockThread:
eiger_plot_instance.start_zmq_consumer()
MockThread.assert_called_once()
MockThread.return_value.start.assert_called_once()
def test_zmq_consumer(eiger_plot_instance, qtbot):
fake_meta = json.dumps({"type": "int32", "shape": (2, 2)}).encode("utf-8")
fake_data = np.array([[1, 2], [3, 4]], dtype="int32").tobytes()
with patch("zmq.Context", autospec=True) as MockContext:
mock_socket = MagicMock()
mock_socket.recv_multipart.side_effect = ((fake_meta, fake_data),)
MockContext.return_value.socket.return_value = mock_socket
# Mocking the update_signal to check if it gets emitted
eiger_plot_instance.update_signal = MagicMock()
with patch("zmq.Poller"):
# will do only 1 iteration of the loop in the thread
eiger_plot_instance._zmq_consumer_exit_event.set()
# Run the method under test
consumer_thread = eiger_plot_instance.start_zmq_consumer()
consumer_thread.join()
# Check if zmq methods are called
# MockContext.assert_called_once()
assert MockContext.call_count == 1
mock_socket.connect.assert_called_with("tcp://129.129.95.38:20000")
mock_socket.setsockopt_string.assert_called_with(zmq.SUBSCRIBE, "")
mock_socket.recv_multipart.assert_called()
# Check if update_signal was emitted
eiger_plot_instance.update_signal.emit.assert_called_once()
# Validate the image data
np.testing.assert_array_equal(
eiger_plot_instance.image, np.array([[1, 2], [3, 4]], dtype="int32")
)

View File

@ -239,14 +239,14 @@ def test_absolute_save_current_coordinates(motor_absolute_widget):
motor_absolute_widget.coordinates_signal.connect(capture_emit)
# Trigger saving current coordinates
motor_absolute_widget.pushButton_save.click()
motor_absolute_widget.ui.pushButton_save.click()
assert emitted_coordinates == [(motor_x_value, motor_y_value)]
def test_absolute_set_absolute_coordinates(motor_absolute_widget):
motor_absolute_widget.spinBox_absolute_x.setValue(5)
motor_absolute_widget.spinBox_absolute_y.setValue(10)
motor_absolute_widget.ui.spinBox_absolute_x.setValue(5)
motor_absolute_widget.ui.spinBox_absolute_y.setValue(10)
# Connect to the coordinates_signal to capture emitted values
emitted_values = []
@ -257,7 +257,7 @@ def test_absolute_set_absolute_coordinates(motor_absolute_widget):
motor_absolute_widget.coordinates_signal.connect(capture_coordinates)
# Simulate button click for absolute movement
motor_absolute_widget.pushButton_set.click()
motor_absolute_widget.ui.pushButton_set.click()
assert emitted_values == [(5, 10)]
@ -265,14 +265,14 @@ def test_absolute_set_absolute_coordinates(motor_absolute_widget):
def test_absolute_go_absolute_coordinates(motor_absolute_widget):
motor_absolute_widget.change_motors("samx", "samy")
motor_absolute_widget.spinBox_absolute_x.setValue(5)
motor_absolute_widget.spinBox_absolute_y.setValue(10)
motor_absolute_widget.ui.spinBox_absolute_x.setValue(5)
motor_absolute_widget.ui.spinBox_absolute_y.setValue(10)
with patch(
"bec_widgets.widgets.motor_control.motor_control.MotorThread.move_absolute",
new_callable=MagicMock,
) as mock_move_absolute:
motor_absolute_widget.pushButton_go_absolute.click()
motor_absolute_widget.ui.pushButton_go_absolute.click()
mock_move_absolute.assert_called_once_with("samx", "samy", (5, 10))
@ -292,8 +292,8 @@ def test_set_precision(motor_absolute_widget):
motor_absolute_widget.on_config_update(CONFIG_DEFAULT)
motor_absolute_widget.set_precision(2)
assert motor_absolute_widget.spinBox_absolute_x.decimals() == 2
assert motor_absolute_widget.spinBox_absolute_y.decimals() == 2
assert motor_absolute_widget.ui.spinBox_absolute_x.decimals() == 2
assert motor_absolute_widget.ui.spinBox_absolute_y.decimals() == 2
#######################################################
@ -338,29 +338,29 @@ def test_initialization_and_config_update(motor_relative_widget):
def test_move_motor_relative(motor_relative_widget):
motor_relative_widget.on_config_update(CONFIG_DEFAULT)
# Set step sizes
motor_relative_widget.spinBox_step_x.setValue(1)
motor_relative_widget.spinBox_step_y.setValue(1)
motor_relative_widget.ui.spinBox_step_x.setValue(1)
motor_relative_widget.ui.spinBox_step_y.setValue(1)
# Mock the move_relative method
motor_relative_widget.motor_thread.move_relative = MagicMock()
# Simulate button clicks
motor_relative_widget.toolButton_right.click()
motor_relative_widget.ui.toolButton_right.click()
motor_relative_widget.motor_thread.move_relative.assert_called_with(
motor_relative_widget.motor_x, 1
)
motor_relative_widget.toolButton_left.click()
motor_relative_widget.ui.toolButton_left.click()
motor_relative_widget.motor_thread.move_relative.assert_called_with(
motor_relative_widget.motor_x, -1
)
motor_relative_widget.toolButton_up.click()
motor_relative_widget.ui.toolButton_up.click()
motor_relative_widget.motor_thread.move_relative.assert_called_with(
motor_relative_widget.motor_y, 1
)
motor_relative_widget.toolButton_down.click()
motor_relative_widget.ui.toolButton_down.click()
motor_relative_widget.motor_thread.move_relative.assert_called_with(
motor_relative_widget.motor_y, -1
)
@ -376,21 +376,21 @@ def test_precision_update(motor_relative_widget):
motor_relative_widget.precision_signal.connect(capture_precision)
# Update precision
motor_relative_widget.spinBox_precision.setValue(1)
motor_relative_widget.ui.spinBox_precision.setValue(1)
assert emitted_values == [1]
assert motor_relative_widget.spinBox_step_x.decimals() == 1
assert motor_relative_widget.spinBox_step_y.decimals() == 1
assert motor_relative_widget.ui.spinBox_step_x.decimals() == 1
assert motor_relative_widget.ui.spinBox_step_y.decimals() == 1
def test_sync_step_sizes(motor_relative_widget):
motor_relative_widget.on_config_update(CONFIG_DEFAULT)
motor_relative_widget.checkBox_same_xy.setChecked(True)
motor_relative_widget.ui.checkBox_same_xy.setChecked(True)
# Change step size for X
motor_relative_widget.spinBox_step_x.setValue(2)
motor_relative_widget.ui.spinBox_step_x.setValue(2)
assert motor_relative_widget.spinBox_step_y.value() == 2
assert motor_relative_widget.ui.spinBox_step_y.value() == 2
def test_change_motor_relative(motor_relative_widget):
@ -420,11 +420,11 @@ def test_delete_selected_row(motor_coordinate_table):
motor_coordinate_table.add_coordinate((3.0, 4.0))
# Select the row
motor_coordinate_table.table.selectRow(0)
motor_coordinate_table.ui.table.selectRow(0)
# Delete the selected row
motor_coordinate_table.delete_selected_row()
assert motor_coordinate_table.table.rowCount() == 1
assert motor_coordinate_table.ui.table.rowCount() == 1
def test_add_coordinate_and_table_update(motor_coordinate_table):
@ -433,20 +433,24 @@ def test_add_coordinate_and_table_update(motor_coordinate_table):
# Add coordinate in Individual mode
motor_coordinate_table.add_coordinate((1.0, 2.0))
assert motor_coordinate_table.table.rowCount() == 1
assert motor_coordinate_table.ui.table.rowCount() == 1
# Check if the coordinates match
x_item_individual = motor_coordinate_table.table.cellWidget(0, 3) # Assuming X is in column 3
y_item_individual = motor_coordinate_table.table.cellWidget(0, 4) # Assuming Y is in column 4
x_item_individual = motor_coordinate_table.ui.table.cellWidget(
0, 3
) # Assuming X is in column 3
y_item_individual = motor_coordinate_table.ui.table.cellWidget(
0, 4
) # Assuming Y is in column 4
assert float(x_item_individual.text()) == 1.0
assert float(y_item_individual.text()) == 2.0
# Switch to Start/Stop and add coordinates
motor_coordinate_table.comboBox_mode.setCurrentIndex(1) # Switch mode
motor_coordinate_table.ui.comboBox_mode.setCurrentIndex(1) # Switch mode
motor_coordinate_table.add_coordinate((3.0, 4.0))
motor_coordinate_table.add_coordinate((5.0, 6.0))
assert motor_coordinate_table.table.rowCount() == 1
assert motor_coordinate_table.ui.table.rowCount() == 1
def test_plot_coordinates_signal(motor_coordinate_table):
@ -466,26 +470,26 @@ def test_plot_coordinates_signal(motor_coordinate_table):
assert received
def test_move_motor_action(motor_coordinate_table):
# Add a coordinate
motor_coordinate_table.add_coordinate((1.0, 2.0))
# Mock the motor thread move_absolute function
motor_coordinate_table.motor_thread.move_absolute = MagicMock()
# Trigger the move action
move_button = motor_coordinate_table.table.cellWidget(0, 1)
move_button.click()
motor_coordinate_table.motor_thread.move_absolute.assert_called_with(
motor_coordinate_table.motor_x, motor_coordinate_table.motor_y, (1.0, 2.0)
)
# def test_move_motor_action(motor_coordinate_table,qtbot):#TODO enable again after table refactor
# # Add a coordinate
# motor_coordinate_table.add_coordinate((1.0, 2.0))
#
# # Mock the motor thread move_absolute function
# motor_coordinate_table.motor_thread.move_absolute = MagicMock()
#
# # Trigger the move action
# move_button = motor_coordinate_table.table.cellWidget(0, 1)
# move_button.click()
#
# motor_coordinate_table.motor_thread.move_absolute.assert_called_with(
# motor_coordinate_table.motor_x, motor_coordinate_table.motor_y, (1.0, 2.0)
# )
def test_plot_coordinates_signal_individual(motor_coordinate_table, qtbot):
motor_coordinate_table.warning_message = False
motor_coordinate_table.set_precision(3)
motor_coordinate_table.comboBox_mode.setCurrentIndex(0)
motor_coordinate_table.ui.comboBox_mode.setCurrentIndex(0)
# This list will store the signals emitted during the test
emitted_signals = []
@ -506,8 +510,8 @@ def test_plot_coordinates_signal_individual(motor_coordinate_table, qtbot):
assert len(coordinates) > 0, "Coordinates list is empty."
assert reference_tag == "Individual"
assert color == "green"
assert motor_coordinate_table.table.cellWidget(0, 3).text() == "1.000"
assert motor_coordinate_table.table.cellWidget(0, 4).text() == "2.000"
assert motor_coordinate_table.ui.table.cellWidget(0, 3).text() == "1.000"
assert motor_coordinate_table.ui.table.cellWidget(0, 4).text() == "2.000"
#######################################################