From a2f7aa58f9639b76b4243d4d5f5bf0efd7b98054 Mon Sep 17 00:00:00 2001
From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com>
Date: Thu, 17 Aug 2023 16:05:10 +0200
Subject: [PATCH 01/29] refactor: folder organization changed
---
bec_widgets/examples/__init__.py | 0
.../{qt_utils/utils_example.py => examples/crosshair_example.py} | 0
bec_widgets/widgets/__init__.py | 0
3 files changed, 0 insertions(+), 0 deletions(-)
create mode 100644 bec_widgets/examples/__init__.py
rename bec_widgets/{qt_utils/utils_example.py => examples/crosshair_example.py} (100%)
create mode 100644 bec_widgets/widgets/__init__.py
diff --git a/bec_widgets/examples/__init__.py b/bec_widgets/examples/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/bec_widgets/qt_utils/utils_example.py b/bec_widgets/examples/crosshair_example.py
similarity index 100%
rename from bec_widgets/qt_utils/utils_example.py
rename to bec_widgets/examples/crosshair_example.py
diff --git a/bec_widgets/widgets/__init__.py b/bec_widgets/widgets/__init__.py
new file mode 100644
index 00000000..e69de29b
From 947ba9f8b730e96082cb51ff6894734a0e119ca1 Mon Sep 17 00:00:00 2001
From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com>
Date: Fri, 18 Aug 2023 10:38:52 +0200
Subject: [PATCH 02/29] feat: motor_example.py created, motor samx and samy can
be moved by buttons
---
bec_widgets/examples/__init__.py | 0
.../motor_movement/motor_controller.ui | 160 ++++++++++++++++++
.../examples/motor_movement/motor_example.py | 68 ++++++++
.../{ => plotting}/crosshair_example.py | 3 +-
4 files changed, 230 insertions(+), 1 deletion(-)
delete mode 100644 bec_widgets/examples/__init__.py
create mode 100644 bec_widgets/examples/motor_movement/motor_controller.ui
create mode 100644 bec_widgets/examples/motor_movement/motor_example.py
rename bec_widgets/examples/{ => plotting}/crosshair_example.py (99%)
diff --git a/bec_widgets/examples/__init__.py b/bec_widgets/examples/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/bec_widgets/examples/motor_movement/motor_controller.ui b/bec_widgets/examples/motor_movement/motor_controller.ui
new file mode 100644
index 00000000..ebb64c7b
--- /dev/null
+++ b/bec_widgets/examples/motor_movement/motor_controller.ui
@@ -0,0 +1,160 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 800
+ 266
+
+
+
+ Motor Controller
+
+
+ -
+
+
+ false
+
+
+ Motor Limits
+
+
+
-
+
+
+ + samy
+
+
+
+ -
+
+
+ -
+
+
+ Update
+
+
+
+ -
+
+
+ -
+
+
+ - samy
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ - samx
+
+
+
+ -
+
+
+ - samx
+
+
+
+
+
+
+ -
+
+
+ false
+
+
+
+ -
+
+
+ Motor Controls
+
+
+
-
+
+
+ 5
+
+
+
+ -
+
+
+ ...
+
+
+ Qt::LeftArrow
+
+
+
+ -
+
+
+ ...
+
+
+ Qt::UpArrow
+
+
+
+ -
+
+
+ ...
+
+
+ Qt::DownArrow
+
+
+
+ -
+
+
+ ...
+
+
+ Qt::RightArrow
+
+
+
+
+
+
+ -
+
+
+ Motor Status
+
+
+ Qt::AlignCenter
+
+
+
+
+
+
+
+ GraphicsLayoutWidget
+ QGraphicsView
+
+
+
+
+
+
diff --git a/bec_widgets/examples/motor_movement/motor_example.py b/bec_widgets/examples/motor_movement/motor_example.py
new file mode 100644
index 00000000..2c0d5106
--- /dev/null
+++ b/bec_widgets/examples/motor_movement/motor_example.py
@@ -0,0 +1,68 @@
+import os
+
+from PyQt5.QtCore import pyqtSignal
+from PyQt5.QtWidgets import QApplication, QWidget
+from pyqtgraph.Qt import QtCore, QtWidgets, uic
+import pyqtgraph as pg
+
+
+class MotorApp(QWidget):
+ motor_position = pyqtSignal(float) # TODO hook to motor position update
+
+ def __init__(self):
+ super().__init__()
+ current_path = os.path.dirname(__file__)
+ uic.loadUi(os.path.join(current_path, "motor_controller.ui"), self)
+
+ # UI
+
+ self.init_ui()
+
+ def init_ui(self):
+ """Setup all ui elements"""
+ ##########################
+ # 2D Plot
+ ##########################
+
+ self.label_coorditanes = self.glw.addLabel("Current Motor Coordinates", row=0, col=0)
+ self.plot_map = self.glw.addPlot(row=1, col=0)
+ self.image_map = pg.ImageItem()
+ self.plot_map.addItem(self.image_map)
+
+ ##########################
+ # Signals
+ ##########################
+ # Buttons - Motor Movement
+ self.toolButton_right.clicked.connect(
+ lambda: self.move_motor(dev.samx, self.spinBox_step.value())
+ )
+ self.toolButton_left.clicked.connect(
+ lambda: self.move_motor(dev.samx, -self.spinBox_step.value())
+ )
+ self.toolButton_up.clicked.connect(
+ lambda: self.move_motor(dev.samy, self.spinBox_step.value())
+ )
+ self.toolButton_down.clicked.connect(
+ lambda: self.move_motor(dev.samy, -self.spinBox_step.value())
+ )
+
+ # self.toolButton_left.clicked.connect(lambda: print(self.client))
+
+ def move_motor(self, motor, value: float) -> None:
+ scans.mv(motor, value, relative=True)
+ self.motor_position.emit(motor.position)
+
+
+if __name__ == "__main__":
+ from bec_widgets.bec_dispatcher import bec_dispatcher
+
+ # Client initialization
+ client = bec_dispatcher.client
+ client.start()
+ dev = client.device_manager.devices
+ scans = client.scans
+
+ app = QApplication([])
+ window = MotorApp()
+ window.show()
+ app.exec_()
diff --git a/bec_widgets/examples/crosshair_example.py b/bec_widgets/examples/plotting/crosshair_example.py
similarity index 99%
rename from bec_widgets/examples/crosshair_example.py
rename to bec_widgets/examples/plotting/crosshair_example.py
index f15ed110..5aed65aa 100644
--- a/bec_widgets/examples/crosshair_example.py
+++ b/bec_widgets/examples/plotting/crosshair_example.py
@@ -12,7 +12,8 @@ from PyQt5.QtWidgets import (
)
from pyqtgraph import mkPen
from pyqtgraph.Qt import QtCore
-from crosshair import Crosshair
+from bec_widgets.qt_utils import Crosshair
+
class ExampleApp(QWidget):
From e6952a6d13c84487fd6ab08056f1f5b46d594b8a Mon Sep 17 00:00:00 2001
From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com>
Date: Fri, 18 Aug 2023 11:30:31 +0200
Subject: [PATCH 03/29] feat: map of motor position
---
.../motor_movement/motor_controller.ui | 254 +++++++++---------
.../examples/motor_movement/motor_example.py | 49 +++-
2 files changed, 177 insertions(+), 126 deletions(-)
diff --git a/bec_widgets/examples/motor_movement/motor_controller.ui b/bec_widgets/examples/motor_movement/motor_controller.ui
index ebb64c7b..ba2dc0da 100644
--- a/bec_widgets/examples/motor_movement/motor_controller.ui
+++ b/bec_widgets/examples/motor_movement/motor_controller.ui
@@ -13,139 +13,143 @@
Motor Controller
-
- -
-
-
- false
-
-
- Motor Limits
-
-
-
-
-
-
- + samy
-
-
-
- -
-
-
- -
-
-
- Update
-
-
-
- -
-
-
- -
-
-
- - samy
-
-
-
- -
-
-
- -
-
-
- -
-
-
- - samx
-
-
-
- -
-
-
- - samx
-
-
-
-
-
-
- -
-
-
- false
-
-
-
- -
-
-
- Motor Controls
-
-
-
-
-
-
- 5
-
-
-
- -
-
-
- ...
-
-
- Qt::LeftArrow
-
-
-
- -
-
-
- ...
-
-
- Qt::UpArrow
-
-
-
- -
-
-
- ...
-
-
- Qt::DownArrow
-
-
-
- -
-
-
- ...
-
-
- Qt::RightArrow
-
-
-
-
-
-
- -
+
+
-
- Motor Status
+ Motor Position
Qt::AlignCenter
+ -
+
+
-
+
+
+ false
+
+
+
+ -
+
+
+ Motor Controls
+
+
+
-
+
+
+ 5
+
+
+
+ -
+
+
+ ...
+
+
+ Qt::LeftArrow
+
+
+
+ -
+
+
+ ...
+
+
+ Qt::UpArrow
+
+
+
+ -
+
+
+ ...
+
+
+ Qt::DownArrow
+
+
+
+ -
+
+
+ ...
+
+
+ Qt::RightArrow
+
+
+
+
+
+
+ -
+
+
+ false
+
+
+ Motor Limits
+
+
+
-
+
+
+ - samy
+
+
+
+ -
+
+
+ + samy
+
+
+
+ -
+
+
+ - samx
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ - samx
+
+
+
+ -
+
+
+ -
+
+
+ Update
+
+
+
+
+
+
+
+
diff --git a/bec_widgets/examples/motor_movement/motor_example.py b/bec_widgets/examples/motor_movement/motor_example.py
index 2c0d5106..27eaaf73 100644
--- a/bec_widgets/examples/motor_movement/motor_example.py
+++ b/bec_widgets/examples/motor_movement/motor_example.py
@@ -1,5 +1,6 @@
import os
+import numpy as np
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import QApplication, QWidget
from pyqtgraph.Qt import QtCore, QtWidgets, uic
@@ -8,6 +9,7 @@ import pyqtgraph as pg
class MotorApp(QWidget):
motor_position = pyqtSignal(float) # TODO hook to motor position update
+ motor_update = pyqtSignal()
def __init__(self):
super().__init__()
@@ -29,6 +31,10 @@ class MotorApp(QWidget):
self.image_map = pg.ImageItem()
self.plot_map.addItem(self.image_map)
+ self.image_map_data = np.zeros((100, 100), dtype=np.float32)
+ x, y = self.get_xy() # Assuming get_xy returns the motor position
+ self.update_image_map(x, y)
+ self.image_map.setImage(self.image_map_data.T) # Set the image
##########################
# Signals
##########################
@@ -46,11 +52,52 @@ class MotorApp(QWidget):
lambda: self.move_motor(dev.samy, -self.spinBox_step.value())
)
+ # Map
+ self.motor_position.connect(lambda x: self.update_image_map(*self.get_xy()))
+
+ # make keybindings for motor movement
+ self.toolButton_right.setShortcut("Right")
+ self.toolButton_left.setShortcut("Left")
+ self.toolButton_up.setShortcut("Up")
+ self.toolButton_down.setShortcut("Down")
+
# self.toolButton_left.clicked.connect(lambda: print(self.client))
+ self.motor_position.connect(lambda x: print(f"motor position updated: {x}"))
+ self.motor_update.connect(
+ lambda: self.label_status.setText(
+ f"Motor position: ({dev.samx.position():.2f}, {dev.samy.position():.2f})"
+ )
+ )
+
def move_motor(self, motor, value: float) -> None:
scans.mv(motor, value, relative=True)
- self.motor_position.emit(motor.position)
+ motor_position = motor.position()
+
+ self.motor_position.emit(motor_position)
+ self.motor_update.emit()
+
+ def get_xy(self): # TODO decide if useful
+ """Get current motor position"""
+ x = dev.samx.position()
+ y = dev.samy.position()
+ return x, y
+
+ def update_image_map(self, x, y):
+ """Update the image map with the new motor position"""
+
+ # Reduce the brightness of all pixels by a fixed fraction (e.g., 5%)
+ self.image_map_data *= 0.8
+
+ # Modify this mapping as needed
+ pixel_x = int(x)
+ pixel_y = int(y)
+
+ # Set the bright pixel at the new position
+ self.image_map_data[pixel_x, pixel_y] = 255
+
+ # Update the display
+ self.image_map.setImage(self.image_map_data)
if __name__ == "__main__":
From 512e698e26d9eef05b4f430475ccc268b68ad632 Mon Sep 17 00:00:00 2001
From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com>
Date: Fri, 18 Aug 2023 17:06:37 +0200
Subject: [PATCH 04/29] feat: setting map according to motor limits
---
.../motor_movement/motor_controller.ui | 200 +++++++++++++++---
.../examples/motor_movement/motor_example.py | 143 +++++++++++--
2 files changed, 297 insertions(+), 46 deletions(-)
diff --git a/bec_widgets/examples/motor_movement/motor_controller.ui b/bec_widgets/examples/motor_movement/motor_controller.ui
index ba2dc0da..13cc96c5 100644
--- a/bec_widgets/examples/motor_movement/motor_controller.ui
+++ b/bec_widgets/examples/motor_movement/motor_controller.ui
@@ -6,33 +6,74 @@
0
0
- 800
- 266
+ 1289
+ 596
Motor Controller
-
+
-
-
-
- Motor Position
-
-
- Qt::AlignCenter
-
-
-
- -
-
+
+
-
+
+
+ Motor Position
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
-
+
+
+ Go
+
+
+
+ -
+
+
+ Save
+
+
+
+
+
-
- false
+ true
+
+
+ -
+
+
-
+
+
+ General
+
+
+
-
+
+
+ Decimal precision
+
+
+
+ -
+
+
+
+
+
-
@@ -92,7 +133,7 @@
-
- false
+ true
Motor Limits
@@ -120,13 +161,34 @@
-
-
+
+
+ -1000
+
+
+ 1000
+
+
-
-
+
+
+ -1000
+
+
+ 1000
+
+
-
-
+
+
+ -1000
+
+
+ 1000
+
+
-
@@ -136,12 +198,12 @@
-
-
-
- -
-
-
- Update
+
+
+ -1000
+
+
+ 1000
@@ -150,6 +212,94 @@
+ -
+
+
+ 0
+
+
+
+ Saved
+
+
+
-
+
+
+ Go to selected
+
+
+
+ -
+
+
+
+ Show
+
+
+
+
+ X
+
+
+
+
+ Y
+
+
+
+
+
+
+
+
+ Queue
+
+
+ -
+
+
+ Reset Queue
+
+
+
+ -
+
+
+
+ queueID
+
+
+
+
+ scanID
+
+
+
+
+ is_scan
+
+
+
+
+ type
+
+
+
+
+ scan_number
+
+
+
+
+ IQ status
+
+
+
+
+
+
+
+
diff --git a/bec_widgets/examples/motor_movement/motor_example.py b/bec_widgets/examples/motor_movement/motor_example.py
index 27eaaf73..491c50c5 100644
--- a/bec_widgets/examples/motor_movement/motor_example.py
+++ b/bec_widgets/examples/motor_movement/motor_example.py
@@ -1,6 +1,7 @@
import os
import numpy as np
+from PyQt5 import QtGui
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import QApplication, QWidget
from pyqtgraph.Qt import QtCore, QtWidgets, uic
@@ -17,7 +18,14 @@ class MotorApp(QWidget):
uic.loadUi(os.path.join(current_path, "motor_controller.ui"), self)
# UI
+ self.start_x, self.start_y = self.get_xy()
+ self.limit_x, self.limit_y = self.get_motor_limits(dev.samx), self.get_motor_limits(
+ dev.samy
+ )
+ self.label_status.setText(
+ f"Motor position: ({dev.samx.position():.2f}, {dev.samy.position():.2f})"
+ )
self.init_ui()
def init_ui(self):
@@ -31,13 +39,17 @@ class MotorApp(QWidget):
self.image_map = pg.ImageItem()
self.plot_map.addItem(self.image_map)
- self.image_map_data = np.zeros((100, 100), dtype=np.float32)
- x, y = self.get_xy() # Assuming get_xy returns the motor position
- self.update_image_map(x, y)
- self.image_map.setImage(self.image_map_data.T) # Set the image
+ # self.image_map_data = np.zeros((100, 100), dtype=np.float32)
+ # x, y = self.get_xy() # Assuming get_xy returns the motor position
+ # self.update_image_map(x, y)
+ # self.image_map.setImage(self.image_map_data.T) # Set the image
+
+ # self.init_motor_map()
+
##########################
# Signals
##########################
+
# Buttons - Motor Movement
self.toolButton_right.clicked.connect(
lambda: self.move_motor(dev.samx, self.spinBox_step.value())
@@ -52,6 +64,32 @@ class MotorApp(QWidget):
lambda: self.move_motor(dev.samy, -self.spinBox_step.value())
)
+ # SpinBoxes - Motor Limits #TODO make spinboxes own limits updated, currently is [-1000, 1000]
+ # manually set limits before readout #TODO will be removed when spinboxes have own limits
+ # dev.samx.low_limit = -100
+ # dev.samx.high_limit = 100
+ # dev.samy.low_limit = -100
+ # dev.samy.high_limit = 100
+
+ # display initial limits
+ self.spinBox_x_min.setValue(self.limit_x[0])
+ self.spinBox_x_max.setValue(self.limit_x[1])
+ self.spinBox_y_min.setValue(self.limit_y[0])
+ self.spinBox_y_max.setValue(self.limit_y[1])
+ # # change limits of motors for all motors and spinboxes
+ self.spinBox_x_min.valueChanged.connect(
+ lambda x: self.update_motor_limits(dev.samx, low_limit=x)
+ )
+ self.spinBox_x_max.valueChanged.connect(
+ lambda x: self.update_motor_limits(dev.samx, high_limit=x)
+ )
+ self.spinBox_y_min.valueChanged.connect(
+ lambda x: self.update_motor_limits(dev.samy, low_limit=x)
+ )
+ self.spinBox_y_max.valueChanged.connect(
+ lambda x: self.update_motor_limits(dev.samy, high_limit=x)
+ )
+
# Map
self.motor_position.connect(lambda x: self.update_image_map(*self.get_xy()))
@@ -70,8 +108,87 @@ class MotorApp(QWidget):
)
)
+ self.init_motor_map()
+
+ def init_motor_map(self):
+ # Get motor limits
+ limit_x_min, limit_x_max = self.get_motor_limits(dev.samx)
+ limit_y_min, limit_y_max = self.get_motor_limits(dev.samy)
+
+ self.offset_x = limit_x_min
+ self.offset_y = limit_y_min
+
+ # Define the size of the image map based on the motor's limits
+ map_width = limit_x_max - limit_x_min + 1
+ map_height = limit_y_max - limit_y_min + 1
+
+ # Create an empty image map
+ self.background_value = 15
+ self.image_map_data = np.full(
+ (map_width, map_height), self.background_value, dtype=np.float32
+ )
+
+ # Set the initial position on the map
+ x, y = self.get_xy()
+ self.prev_x, self.prev_y = x, y
+ self.update_image_map(x, y)
+
+ # Translate and scale the image item to match the motor coordinates
+ self.tr = QtGui.QTransform()
+ self.tr.translate(limit_x_min, limit_y_min)
+ self.image_map.setTransform(self.tr)
+
+ self.image_map.dataTransform()
+
+ def update_image_map(self, x, y):
+ """Update the image map with the new motor position"""
+
+ # Define the dimming factor
+ dimming_factor = 0.80
+
+ # Apply the dimming factor only to pixels above the background value
+ self.image_map_data[self.image_map_data > self.background_value] *= dimming_factor
+
+ # Mapping of motor coordinates to pixel coordinates
+ pixel_x = int(x - self.offset_x)
+ pixel_y = int(y - self.offset_y)
+
+ # Set the bright pixel at the new position
+ self.image_map_data[pixel_x, pixel_y] = 255
+
+ # Update the display
+ self.image_map.updateImage(self.image_map_data, levels=(0, 255))
+
+ def get_motor_limits(self, motor):
+ """Get the limits of a motor"""
+ high_limit = motor.high_limit
+ low_limit = motor.low_limit
+
+ return low_limit, high_limit
+
+ def update_motor_limits(self, motor, low_limit=None, high_limit=None):
+ # Get current limits
+ current_low_limit, current_high_limit = motor.limits[0], motor.limits[1]
+
+ # Check if the low limit has changed and is not None
+ if low_limit is not None and low_limit != current_low_limit:
+ motor.low_limit = low_limit
+
+ # Check if the high limit has changed and is not None
+ if high_limit is not None and high_limit != current_high_limit:
+ motor.high_limit = high_limit
+
def move_motor(self, motor, value: float) -> None:
- scans.mv(motor, value, relative=True)
+ try:
+ status = scans.mv(motor, value, relative=True)
+
+ while status.status not in ["COMPLETED", "STOPPED"]:
+ print(status.status)
+
+ # return status
+ except Exception as e:
+ print(e)
+
motor_position = motor.position()
self.motor_position.emit(motor_position)
@@ -83,22 +200,6 @@ class MotorApp(QWidget):
y = dev.samy.position()
return x, y
- def update_image_map(self, x, y):
- """Update the image map with the new motor position"""
-
- # Reduce the brightness of all pixels by a fixed fraction (e.g., 5%)
- self.image_map_data *= 0.8
-
- # Modify this mapping as needed
- pixel_x = int(x)
- pixel_y = int(y)
-
- # Set the bright pixel at the new position
- self.image_map_data[pixel_x, pixel_y] = 255
-
- # Update the display
- self.image_map.setImage(self.image_map_data)
-
if __name__ == "__main__":
from bec_widgets.bec_dispatcher import bec_dispatcher
From cac45626fc9a315f9012b110760a92e27e5ed226 Mon Sep 17 00:00:00 2001
From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com>
Date: Mon, 21 Aug 2023 11:02:41 +0200
Subject: [PATCH 05/29] feat: switch for keyboard shortcuts for motor movement
---
.../motor_movement/motor_controller.ui | 182 ++++++++++--------
.../examples/motor_movement/motor_example.py | 56 +++---
2 files changed, 136 insertions(+), 102 deletions(-)
diff --git a/bec_widgets/examples/motor_movement/motor_controller.ui b/bec_widgets/examples/motor_movement/motor_controller.ui
index 13cc96c5..049078aa 100644
--- a/bec_widgets/examples/motor_movement/motor_controller.ui
+++ b/bec_widgets/examples/motor_movement/motor_controller.ui
@@ -79,53 +79,64 @@
Motor Controls
-
- -
-
-
- 5
+
+
-
+
+
+ Move with arrow keys
- -
-
-
- ...
-
-
- Qt::LeftArrow
-
-
-
- -
-
-
- ...
-
-
- Qt::UpArrow
-
-
-
- -
-
-
- ...
-
-
- Qt::DownArrow
-
-
-
- -
-
-
- ...
-
-
- Qt::RightArrow
-
-
+
-
+
+
-
+
+
+ ...
+
+
+ Qt::UpArrow
+
+
+
+ -
+
+
+ ...
+
+
+ Qt::LeftArrow
+
+
+
+ -
+
+
+ 5
+
+
+
+ -
+
+
+ ...
+
+
+ Qt::RightArrow
+
+
+
+ -
+
+
+ ...
+
+
+ Qt::DownArrow
+
+
+
+
@@ -139,28 +150,7 @@
Motor Limits
- -
-
-
- - samy
-
-
-
- -
-
-
- + samy
-
-
-
- -
-
-
- - samx
-
-
-
- -
+
-
-1000
@@ -168,9 +158,12 @@
1000
+
+ -100
+
- -
+
-
-1000
@@ -178,9 +171,19 @@
1000
+
+ 100
+
- -
+
-
+
+
+ - samy
+
+
+
+ -
-1000
@@ -188,16 +191,33 @@
1000
+
+ 100
+
- -
+
-
+
+
+ + samy
+
+
+
+ -
- samx
- -
+
-
+
+
+ - samx
+
+
+
+ -
-1000
@@ -205,6 +225,16 @@
1000
+
+ -100
+
+
+
+ -
+
+
+ Update
+
@@ -222,13 +252,6 @@
Saved
- -
-
-
- Go to selected
-
-
-
-
@@ -246,6 +269,11 @@
Y
+
+
+ Move
+
+
diff --git a/bec_widgets/examples/motor_movement/motor_example.py b/bec_widgets/examples/motor_movement/motor_example.py
index 491c50c5..f442653f 100644
--- a/bec_widgets/examples/motor_movement/motor_example.py
+++ b/bec_widgets/examples/motor_movement/motor_example.py
@@ -2,8 +2,9 @@ import os
import numpy as np
from PyQt5 import QtGui
-from PyQt5.QtCore import pyqtSignal
-from PyQt5.QtWidgets import QApplication, QWidget
+from PyQt5.QtCore import pyqtSignal, Qt
+from PyQt5.QtGui import QKeySequence
+from PyQt5.QtWidgets import QApplication, QWidget, QShortcut
from pyqtgraph.Qt import QtCore, QtWidgets, uic
import pyqtgraph as pg
@@ -64,6 +65,9 @@ class MotorApp(QWidget):
lambda: self.move_motor(dev.samy, -self.spinBox_step.value())
)
+ self.checkBox_enableArrows.stateChanged.connect(self.update_arrow_key_shortcuts)
+ self.update_arrow_key_shortcuts()
+
# SpinBoxes - Motor Limits #TODO make spinboxes own limits updated, currently is [-1000, 1000]
# manually set limits before readout #TODO will be removed when spinboxes have own limits
# dev.samx.low_limit = -100
@@ -76,31 +80,10 @@ class MotorApp(QWidget):
self.spinBox_x_max.setValue(self.limit_x[1])
self.spinBox_y_min.setValue(self.limit_y[0])
self.spinBox_y_max.setValue(self.limit_y[1])
- # # change limits of motors for all motors and spinboxes
- self.spinBox_x_min.valueChanged.connect(
- lambda x: self.update_motor_limits(dev.samx, low_limit=x)
- )
- self.spinBox_x_max.valueChanged.connect(
- lambda x: self.update_motor_limits(dev.samx, high_limit=x)
- )
- self.spinBox_y_min.valueChanged.connect(
- lambda x: self.update_motor_limits(dev.samy, low_limit=x)
- )
- self.spinBox_y_max.valueChanged.connect(
- lambda x: self.update_motor_limits(dev.samy, high_limit=x)
- )
# Map
self.motor_position.connect(lambda x: self.update_image_map(*self.get_xy()))
- # make keybindings for motor movement
- self.toolButton_right.setShortcut("Right")
- self.toolButton_left.setShortcut("Left")
- self.toolButton_up.setShortcut("Up")
- self.toolButton_down.setShortcut("Down")
-
- # self.toolButton_left.clicked.connect(lambda: print(self.client))
-
self.motor_position.connect(lambda x: print(f"motor position updated: {x}"))
self.motor_update.connect(
lambda: self.label_status.setText(
@@ -166,7 +149,9 @@ class MotorApp(QWidget):
return low_limit, high_limit
- def update_motor_limits(self, motor, low_limit=None, high_limit=None):
+ def update_motor_limits(
+ self, motor, low_limit=None, high_limit=None
+ ): # TODO limits cannot be smaller that the current location of motor
# Get current limits
current_low_limit, current_high_limit = motor.limits[0], motor.limits[1]
@@ -200,11 +185,32 @@ class MotorApp(QWidget):
y = dev.samy.position()
return x, y
+ def update_arrow_key_shortcuts(self):
+ if self.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)
+ else:
+ # Clear the shortcuts
+ self.toolButton_right.setShortcut("")
+ self.toolButton_left.setShortcut("")
+ self.toolButton_up.setShortcut("")
+ self.toolButton_down.setShortcut("")
+
if __name__ == "__main__":
from bec_widgets.bec_dispatcher import bec_dispatcher
- # Client initialization
+ # from bec_lib import BECClient
+ # from bec_lib.core import ServiceConfig,RedisConnector
+
+ # client = BECClient()
+ # client.initialize(config=ServiceConfig(config_path="test_config.yaml"))
+ # client.initialize(config=ServiceConfig(config_path="test_config.yaml"))
+
+ # Client initialization - by dispatcher
client = bec_dispatcher.client
client.start()
dev = client.device_manager.devices
From 2d1665c76b8174d9fffa3442afa98fe1ea6ac207 Mon Sep 17 00:00:00 2001
From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com>
Date: Mon, 21 Aug 2023 11:18:01 +0200
Subject: [PATCH 06/29] feat: motor limits can be changed by spinBoxes
---
.../motor_movement/motor_controller.ui | 7 +++
.../examples/motor_movement/motor_example.py | 43 ++++++++++++++++++-
2 files changed, 49 insertions(+), 1 deletion(-)
diff --git a/bec_widgets/examples/motor_movement/motor_controller.ui b/bec_widgets/examples/motor_movement/motor_controller.ui
index 049078aa..ffdfaa19 100644
--- a/bec_widgets/examples/motor_movement/motor_controller.ui
+++ b/bec_widgets/examples/motor_movement/motor_controller.ui
@@ -138,6 +138,13 @@
+ -
+
+
+ Stop Movement
+
+
+
diff --git a/bec_widgets/examples/motor_movement/motor_example.py b/bec_widgets/examples/motor_movement/motor_example.py
index f442653f..56afd090 100644
--- a/bec_widgets/examples/motor_movement/motor_example.py
+++ b/bec_widgets/examples/motor_movement/motor_example.py
@@ -24,6 +24,8 @@ class MotorApp(QWidget):
dev.samy
)
+ print(f"Init limits: samx:{self.limit_x}, samy:{self.limit_y}")
+
self.label_status.setText(
f"Motor position: ({dev.samx.position():.2f}, {dev.samy.position():.2f})"
)
@@ -35,7 +37,9 @@ class MotorApp(QWidget):
# 2D Plot
##########################
- self.label_coorditanes = self.glw.addLabel("Current Motor Coordinates", row=0, col=0)
+ self.label_coorditanes = self.glw.addLabel(
+ f"Motor position: ({dev.samx.position():.2f}, {dev.samy.position():.2f})", row=0, col=0
+ )
self.plot_map = self.glw.addPlot(row=1, col=0)
self.image_map = pg.ImageItem()
self.plot_map.addItem(self.image_map)
@@ -81,6 +85,19 @@ class MotorApp(QWidget):
self.spinBox_y_min.setValue(self.limit_y[0])
self.spinBox_y_max.setValue(self.limit_y[1])
+ # SpinBoxes change color to yellow before updated, limits are updated with update button
+ self.spinBox_x_min.valueChanged.connect(lambda: self.param_changed(self.spinBox_x_min))
+ self.spinBox_x_max.valueChanged.connect(lambda: self.param_changed(self.spinBox_x_max))
+ self.spinBox_y_min.valueChanged.connect(lambda: self.param_changed(self.spinBox_y_min))
+ self.spinBox_y_max.valueChanged.connect(lambda: self.param_changed(self.spinBox_y_max))
+
+ self.pushButton_updateLimits.clicked.connect(
+ lambda: self.update_all_motor_limits(
+ x_limit=[self.spinBox_x_min.value(), self.spinBox_x_max.value()],
+ y_limit=[self.spinBox_y_min.value(), self.spinBox_y_max.value()],
+ )
+ )
+
# Map
self.motor_position.connect(lambda x: self.update_image_map(*self.get_xy()))
@@ -163,6 +180,27 @@ class MotorApp(QWidget):
if high_limit is not None and high_limit != current_high_limit:
motor.high_limit = high_limit
+ # self.init_motor_map() # reinitialize the map with the new limits
+
+ def update_all_motor_limits(self, x_limit: list = None, y_limit: list = None) -> None:
+ # check if current motor position if within the new limits
+
+ current_position = self.get_xy()
+
+ if x_limit is not None:
+ if current_position[0] < x_limit[0] or current_position[0] > x_limit[1]:
+ raise ValueError("Current motor position is outside the new limits (X)")
+ else:
+ self.update_motor_limits(dev.samx, low_limit=x_limit[0], high_limit=x_limit[1])
+
+ if y_limit is not None:
+ if current_position[1] < y_limit[0] or current_position[1] > y_limit[1]:
+ raise ValueError("Current motor position is outside the new limits (Y)")
+ else:
+ self.update_motor_limits(dev.samy, low_limit=y_limit[0], high_limit=y_limit[1])
+
+ self.init_motor_map() # reinitialize the map with the new limits
+
def move_motor(self, motor, value: float) -> None:
try:
status = scans.mv(motor, value, relative=True)
@@ -185,6 +223,9 @@ class MotorApp(QWidget):
y = dev.samy.position()
return x, y
+ def param_changed(self, ui_element):
+ ui_element.setStyleSheet("background-color: #FFA700;")
+
def update_arrow_key_shortcuts(self):
if self.checkBox_enableArrows.isChecked():
# Set the arrow key shortcuts for motor movement
From cbe27e46cfb6282c71844641e1ed6059e8fa96bf Mon Sep 17 00:00:00 2001
From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com>
Date: Mon, 21 Aug 2023 11:55:49 +0200
Subject: [PATCH 07/29] feat: motor move to absolute (X,Y) coordinates
---
.../motor_movement/motor_controller.ui | 37 +++++++++++++++++++
.../examples/motor_movement/motor_example.py | 26 +++++++++++++
2 files changed, 63 insertions(+)
diff --git a/bec_widgets/examples/motor_movement/motor_controller.ui b/bec_widgets/examples/motor_movement/motor_controller.ui
index ffdfaa19..84979648 100644
--- a/bec_widgets/examples/motor_movement/motor_controller.ui
+++ b/bec_widgets/examples/motor_movement/motor_controller.ui
@@ -138,6 +138,43 @@
+ -
+
+
-
+
+
+ X
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ Y
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ Go
+
+
+
+ -
+
+
+ -
+
+
+
+
-
diff --git a/bec_widgets/examples/motor_movement/motor_example.py b/bec_widgets/examples/motor_movement/motor_example.py
index 56afd090..64d0e93c 100644
--- a/bec_widgets/examples/motor_movement/motor_example.py
+++ b/bec_widgets/examples/motor_movement/motor_example.py
@@ -12,6 +12,7 @@ import pyqtgraph as pg
class MotorApp(QWidget):
motor_position = pyqtSignal(float) # TODO hook to motor position update
motor_update = pyqtSignal()
+ motor_position_xy = pyqtSignal(list)
def __init__(self):
super().__init__()
@@ -72,6 +73,13 @@ class MotorApp(QWidget):
self.checkBox_enableArrows.stateChanged.connect(self.update_arrow_key_shortcuts)
self.update_arrow_key_shortcuts()
+ # Move to absolute coordinates
+ self.pushButton_go_absolute.clicked.connect(
+ lambda: self.move_motor_coordinates(
+ [self.spinBox_absolute_x.value(), self.spinBox_absolute_y.value()]
+ )
+ )
+
# SpinBoxes - Motor Limits #TODO make spinboxes own limits updated, currently is [-1000, 1000]
# manually set limits before readout #TODO will be removed when spinboxes have own limits
# dev.samx.low_limit = -100
@@ -100,6 +108,7 @@ class MotorApp(QWidget):
# Map
self.motor_position.connect(lambda x: self.update_image_map(*self.get_xy()))
+ self.motor_update.connect(lambda: self.update_image_map(*self.get_xy()))
self.motor_position.connect(lambda x: print(f"motor position updated: {x}"))
self.motor_update.connect(
@@ -207,6 +216,8 @@ class MotorApp(QWidget):
while status.status not in ["COMPLETED", "STOPPED"]:
print(status.status)
+ motor_position = motor.position()
+ self.motor_position.emit(motor_position)
# return status
except Exception as e:
@@ -217,6 +228,21 @@ class MotorApp(QWidget):
self.motor_position.emit(motor_position)
self.motor_update.emit()
+ def move_motor_coordinates(self, coor: list) -> None:
+ try:
+ status = scans.mv(dev.samx, coor[0], dev.samy, coor[1], relative=False)
+
+ while status.status not in ["COMPLETED", "STOPPED"]:
+ print(status.status)
+
+ except Exception as e:
+ print(e)
+
+ # motor_position = self.get_xy()
+ # self.motor_position_xy.emit(motor_position)
+
+ self.motor_update.emit()
+
def get_xy(self): # TODO decide if useful
"""Get current motor position"""
x = dev.samx.position()
From 92388c3cab7e024978aaa2906afbd698015dec66 Mon Sep 17 00:00:00 2001
From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com>
Date: Mon, 21 Aug 2023 13:48:48 +0200
Subject: [PATCH 08/29] feat: table with coordinates getting initial
coordinates of motor
---
.../motor_movement/motor_controller.ui | 9 ++-
.../examples/motor_movement/motor_example.py | 55 +++++++++++++++++--
2 files changed, 58 insertions(+), 6 deletions(-)
diff --git a/bec_widgets/examples/motor_movement/motor_controller.ui b/bec_widgets/examples/motor_movement/motor_controller.ui
index 84979648..8d5a6102 100644
--- a/bec_widgets/examples/motor_movement/motor_controller.ui
+++ b/bec_widgets/examples/motor_movement/motor_controller.ui
@@ -293,11 +293,16 @@
- Saved
+ Coordinates
-
-
+
+
+
+ Tag
+
+
Show
diff --git a/bec_widgets/examples/motor_movement/motor_example.py b/bec_widgets/examples/motor_movement/motor_example.py
index 64d0e93c..e0c3f696 100644
--- a/bec_widgets/examples/motor_movement/motor_example.py
+++ b/bec_widgets/examples/motor_movement/motor_example.py
@@ -1,12 +1,11 @@
import os
import numpy as np
+import pyqtgraph as pg
from PyQt5 import QtGui
from PyQt5.QtCore import pyqtSignal, Qt
-from PyQt5.QtGui import QKeySequence
-from PyQt5.QtWidgets import QApplication, QWidget, QShortcut
-from pyqtgraph.Qt import QtCore, QtWidgets, uic
-import pyqtgraph as pg
+from PyQt5.QtWidgets import QApplication, QWidget
+from pyqtgraph.Qt import QtWidgets, uic
class MotorApp(QWidget):
@@ -117,6 +116,11 @@ class MotorApp(QWidget):
)
)
+ # Coordinates table
+ self.generate_table_coordinate(
+ self.tableWidget_coordinates, self.get_xy(), tag="Initial", precision=0
+ )
+
self.init_motor_map()
def init_motor_map(self):
@@ -208,6 +212,14 @@ class MotorApp(QWidget):
else:
self.update_motor_limits(dev.samy, low_limit=y_limit[0], high_limit=y_limit[1])
+ for spinBox in (
+ self.spinBox_x_min,
+ self.spinBox_x_max,
+ self.spinBox_y_min,
+ self.spinBox_y_max,
+ ):
+ spinBox.setStyleSheet("")
+
self.init_motor_map() # reinitialize the map with the new limits
def move_motor(self, motor, value: float) -> None:
@@ -266,6 +278,41 @@ class MotorApp(QWidget):
self.toolButton_up.setShortcut("")
self.toolButton_down.setShortcut("")
+ def generate_table_coordinate(
+ self, table: QtWidgets.QTableWidget, coordinates: list, tag: str = None, precision: int = 0
+ ) -> None:
+ current_row_count = table.rowCount()
+
+ table.setRowCount(current_row_count + 1)
+
+ # table.insertRow(current_row_count)
+
+ checkBox = QtWidgets.QCheckBox()
+ checkBox.setChecked(True)
+ button = QtWidgets.QPushButton("Go")
+
+ table.setItem(current_row_count, 0, QtWidgets.QTableWidgetItem(str(tag)))
+ table.setCellWidget(current_row_count, 1, checkBox)
+ table.setItem(
+ current_row_count, 2, QtWidgets.QTableWidgetItem(str(f"{coordinates[0]:.{precision}f}"))
+ )
+ table.setItem(
+ current_row_count, 3, QtWidgets.QTableWidgetItem(str(f"{coordinates[1]:.{precision}f}"))
+ )
+ table.setCellWidget(current_row_count, 4, button)
+
+ # hook signals of table
+ button.clicked.connect(
+ lambda: self.move_motor_coordinates(
+ [
+ float(table.item(current_row_count, 2).text()),
+ float(table.item(current_row_count, 3).text()),
+ ]
+ )
+ )
+
+ table.resizeColumnsToContents()
+
if __name__ == "__main__":
from bec_widgets.bec_dispatcher import bec_dispatcher
From af2fcffd5f4efa49e3cd728e4d481665584af941 Mon Sep 17 00:00:00 2001
From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com>
Date: Tue, 22 Aug 2023 18:16:44 +0200
Subject: [PATCH 09/29] refactor: motor movement as a QThread
---
.../motor_movement/motor_controller.ui | 16 +-
.../examples/motor_movement/motor_example.py | 244 +++++++++++++++---
2 files changed, 222 insertions(+), 38 deletions(-)
diff --git a/bec_widgets/examples/motor_movement/motor_controller.ui b/bec_widgets/examples/motor_movement/motor_controller.ui
index 8d5a6102..47521271 100644
--- a/bec_widgets/examples/motor_movement/motor_controller.ui
+++ b/bec_widgets/examples/motor_movement/motor_controller.ui
@@ -60,17 +60,27 @@
General
-
-
-
+
+
-
Decimal precision
- -
+
-
+ -
+
+
+ Speed
+
+
+
+ -
+
+
diff --git a/bec_widgets/examples/motor_movement/motor_example.py b/bec_widgets/examples/motor_movement/motor_example.py
index e0c3f696..c7b49a6c 100644
--- a/bec_widgets/examples/motor_movement/motor_example.py
+++ b/bec_widgets/examples/motor_movement/motor_example.py
@@ -3,15 +3,19 @@ import os
import numpy as np
import pyqtgraph as pg
from PyQt5 import QtGui
+from PyQt5.QtCore import QThread
from PyQt5.QtCore import pyqtSignal, Qt
from PyQt5.QtWidgets import QApplication, QWidget
from pyqtgraph.Qt import QtWidgets, uic
+from bec_lib.core import MessageEndpoints, BECMessage
+
class MotorApp(QWidget):
motor_position = pyqtSignal(float) # TODO hook to motor position update
motor_update = pyqtSignal()
motor_position_xy = pyqtSignal(list)
+ coordinates_updated = pyqtSignal(float, float)
def __init__(self):
super().__init__()
@@ -20,6 +24,7 @@ class MotorApp(QWidget):
# UI
self.start_x, self.start_y = self.get_xy()
+ self.current_x, self.current_y = self.get_xy()
self.limit_x, self.limit_y = self.get_motor_limits(dev.samx), self.get_motor_limits(
dev.samy
)
@@ -29,8 +34,39 @@ class MotorApp(QWidget):
self.label_status.setText(
f"Motor position: ({dev.samx.position():.2f}, {dev.samy.position():.2f})"
)
+
+ # self.samx_consumer = client.connector.consumer(topics=[MessageEndpoints.device_readback('samx'),
+ # MessageEndpoints.device_readback('samy')],
+ # cb=self._device_status_callback,parent=self)
+ # self.samx_consumer.start()
+
+ # self.samx_consumer = client.connector.consumer(
+ # topics=[MessageEndpoints.device_readback("samx")],
+ # cb=self._device_status_callback_samx,
+ # parent=self,
+ # )
+ #
+ # self.samy_consumer = client.connector.consumer(
+ # topics=[MessageEndpoints.device_readback("samy")],
+ # cb=self._device_status_callback_samy,
+ # parent=self,
+ # )
+ # self.samx_consumer.start()
+ # self.samy_consumer.start()
+
+ self.motor_thread = MotorMovement()
+
self.init_ui()
+ def on_move_to_coordinates_button_clicked(self):
+ x = self.spinBox_absolute_x.value()
+ y = self.spinBox_absolute_y.value()
+ target_coordinates = (x, y)
+ self.motor_thread.move_to_coordinates(target_coordinates)
+
+ def on_move_relative_button_clicked(self, motor, value: float):
+ self.motor_thread.move_relative(motor, value)
+
def init_ui(self):
"""Setup all ui elements"""
##########################
@@ -56,17 +92,30 @@ class MotorApp(QWidget):
##########################
# Buttons - Motor Movement
+ # self.toolButton_right.clicked.connect(
+ # lambda: self.move_motor(dev.samx, self.spinBox_step.value())
+ # )
+ # self.toolButton_left.clicked.connect(
+ # lambda: self.move_motor(dev.samx, -self.spinBox_step.value())
+ # )
+ # self.toolButton_up.clicked.connect(
+ # lambda: self.move_motor(dev.samy, self.spinBox_step.value())
+ # )
+ # self.toolButton_down.clicked.connect(
+ # lambda: self.move_motor(dev.samy, -self.spinBox_step.value())
+ # )
+
self.toolButton_right.clicked.connect(
- lambda: self.move_motor(dev.samx, self.spinBox_step.value())
+ lambda: self.on_move_relative_button_clicked(dev.samx, self.spinBox_step.value())
)
self.toolButton_left.clicked.connect(
- lambda: self.move_motor(dev.samx, -self.spinBox_step.value())
+ lambda: self.on_move_relative_button_clicked(dev.samx, -self.spinBox_step.value())
)
self.toolButton_up.clicked.connect(
- lambda: self.move_motor(dev.samy, self.spinBox_step.value())
+ lambda: self.on_move_relative_button_clicked(dev.samy, self.spinBox_step.value())
)
self.toolButton_down.clicked.connect(
- lambda: self.move_motor(dev.samy, -self.spinBox_step.value())
+ lambda: self.on_move_relative_button_clicked(dev.samy, -self.spinBox_step.value())
)
self.checkBox_enableArrows.stateChanged.connect(self.update_arrow_key_shortcuts)
@@ -80,11 +129,6 @@ class MotorApp(QWidget):
)
# SpinBoxes - Motor Limits #TODO make spinboxes own limits updated, currently is [-1000, 1000]
- # manually set limits before readout #TODO will be removed when spinboxes have own limits
- # dev.samx.low_limit = -100
- # dev.samx.high_limit = 100
- # dev.samy.low_limit = -100
- # dev.samy.high_limit = 100
# display initial limits
self.spinBox_x_min.setValue(self.limit_x[0])
@@ -110,10 +154,14 @@ class MotorApp(QWidget):
self.motor_update.connect(lambda: self.update_image_map(*self.get_xy()))
self.motor_position.connect(lambda x: print(f"motor position updated: {x}"))
- self.motor_update.connect(
- lambda: self.label_status.setText(
- f"Motor position: ({dev.samx.position():.2f}, {dev.samy.position():.2f})"
- )
+ # self.motor_thread.coordinates_updated.connect(
+ # lambda: self.label_status.setText(
+ # f"Motor position: ({dev.samx.position():.2f}, {dev.samy.position():.2f})"
+ # )
+ # )
+
+ self.motor_thread.coordinates_updated.connect(
+ lambda x, y: self.update_image_map(int(x), int(y))
)
# Coordinates table
@@ -157,10 +205,10 @@ class MotorApp(QWidget):
"""Update the image map with the new motor position"""
# Define the dimming factor
- dimming_factor = 0.80
+ dimming_factor = 0.95
# Apply the dimming factor only to pixels above the background value
- self.image_map_data[self.image_map_data > self.background_value] *= dimming_factor
+ self.image_map_data[self.image_map_data > 50] *= dimming_factor
# Mapping of motor coordinates to pixel coordinates
pixel_x = int(x - self.offset_x)
@@ -223,29 +271,37 @@ class MotorApp(QWidget):
self.init_motor_map() # reinitialize the map with the new limits
def move_motor(self, motor, value: float) -> None:
- try:
- status = scans.mv(motor, value, relative=True)
+ self.current_x, self.current_y = self.get_xy()
+ scans.mv(motor, value, relative=True)
- while status.status not in ["COMPLETED", "STOPPED"]:
- print(status.status)
- motor_position = motor.position()
- self.motor_position.emit(motor_position)
+ # def move_motor(self, motor, value: float) -> None:
+ # # self.current_x, self.current_y = self.get_xy()
+ # scans.mv(motor, value, relative=True)
+ # try:
+ # status = scans.mv(motor, value, relative=True)
+ #
+ # # while status.status not in ["COMPLETED", "STOPPED"]:
+ # # print(status.status)
+ # # print(f"motor read{motor.read(cached=True)}")
+ # # motor_position = motor.position()
+ # # self.motor_position.emit(motor_position)
+ #
+ # # return status
+ # except Exception as e:
+ # print(e)
- # return status
- except Exception as e:
- print(e)
+ # motor_position = motor.position()
- motor_position = motor.position()
-
- self.motor_position.emit(motor_position)
- self.motor_update.emit()
+ # self.motor_position.emit(motor_position)
+ # self.motor_update.emit()
def move_motor_coordinates(self, coor: list) -> None:
+ scans.mv(dev.samx, coor[0], dev.samy, coor[1], relative=False)
try:
status = scans.mv(dev.samx, coor[0], dev.samy, coor[1], relative=False)
- while status.status not in ["COMPLETED", "STOPPED"]:
- print(status.status)
+ # while status.status not in ["COMPLETED", "STOPPED"]:
+ # print(status.status)
except Exception as e:
print(e)
@@ -313,19 +369,137 @@ class MotorApp(QWidget):
table.resizeColumnsToContents()
+ # @staticmethod
+ # def _device_status_callback(msg, *, parent, **_kwargs) -> None:
+ # deviceMSG = BECMessage.DeviceMessage.loads(msg.value)
+ # # current_coordinates = (deviceMSG.content['signals']['samx']['value'],deviceMSG.content['signals']['samy']['value'])
+ # # print(f'current coordinates: {current_coordinates}')
+ # # parent.scan_storage.update_with_scan_status(scan)
+ # print(deviceMSG)
+
+ # print(deviceMSG.metadata)
+ # parent.motor_update.emit(deviceMSG)
+
+ # @staticmethod
+ # def _device_status_callback_samx(msg, *, parent, **_kwargs) -> None:
+ # deviceMSG = BECMessage.DeviceMessage.loads(msg.value)
+ # x = deviceMSG.content["signals"]["samx"]["value"]
+ # print(f"x: {x}")
+ # # Assuming you have a way to retrieve or store the current samy value
+ # y = parent.current_y
+ # parent.coordinates_updated.emit(x, y)
+ #
+ # @staticmethod
+ # def _device_status_callback_samy(msg, *, parent, **_kwargs) -> None:
+ # deviceMSG = BECMessage.DeviceMessage.loads(msg.value)
+ # y = deviceMSG.content["signals"]["samy"]["value"]
+ # print(f"y: {y}")
+ # # # Assuming you have a way to retrieve or store the current samx value
+ # x = parent.current_x
+ # parent.coordinates_updated.emit(x, y)
+
+
+class MotorMovement(QThread):
+ coordinates_updated = pyqtSignal(float, float) # Signal to emit current coordinates
+
+ # progress_updated = pyqtSignal(int) # Signal to emit progress percentage
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ # self.initial_coordinates = None
+ self.target_coordinates = None
+ self.running = False
+
+ # Initialize current coordinates with the provided initial coordinates
+ self.current_x, self.current_y = None, None
+
+ # Create consumer for samx and samy
+ self.samx_consumer = client.connector.consumer(
+ topics=[MessageEndpoints.device_readback("samx")],
+ cb=self._device_status_callback_samx,
+ parent=self,
+ )
+
+ self.samy_consumer = client.connector.consumer(
+ topics=[MessageEndpoints.device_readback("samy")],
+ cb=self._device_status_callback_samy,
+ parent=self,
+ )
+
+ self.samx_consumer.start()
+ self.samy_consumer.start()
+
+ self.get_xy()
+
+ def move_to_coordinates(self, target_coordinates: tuple):
+ self.action = "move_to_coordinates"
+ self.target_coordinates = target_coordinates
+ self.start()
+
+ def move_relative(self, motor, value: float):
+ self.action = "move_relative"
+ self.motor = motor
+ self.value = value
+ self.start()
+
+ def run(self):
+ if self.action == "move_to_coordinates":
+ self.move_motor_coordinate()
+ elif self.action == "move_relative":
+ self.move_motor_relative(self.motor, self.value)
+
+ def set_target_coordinates(self, target_coordinates: tuple) -> None:
+ self.target_coordinates = target_coordinates
+
+ def get_xy(self) -> tuple:
+ """Get current motor position"""
+ self.current_x = dev.samx.position()
+ self.current_y = dev.samy.position()
+
+ def move_motor_coordinate(self):
+ """Move the motor to the specified coordinates"""
+ self.get_xy()
+ scans.mv(
+ dev.samx,
+ self.target_coordinates[0],
+ dev.samy,
+ self.target_coordinates[1],
+ relative=False,
+ )
+
+ def move_motor_relative(self, motor, value: float) -> None:
+ self.get_xy()
+ scans.mv(motor, value, relative=True)
+
+ @staticmethod
+ def _device_status_callback_samx(msg, *, parent, **_kwargs) -> None:
+ deviceMSG = BECMessage.DeviceMessage.loads(msg.value)
+ parent.current_x = deviceMSG.content["signals"]["samx"]["value"]
+ print(f"samx moving: {parent.current_x,parent.current_y}")
+ # y = parent.current_y
+ parent.coordinates_updated.emit(parent.current_x, parent.current_y)
+
+ @staticmethod
+ def _device_status_callback_samy(msg, *, parent, **_kwargs) -> None:
+ deviceMSG = BECMessage.DeviceMessage.loads(msg.value)
+ parent.current_y = deviceMSG.content["signals"]["samy"][
+ "value"
+ ] # TODO can be move to parent instance
+ print(f"samy moving: {parent.current_x,parent.current_y}")
+ # x = parent.current_y
+ parent.coordinates_updated.emit(parent.current_x, parent.current_y)
+
if __name__ == "__main__":
- from bec_widgets.bec_dispatcher import bec_dispatcher
+ from bec_lib import BECClient
- # from bec_lib import BECClient
# from bec_lib.core import ServiceConfig,RedisConnector
- # client = BECClient()
- # client.initialize(config=ServiceConfig(config_path="test_config.yaml"))
+ client = BECClient()
# client.initialize(config=ServiceConfig(config_path="test_config.yaml"))
# Client initialization - by dispatcher
- client = bec_dispatcher.client
+ # client = bec_dispatcher.client
client.start()
dev = client.device_manager.devices
scans = client.scans
From 11aa15fefda7433e885cc8586f93c97af83b0c48 Mon Sep 17 00:00:00 2001
From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com>
Date: Tue, 22 Aug 2023 18:55:55 +0200
Subject: [PATCH 10/29] fix: motor movement absolute fixed - movement by thread
---
.../examples/motor_movement/motor_example.py | 144 +++---------------
1 file changed, 17 insertions(+), 127 deletions(-)
diff --git a/bec_widgets/examples/motor_movement/motor_example.py b/bec_widgets/examples/motor_movement/motor_example.py
index c7b49a6c..00e265bb 100644
--- a/bec_widgets/examples/motor_movement/motor_example.py
+++ b/bec_widgets/examples/motor_movement/motor_example.py
@@ -25,6 +25,7 @@ class MotorApp(QWidget):
# UI
self.start_x, self.start_y = self.get_xy()
self.current_x, self.current_y = self.get_xy()
+
self.limit_x, self.limit_y = self.get_motor_limits(dev.samx), self.get_motor_limits(
dev.samy
)
@@ -35,40 +36,22 @@ class MotorApp(QWidget):
f"Motor position: ({dev.samx.position():.2f}, {dev.samy.position():.2f})"
)
- # self.samx_consumer = client.connector.consumer(topics=[MessageEndpoints.device_readback('samx'),
- # MessageEndpoints.device_readback('samy')],
- # cb=self._device_status_callback,parent=self)
- # self.samx_consumer.start()
-
- # self.samx_consumer = client.connector.consumer(
- # topics=[MessageEndpoints.device_readback("samx")],
- # cb=self._device_status_callback_samx,
- # parent=self,
- # )
- #
- # self.samy_consumer = client.connector.consumer(
- # topics=[MessageEndpoints.device_readback("samy")],
- # cb=self._device_status_callback_samy,
- # parent=self,
- # )
- # self.samx_consumer.start()
- # self.samy_consumer.start()
-
self.motor_thread = MotorMovement()
self.init_ui()
- def on_move_to_coordinates_button_clicked(self):
- x = self.spinBox_absolute_x.value()
- y = self.spinBox_absolute_y.value()
+ def move_motor_absolute(self, x, y):
+ # x = self.spinBox_absolute_x.value()
+ # y = self.spinBox_absolute_y.value()
target_coordinates = (x, y)
self.motor_thread.move_to_coordinates(target_coordinates)
- def on_move_relative_button_clicked(self, motor, value: float):
+ def move_motor_relative(self, motor, value: float):
self.motor_thread.move_relative(motor, value)
def init_ui(self):
"""Setup all ui elements"""
+
##########################
# 2D Plot
##########################
@@ -80,42 +63,21 @@ class MotorApp(QWidget):
self.image_map = pg.ImageItem()
self.plot_map.addItem(self.image_map)
- # self.image_map_data = np.zeros((100, 100), dtype=np.float32)
- # x, y = self.get_xy() # Assuming get_xy returns the motor position
- # self.update_image_map(x, y)
- # self.image_map.setImage(self.image_map_data.T) # Set the image
-
- # self.init_motor_map()
-
##########################
# Signals
##########################
- # Buttons - Motor Movement
- # self.toolButton_right.clicked.connect(
- # lambda: self.move_motor(dev.samx, self.spinBox_step.value())
- # )
- # self.toolButton_left.clicked.connect(
- # lambda: self.move_motor(dev.samx, -self.spinBox_step.value())
- # )
- # self.toolButton_up.clicked.connect(
- # lambda: self.move_motor(dev.samy, self.spinBox_step.value())
- # )
- # self.toolButton_down.clicked.connect(
- # lambda: self.move_motor(dev.samy, -self.spinBox_step.value())
- # )
-
self.toolButton_right.clicked.connect(
- lambda: self.on_move_relative_button_clicked(dev.samx, self.spinBox_step.value())
+ lambda: self.move_motor_relative(dev.samx, self.spinBox_step.value())
)
self.toolButton_left.clicked.connect(
- lambda: self.on_move_relative_button_clicked(dev.samx, -self.spinBox_step.value())
+ lambda: self.move_motor_relative(dev.samx, -self.spinBox_step.value())
)
self.toolButton_up.clicked.connect(
- lambda: self.on_move_relative_button_clicked(dev.samy, self.spinBox_step.value())
+ lambda: self.move_motor_relative(dev.samy, self.spinBox_step.value())
)
self.toolButton_down.clicked.connect(
- lambda: self.on_move_relative_button_clicked(dev.samy, -self.spinBox_step.value())
+ lambda: self.move_motor_relative(dev.samy, -self.spinBox_step.value())
)
self.checkBox_enableArrows.stateChanged.connect(self.update_arrow_key_shortcuts)
@@ -123,8 +85,8 @@ class MotorApp(QWidget):
# Move to absolute coordinates
self.pushButton_go_absolute.clicked.connect(
- lambda: self.move_motor_coordinates(
- [self.spinBox_absolute_x.value(), self.spinBox_absolute_y.value()]
+ lambda: self.move_motor_absolute(
+ self.spinBox_absolute_x.value(), self.spinBox_absolute_y.value()
)
)
@@ -270,54 +232,14 @@ class MotorApp(QWidget):
self.init_motor_map() # reinitialize the map with the new limits
- def move_motor(self, motor, value: float) -> None:
- self.current_x, self.current_y = self.get_xy()
- scans.mv(motor, value, relative=True)
-
- # def move_motor(self, motor, value: float) -> None:
- # # self.current_x, self.current_y = self.get_xy()
- # scans.mv(motor, value, relative=True)
- # try:
- # status = scans.mv(motor, value, relative=True)
- #
- # # while status.status not in ["COMPLETED", "STOPPED"]:
- # # print(status.status)
- # # print(f"motor read{motor.read(cached=True)}")
- # # motor_position = motor.position()
- # # self.motor_position.emit(motor_position)
- #
- # # return status
- # except Exception as e:
- # print(e)
-
- # motor_position = motor.position()
-
- # self.motor_position.emit(motor_position)
- # self.motor_update.emit()
-
- def move_motor_coordinates(self, coor: list) -> None:
- scans.mv(dev.samx, coor[0], dev.samy, coor[1], relative=False)
- try:
- status = scans.mv(dev.samx, coor[0], dev.samy, coor[1], relative=False)
-
- # while status.status not in ["COMPLETED", "STOPPED"]:
- # print(status.status)
-
- except Exception as e:
- print(e)
-
- # motor_position = self.get_xy()
- # self.motor_position_xy.emit(motor_position)
-
- self.motor_update.emit()
-
def get_xy(self): # TODO decide if useful
"""Get current motor position"""
x = dev.samx.position()
y = dev.samy.position()
return x, y
- def param_changed(self, ui_element):
+ @staticmethod
+ def param_changed(ui_element):
ui_element.setStyleSheet("background-color: #FFA700;")
def update_arrow_key_shortcuts(self):
@@ -359,45 +281,13 @@ class MotorApp(QWidget):
# hook signals of table
button.clicked.connect(
- lambda: self.move_motor_coordinates(
- [
- float(table.item(current_row_count, 2).text()),
- float(table.item(current_row_count, 3).text()),
- ]
+ lambda: self.move_motor_absolute(
+ float(table.item(current_row_count, 2).text()),
+ float(table.item(current_row_count, 3).text()),
)
)
-
table.resizeColumnsToContents()
- # @staticmethod
- # def _device_status_callback(msg, *, parent, **_kwargs) -> None:
- # deviceMSG = BECMessage.DeviceMessage.loads(msg.value)
- # # current_coordinates = (deviceMSG.content['signals']['samx']['value'],deviceMSG.content['signals']['samy']['value'])
- # # print(f'current coordinates: {current_coordinates}')
- # # parent.scan_storage.update_with_scan_status(scan)
- # print(deviceMSG)
-
- # print(deviceMSG.metadata)
- # parent.motor_update.emit(deviceMSG)
-
- # @staticmethod
- # def _device_status_callback_samx(msg, *, parent, **_kwargs) -> None:
- # deviceMSG = BECMessage.DeviceMessage.loads(msg.value)
- # x = deviceMSG.content["signals"]["samx"]["value"]
- # print(f"x: {x}")
- # # Assuming you have a way to retrieve or store the current samy value
- # y = parent.current_y
- # parent.coordinates_updated.emit(x, y)
- #
- # @staticmethod
- # def _device_status_callback_samy(msg, *, parent, **_kwargs) -> None:
- # deviceMSG = BECMessage.DeviceMessage.loads(msg.value)
- # y = deviceMSG.content["signals"]["samy"]["value"]
- # print(f"y: {y}")
- # # # Assuming you have a way to retrieve or store the current samx value
- # x = parent.current_x
- # parent.coordinates_updated.emit(x, y)
-
class MotorMovement(QThread):
coordinates_updated = pyqtSignal(float, float) # Signal to emit current coordinates
From 349c06bcac6c61c96681a00dd846741898dedf30 Mon Sep 17 00:00:00 2001
From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com>
Date: Wed, 23 Aug 2023 12:57:48 +0200
Subject: [PATCH 11/29] refactor: getting motor limits and coordinates moved to
MotorControl(QThread)
---
.../motor_movement/motor_controller.ui | 2 +-
.../examples/motor_movement/motor_example.py | 220 +++++++++---------
2 files changed, 109 insertions(+), 113 deletions(-)
diff --git a/bec_widgets/examples/motor_movement/motor_controller.ui b/bec_widgets/examples/motor_movement/motor_controller.ui
index 47521271..9bda1f41 100644
--- a/bec_widgets/examples/motor_movement/motor_controller.ui
+++ b/bec_widgets/examples/motor_movement/motor_controller.ui
@@ -260,7 +260,7 @@
-
- - samx
+ + samx
diff --git a/bec_widgets/examples/motor_movement/motor_example.py b/bec_widgets/examples/motor_movement/motor_example.py
index 00e265bb..3e034194 100644
--- a/bec_widgets/examples/motor_movement/motor_example.py
+++ b/bec_widgets/examples/motor_movement/motor_example.py
@@ -3,7 +3,7 @@ import os
import numpy as np
import pyqtgraph as pg
from PyQt5 import QtGui
-from PyQt5.QtCore import QThread
+from PyQt5.QtCore import QThread, pyqtSlot
from PyQt5.QtCore import pyqtSignal, Qt
from PyQt5.QtWidgets import QApplication, QWidget
from pyqtgraph.Qt import QtWidgets, uic
@@ -12,9 +12,6 @@ from bec_lib.core import MessageEndpoints, BECMessage
class MotorApp(QWidget):
- motor_position = pyqtSignal(float) # TODO hook to motor position update
- motor_update = pyqtSignal()
- motor_position_xy = pyqtSignal(list)
coordinates_updated = pyqtSignal(float, float)
def __init__(self):
@@ -22,34 +19,51 @@ class MotorApp(QWidget):
current_path = os.path.dirname(__file__)
uic.loadUi(os.path.join(current_path, "motor_controller.ui"), self)
- # UI
- self.start_x, self.start_y = self.get_xy()
- self.current_x, self.current_y = self.get_xy()
+ self.limit_x, self.limit_y = None, None
- self.limit_x, self.limit_y = self.get_motor_limits(dev.samx), self.get_motor_limits(
- dev.samy
- )
+ # QThread for motor movement + signals
+ self.motor_thread = MotorControl()
+ self.motor_thread.limits_retrieved.connect(self.update_limits)
+
+ # UI
+ self.init_ui()
+
+ # Initialize current coordinates with the provided initial coordinates
+ self.motor_thread.retrieve_motor_limits(dev.samx, dev.samy)
print(f"Init limits: samx:{self.limit_x}, samy:{self.limit_y}")
self.label_status.setText(
f"Motor position: ({dev.samx.position():.2f}, {dev.samy.position():.2f})"
- )
+ ) # TODO has to be change to get positions from thread not directly from device
- self.motor_thread = MotorMovement()
+ @pyqtSlot(list, list)
+ def update_limits(self, x_limits: list, y_limits: list) -> None:
+ self.limit_x = x_limits
+ self.limit_y = y_limits
+ self.spinBox_x_min.setValue(self.limit_x[0])
+ self.spinBox_x_max.setValue(self.limit_x[1])
+ self.spinBox_y_min.setValue(self.limit_y[0])
+ self.spinBox_y_max.setValue(self.limit_y[1])
- self.init_ui()
+ for spinBox in (
+ self.spinBox_x_min,
+ self.spinBox_x_max,
+ self.spinBox_y_min,
+ self.spinBox_y_max,
+ ):
+ spinBox.setStyleSheet("")
- def move_motor_absolute(self, x, y):
- # x = self.spinBox_absolute_x.value()
- # y = self.spinBox_absolute_y.value()
+ self.init_motor_map() # reinitialize the map with the new limits
+
+ def move_motor_absolute(self, x: float, y: float) -> None:
target_coordinates = (x, y)
self.motor_thread.move_to_coordinates(target_coordinates)
- def move_motor_relative(self, motor, value: float):
+ def move_motor_relative(self, motor, value: float) -> None:
self.motor_thread.move_relative(motor, value)
- def init_ui(self):
+ def init_ui(self) -> None:
"""Setup all ui elements"""
##########################
@@ -80,6 +94,7 @@ class MotorApp(QWidget):
lambda: self.move_motor_relative(dev.samy, -self.spinBox_step.value())
)
+ # Switch between key shortcuts active
self.checkBox_enableArrows.stateChanged.connect(self.update_arrow_key_shortcuts)
self.update_arrow_key_shortcuts()
@@ -92,12 +107,6 @@ class MotorApp(QWidget):
# SpinBoxes - Motor Limits #TODO make spinboxes own limits updated, currently is [-1000, 1000]
- # display initial limits
- self.spinBox_x_min.setValue(self.limit_x[0])
- self.spinBox_x_max.setValue(self.limit_x[1])
- self.spinBox_y_min.setValue(self.limit_y[0])
- self.spinBox_y_max.setValue(self.limit_y[1])
-
# SpinBoxes change color to yellow before updated, limits are updated with update button
self.spinBox_x_min.valueChanged.connect(lambda: self.param_changed(self.spinBox_x_min))
self.spinBox_x_max.valueChanged.connect(lambda: self.param_changed(self.spinBox_x_max))
@@ -111,28 +120,19 @@ class MotorApp(QWidget):
)
)
- # Map
- self.motor_position.connect(lambda x: self.update_image_map(*self.get_xy()))
- self.motor_update.connect(lambda: self.update_image_map(*self.get_xy()))
-
- self.motor_position.connect(lambda x: print(f"motor position updated: {x}"))
- # self.motor_thread.coordinates_updated.connect(
- # lambda: self.label_status.setText(
- # f"Motor position: ({dev.samx.position():.2f}, {dev.samy.position():.2f})"
- # )
- # )
-
+ # TODO map with floats as well -> or decide system for higher precision
self.motor_thread.coordinates_updated.connect(
lambda x, y: self.update_image_map(int(x), int(y))
)
# Coordinates table
self.generate_table_coordinate(
- self.tableWidget_coordinates, self.get_xy(), tag="Initial", precision=0
+ self.tableWidget_coordinates,
+ self.motor_thread.retrieve_coordinates(),
+ tag="Initial",
+ precision=0,
)
- self.init_motor_map()
-
def init_motor_map(self):
# Get motor limits
limit_x_min, limit_x_max = self.get_motor_limits(dev.samx)
@@ -152,8 +152,7 @@ class MotorApp(QWidget):
)
# Set the initial position on the map
- x, y = self.get_xy()
- self.prev_x, self.prev_y = x, y
+ x, y = self.motor_thread.retrieve_coordinates()
self.update_image_map(x, y)
# Translate and scale the image item to match the motor coordinates
@@ -189,58 +188,10 @@ class MotorApp(QWidget):
return low_limit, high_limit
- def update_motor_limits(
- self, motor, low_limit=None, high_limit=None
- ): # TODO limits cannot be smaller that the current location of motor
- # Get current limits
- current_low_limit, current_high_limit = motor.limits[0], motor.limits[1]
-
- # Check if the low limit has changed and is not None
- if low_limit is not None and low_limit != current_low_limit:
- motor.low_limit = low_limit
-
- # Check if the high limit has changed and is not None
- if high_limit is not None and high_limit != current_high_limit:
- motor.high_limit = high_limit
-
- # self.init_motor_map() # reinitialize the map with the new limits
-
- def update_all_motor_limits(self, x_limit: list = None, y_limit: list = None) -> None:
- # check if current motor position if within the new limits
-
- current_position = self.get_xy()
-
- if x_limit is not None:
- if current_position[0] < x_limit[0] or current_position[0] > x_limit[1]:
- raise ValueError("Current motor position is outside the new limits (X)")
- else:
- self.update_motor_limits(dev.samx, low_limit=x_limit[0], high_limit=x_limit[1])
-
- if y_limit is not None:
- if current_position[1] < y_limit[0] or current_position[1] > y_limit[1]:
- raise ValueError("Current motor position is outside the new limits (Y)")
- else:
- self.update_motor_limits(dev.samy, low_limit=y_limit[0], high_limit=y_limit[1])
-
- for spinBox in (
- self.spinBox_x_min,
- self.spinBox_x_max,
- self.spinBox_y_min,
- self.spinBox_y_max,
- ):
- spinBox.setStyleSheet("")
-
- self.init_motor_map() # reinitialize the map with the new limits
-
- def get_xy(self): # TODO decide if useful
- """Get current motor position"""
- x = dev.samx.position()
- y = dev.samy.position()
- return x, y
-
- @staticmethod
- def param_changed(ui_element):
- ui_element.setStyleSheet("background-color: #FFA700;")
+ def update_all_motor_limits(
+ self, x_limit: list = None, y_limit: list = None
+ ) -> None: # TODO will be moved to thread
+ self.motor_thread.update_all_motor_limits(x_limit=x_limit, y_limit=y_limit)
def update_arrow_key_shortcuts(self):
if self.checkBox_enableArrows.isChecked():
@@ -257,7 +208,7 @@ class MotorApp(QWidget):
self.toolButton_down.setShortcut("")
def generate_table_coordinate(
- self, table: QtWidgets.QTableWidget, coordinates: list, tag: str = None, precision: int = 0
+ self, table: QtWidgets.QTableWidget, coordinates: tuple, tag: str = None, precision: int = 0
) -> None:
current_row_count = table.rowCount()
@@ -288,20 +239,26 @@ class MotorApp(QWidget):
)
table.resizeColumnsToContents()
+ @staticmethod
+ def param_changed(ui_element):
+ ui_element.setStyleSheet("background-color: #FFA700;")
-class MotorMovement(QThread):
+
+class MotorControl(QThread):
coordinates_updated = pyqtSignal(float, float) # Signal to emit current coordinates
-
- # progress_updated = pyqtSignal(int) # Signal to emit progress percentage
+ limits_retrieved = pyqtSignal(list, list) # Signal to emit current limits (samx, samy)
+ # progress_updated = pyqtSignal(int) #TODO Signal to emit progress percentage
def __init__(self, parent=None):
super().__init__(parent)
- # self.initial_coordinates = None
self.target_coordinates = None
self.running = False
# Initialize current coordinates with the provided initial coordinates
- self.current_x, self.current_y = None, None
+ (
+ self.current_x,
+ self.current_y,
+ ) = self.get_coordinates() # TODO maybe not needed, coordinates always stored in redis
# Create consumer for samx and samy
self.samx_consumer = client.connector.consumer(
@@ -319,7 +276,56 @@ class MotorMovement(QThread):
self.samx_consumer.start()
self.samy_consumer.start()
- self.get_xy()
+ def get_coordinates(self) -> tuple:
+ # TODO decide if the get_coordinates and retrieve coordinates makes sense
+ # TODO or move also to signal/slot as in the case of limits
+ # TODO or move just to redis CB function
+ """Get current motor position"""
+ x = dev.samx.position()
+ y = dev.samy.position()
+ return x, y
+
+ def retrieve_coordinates(self) -> tuple:
+ """Get current motor position for export to main app"""
+ return self.current_x, self.current_y
+
+ def get_motor_limits(self, motor) -> list:
+ """Get the limits of a motor"""
+ return motor.limits
+
+ def retrieve_motor_limits(self, motor_x, motor_y):
+ limit_x = self.get_motor_limits(motor_x)
+ limit_y = self.get_motor_limits(motor_y)
+ self.limits_retrieved.emit(limit_x, limit_y)
+
+ def update_motor_limits(self, motor, low_limit=None, high_limit=None) -> None:
+ current_low_limit, current_high_limit = self.get_motor_limits(motor)
+
+ # Check if the low limit has changed and is not None
+ if low_limit is not None and low_limit != current_low_limit:
+ motor.low_limit = low_limit
+
+ # Check if the high limit has changed and is not None
+ if high_limit is not None and high_limit != current_high_limit:
+ motor.high_limit = high_limit
+
+ def update_all_motor_limits(self, x_limit: list = None, y_limit: list = None) -> None:
+ # TODO can be generalized to any motor not just samx samy
+ current_position = self.get_coordinates()
+
+ if x_limit is not None:
+ if current_position[0] < x_limit[0] or current_position[0] > x_limit[1]:
+ raise ValueError("Current motor position is outside the new limits (X)")
+ else:
+ self.update_motor_limits(dev.samx, low_limit=x_limit[0], high_limit=x_limit[1])
+
+ if y_limit is not None:
+ if current_position[1] < y_limit[0] or current_position[1] > y_limit[1]:
+ raise ValueError("Current motor position is outside the new limits (Y)")
+ else:
+ self.update_motor_limits(dev.samy, low_limit=y_limit[0], high_limit=y_limit[1])
+
+ self.retrieve_motor_limits(dev.samx, dev.samy)
def move_to_coordinates(self, target_coordinates: tuple):
self.action = "move_to_coordinates"
@@ -341,14 +347,8 @@ class MotorMovement(QThread):
def set_target_coordinates(self, target_coordinates: tuple) -> None:
self.target_coordinates = target_coordinates
- def get_xy(self) -> tuple:
- """Get current motor position"""
- self.current_x = dev.samx.position()
- self.current_y = dev.samy.position()
-
- def move_motor_coordinate(self):
+ def move_motor_coordinate(self) -> None:
"""Move the motor to the specified coordinates"""
- self.get_xy()
scans.mv(
dev.samx,
self.target_coordinates[0],
@@ -358,25 +358,21 @@ class MotorMovement(QThread):
)
def move_motor_relative(self, motor, value: float) -> None:
- self.get_xy()
scans.mv(motor, value, relative=True)
+ # TODO decide if one or multiple callback functions
@staticmethod
def _device_status_callback_samx(msg, *, parent, **_kwargs) -> None:
deviceMSG = BECMessage.DeviceMessage.loads(msg.value)
parent.current_x = deviceMSG.content["signals"]["samx"]["value"]
print(f"samx moving: {parent.current_x,parent.current_y}")
- # y = parent.current_y
parent.coordinates_updated.emit(parent.current_x, parent.current_y)
@staticmethod
def _device_status_callback_samy(msg, *, parent, **_kwargs) -> None:
deviceMSG = BECMessage.DeviceMessage.loads(msg.value)
- parent.current_y = deviceMSG.content["signals"]["samy"][
- "value"
- ] # TODO can be move to parent instance
+ parent.current_y = deviceMSG.content["signals"]["samy"]["value"]
print(f"samy moving: {parent.current_x,parent.current_y}")
- # x = parent.current_y
parent.coordinates_updated.emit(parent.current_x, parent.current_y)
From 95ead7117e59e0979aec51b85b49537ab728cad4 Mon Sep 17 00:00:00 2001
From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com>
Date: Wed, 23 Aug 2023 13:04:23 +0200
Subject: [PATCH 12/29] fix: init_motor_map receive motor position from
motor_thread
---
.../examples/motor_movement/motor_example.py | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/bec_widgets/examples/motor_movement/motor_example.py b/bec_widgets/examples/motor_movement/motor_example.py
index 3e034194..a946e8bd 100644
--- a/bec_widgets/examples/motor_movement/motor_example.py
+++ b/bec_widgets/examples/motor_movement/motor_example.py
@@ -135,8 +135,8 @@ class MotorApp(QWidget):
def init_motor_map(self):
# Get motor limits
- limit_x_min, limit_x_max = self.get_motor_limits(dev.samx)
- limit_y_min, limit_y_max = self.get_motor_limits(dev.samy)
+ limit_x_min, limit_x_max = self.motor_thread.get_motor_limits(dev.samx)
+ limit_y_min, limit_y_max = self.motor_thread.get_motor_limits(dev.samy)
self.offset_x = limit_x_min
self.offset_y = limit_y_min
@@ -181,12 +181,12 @@ class MotorApp(QWidget):
# Update the display
self.image_map.updateImage(self.image_map_data, levels=(0, 255))
- def get_motor_limits(self, motor):
- """Get the limits of a motor"""
- high_limit = motor.high_limit
- low_limit = motor.low_limit
-
- return low_limit, high_limit
+ # def get_motor_limits(self, motor):
+ # """Get the limits of a motor"""
+ # high_limit = motor.high_limit
+ # low_limit = motor.low_limit
+ #
+ # return low_limit, high_limit
def update_all_motor_limits(
self, x_limit: list = None, y_limit: list = None
From 8be98c9bb6af941a69c593c62d5c52339d2262bc Mon Sep 17 00:00:00 2001
From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com>
Date: Wed, 23 Aug 2023 13:17:35 +0200
Subject: [PATCH 13/29] feat: going to absolute coordinates saves coordinate in
the table for later use with tag
---
.../examples/motor_movement/motor_example.py | 19 ++++++++++++-------
.../examples/plotting/crosshair_example.py | 2 --
2 files changed, 12 insertions(+), 9 deletions(-)
diff --git a/bec_widgets/examples/motor_movement/motor_example.py b/bec_widgets/examples/motor_movement/motor_example.py
index a946e8bd..a8dcc571 100644
--- a/bec_widgets/examples/motor_movement/motor_example.py
+++ b/bec_widgets/examples/motor_movement/motor_example.py
@@ -27,6 +27,7 @@ class MotorApp(QWidget):
# UI
self.init_ui()
+ self.tag_N = 1 # position label for saved coordinates
# Initialize current coordinates with the provided initial coordinates
self.motor_thread.retrieve_motor_limits(dev.samx, dev.samy)
@@ -104,6 +105,7 @@ class MotorApp(QWidget):
self.spinBox_absolute_x.value(), self.spinBox_absolute_y.value()
)
)
+ self.pushButton_go_absolute.clicked.connect(self.save_absolute_coordinates)
# SpinBoxes - Motor Limits #TODO make spinboxes own limits updated, currently is [-1000, 1000]
@@ -181,13 +183,6 @@ class MotorApp(QWidget):
# Update the display
self.image_map.updateImage(self.image_map_data, levels=(0, 255))
- # def get_motor_limits(self, motor):
- # """Get the limits of a motor"""
- # high_limit = motor.high_limit
- # low_limit = motor.low_limit
- #
- # return low_limit, high_limit
-
def update_all_motor_limits(
self, x_limit: list = None, y_limit: list = None
) -> None: # TODO will be moved to thread
@@ -239,6 +234,16 @@ class MotorApp(QWidget):
)
table.resizeColumnsToContents()
+ def save_absolute_coordinates(self):
+ self.generate_table_coordinate(
+ self.tableWidget_coordinates,
+ (self.spinBox_absolute_x.value(), self.spinBox_absolute_y.value()),
+ tag=f"Pos {self.tag_N}",
+ precision=0,
+ )
+
+ self.tag_N += 1
+
@staticmethod
def param_changed(ui_element):
ui_element.setStyleSheet("background-color: #FFA700;")
diff --git a/bec_widgets/examples/plotting/crosshair_example.py b/bec_widgets/examples/plotting/crosshair_example.py
index 5aed65aa..f573ad69 100644
--- a/bec_widgets/examples/plotting/crosshair_example.py
+++ b/bec_widgets/examples/plotting/crosshair_example.py
@@ -15,10 +15,8 @@ from pyqtgraph.Qt import QtCore
from bec_widgets.qt_utils import Crosshair
-
class ExampleApp(QWidget):
def __init__(self):
- """Example application for using the Crosshair class"""
super().__init__()
# Layout
From 3f6d5c66411459703c402f7449e8b1abae9a2b08 Mon Sep 17 00:00:00 2001
From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com>
Date: Wed, 23 Aug 2023 16:48:23 +0200
Subject: [PATCH 14/29] feat: motor coordinates are now scatter instead of
image
---
.../examples/motor_movement/motor_example.py | 111 ++++++++++++++----
1 file changed, 86 insertions(+), 25 deletions(-)
diff --git a/bec_widgets/examples/motor_movement/motor_example.py b/bec_widgets/examples/motor_movement/motor_example.py
index a8dcc571..e9e2670f 100644
--- a/bec_widgets/examples/motor_movement/motor_example.py
+++ b/bec_widgets/examples/motor_movement/motor_example.py
@@ -20,6 +20,8 @@ class MotorApp(QWidget):
uic.loadUi(os.path.join(current_path, "motor_controller.ui"), self)
self.limit_x, self.limit_y = None, None
+ # Coordinates tracking
+ self.motor_positions = []
# QThread for motor movement + signals
self.motor_thread = MotorControl()
@@ -38,6 +40,13 @@ class MotorApp(QWidget):
f"Motor position: ({dev.samx.position():.2f}, {dev.samy.position():.2f})"
) # TODO has to be change to get positions from thread not directly from device
+ # self.background_map_scale = 10
+ # self.visited_coordinates = {}
+ # self.precision = 2 # Define the decimal precision
+
+ # Initialize the image map
+ self.init_motor_map()
+
@pyqtSlot(list, list)
def update_limits(self, x_limits: list, y_limits: list) -> None:
self.limit_x = x_limits
@@ -75,8 +84,12 @@ class MotorApp(QWidget):
f"Motor position: ({dev.samx.position():.2f}, {dev.samy.position():.2f})", row=0, col=0
)
self.plot_map = self.glw.addPlot(row=1, col=0)
- self.image_map = pg.ImageItem()
- self.plot_map.addItem(self.image_map)
+ self.limit_map = pg.ImageItem()
+ self.plot_map.addItem(self.limit_map)
+ self.motor_map = pg.ScatterPlotItem(
+ size=2, pen=pg.mkPen(None), brush=pg.mkBrush(255, 255, 255, 255)
+ )
+ self.plot_map.addItem(self.motor_map)
##########################
# Signals
@@ -124,7 +137,7 @@ class MotorApp(QWidget):
# TODO map with floats as well -> or decide system for higher precision
self.motor_thread.coordinates_updated.connect(
- lambda x, y: self.update_image_map(int(x), int(y))
+ lambda x, y: self.update_image_map(round(x, 2), round(y, 2))
)
# Coordinates table
@@ -149,39 +162,87 @@ class MotorApp(QWidget):
# Create an empty image map
self.background_value = 15
- self.image_map_data = np.full(
+ self.limit_map_data = np.full(
(map_width, map_height), self.background_value, dtype=np.float32
)
+ self.limit_map.setImage(self.limit_map_data)
+ # # Set the initial position on the map
+ init_pos = self.motor_thread.retrieve_coordinates()
+ init_brush = pg.mkBrush(255, 255, 255, 255)
+ self.motor_positions.append({"pos": init_pos, "brush": init_brush})
- # Set the initial position on the map
- x, y = self.motor_thread.retrieve_coordinates()
- self.update_image_map(x, y)
+ self.motor_map.setData(
+ pos=[point["pos"] for point in self.motor_positions],
+ brush=[point["brush"] for point in self.motor_positions],
+ )
+ # self.update_image_map(x, y)
+ #
# Translate and scale the image item to match the motor coordinates
self.tr = QtGui.QTransform()
self.tr.translate(limit_x_min, limit_y_min)
- self.image_map.setTransform(self.tr)
-
- self.image_map.dataTransform()
+ self.limit_map.setTransform(self.tr)
+ #
+ # self.image_map.dataTransform()
def update_image_map(self, x, y):
- """Update the image map with the new motor position"""
+ # Dim previous points
+ # for point in self.motor_positions.values():
+ # point["brush"] = pg.mkBrush(
+ # 255, 255, 255, max(50, point["brush"].color().alpha() * 0.95)
+ # )
- # Define the dimming factor
- dimming_factor = 0.95
+ # Add new point with full brightness
+ new_pos = (x, y)
+ new_brush = pg.mkBrush(255, 255, 255, 255)
+ self.motor_positions.append({"pos": new_pos, "brush": new_brush})
+ self.motor_map.setData(
+ pos=[point["pos"] for point in self.motor_positions],
+ brush=[point["brush"] for point in self.motor_positions],
+ )
- # Apply the dimming factor only to pixels above the background value
- self.image_map_data[self.image_map_data > 50] *= dimming_factor
+ # new_point = {"pos": key, "brush": pg.mkBrush(255, 255, 255, 255)}
+ # self.motor_positions[key] = new_point
+ #
+ # Update ScatterPlotItem
+ # positions = np.array([point["pos"] for point in self.motor_positions.values()])
+ # brushes = [point["brush"] for point in self.motor_positions.values()]
+ # self.scatter_plot_item.setData(pos=positions, brush=brushes)
- # Mapping of motor coordinates to pixel coordinates
- pixel_x = int(x - self.offset_x)
- pixel_y = int(y - self.offset_y)
+ # # Dim previous points
+ # for point in self.motor_positions.values():
+ # point["brush"] = pg.mkBrush(
+ # 255, 255, 255, max(50, point["brush"].color().alpha() * 0.95)
+ # )
+ #
+ # # Add new point with full brightness
+ # key = (x, y)
+ # new_point = {"pos": key, "brush": pg.mkBrush(255, 255, 255, 255)}
+ # self.motor_positions[key] = new_point
+ #
+ # # Update ScatterPlotItem
+ # positions = [point["pos"] for point in self.motor_positions.values()]
+ # brushes = [point["brush"] for point in self.motor_positions.values()]
+ # self.motor_map.setData(positions[0], positions[1], brush=brushes)
- # Set the bright pixel at the new position
- self.image_map_data[pixel_x, pixel_y] = 255
-
- # Update the display
- self.image_map.updateImage(self.image_map_data, levels=(0, 255))
+ # def update_image_map(self, x, y):
+ # """Update the image map with the new motor position"""
+ #
+ # # Define the dimming factor
+ # dimming_factor = 0.95
+ #
+ # # Apply the dimming factor only to pixels above the background value
+ # self.image_map_data[self.image_map_data > 50] *= dimming_factor
+ #
+ # # Mapping of motor coordinates to pixel coordinates
+ # pixel_x = int(x - self.offset_x)
+ # pixel_y = int(y - self.offset_y)
+ #
+ # # Set the bright pixel at the new position
+ # self.image_map_data[pixel_x, pixel_y] = 255
+ #
+ # # Update the display
+ # self.image_map.updateImage(self.image_map_data, levels=(0, 255))
def update_all_motor_limits(
self, x_limit: list = None, y_limit: list = None
@@ -370,14 +431,14 @@ class MotorControl(QThread):
def _device_status_callback_samx(msg, *, parent, **_kwargs) -> None:
deviceMSG = BECMessage.DeviceMessage.loads(msg.value)
parent.current_x = deviceMSG.content["signals"]["samx"]["value"]
- print(f"samx moving: {parent.current_x,parent.current_y}")
+ # print(f"samx moving: {parent.current_x,parent.current_y}")
parent.coordinates_updated.emit(parent.current_x, parent.current_y)
@staticmethod
def _device_status_callback_samy(msg, *, parent, **_kwargs) -> None:
deviceMSG = BECMessage.DeviceMessage.loads(msg.value)
parent.current_y = deviceMSG.content["signals"]["samy"]["value"]
- print(f"samy moving: {parent.current_x,parent.current_y}")
+ # print(f"samy moving: {parent.current_x,parent.current_y}")
parent.coordinates_updated.emit(parent.current_x, parent.current_y)
From ed842931971fbf87ed2f3e366eb822531ef5aacc Mon Sep 17 00:00:00 2001
From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com>
Date: Wed, 23 Aug 2023 17:21:44 +0200
Subject: [PATCH 15/29] feat: controls are disabled while motor is moving and
enabled when motor movement is finished
---
.../examples/motor_movement/motor_example.py | 21 +++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/bec_widgets/examples/motor_movement/motor_example.py b/bec_widgets/examples/motor_movement/motor_example.py
index e9e2670f..fa4c38b8 100644
--- a/bec_widgets/examples/motor_movement/motor_example.py
+++ b/bec_widgets/examples/motor_movement/motor_example.py
@@ -66,11 +66,25 @@ class MotorApp(QWidget):
self.init_motor_map() # reinitialize the map with the new limits
+ @pyqtSlot()
+ def enable_motor_control(self):
+ self.motorControl.setEnabled(True)
+
+ def enable_motor_controls(self, disable: bool) -> None:
+ # Disable or enable all controls within the motorControl group box
+ for widget in self.motorControl.findChildren(QtWidgets.QWidget):
+ widget.setEnabled(disable)
+
+ # Enable the pushButton_stop if the motor is moving
+ self.pushButton_stop.setEnabled(not disable)
+
def move_motor_absolute(self, x: float, y: float) -> None:
+ self.enable_motor_controls(False)
target_coordinates = (x, y)
self.motor_thread.move_to_coordinates(target_coordinates)
def move_motor_relative(self, motor, value: float) -> None:
+ self.enable_motor_controls(False)
self.motor_thread.move_relative(motor, value)
def init_ui(self) -> None:
@@ -120,6 +134,8 @@ class MotorApp(QWidget):
)
self.pushButton_go_absolute.clicked.connect(self.save_absolute_coordinates)
+ self.motor_thread.move_finished.connect(lambda: self.enable_motor_controls(True))
+
# SpinBoxes - Motor Limits #TODO make spinboxes own limits updated, currently is [-1000, 1000]
# SpinBoxes change color to yellow before updated, limits are updated with update button
@@ -313,6 +329,7 @@ class MotorApp(QWidget):
class MotorControl(QThread):
coordinates_updated = pyqtSignal(float, float) # Signal to emit current coordinates
limits_retrieved = pyqtSignal(list, list) # Signal to emit current limits (samx, samy)
+ move_finished = pyqtSignal() # Signal to emit when the move is finished
# progress_updated = pyqtSignal(int) #TODO Signal to emit progress percentage
def __init__(self, parent=None):
@@ -433,6 +450,8 @@ class MotorControl(QThread):
parent.current_x = deviceMSG.content["signals"]["samx"]["value"]
# print(f"samx moving: {parent.current_x,parent.current_y}")
parent.coordinates_updated.emit(parent.current_x, parent.current_y)
+ if deviceMSG.content["signals"]["samx_motor_is_moving"]["value"] == 0:
+ parent.move_finished.emit()
@staticmethod
def _device_status_callback_samy(msg, *, parent, **_kwargs) -> None:
@@ -440,6 +459,8 @@ class MotorControl(QThread):
parent.current_y = deviceMSG.content["signals"]["samy"]["value"]
# print(f"samy moving: {parent.current_x,parent.current_y}")
parent.coordinates_updated.emit(parent.current_x, parent.current_y)
+ if deviceMSG.content["signals"]["samy_motor_is_moving"]["value"] == 0:
+ parent.move_finished.emit()
if __name__ == "__main__":
From 187c748e87264448d5026d9fa2f15b5fc9a55949 Mon Sep 17 00:00:00 2001
From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com>
Date: Thu, 24 Aug 2023 17:22:21 +0200
Subject: [PATCH 16/29] feat: stop movement function, one callback function for
2 motors, move_finished is emitted in move_motor function not in callback
---
.../motor_movement/motor_controller.ui | 10 --
.../examples/motor_movement/motor_example.py | 126 ++++++------------
2 files changed, 41 insertions(+), 95 deletions(-)
diff --git a/bec_widgets/examples/motor_movement/motor_controller.ui b/bec_widgets/examples/motor_movement/motor_controller.ui
index 9bda1f41..cded821d 100644
--- a/bec_widgets/examples/motor_movement/motor_controller.ui
+++ b/bec_widgets/examples/motor_movement/motor_controller.ui
@@ -16,16 +16,6 @@
-
-
-
-
-
- Motor Position
-
-
- Qt::AlignCenter
-
-
-
-
-
diff --git a/bec_widgets/examples/motor_movement/motor_example.py b/bec_widgets/examples/motor_movement/motor_example.py
index fa4c38b8..a8c2252f 100644
--- a/bec_widgets/examples/motor_movement/motor_example.py
+++ b/bec_widgets/examples/motor_movement/motor_example.py
@@ -36,10 +36,6 @@ class MotorApp(QWidget):
print(f"Init limits: samx:{self.limit_x}, samy:{self.limit_y}")
- self.label_status.setText(
- f"Motor position: ({dev.samx.position():.2f}, {dev.samy.position():.2f})"
- ) # TODO has to be change to get positions from thread not directly from device
-
# self.background_map_scale = 10
# self.visited_coordinates = {}
# self.precision = 2 # Define the decimal precision
@@ -106,7 +102,7 @@ class MotorApp(QWidget):
self.plot_map.addItem(self.motor_map)
##########################
- # Signals
+ # Motor movements signals
##########################
self.toolButton_right.clicked.connect(
@@ -133,9 +129,15 @@ class MotorApp(QWidget):
)
)
self.pushButton_go_absolute.clicked.connect(self.save_absolute_coordinates)
-
self.motor_thread.move_finished.connect(lambda: self.enable_motor_controls(True))
+ # Stop Button
+ self.pushButton_stop.clicked.connect(self.motor_thread.stop_movement)
+
+ ##########################
+ # Motor limits signals
+ ##########################
+
# SpinBoxes - Motor Limits #TODO make spinboxes own limits updated, currently is [-1000, 1000]
# SpinBoxes change color to yellow before updated, limits are updated with update button
@@ -192,21 +194,19 @@ class MotorApp(QWidget):
brush=[point["brush"] for point in self.motor_positions],
)
- # self.update_image_map(x, y)
- #
# Translate and scale the image item to match the motor coordinates
self.tr = QtGui.QTransform()
self.tr.translate(limit_x_min, limit_y_min)
self.limit_map.setTransform(self.tr)
- #
- # self.image_map.dataTransform()
def update_image_map(self, x, y):
+ # Update label
+ self.label_coorditanes.setText(f"Motor position: ({x:.2f}, {y:.2f})")
+
# Dim previous points
- # for point in self.motor_positions.values():
- # point["brush"] = pg.mkBrush(
- # 255, 255, 255, max(50, point["brush"].color().alpha() * 0.95)
- # )
+ last_brush = self.motor_positions[-1]["brush"].color().getRgb()
+ dimmer_brush = tuple(max(int(0.8 * c), 50) for c in last_brush[:3]) + (255,)
+ self.motor_positions[-1]["brush"] = pg.mkBrush(dimmer_brush)
# Add new point with full brightness
new_pos = (x, y)
@@ -217,49 +217,6 @@ class MotorApp(QWidget):
brush=[point["brush"] for point in self.motor_positions],
)
- # new_point = {"pos": key, "brush": pg.mkBrush(255, 255, 255, 255)}
- # self.motor_positions[key] = new_point
- #
- # Update ScatterPlotItem
- # positions = np.array([point["pos"] for point in self.motor_positions.values()])
- # brushes = [point["brush"] for point in self.motor_positions.values()]
- # self.scatter_plot_item.setData(pos=positions, brush=brushes)
-
- # # Dim previous points
- # for point in self.motor_positions.values():
- # point["brush"] = pg.mkBrush(
- # 255, 255, 255, max(50, point["brush"].color().alpha() * 0.95)
- # )
- #
- # # Add new point with full brightness
- # key = (x, y)
- # new_point = {"pos": key, "brush": pg.mkBrush(255, 255, 255, 255)}
- # self.motor_positions[key] = new_point
- #
- # # Update ScatterPlotItem
- # positions = [point["pos"] for point in self.motor_positions.values()]
- # brushes = [point["brush"] for point in self.motor_positions.values()]
- # self.motor_map.setData(positions[0], positions[1], brush=brushes)
-
- # def update_image_map(self, x, y):
- # """Update the image map with the new motor position"""
- #
- # # Define the dimming factor
- # dimming_factor = 0.95
- #
- # # Apply the dimming factor only to pixels above the background value
- # self.image_map_data[self.image_map_data > 50] *= dimming_factor
- #
- # # Mapping of motor coordinates to pixel coordinates
- # pixel_x = int(x - self.offset_x)
- # pixel_y = int(y - self.offset_y)
- #
- # # Set the bright pixel at the new position
- # self.image_map_data[pixel_x, pixel_y] = 255
- #
- # # Update the display
- # self.image_map.updateImage(self.image_map_data, levels=(0, 255))
-
def update_all_motor_limits(
self, x_limit: list = None, y_limit: list = None
) -> None: # TODO will be moved to thread
@@ -343,27 +300,25 @@ class MotorControl(QThread):
self.current_y,
) = self.get_coordinates() # TODO maybe not needed, coordinates always stored in redis
- # Create consumer for samx and samy
- self.samx_consumer = client.connector.consumer(
- topics=[MessageEndpoints.device_readback("samx")],
- cb=self._device_status_callback_samx,
+ self.active_devices = ["samx", "samy"]
+
+ self.motors_consumer = client.connector.consumer(
+ topics=[
+ MessageEndpoints.device_readback(self.active_devices[0]),
+ MessageEndpoints.device_readback(self.active_devices[1]),
+ ],
+ cb=self._device_status_callback_motors,
parent=self,
)
- self.samy_consumer = client.connector.consumer(
- topics=[MessageEndpoints.device_readback("samy")],
- cb=self._device_status_callback_samy,
- parent=self,
- )
-
- self.samx_consumer.start()
- self.samy_consumer.start()
+ self.motors_consumer.start()
def get_coordinates(self) -> tuple:
# TODO decide if the get_coordinates and retrieve coordinates makes sense
# TODO or move also to signal/slot as in the case of limits
# TODO or move just to redis CB function
"""Get current motor position"""
+ dev.samx.read(cached=True) # TODO to get last message ['value'] -> last position
x = dev.samx.position()
y = dev.samy.position()
return x, y
@@ -432,7 +387,7 @@ class MotorControl(QThread):
def move_motor_coordinate(self) -> None:
"""Move the motor to the specified coordinates"""
- scans.mv(
+ status = scans.mv(
dev.samx,
self.target_coordinates[0],
dev.samy,
@@ -440,27 +395,27 @@ class MotorControl(QThread):
relative=False,
)
+ status.wait()
+ self.move_finished.emit()
+
def move_motor_relative(self, motor, value: float) -> None:
- scans.mv(motor, value, relative=True)
+ status = scans.mv(motor, value, relative=True)
- # TODO decide if one or multiple callback functions
- @staticmethod
- def _device_status_callback_samx(msg, *, parent, **_kwargs) -> None:
- deviceMSG = BECMessage.DeviceMessage.loads(msg.value)
- parent.current_x = deviceMSG.content["signals"]["samx"]["value"]
- # print(f"samx moving: {parent.current_x,parent.current_y}")
- parent.coordinates_updated.emit(parent.current_x, parent.current_y)
- if deviceMSG.content["signals"]["samx_motor_is_moving"]["value"] == 0:
- parent.move_finished.emit()
+ status.wait()
+ self.move_finished.emit()
+
+ def stop_movement(self):
+ queue.request_scan_abortion()
+ queue.request_queue_reset()
@staticmethod
- def _device_status_callback_samy(msg, *, parent, **_kwargs) -> None:
+ def _device_status_callback_motors(msg, *, parent, **_kwargs) -> None:
deviceMSG = BECMessage.DeviceMessage.loads(msg.value)
- parent.current_y = deviceMSG.content["signals"]["samy"]["value"]
- # print(f"samy moving: {parent.current_x,parent.current_y}")
+ if parent.active_devices[0] in deviceMSG.content["signals"]:
+ parent.current_x = deviceMSG.content["signals"][parent.active_devices[0]]["value"]
+ elif parent.active_devices[1] in deviceMSG.content["signals"]:
+ parent.current_y = deviceMSG.content["signals"][parent.active_devices[1]]["value"]
parent.coordinates_updated.emit(parent.current_x, parent.current_y)
- if deviceMSG.content["signals"]["samy_motor_is_moving"]["value"] == 0:
- parent.move_finished.emit()
if __name__ == "__main__":
@@ -476,6 +431,7 @@ if __name__ == "__main__":
client.start()
dev = client.device_manager.devices
scans = client.scans
+ queue = client.queue
app = QApplication([])
window = MotorApp()
From 3e408b304ba6b755fc472ea0d39d457b93b55be9 Mon Sep 17 00:00:00 2001
From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com>
Date: Thu, 24 Aug 2023 18:18:12 +0200
Subject: [PATCH 17/29] refactor: migrate to use just np.array for tracking
position, only latest N points are being dimmed.
---
.../examples/motor_movement/motor_example.py | 36 +++++++++----------
1 file changed, 17 insertions(+), 19 deletions(-)
diff --git a/bec_widgets/examples/motor_movement/motor_example.py b/bec_widgets/examples/motor_movement/motor_example.py
index a8c2252f..bf6c2b3d 100644
--- a/bec_widgets/examples/motor_movement/motor_example.py
+++ b/bec_widgets/examples/motor_movement/motor_example.py
@@ -20,8 +20,9 @@ class MotorApp(QWidget):
uic.loadUi(os.path.join(current_path, "motor_controller.ui"), self)
self.limit_x, self.limit_y = None, None
+
# Coordinates tracking
- self.motor_positions = []
+ self.motor_positions = np.array([])
# QThread for motor movement + signals
self.motor_thread = MotorControl()
@@ -184,15 +185,13 @@ class MotorApp(QWidget):
(map_width, map_height), self.background_value, dtype=np.float32
)
self.limit_map.setImage(self.limit_map_data)
- # # Set the initial position on the map
- init_pos = self.motor_thread.retrieve_coordinates()
- init_brush = pg.mkBrush(255, 255, 255, 255)
- self.motor_positions.append({"pos": init_pos, "brush": init_brush})
- self.motor_map.setData(
- pos=[point["pos"] for point in self.motor_positions],
- brush=[point["brush"] for point in self.motor_positions],
- )
+ # Set the initial position on the map
+ init_pos = self.motor_thread.retrieve_coordinates()
+ self.motor_positions = np.array([init_pos])
+ self.brushes = [pg.mkBrush(255, 255, 255, 255)]
+
+ self.motor_map.setData(pos=self.motor_positions, brush=self.brushes)
# Translate and scale the image item to match the motor coordinates
self.tr = QtGui.QTransform()
@@ -203,19 +202,18 @@ class MotorApp(QWidget):
# Update label
self.label_coorditanes.setText(f"Motor position: ({x:.2f}, {y:.2f})")
- # Dim previous points
- last_brush = self.motor_positions[-1]["brush"].color().getRgb()
- dimmer_brush = tuple(max(int(0.8 * c), 50) for c in last_brush[:3]) + (255,)
- self.motor_positions[-1]["brush"] = pg.mkBrush(dimmer_brush)
+ # Determine brushes based on position in the array
+ self.brushes = [pg.mkBrush(50, 50, 50, 255)] * len(self.motor_positions)
+ for i in range(1, min(6, len(self.motor_positions) + 1)):
+ brightness = max(50, 255 - 20 * (i - 1))
+ self.brushes[-i] = pg.mkBrush(brightness, brightness, brightness, 255)
# Add new point with full brightness
new_pos = (x, y)
- new_brush = pg.mkBrush(255, 255, 255, 255)
- self.motor_positions.append({"pos": new_pos, "brush": new_brush})
- self.motor_map.setData(
- pos=[point["pos"] for point in self.motor_positions],
- brush=[point["brush"] for point in self.motor_positions],
- )
+ self.motor_positions = np.vstack((self.motor_positions, new_pos))
+ self.brushes.append(pg.mkBrush(255, 255, 255, 255))
+
+ self.motor_map.setData(pos=self.motor_positions, brush=self.brushes)
def update_all_motor_limits(
self, x_limit: list = None, y_limit: list = None
From 9eae697df8a2f3961454db9ed397353f110c0e67 Mon Sep 17 00:00:00 2001
From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com>
Date: Thu, 24 Aug 2023 18:22:50 +0200
Subject: [PATCH 18/29] feat: ability to choose how many points should be
dimmed before reaching the threshold + total number of point which should be
stored.
---
.../examples/motor_movement/motor_example.py | 19 +++++++++++++------
1 file changed, 13 insertions(+), 6 deletions(-)
diff --git a/bec_widgets/examples/motor_movement/motor_example.py b/bec_widgets/examples/motor_movement/motor_example.py
index bf6c2b3d..a6ca112a 100644
--- a/bec_widgets/examples/motor_movement/motor_example.py
+++ b/bec_widgets/examples/motor_movement/motor_example.py
@@ -23,6 +23,9 @@ class MotorApp(QWidget):
# Coordinates tracking
self.motor_positions = np.array([])
+ self.max_points = 50 # Maximum number of points to keep
+ self.num_dim_points = 15 # Number of points to dim gradually
+ self.precision = 2 # Define the decimal precision
# QThread for motor movement + signals
self.motor_thread = MotorControl()
@@ -39,7 +42,6 @@ class MotorApp(QWidget):
# self.background_map_scale = 10
# self.visited_coordinates = {}
- # self.precision = 2 # Define the decimal precision
# Initialize the image map
self.init_motor_map()
@@ -202,16 +204,21 @@ class MotorApp(QWidget):
# Update label
self.label_coorditanes.setText(f"Motor position: ({x:.2f}, {y:.2f})")
+ # Add new point with full brightness
+ new_pos = np.array([x, y])
+ self.motor_positions = np.vstack((self.motor_positions, new_pos))
+
+ # If the number of points exceeds max_points, delete the oldest points
+ if len(self.motor_positions) > self.max_points:
+ self.motor_positions = self.motor_positions[-self.max_points :]
+
# Determine brushes based on position in the array
self.brushes = [pg.mkBrush(50, 50, 50, 255)] * len(self.motor_positions)
- for i in range(1, min(6, len(self.motor_positions) + 1)):
+ for i in range(1, min(self.num_dim_points + 1, len(self.motor_positions) + 1)):
brightness = max(50, 255 - 20 * (i - 1))
self.brushes[-i] = pg.mkBrush(brightness, brightness, brightness, 255)
- # Add new point with full brightness
- new_pos = (x, y)
- self.motor_positions = np.vstack((self.motor_positions, new_pos))
- self.brushes.append(pg.mkBrush(255, 255, 255, 255))
+ self.brushes[-1] = pg.mkBrush(255, 255, 255, 255) # Newest point is always full brightness
self.motor_map.setData(pos=self.motor_positions, brush=self.brushes)
From 3c0e5955d40a67935b8fb064d5c52fd3f29bd1a1 Mon Sep 17 00:00:00 2001
From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com>
Date: Thu, 24 Aug 2023 18:35:52 +0200
Subject: [PATCH 19/29] feat: keyboard shortcut to go to coordinates
---
bec_widgets/line_plot.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/bec_widgets/line_plot.py b/bec_widgets/line_plot.py
index 53485989..ca2371b5 100644
--- a/bec_widgets/line_plot.py
+++ b/bec_widgets/line_plot.py
@@ -26,7 +26,7 @@ class BasicPlot(QtWidgets.QWidget):
update_signal = pyqtSignal()
roi_signal = pyqtSignal(tuple)
- def __init__(self, name="", y_value_list=["gauss_bpm"]) -> None:
+ def __init__(self, name="", y_value_list=["samx", "samy"]) -> None:
"""
Basic plot widget for displaying scan data.
@@ -387,7 +387,7 @@ if __name__ == "__main__":
"--signals",
help="specify recorded signals",
nargs="+",
- default=["gauss_bpm"],
+ default=["samx", "samy"],
)
# default = ["gauss_bpm", "bpm4i", "bpm5i", "bpm6i", "xert"],
# dispatcher = bec_dispatcher
From 0226188079f1dac4eece6b1a6fa330620f1504bc Mon Sep 17 00:00:00 2001
From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com>
Date: Fri, 25 Aug 2023 11:36:49 +0200
Subject: [PATCH 20/29] feat: new GUI
---
.../motor_movement/motor_controller.ui | 733 +++++++++++-------
.../examples/motor_movement/motor_example.py | 53 +-
2 files changed, 498 insertions(+), 288 deletions(-)
diff --git a/bec_widgets/examples/motor_movement/motor_controller.ui b/bec_widgets/examples/motor_movement/motor_controller.ui
index cded821d..5d0d8143 100644
--- a/bec_widgets/examples/motor_movement/motor_controller.ui
+++ b/bec_widgets/examples/motor_movement/motor_controller.ui
@@ -6,288 +6,268 @@
0
0
- 1289
- 596
+ 1115
+ 506
Motor Controller
-
+
-
-
-
-
-
-
-
-
-
- Go
-
-
-
- -
-
-
- Save
-
-
-
-
-
- -
-
-
- true
-
-
-
-
+
+
+ true
+
+
-
-
-
-
-
-
- General
-
-
-
-
-
-
- Decimal precision
-
-
-
- -
-
-
- -
-
-
- Speed
-
-
-
- -
-
-
-
-
-
- -
-
-
- Motor Controls
-
-
-
-
-
-
- Move with arrow keys
-
-
-
- -
-
-
-
-
-
- ...
-
-
- Qt::UpArrow
-
-
-
- -
-
-
- ...
-
-
- Qt::LeftArrow
-
-
-
- -
-
-
- 5
-
-
-
- -
-
-
- ...
-
-
- Qt::RightArrow
-
-
-
- -
-
-
- ...
-
-
- Qt::DownArrow
-
-
-
-
-
- -
-
-
-
-
-
- X
-
-
- Qt::AlignCenter
-
-
-
- -
-
-
- Y
-
-
- Qt::AlignCenter
-
-
-
- -
-
-
- Go
-
-
-
- -
-
-
- -
-
-
-
-
- -
-
-
- Stop Movement
-
-
-
-
-
-
- -
-
-
- true
-
-
- Motor Limits
-
-
-
-
-
-
- -1000
-
-
- 1000
-
-
- -100
-
-
-
- -
-
-
- -1000
-
-
- 1000
-
-
- 100
-
-
-
- -
-
-
- - samy
-
-
-
- -
-
-
- -1000
-
-
- 1000
-
-
- 100
-
-
-
- -
-
-
- + samy
-
-
-
- -
-
-
- + samx
-
-
-
- -
-
-
- - samx
-
-
-
- -
-
-
- -1000
-
-
- 1000
-
-
- -100
-
-
-
- -
-
-
- Update
-
-
-
-
-
-
-
+
+
+
+ 221
+ 471
+
+
+
+ -
+
+
+ Motor Selection
+
+
+
-
+
+
+ Motor Y
+
+
+
+ -
+
+
+ Motor X
+
+
+
+ -
+
+
+ -
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 18
+
+
+
+
+ -
+
+
+ Motor Relative
+
+
+
-
+
+
+ Move with arrow keys
+
+
+
+ -
+
+
+ QLayout::SetDefaultConstraint
+
+
-
+
+
+ ...
+
+
+ Qt::UpArrow
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ 5
+
+
+
+ -
+
+
+ ...
+
+
+ Qt::DownArrow
+
+
+
+ -
+
+
+ ...
+
+
+ Qt::LeftArrow
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+ -
+
+
+ ...
+
+
+ Qt::RightArrow
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 13
+
+
+
+
+ -
+
+
+ Move Absolute
+
+
+
-
+
+
-
+
+
+ X
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ Y
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ Go
+
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+ Stop Movement
+
+
+
+
+
+
+
+
-
-
+
+
+ true
+
0
@@ -327,6 +307,209 @@
+
+
+ Settings
+
+
+ -
+
+
+ Motor Limits
+
+
+
-
+
+
+ Qt::AlignCenter
+
+
+ -1000
+
+
+ 1000
+
+
+ -100
+
+
+
+ -
+
+
+ Update
+
+
+
+ -
+
+
+ -1000
+
+
+ 1000
+
+
+ 100
+
+
+
+ -
+
+
+ + Y
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ Qt::AlignCenter
+
+
+ -1000
+
+
+ 1000
+
+
+ 100
+
+
+
+ -
+
+
+ -1000
+
+
+ 1000
+
+
+ -100
+
+
+
+ -
+
+
+ - Y
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ - X
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ + X
+
+
+ Qt::AlignCenter
+
+
+
+
+
+
+ -
+
+
+ Motor config
+
+
+
-
+
+
+ -
+
+
+ -
+
+
+ Tolerance
+
+
+
+ -
+
+
+ Update Frequency
+
+
+
+ -
+
+
+ -
+
+
+ Decimal Precision
+
+
+
+ -
+
+
+ -
+
+
+ X
+
+
+
+ -
+
+
+ Speed
+
+
+
+ -
+
+
+ Y
+
+
+
+ -
+
+
+ X = Y
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+
+
+
Queue
@@ -334,6 +517,9 @@
-
+
+ false
+
Reset Queue
@@ -341,6 +527,9 @@
-
+
+ false
+
queueID
diff --git a/bec_widgets/examples/motor_movement/motor_example.py b/bec_widgets/examples/motor_movement/motor_example.py
index a6ca112a..5d32d97d 100644
--- a/bec_widgets/examples/motor_movement/motor_example.py
+++ b/bec_widgets/examples/motor_movement/motor_example.py
@@ -11,6 +11,18 @@ from pyqtgraph.Qt import QtWidgets, uic
from bec_lib.core import MessageEndpoints, BECMessage
+# TODO - General features
+# - setting motor speed and frequency
+# - setting motor acceleration
+# - updating motor precision
+# - put motor selection dropdown or listwidget
+# - put motor status (moving, stopped, etc)
+# - remove all hardcoded references to samx and samy
+# - add spinBox for motor scatter size
+# - add mouse interactions with the plot -> click to select coordinates, double click to move?
+# - adjust right click actions
+
+
class MotorApp(QWidget):
coordinates_updated = pyqtSignal(float, float)
@@ -23,9 +35,8 @@ class MotorApp(QWidget):
# Coordinates tracking
self.motor_positions = np.array([])
- self.max_points = 50 # Maximum number of points to keep
+ self.max_points = 5000 # Maximum number of points to keep
self.num_dim_points = 15 # Number of points to dim gradually
- self.precision = 2 # Define the decimal precision
# QThread for motor movement + signals
self.motor_thread = MotorControl()
@@ -40,9 +51,6 @@ class MotorApp(QWidget):
print(f"Init limits: samx:{self.limit_x}, samy:{self.limit_y}")
- # self.background_map_scale = 10
- # self.visited_coordinates = {}
-
# Initialize the image map
self.init_motor_map()
@@ -103,6 +111,14 @@ class MotorApp(QWidget):
size=2, pen=pg.mkPen(None), brush=pg.mkBrush(255, 255, 255, 255)
)
self.plot_map.addItem(self.motor_map)
+ self.plot_map.showGrid(x=True, y=True)
+
+ ##########################
+ # Motor General setting
+ ##########################
+
+ # TODO make function to update precision
+ self.precision = 2 # self.spinBox_precision.value() # Define the decimal precision
##########################
# Motor movements signals
@@ -132,6 +148,9 @@ class MotorApp(QWidget):
)
)
self.pushButton_go_absolute.clicked.connect(self.save_absolute_coordinates)
+ self.pushButton_go_absolute.setShortcut("Ctrl+G")
+ self.pushButton_go_absolute.setToolTip("Ctrl+G")
+
self.motor_thread.move_finished.connect(lambda: self.enable_motor_controls(True))
# Stop Button
@@ -158,7 +177,7 @@ class MotorApp(QWidget):
# TODO map with floats as well -> or decide system for higher precision
self.motor_thread.coordinates_updated.connect(
- lambda x, y: self.update_image_map(round(x, 2), round(y, 2))
+ lambda x, y: self.update_image_map(round(x, self.precision), round(y, self.precision))
)
# Coordinates table
@@ -294,19 +313,23 @@ class MotorControl(QThread):
move_finished = pyqtSignal() # Signal to emit when the move is finished
# progress_updated = pyqtSignal(int) #TODO Signal to emit progress percentage
- def __init__(self, parent=None):
+ def __init__(
+ self,
+ active_devices=["samx", "samy"],
+ parent=None,
+ ):
super().__init__(parent)
self.target_coordinates = None
self.running = False
+ self.active_devices = active_devices
+
# Initialize current coordinates with the provided initial coordinates
(
self.current_x,
self.current_y,
) = self.get_coordinates() # TODO maybe not needed, coordinates always stored in redis
- self.active_devices = ["samx", "samy"]
-
self.motors_consumer = client.connector.consumer(
topics=[
MessageEndpoints.device_readback(self.active_devices[0]),
@@ -319,13 +342,9 @@ class MotorControl(QThread):
self.motors_consumer.start()
def get_coordinates(self) -> tuple:
- # TODO decide if the get_coordinates and retrieve coordinates makes sense
- # TODO or move also to signal/slot as in the case of limits
- # TODO or move just to redis CB function
"""Get current motor position"""
- dev.samx.read(cached=True) # TODO to get last message ['value'] -> last position
- x = dev.samx.position()
- y = dev.samy.position()
+ x = dev.samx.read(cached=True)["value"] # TODO remove hardcoded samx and samy
+ y = dev.samx.read(cached=True)["value"]
return x, y
def retrieve_coordinates(self) -> tuple:
@@ -360,7 +379,9 @@ class MotorControl(QThread):
if current_position[0] < x_limit[0] or current_position[0] > x_limit[1]:
raise ValueError("Current motor position is outside the new limits (X)")
else:
- self.update_motor_limits(dev.samx, low_limit=x_limit[0], high_limit=x_limit[1])
+ self.update_motor_limits(
+ dev.samx, low_limit=x_limit[0], high_limit=x_limit[1]
+ ) # TODO Remove hardcoded samx and samy
if y_limit is not None:
if current_position[1] < y_limit[0] or current_position[1] > y_limit[1]:
From cab32be0092185870b5a12398103475342c8b1fd Mon Sep 17 00:00:00 2001
From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com>
Date: Fri, 25 Aug 2023 16:07:05 +0200
Subject: [PATCH 21/29] feat: motor selection
---
.../motor_movement/motor_controller.ui | 29 ++--
.../examples/motor_movement/motor_example.py | 161 ++++++++++++++----
2 files changed, 144 insertions(+), 46 deletions(-)
diff --git a/bec_widgets/examples/motor_movement/motor_controller.ui b/bec_widgets/examples/motor_movement/motor_controller.ui
index 5d0d8143..fbb122ff 100644
--- a/bec_widgets/examples/motor_movement/motor_controller.ui
+++ b/bec_widgets/examples/motor_movement/motor_controller.ui
@@ -6,8 +6,8 @@
0
0
- 1115
- 506
+ 1120
+ 547
@@ -43,6 +43,12 @@
+ -
+
+
+ -
+
+
-
@@ -50,11 +56,12 @@
- -
-
-
- -
-
+
-
+
+
+ Connect Motors
+
+
@@ -208,7 +215,7 @@
-
-
+
Move Absolute
@@ -271,7 +278,7 @@
0
-
+
Coordinates
@@ -307,7 +314,7 @@
-
+
Settings
@@ -510,7 +517,7 @@
-
+
Queue
diff --git a/bec_widgets/examples/motor_movement/motor_example.py b/bec_widgets/examples/motor_movement/motor_example.py
index 5d32d97d..cba06a52 100644
--- a/bec_widgets/examples/motor_movement/motor_example.py
+++ b/bec_widgets/examples/motor_movement/motor_example.py
@@ -31,6 +31,10 @@ class MotorApp(QWidget):
current_path = os.path.dirname(__file__)
uic.loadUi(os.path.join(current_path, "motor_controller.ui"), self)
+ # Motor Control Thread
+ self.motor_thread = MotorControl()
+
+ self.motor_x, self.motor_y = None, None # dev.samx, dev.samy
self.limit_x, self.limit_y = None, None
# Coordinates tracking
@@ -39,21 +43,44 @@ class MotorApp(QWidget):
self.num_dim_points = 15 # Number of points to dim gradually
# QThread for motor movement + signals
- self.motor_thread = MotorControl()
+ self.motor_thread.motors_loaded.connect(self.get_available_motors)
+ self.motor_thread.motors_selected.connect(self.get_selected_motors)
self.motor_thread.limits_retrieved.connect(self.update_limits)
# UI
self.init_ui()
self.tag_N = 1 # position label for saved coordinates
- # Initialize current coordinates with the provided initial coordinates
- self.motor_thread.retrieve_motor_limits(dev.samx, dev.samy)
+ # Get all motors available
+ self.motor_thread.retrieve_all_motors()
- print(f"Init limits: samx:{self.limit_x}, samy:{self.limit_y}")
+ # Initialize current coordinates with the provided initial coordinates
+ # self.motor_thread.retrieve_motor_limits(self.motor_x, self.motor_y) #TODO move after connection of motors
+
+ # print(f"Init limits: samx:{self.limit_x}, samy:{self.limit_y}") # TODO get to motor connect
# Initialize the image map
+ # self.init_motor_map()
+
+ def connect_motor(self, motor_x_name: str, motor_y_name: str):
+ self.motor_thread.connect_motors(motor_x_name, motor_y_name)
+ self.motor_thread.retrieve_motor_limits(self.motor_x, self.motor_y)
self.init_motor_map()
+ self.motorControl.setEnabled(True)
+ self.motorControl_absolute.setEnabled(True)
+ self.tabWidget_tables.setTabEnabled(1, True)
+
+ @pyqtSlot(object, object)
+ def get_selected_motors(self, motor_x, motor_y):
+ self.motor_x, self.motor_y = motor_x, motor_y
+
+ @pyqtSlot(list, list)
+ def get_available_motors(self, motors_x, motors_y):
+ self.comboBox_motor_x.addItems(motors_x)
+ self.comboBox_motor_y.addItems(motors_y)
+ print(f"got motors {motors_x} and {motors_y}")
+
@pyqtSlot(list, list)
def update_limits(self, x_limits: list, y_limits: list) -> None:
self.limit_x = x_limits
@@ -101,9 +128,12 @@ class MotorApp(QWidget):
# 2D Plot
##########################
+ # self.label_coorditanes = self.glw.addLabel(
+ # f"Motor position: ({dev.samx.position():.2f}, {dev.samy.position():.2f})", row=0, col=0
+ # ) #TODO remove hardcoded samx and samy
self.label_coorditanes = self.glw.addLabel(
- f"Motor position: ({dev.samx.position():.2f}, {dev.samy.position():.2f})", row=0, col=0
- )
+ f"Motor position: (X, Y)", row=0, col=0
+ ) # TODO remove hardcoded samx and samy
self.plot_map = self.glw.addPlot(row=1, col=0)
self.limit_map = pg.ImageItem()
self.plot_map.addItem(self.limit_map)
@@ -125,16 +155,16 @@ class MotorApp(QWidget):
##########################
self.toolButton_right.clicked.connect(
- lambda: self.move_motor_relative(dev.samx, self.spinBox_step.value())
+ lambda: self.move_motor_relative(self.motor_x, self.spinBox_step.value())
)
self.toolButton_left.clicked.connect(
- lambda: self.move_motor_relative(dev.samx, -self.spinBox_step.value())
+ lambda: self.move_motor_relative(self.motor_x, -self.spinBox_step.value())
)
self.toolButton_up.clicked.connect(
- lambda: self.move_motor_relative(dev.samy, self.spinBox_step.value())
+ lambda: self.move_motor_relative(self.motor_y, self.spinBox_step.value())
)
self.toolButton_down.clicked.connect(
- lambda: self.move_motor_relative(dev.samy, -self.spinBox_step.value())
+ lambda: self.move_motor_relative(self.motor_y, -self.spinBox_step.value())
)
# Switch between key shortcuts active
@@ -188,10 +218,23 @@ class MotorApp(QWidget):
precision=0,
)
+ # Motor connections
+ self.pushButton_connecMotors.clicked.connect(
+ lambda: self.connect_motor(
+ self.comboBox_motor_x.currentText(), self.comboBox_motor_y.currentText()
+ )
+ )
+
+ # Check if there are any motors connected
+ if self.motor_x or self.motor_y is None:
+ self.motorControl.setEnabled(False)
+ self.motorControl_absolute.setEnabled(False)
+ self.tabWidget_tables.setTabEnabled(1, False)
+
def init_motor_map(self):
# Get motor limits
- limit_x_min, limit_x_max = self.motor_thread.get_motor_limits(dev.samx)
- limit_y_min, limit_y_max = self.motor_thread.get_motor_limits(dev.samy)
+ limit_x_min, limit_x_max = self.motor_thread.get_motor_limits(self.motor_x)
+ limit_y_min, limit_y_max = self.motor_thread.get_motor_limits(self.motor_y)
self.offset_x = limit_x_min
self.offset_y = limit_y_min
@@ -241,9 +284,7 @@ class MotorApp(QWidget):
self.motor_map.setData(pos=self.motor_positions, brush=self.brushes)
- def update_all_motor_limits(
- self, x_limit: list = None, y_limit: list = None
- ) -> None: # TODO will be moved to thread
+ def update_all_motor_limits(self, x_limit: list = None, y_limit: list = None) -> None:
self.motor_thread.update_all_motor_limits(x_limit=x_limit, y_limit=y_limit)
def update_arrow_key_shortcuts(self):
@@ -311,6 +352,8 @@ class MotorControl(QThread):
coordinates_updated = pyqtSignal(float, float) # Signal to emit current coordinates
limits_retrieved = pyqtSignal(list, list) # Signal to emit current limits (samx, samy)
move_finished = pyqtSignal() # Signal to emit when the move is finished
+ motors_loaded = pyqtSignal(list, list) # Signal to emit when the motors are loaded
+ motors_selected = pyqtSignal(object, object) # Signal to emit when the motors are selected
# progress_updated = pyqtSignal(int) #TODO Signal to emit progress percentage
def __init__(
@@ -319,21 +362,50 @@ class MotorControl(QThread):
parent=None,
):
super().__init__(parent)
+
+ self.motor_x, self.motor_y = None, None
+ self.current_x, self.current_y = None, None
+
+ self.motors_consumer = None
+
+ # Get all available motors in the client
+ self.all_motors = self.get_all_motors()
+ self.all_motors_names = self.get_all_motors_names()
+ self.retrieve_all_motors() # send motor list to GUI
+
+ self.connect_motors("samx", "samy")
+
self.target_coordinates = None
- self.running = False
- self.active_devices = active_devices
+ self.motor_x_debug = dev.samx
+ # self.all_motors = self.get_all_motors()
+ # self.all_motors_names = self.get_all_motors_names()
- # Initialize current coordinates with the provided initial coordinates
- (
- self.current_x,
- self.current_y,
- ) = self.get_coordinates() # TODO maybe not needed, coordinates always stored in redis
+ # find index of samx in self.all_motors_names
+ # self.motor_x_index = self.all_motors_names.index(self.active_devices[0])
+
+ print(f"Motor debug: {self.motor_x_debug}")
+
+ def motor_by_string(self, motor_x_name: str, motor_y_name: str) -> tuple:
+ motor_x_index = self.all_motors_names.index(motor_x_name)
+ motor_y_index = self.all_motors_names.index(motor_y_name)
+
+ motor_x = self.all_motors[motor_x_index]
+ motor_y = self.all_motors[motor_y_index]
+ return motor_x, motor_y
+
+ def connect_motors(self, motor_x_name: str, motor_y_name: str) -> None:
+ self.motor_x, self.motor_y = self.motor_by_string(motor_x_name, motor_y_name)
+
+ (self.current_x, self.current_y) = self.get_coordinates()
+
+ if self.motors_consumer is not None:
+ self.motors_consumer.shutdown()
self.motors_consumer = client.connector.consumer(
topics=[
- MessageEndpoints.device_readback(self.active_devices[0]),
- MessageEndpoints.device_readback(self.active_devices[1]),
+ MessageEndpoints.device_readback(self.motor_x.name),
+ MessageEndpoints.device_readback(self.motor_y.name),
],
cb=self._device_status_callback_motors,
parent=self,
@@ -341,10 +413,29 @@ class MotorControl(QThread):
self.motors_consumer.start()
+ self.motors_selected.emit(self.motor_x, self.motor_y)
+
+ def get_all_motors(self) -> list:
+ all_motors = client.device_manager.devices.acquisition_group("motor")
+ return all_motors
+
+ def get_all_motors_names(self) -> list:
+ all_motors = client.device_manager.devices.acquisition_group("motor")
+ all_motors_names = [motor.name for motor in all_motors]
+ return all_motors_names
+
+ def retrieve_all_motors(self):
+ self.all_motors = self.get_all_motors()
+ self.all_motors_names = self.get_all_motors_names()
+ self.motors_loaded.emit(self.all_motors_names, self.all_motors_names)
+ print("motors sent to GUI")
+
+ return self.all_motors, self.all_motors_names
+
def get_coordinates(self) -> tuple:
"""Get current motor position"""
- x = dev.samx.read(cached=True)["value"] # TODO remove hardcoded samx and samy
- y = dev.samx.read(cached=True)["value"]
+ x = self.motor_x.read(cached=True)["value"] # TODO remove hardcoded samx and samy
+ y = self.motor_y.read(cached=True)["value"]
return x, y
def retrieve_coordinates(self) -> tuple:
@@ -380,16 +471,16 @@ class MotorControl(QThread):
raise ValueError("Current motor position is outside the new limits (X)")
else:
self.update_motor_limits(
- dev.samx, low_limit=x_limit[0], high_limit=x_limit[1]
+ self.motor_x, low_limit=x_limit[0], high_limit=x_limit[1]
) # TODO Remove hardcoded samx and samy
if y_limit is not None:
if current_position[1] < y_limit[0] or current_position[1] > y_limit[1]:
raise ValueError("Current motor position is outside the new limits (Y)")
else:
- self.update_motor_limits(dev.samy, low_limit=y_limit[0], high_limit=y_limit[1])
+ self.update_motor_limits(self.motor_y, low_limit=y_limit[0], high_limit=y_limit[1])
- self.retrieve_motor_limits(dev.samx, dev.samy)
+ self.retrieve_motor_limits(self.motor_x, self.motor_y)
def move_to_coordinates(self, target_coordinates: tuple):
self.action = "move_to_coordinates"
@@ -414,9 +505,9 @@ class MotorControl(QThread):
def move_motor_coordinate(self) -> None:
"""Move the motor to the specified coordinates"""
status = scans.mv(
- dev.samx,
+ self.motor_x,
self.target_coordinates[0],
- dev.samy,
+ self.motor_y,
self.target_coordinates[1],
relative=False,
)
@@ -437,10 +528,10 @@ class MotorControl(QThread):
@staticmethod
def _device_status_callback_motors(msg, *, parent, **_kwargs) -> None:
deviceMSG = BECMessage.DeviceMessage.loads(msg.value)
- if parent.active_devices[0] in deviceMSG.content["signals"]:
- parent.current_x = deviceMSG.content["signals"][parent.active_devices[0]]["value"]
- elif parent.active_devices[1] in deviceMSG.content["signals"]:
- parent.current_y = deviceMSG.content["signals"][parent.active_devices[1]]["value"]
+ if parent.motor_x.name in deviceMSG.content["signals"]:
+ parent.current_x = deviceMSG.content["signals"][parent.motor_x.name]["value"]
+ elif parent.motor_y.name in deviceMSG.content["signals"]:
+ parent.current_y = deviceMSG.content["signals"][parent.motor_y.name]["value"]
parent.coordinates_updated.emit(parent.current_x, parent.current_y)
From c7e35d7da69853343aa7eee53c8ad988eb490d93 Mon Sep 17 00:00:00 2001
From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com>
Date: Fri, 25 Aug 2023 16:17:58 +0200
Subject: [PATCH 22/29] fix: motor selection is disabled while motor is moving
---
bec_widgets/examples/motor_movement/motor_example.py | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/bec_widgets/examples/motor_movement/motor_example.py b/bec_widgets/examples/motor_movement/motor_example.py
index cba06a52..fe6522ea 100644
--- a/bec_widgets/examples/motor_movement/motor_example.py
+++ b/bec_widgets/examples/motor_movement/motor_example.py
@@ -15,9 +15,7 @@ from bec_lib.core import MessageEndpoints, BECMessage
# - setting motor speed and frequency
# - setting motor acceleration
# - updating motor precision
-# - put motor selection dropdown or listwidget
# - put motor status (moving, stopped, etc)
-# - remove all hardcoded references to samx and samy
# - add spinBox for motor scatter size
# - add mouse interactions with the plot -> click to select coordinates, double click to move?
# - adjust right click actions
@@ -34,7 +32,7 @@ class MotorApp(QWidget):
# Motor Control Thread
self.motor_thread = MotorControl()
- self.motor_x, self.motor_y = None, None # dev.samx, dev.samy
+ self.motor_x, self.motor_y = None, None
self.limit_x, self.limit_y = None, None
# Coordinates tracking
@@ -105,8 +103,11 @@ class MotorApp(QWidget):
self.motorControl.setEnabled(True)
def enable_motor_controls(self, disable: bool) -> None:
- # Disable or enable all controls within the motorControl group box
- for widget in self.motorControl.findChildren(QtWidgets.QWidget):
+ self.motorControl.setEnabled(disable)
+ self.motorSelection.setEnabled(disable)
+
+ # Disable or enable all controls within the motorControl_absolute group box
+ for widget in self.motorControl_absolute.findChildren(QtWidgets.QWidget):
widget.setEnabled(disable)
# Enable the pushButton_stop if the motor is moving
@@ -358,7 +359,6 @@ class MotorControl(QThread):
def __init__(
self,
- active_devices=["samx", "samy"],
parent=None,
):
super().__init__(parent)
From 64708bc1b2e6a4256da9123d0215fc87e0afa455 Mon Sep 17 00:00:00 2001
From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com>
Date: Fri, 25 Aug 2023 16:20:05 +0200
Subject: [PATCH 23/29] fix: line_plot.py default changed back to "gauss_bpm"
---
bec_widgets/line_plot.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/bec_widgets/line_plot.py b/bec_widgets/line_plot.py
index ca2371b5..53485989 100644
--- a/bec_widgets/line_plot.py
+++ b/bec_widgets/line_plot.py
@@ -26,7 +26,7 @@ class BasicPlot(QtWidgets.QWidget):
update_signal = pyqtSignal()
roi_signal = pyqtSignal(tuple)
- def __init__(self, name="", y_value_list=["samx", "samy"]) -> None:
+ def __init__(self, name="", y_value_list=["gauss_bpm"]) -> None:
"""
Basic plot widget for displaying scan data.
@@ -387,7 +387,7 @@ if __name__ == "__main__":
"--signals",
help="specify recorded signals",
nargs="+",
- default=["samx", "samy"],
+ default=["gauss_bpm"],
)
# default = ["gauss_bpm", "bpm4i", "bpm5i", "bpm6i", "xert"],
# dispatcher = bec_dispatcher
From 7575c91c9949a4c6b0c92efa24757509b476c086 Mon Sep 17 00:00:00 2001
From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com>
Date: Fri, 25 Aug 2023 16:50:18 +0200
Subject: [PATCH 24/29] refactor: introduced MotorActions enum to replace
hardcoded strings + project cleanup
---
.../examples/motor_movement/motor_example.py | 77 +++++++------------
1 file changed, 29 insertions(+), 48 deletions(-)
diff --git a/bec_widgets/examples/motor_movement/motor_example.py b/bec_widgets/examples/motor_movement/motor_example.py
index fe6522ea..6d6cdf9f 100644
--- a/bec_widgets/examples/motor_movement/motor_example.py
+++ b/bec_widgets/examples/motor_movement/motor_example.py
@@ -1,6 +1,7 @@
import os
import numpy as np
+from enum import Enum
import pyqtgraph as pg
from PyQt5 import QtGui
from PyQt5.QtCore import QThread, pyqtSlot
@@ -19,6 +20,7 @@ from bec_lib.core import MessageEndpoints, BECMessage
# - add spinBox for motor scatter size
# - add mouse interactions with the plot -> click to select coordinates, double click to move?
# - adjust right click actions
+# - implement logic to check if motor actually has limits
class MotorApp(QWidget):
@@ -52,14 +54,6 @@ class MotorApp(QWidget):
# Get all motors available
self.motor_thread.retrieve_all_motors()
- # Initialize current coordinates with the provided initial coordinates
- # self.motor_thread.retrieve_motor_limits(self.motor_x, self.motor_y) #TODO move after connection of motors
-
- # print(f"Init limits: samx:{self.limit_x}, samy:{self.limit_y}") # TODO get to motor connect
-
- # Initialize the image map
- # self.init_motor_map()
-
def connect_motor(self, motor_x_name: str, motor_y_name: str):
self.motor_thread.connect_motors(motor_x_name, motor_y_name)
self.motor_thread.retrieve_motor_limits(self.motor_x, self.motor_y)
@@ -69,6 +63,13 @@ class MotorApp(QWidget):
self.motorControl_absolute.setEnabled(True)
self.tabWidget_tables.setTabEnabled(1, True)
+ self.generate_table_coordinate(
+ self.tableWidget_coordinates,
+ self.motor_thread.retrieve_coordinates(),
+ tag=f"{motor_x_name},{motor_y_name}",
+ precision=0,
+ )
+
@pyqtSlot(object, object)
def get_selected_motors(self, motor_x, motor_y):
self.motor_x, self.motor_y = motor_x, motor_y
@@ -129,12 +130,7 @@ class MotorApp(QWidget):
# 2D Plot
##########################
- # self.label_coorditanes = self.glw.addLabel(
- # f"Motor position: ({dev.samx.position():.2f}, {dev.samy.position():.2f})", row=0, col=0
- # ) #TODO remove hardcoded samx and samy
- self.label_coorditanes = self.glw.addLabel(
- f"Motor position: (X, Y)", row=0, col=0
- ) # TODO remove hardcoded samx and samy
+ self.label_coorditanes = self.glw.addLabel(f"Motor position: (X, Y)", row=0, col=0)
self.plot_map = self.glw.addPlot(row=1, col=0)
self.limit_map = pg.ImageItem()
self.plot_map.addItem(self.limit_map)
@@ -211,14 +207,6 @@ class MotorApp(QWidget):
lambda x, y: self.update_image_map(round(x, self.precision), round(y, self.precision))
)
- # Coordinates table
- self.generate_table_coordinate(
- self.tableWidget_coordinates,
- self.motor_thread.retrieve_coordinates(),
- tag="Initial",
- precision=0,
- )
-
# Motor connections
self.pushButton_connecMotors.clicked.connect(
lambda: self.connect_motor(
@@ -265,7 +253,7 @@ class MotorApp(QWidget):
def update_image_map(self, x, y):
# Update label
- self.label_coorditanes.setText(f"Motor position: ({x:.2f}, {y:.2f})")
+ self.label_coorditanes.setText(f"Motor position: ({x}, {y})")
# Add new point with full brightness
new_pos = np.array([x, y])
@@ -349,9 +337,14 @@ class MotorApp(QWidget):
ui_element.setStyleSheet("background-color: #FFA700;")
+class MotorActions(Enum):
+ MOVE_TO_COORDINATES = "move_to_coordinates"
+ MOVE_RELATIVE = "move_relative"
+
+
class MotorControl(QThread):
coordinates_updated = pyqtSignal(float, float) # Signal to emit current coordinates
- limits_retrieved = pyqtSignal(list, list) # Signal to emit current limits (samx, samy)
+ limits_retrieved = pyqtSignal(list, list) # Signal to emit current limits
move_finished = pyqtSignal() # Signal to emit when the move is finished
motors_loaded = pyqtSignal(list, list) # Signal to emit when the motors are loaded
motors_selected = pyqtSignal(object, object) # Signal to emit when the motors are selected
@@ -363,6 +356,8 @@ class MotorControl(QThread):
):
super().__init__(parent)
+ self.action = None
+
self.motor_x, self.motor_y = None, None
self.current_x, self.current_y = None, None
@@ -373,19 +368,8 @@ class MotorControl(QThread):
self.all_motors_names = self.get_all_motors_names()
self.retrieve_all_motors() # send motor list to GUI
- self.connect_motors("samx", "samy")
-
self.target_coordinates = None
- self.motor_x_debug = dev.samx
- # self.all_motors = self.get_all_motors()
- # self.all_motors_names = self.get_all_motors_names()
-
- # find index of samx in self.all_motors_names
- # self.motor_x_index = self.all_motors_names.index(self.active_devices[0])
-
- print(f"Motor debug: {self.motor_x_debug}")
-
def motor_by_string(self, motor_x_name: str, motor_y_name: str) -> tuple:
motor_x_index = self.all_motors_names.index(motor_x_name)
motor_y_index = self.all_motors_names.index(motor_y_name)
@@ -434,7 +418,7 @@ class MotorControl(QThread):
def get_coordinates(self) -> tuple:
"""Get current motor position"""
- x = self.motor_x.read(cached=True)["value"] # TODO remove hardcoded samx and samy
+ x = self.motor_x.read(cached=True)["value"]
y = self.motor_y.read(cached=True)["value"]
return x, y
@@ -463,16 +447,13 @@ class MotorControl(QThread):
motor.high_limit = high_limit
def update_all_motor_limits(self, x_limit: list = None, y_limit: list = None) -> None:
- # TODO can be generalized to any motor not just samx samy
current_position = self.get_coordinates()
if x_limit is not None:
if current_position[0] < x_limit[0] or current_position[0] > x_limit[1]:
raise ValueError("Current motor position is outside the new limits (X)")
else:
- self.update_motor_limits(
- self.motor_x, low_limit=x_limit[0], high_limit=x_limit[1]
- ) # TODO Remove hardcoded samx and samy
+ self.update_motor_limits(self.motor_x, low_limit=x_limit[0], high_limit=x_limit[1])
if y_limit is not None:
if current_position[1] < y_limit[0] or current_position[1] > y_limit[1]:
@@ -483,26 +464,26 @@ class MotorControl(QThread):
self.retrieve_motor_limits(self.motor_x, self.motor_y)
def move_to_coordinates(self, target_coordinates: tuple):
- self.action = "move_to_coordinates"
+ self.action = MotorActions.MOVE_TO_COORDINATES
self.target_coordinates = target_coordinates
self.start()
def move_relative(self, motor, value: float):
- self.action = "move_relative"
+ self.action = MotorActions.MOVE_RELATIVE
self.motor = motor
self.value = value
self.start()
def run(self):
- if self.action == "move_to_coordinates":
- self.move_motor_coordinate()
- elif self.action == "move_relative":
- self.move_motor_relative(self.motor, self.value)
+ if self.action == MotorActions.MOVE_TO_COORDINATES:
+ self._move_motor_coordinate()
+ elif self.action == MotorActions.MOVE_RELATIVE:
+ self._move_motor_relative(self.motor, self.value)
def set_target_coordinates(self, target_coordinates: tuple) -> None:
self.target_coordinates = target_coordinates
- def move_motor_coordinate(self) -> None:
+ def _move_motor_coordinate(self) -> None:
"""Move the motor to the specified coordinates"""
status = scans.mv(
self.motor_x,
@@ -515,7 +496,7 @@ class MotorControl(QThread):
status.wait()
self.move_finished.emit()
- def move_motor_relative(self, motor, value: float) -> None:
+ def _move_motor_relative(self, motor, value: float) -> None:
status = scans.mv(motor, value, relative=True)
status.wait()
From 5dd0af6894a5d97457d60ef18b098e40856e4875 Mon Sep 17 00:00:00 2001
From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com>
Date: Fri, 25 Aug 2023 17:22:27 +0200
Subject: [PATCH 25/29] feat: delete coordinate table row by DELETE or
BACKSPACE key
---
.../motor_movement/motor_controller.ui | 6 +++
.../examples/motor_movement/motor_example.py | 44 ++++++++++++++-----
2 files changed, 38 insertions(+), 12 deletions(-)
diff --git a/bec_widgets/examples/motor_movement/motor_controller.ui b/bec_widgets/examples/motor_movement/motor_controller.ui
index fbb122ff..2043a83a 100644
--- a/bec_widgets/examples/motor_movement/motor_controller.ui
+++ b/bec_widgets/examples/motor_movement/motor_controller.ui
@@ -285,6 +285,12 @@
-
+
+ QAbstractItemView::MultiSelection
+
+
+ QAbstractItemView::SelectRows
+
Tag
diff --git a/bec_widgets/examples/motor_movement/motor_example.py b/bec_widgets/examples/motor_movement/motor_example.py
index 6d6cdf9f..a1afc2b1 100644
--- a/bec_widgets/examples/motor_movement/motor_example.py
+++ b/bec_widgets/examples/motor_movement/motor_example.py
@@ -9,6 +9,9 @@ from PyQt5.QtCore import pyqtSignal, Qt
from PyQt5.QtWidgets import QApplication, QWidget
from pyqtgraph.Qt import QtWidgets, uic
+from PyQt5.QtGui import QKeySequence
+from PyQt5.QtWidgets import QShortcut
+
from bec_lib.core import MessageEndpoints, BECMessage
@@ -220,6 +223,12 @@ class MotorApp(QWidget):
self.motorControl_absolute.setEnabled(False)
self.tabWidget_tables.setTabEnabled(1, False)
+ # Keyboard shortcuts
+ delete_shortcut = QShortcut(QKeySequence("Delete"), self)
+ backspace_shortcut = QShortcut(QKeySequence("Backspace"), self)
+ delete_shortcut.activated.connect(self.delete_selected_row)
+ backspace_shortcut.activated.connect(self.delete_selected_row)
+
def init_motor_map(self):
# Get motor limits
limit_x_min, limit_x_max = self.motor_thread.get_motor_limits(self.motor_x)
@@ -322,6 +331,15 @@ class MotorApp(QWidget):
)
table.resizeColumnsToContents()
+ def delete_selected_row(self):
+ selected_rows = self.tableWidget_coordinates.selectionModel().selectedRows()
+
+ # If you allow multiple selections, you may want to loop through all selected rows
+ for row in reversed(selected_rows): # Reverse to delete from the end
+ self.tableWidget_coordinates.removeRow(row.row())
+
+ print(f"deleted {selected_rows}")
+
def save_absolute_coordinates(self):
self.generate_table_coordinate(
self.tableWidget_coordinates,
@@ -357,18 +375,7 @@ class MotorControl(QThread):
super().__init__(parent)
self.action = None
-
- self.motor_x, self.motor_y = None, None
- self.current_x, self.current_y = None, None
-
- self.motors_consumer = None
-
- # Get all available motors in the client
- self.all_motors = self.get_all_motors()
- self.all_motors_names = self.get_all_motors_names()
- self.retrieve_all_motors() # send motor list to GUI
-
- self.target_coordinates = None
+ self._initialize_motor()
def motor_by_string(self, motor_x_name: str, motor_y_name: str) -> tuple:
motor_x_index = self.all_motors_names.index(motor_x_name)
@@ -483,6 +490,19 @@ class MotorControl(QThread):
def set_target_coordinates(self, target_coordinates: tuple) -> None:
self.target_coordinates = target_coordinates
+ def _initialize_motor(self) -> None:
+ self.motor_x, self.motor_y = None, None
+ self.current_x, self.current_y = None, None
+
+ self.motors_consumer = None
+
+ # Get all available motors in the client
+ self.all_motors = self.get_all_motors()
+ self.all_motors_names = self.get_all_motors_names()
+ self.retrieve_all_motors() # send motor list to GUI
+
+ self.target_coordinates = None
+
def _move_motor_coordinate(self) -> None:
"""Move the motor to the specified coordinates"""
status = scans.mv(
From ce9816480b82373895b602d1a1bca7d1d9725f01 Mon Sep 17 00:00:00 2001
From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com>
Date: Fri, 25 Aug 2023 18:06:33 +0200
Subject: [PATCH 26/29] feat: speed and frequency is retrieved from devices
---
.../motor_movement/motor_controller.ui | 168 +++++++++++++-----
.../examples/motor_movement/motor_example.py | 60 ++++++-
2 files changed, 177 insertions(+), 51 deletions(-)
diff --git a/bec_widgets/examples/motor_movement/motor_controller.ui b/bec_widgets/examples/motor_movement/motor_controller.ui
index 2043a83a..4b32a4ae 100644
--- a/bec_widgets/examples/motor_movement/motor_controller.ui
+++ b/bec_widgets/examples/motor_movement/motor_controller.ui
@@ -6,14 +6,14 @@
0
0
- 1120
- 547
+ 1129
+ 542
Motor Controller
-
+
-
@@ -120,13 +120,6 @@
- -
-
-
- 5
-
-
-
-
@@ -196,6 +189,9 @@
+ -
+
+
@@ -250,10 +246,10 @@
-
-
+
-
-
+
@@ -276,7 +272,7 @@
true
- 0
+ 1
@@ -326,7 +322,7 @@
-
-
+
Motor Limits
@@ -356,6 +352,9 @@
-
+
+ Qt::AlignCenter
+
-1000
@@ -395,6 +394,9 @@
-
+
+ Qt::AlignCenter
+
-1000
@@ -442,19 +444,16 @@
-
- Motor config
+ Motor Config
-
-
-
-
- -
-
-
- -
-
-
- Tolerance
+
-
+
+
+ Qt::AlignCenter
+
+
+ 500
@@ -465,23 +464,23 @@
- -
-
-
- -
-
+
-
+
+
+ false
+
- Decimal Precision
+ Tolerance
- -
-
-
- -
-
+
-
+
- X
+ Y
+
+
+ Qt::AlignCenter
@@ -492,31 +491,102 @@
- -
-
-
- Y
+
-
+
+
+ false
+
+
+ Qt::AlignCenter
- -
-
+
-
+
+
+ false
+
- X = Y
+ Decimal Precision
+
+
+
+ -
+
+
+ false
+
+
+ Qt::AlignCenter
-
-
+
+
+ Qt::AlignCenter
+
+
+ 1000
+
+
- -
-
+
-
+
+
+ X
+
+
+ Qt::AlignCenter
+
+
-
-
+
+
+ false
+
+
+ Qt::AlignCenter
+
+
-
-
+
+
+ false
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ Qt::AlignCenter
+
+
+ 1000
+
+
+
+ -
+
+
+ Qt::AlignCenter
+
+
+ 500
+
+
+
+ -
+
+
+ Update
+
+
diff --git a/bec_widgets/examples/motor_movement/motor_example.py b/bec_widgets/examples/motor_movement/motor_example.py
index a1afc2b1..85afab16 100644
--- a/bec_widgets/examples/motor_movement/motor_example.py
+++ b/bec_widgets/examples/motor_movement/motor_example.py
@@ -49,6 +49,8 @@ class MotorApp(QWidget):
self.motor_thread.motors_loaded.connect(self.get_available_motors)
self.motor_thread.motors_selected.connect(self.get_selected_motors)
self.motor_thread.limits_retrieved.connect(self.update_limits)
+ self.motor_thread.speed_retrieved.connect(self.update_speed)
+ self.motor_thread.update_frequency_retrieved.connect(self.update_update_frequency)
# UI
self.init_ui()
@@ -60,6 +62,8 @@ class MotorApp(QWidget):
def connect_motor(self, motor_x_name: str, motor_y_name: str):
self.motor_thread.connect_motors(motor_x_name, motor_y_name)
self.motor_thread.retrieve_motor_limits(self.motor_x, self.motor_y)
+ self.motor_thread.retrieve_motor_speed(self.motor_x, self.motor_y)
+ self.motor_thread.retrieve_motor_update_frequency(self.motor_x, self.motor_y)
self.init_motor_map()
self.motorControl.setEnabled(True)
@@ -102,6 +106,20 @@ class MotorApp(QWidget):
self.init_motor_map() # reinitialize the map with the new limits
+ @pyqtSlot(int, int)
+ def update_speed(self, speed_x, speed_y):
+ self.spinBox_speed_x.setValue(speed_x)
+ self.spinBox_speed_y.setValue(speed_y)
+ for spinBox in (self.spinBox_speed_x, self.spinBox_speed_y):
+ spinBox.setStyleSheet("")
+
+ @pyqtSlot(int, int)
+ def update_update_frequency(self, update_frequency_x, update_frequency_y):
+ self.spinBox_update_frequency_x.setValue(update_frequency_x)
+ self.spinBox_update_frequency_y.setValue(update_frequency_y)
+ for spinBox in (self.spinBox_update_frequency_x, self.spinBox_update_frequency_y):
+ spinBox.setStyleSheet("")
+
@pyqtSlot()
def enable_motor_control(self):
self.motorControl.setEnabled(True)
@@ -229,6 +247,8 @@ class MotorApp(QWidget):
delete_shortcut.activated.connect(self.delete_selected_row)
backspace_shortcut.activated.connect(self.delete_selected_row)
+ # Get speed and update frequency
+
def init_motor_map(self):
# Get motor limits
limit_x_min, limit_x_max = self.motor_thread.get_motor_limits(self.motor_x)
@@ -306,8 +326,6 @@ class MotorApp(QWidget):
table.setRowCount(current_row_count + 1)
- # table.insertRow(current_row_count)
-
checkBox = QtWidgets.QCheckBox()
checkBox.setChecked(True)
button = QtWidgets.QPushButton("Go")
@@ -363,6 +381,8 @@ class MotorActions(Enum):
class MotorControl(QThread):
coordinates_updated = pyqtSignal(float, float) # Signal to emit current coordinates
limits_retrieved = pyqtSignal(list, list) # Signal to emit current limits
+ speed_retrieved = pyqtSignal(int, int) # Signal to emit current speed
+ update_frequency_retrieved = pyqtSignal(int, int) # Signal to emit current update frequency
move_finished = pyqtSignal() # Signal to emit when the move is finished
motors_loaded = pyqtSignal(list, list) # Signal to emit when the motors are loaded
motors_selected = pyqtSignal(object, object) # Signal to emit when the motors are selected
@@ -437,6 +457,42 @@ class MotorControl(QThread):
"""Get the limits of a motor"""
return motor.limits
+ def get_motor_config(self, motor) -> dict:
+ """Get the configuration of a motor""" # TODO at this moment just for speed and update_frequency
+ return motor.get_device_config()
+
+ def get_motor_speed(self, motor) -> float:
+ """Get the speed of a motor"""
+ return motor.speed
+
+ def set_motor_speed(self, motor, speed: float) -> None:
+ """Set the speed of a motor"""
+ motor.speed = speed
+
+ def get_motor_update_frequency(self, motor) -> float:
+ """Get the update frequency of a motor"""
+ return motor.update_frequency
+
+ def set_motor_update_frequency(self, motor, update_frequency: float) -> None:
+ """Set the update frequency of a motor"""
+ motor.update_frequency = update_frequency
+
+ def retrieve_motor_speed(
+ self, motor_x, motor_y
+ ) -> None: # TODO can be migrated to some general config function
+ """Get the speed of a motor"""
+ speed_x = motor_x.get_device_config()["speed"]
+ speed_y = motor_y.get_device_config()["speed"]
+ self.speed_retrieved.emit(int(speed_x), int(speed_y))
+
+ def retrieve_motor_update_frequency(
+ self, motor_x, motor_y
+ ) -> None: # TODO can be migrated to some general config function
+ """Get the speed of a motor"""
+ update_frequency_x = motor_x.get_device_config()["update_frequency"]
+ update_frequency_y = motor_y.get_device_config()["update_frequency"]
+ self.update_frequency_retrieved.emit(int(update_frequency_x), int(update_frequency_y))
+
def retrieve_motor_limits(self, motor_x, motor_y):
limit_x = self.get_motor_limits(motor_x)
limit_y = self.get_motor_limits(motor_y)
From f391a2fd004f1dc8187cfe12d60f856427ae01ec Mon Sep 17 00:00:00 2001
From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com>
Date: Fri, 25 Aug 2023 18:14:08 +0200
Subject: [PATCH 27/29] feat: speed and frequency can be updated from GUI
---
.../motor_movement/motor_controller.ui | 2 +-
.../examples/motor_movement/motor_example.py | 53 +++++++++++++------
2 files changed, 39 insertions(+), 16 deletions(-)
diff --git a/bec_widgets/examples/motor_movement/motor_controller.ui b/bec_widgets/examples/motor_movement/motor_controller.ui
index 4b32a4ae..325ecbc0 100644
--- a/bec_widgets/examples/motor_movement/motor_controller.ui
+++ b/bec_widgets/examples/motor_movement/motor_controller.ui
@@ -582,7 +582,7 @@
-
-
+
Update
diff --git a/bec_widgets/examples/motor_movement/motor_example.py b/bec_widgets/examples/motor_movement/motor_example.py
index 85afab16..2f8129bd 100644
--- a/bec_widgets/examples/motor_movement/motor_example.py
+++ b/bec_widgets/examples/motor_movement/motor_example.py
@@ -146,6 +146,7 @@ class MotorApp(QWidget):
def init_ui(self) -> None:
"""Setup all ui elements"""
+ # TODO can be separated to multiple functions
##########################
# 2D Plot
@@ -216,6 +217,19 @@ class MotorApp(QWidget):
self.spinBox_y_min.valueChanged.connect(lambda: self.param_changed(self.spinBox_y_min))
self.spinBox_y_max.valueChanged.connect(lambda: self.param_changed(self.spinBox_y_max))
+ # SpinBoxes - Motor Speed
+ self.spinBox_speed_x.valueChanged.connect(lambda: self.param_changed(self.spinBox_speed_x))
+ self.spinBox_speed_y.valueChanged.connect(lambda: self.param_changed(self.spinBox_speed_y))
+
+ # SpinBoxes - Motor Update Frequency
+ self.spinBox_update_frequency_x.valueChanged.connect(
+ lambda: self.param_changed(self.spinBox_update_frequency_x)
+ )
+ self.spinBox_update_frequency_y.valueChanged.connect(
+ lambda: self.param_changed(self.spinBox_update_frequency_y)
+ )
+
+ # Confog updates
self.pushButton_updateLimits.clicked.connect(
lambda: self.update_all_motor_limits(
x_limit=[self.spinBox_x_min.value(), self.spinBox_x_max.value()],
@@ -223,6 +237,16 @@ class MotorApp(QWidget):
)
)
+ self.pushButton_update_config.clicked.connect(
+ lambda: self.update_all_config(
+ speed=[self.spinBox_speed_x.value(), self.spinBox_speed_y.value()],
+ update_frequency=[
+ self.spinBox_update_frequency_x.value(),
+ self.spinBox_update_frequency_y.value(),
+ ],
+ )
+ )
+
# TODO map with floats as well -> or decide system for higher precision
self.motor_thread.coordinates_updated.connect(
lambda x, y: self.update_image_map(round(x, self.precision), round(y, self.precision))
@@ -247,8 +271,6 @@ class MotorApp(QWidget):
delete_shortcut.activated.connect(self.delete_selected_row)
backspace_shortcut.activated.connect(self.delete_selected_row)
- # Get speed and update frequency
-
def init_motor_map(self):
# Get motor limits
limit_x_min, limit_x_max = self.motor_thread.get_motor_limits(self.motor_x)
@@ -305,6 +327,10 @@ class MotorApp(QWidget):
def update_all_motor_limits(self, x_limit: list = None, y_limit: list = None) -> None:
self.motor_thread.update_all_motor_limits(x_limit=x_limit, y_limit=y_limit)
+ def update_all_config(self, speed: list = None, update_frequency: list = None) -> None:
+ # TODO now only speed and update frequency
+ self.motor_thread.update_all_config(speed=speed, update_frequency=update_frequency)
+
def update_arrow_key_shortcuts(self):
if self.checkBox_enableArrows.isChecked():
# Set the arrow key shortcuts for motor movement
@@ -461,21 +487,18 @@ class MotorControl(QThread):
"""Get the configuration of a motor""" # TODO at this moment just for speed and update_frequency
return motor.get_device_config()
- def get_motor_speed(self, motor) -> float:
- """Get the speed of a motor"""
- return motor.speed
+ def update_all_config(self, speed: list = None, update_frequency: list = None) -> None:
+ # TODO now only speed and update frequency
+ if speed is not None:
+ self.motor_x.set_device_config({"speed": speed[0]})
+ self.motor_y.set_device_config({"speed": speed[1]})
- def set_motor_speed(self, motor, speed: float) -> None:
- """Set the speed of a motor"""
- motor.speed = speed
+ if update_frequency is not None:
+ self.motor_x.set_device_config({"update_frequency": update_frequency[0]})
+ self.motor_y.set_device_config({"update_frequency": update_frequency[1]})
- def get_motor_update_frequency(self, motor) -> float:
- """Get the update frequency of a motor"""
- return motor.update_frequency
-
- def set_motor_update_frequency(self, motor, update_frequency: float) -> None:
- """Set the update frequency of a motor"""
- motor.update_frequency = update_frequency
+ self.retrieve_motor_speed(self.motor_x, self.motor_y)
+ self.retrieve_motor_update_frequency(self.motor_x, self.motor_y)
def retrieve_motor_speed(
self, motor_x, motor_y
From e0b52fcedca46d913d1677b45f9815eccd92e8f7 Mon Sep 17 00:00:00 2001
From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com>
Date: Mon, 28 Aug 2023 10:08:18 +0200
Subject: [PATCH 28/29] feat: total number of points, scatter size and number
of point to dim after last position can be changed from GUI
---
.../motor_movement/motor_controller.ui | 293 ++++++++++++------
.../examples/motor_movement/motor_example.py | 46 ++-
2 files changed, 240 insertions(+), 99 deletions(-)
diff --git a/bec_widgets/examples/motor_movement/motor_controller.ui b/bec_widgets/examples/motor_movement/motor_controller.ui
index 325ecbc0..66c42c82 100644
--- a/bec_widgets/examples/motor_movement/motor_controller.ui
+++ b/bec_widgets/examples/motor_movement/motor_controller.ui
@@ -7,13 +7,13 @@
0
0
1129
- 542
+ 550
Motor Controller
-
+
-
@@ -190,7 +190,17 @@
-
-
+
+
+ 0.100000000000000
+
+
+ 0.100000000000000
+
+
+ 1.000000000000000
+
+
@@ -246,10 +256,30 @@
-
-
+
+
+ -500.000000000000000
+
+
+ 500.000000000000000
+
+
+ 0.100000000000000
+
+
-
-
+
+
+ -500.000000000000000
+
+
+ 500.000000000000000
+
+
+ 0.100000000000000
+
+
@@ -272,7 +302,7 @@
true
- 1
+ 0
@@ -447,50 +477,6 @@
Motor Config
- -
-
-
- Qt::AlignCenter
-
-
- 500
-
-
-
- -
-
-
- Update Frequency
-
-
-
- -
-
-
- false
-
-
- Tolerance
-
-
-
- -
-
-
- Y
-
-
- Qt::AlignCenter
-
-
-
- -
-
-
- Speed
-
-
-
-
@@ -501,46 +487,6 @@
- -
-
-
- false
-
-
- Decimal Precision
-
-
-
- -
-
-
- false
-
-
- Qt::AlignCenter
-
-
-
- -
-
-
- Qt::AlignCenter
-
-
- 1000
-
-
-
- -
-
-
- X
-
-
- Qt::AlignCenter
-
-
-
-
@@ -551,16 +497,23 @@
- -
-
-
- false
+
-
+
+
+ X
Qt::AlignCenter
+ -
+
+
+ Speed
+
+
+
-
@@ -581,6 +534,99 @@
+ -
+
+
+ Qt::AlignCenter
+
+
+ 1000
+
+
+
+ -
+
+
+ Y
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ Max Points
+
+
+
+ -
+
+
+ Qt::AlignCenter
+
+
+ 10
+
+
+ 1000
+
+
+ 10
+
+
+ 100
+
+
+
+ -
+
+
+ Update Frequency
+
+
+
+ -
+
+
+ false
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ false
+
+
+ Tolerance
+
+
+
+ -
+
+
+ false
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ Qt::AlignCenter
+
+
+ 500
+
+
+
-
@@ -588,6 +634,65 @@
+ -
+
+
+ false
+
+
+ Decimal Precision
+
+
+
+ -
+
+
+ N dim
+
+
+
+ -
+
+
+ Qt::AlignCenter
+
+
+ 100
+
+
+ 10000
+
+
+ 100
+
+
+ 5000
+
+
+
+ -
+
+
+ Scatter Size
+
+
+
+ -
+
+
+ Qt::AlignCenter
+
+
+ 1
+
+
+ 15
+
+
+ 5
+
+
+
diff --git a/bec_widgets/examples/motor_movement/motor_example.py b/bec_widgets/examples/motor_movement/motor_example.py
index 2f8129bd..fe2a8bb6 100644
--- a/bec_widgets/examples/motor_movement/motor_example.py
+++ b/bec_widgets/examples/motor_movement/motor_example.py
@@ -44,6 +44,7 @@ class MotorApp(QWidget):
self.motor_positions = np.array([])
self.max_points = 5000 # Maximum number of points to keep
self.num_dim_points = 15 # Number of points to dim gradually
+ self.scatter_size = 5
# QThread for motor movement + signals
self.motor_thread.motors_loaded.connect(self.get_available_motors)
@@ -144,6 +145,18 @@ class MotorApp(QWidget):
self.enable_motor_controls(False)
self.motor_thread.move_relative(motor, value)
+ def update_plot_setting(self, max_points, num_dim_points, scatter_size):
+ self.max_points = max_points
+ self.num_dim_points = num_dim_points
+ self.scatter_size = scatter_size
+
+ for spinBox in (
+ self.spinBox_max_points,
+ self.spinBox_num_dim_points,
+ self.spinBox_scatter_size,
+ ):
+ spinBox.setStyleSheet("")
+
def init_ui(self) -> None:
"""Setup all ui elements"""
# TODO can be separated to multiple functions
@@ -157,7 +170,7 @@ class MotorApp(QWidget):
self.limit_map = pg.ImageItem()
self.plot_map.addItem(self.limit_map)
self.motor_map = pg.ScatterPlotItem(
- size=2, pen=pg.mkPen(None), brush=pg.mkBrush(255, 255, 255, 255)
+ size=self.scatter_size, pen=pg.mkPen(None), brush=pg.mkBrush(255, 255, 255, 255)
)
self.plot_map.addItem(self.motor_map)
self.plot_map.showGrid(x=True, y=True)
@@ -206,7 +219,7 @@ class MotorApp(QWidget):
self.pushButton_stop.clicked.connect(self.motor_thread.stop_movement)
##########################
- # Motor limits signals
+ # Motor Configs
##########################
# SpinBoxes - Motor Limits #TODO make spinboxes own limits updated, currently is [-1000, 1000]
@@ -229,7 +242,18 @@ class MotorApp(QWidget):
lambda: self.param_changed(self.spinBox_update_frequency_y)
)
- # Confog updates
+ # SpinBoxes - Max Points and N Dim Points
+ self.spinBox_max_points.valueChanged.connect(
+ lambda: self.param_changed(self.spinBox_max_points)
+ )
+ self.spinBox_num_dim_points.valueChanged.connect(
+ lambda: self.param_changed(self.spinBox_num_dim_points)
+ )
+ self.spinBox_scatter_size.valueChanged.connect(
+ lambda: self.param_changed(self.spinBox_scatter_size)
+ )
+
+ # Config updates
self.pushButton_updateLimits.clicked.connect(
lambda: self.update_all_motor_limits(
x_limit=[self.spinBox_x_min.value(), self.spinBox_x_max.value()],
@@ -237,6 +261,14 @@ class MotorApp(QWidget):
)
)
+ self.pushButton_update_config.clicked.connect(
+ lambda: self.update_plot_setting(
+ max_points=self.spinBox_max_points.value(),
+ num_dim_points=self.spinBox_num_dim_points.value(),
+ scatter_size=self.spinBox_scatter_size.value(),
+ )
+ )
+
self.pushButton_update_config.clicked.connect(
lambda: self.update_all_config(
speed=[self.spinBox_speed_x.value(), self.spinBox_speed_y.value()],
@@ -316,13 +348,17 @@ class MotorApp(QWidget):
# Determine brushes based on position in the array
self.brushes = [pg.mkBrush(50, 50, 50, 255)] * len(self.motor_positions)
+
+ # Calculate the decrement step based on self.num_dim_points
+ decrement_step = (255 - 50) / self.num_dim_points
+
for i in range(1, min(self.num_dim_points + 1, len(self.motor_positions) + 1)):
- brightness = max(50, 255 - 20 * (i - 1))
+ brightness = max(50, 255 - decrement_step * (i - 1))
self.brushes[-i] = pg.mkBrush(brightness, brightness, brightness, 255)
self.brushes[-1] = pg.mkBrush(255, 255, 255, 255) # Newest point is always full brightness
- self.motor_map.setData(pos=self.motor_positions, brush=self.brushes)
+ self.motor_map.setData(pos=self.motor_positions, brush=self.brushes, size=self.scatter_size)
def update_all_motor_limits(self, x_limit: list = None, y_limit: list = None) -> None:
self.motor_thread.update_all_motor_limits(x_limit=x_limit, y_limit=y_limit)
From 413e4356cfde6e2432682332e470eb69427ad397 Mon Sep 17 00:00:00 2001
From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com>
Date: Mon, 28 Aug 2023 10:12:32 +0200
Subject: [PATCH 29/29] feat: labels of current motors are shown in motors
limits
---
bec_widgets/examples/motor_movement/motor_example.py | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/bec_widgets/examples/motor_movement/motor_example.py b/bec_widgets/examples/motor_movement/motor_example.py
index fe2a8bb6..f926918f 100644
--- a/bec_widgets/examples/motor_movement/motor_example.py
+++ b/bec_widgets/examples/motor_movement/motor_example.py
@@ -86,7 +86,6 @@ class MotorApp(QWidget):
def get_available_motors(self, motors_x, motors_y):
self.comboBox_motor_x.addItems(motors_x)
self.comboBox_motor_y.addItems(motors_y)
- print(f"got motors {motors_x} and {motors_y}")
@pyqtSlot(list, list)
def update_limits(self, x_limits: list, y_limits: list) -> None:
@@ -105,6 +104,12 @@ class MotorApp(QWidget):
):
spinBox.setStyleSheet("")
+ # TODO - names can be get from MotorController
+ self.label_Y_max.setText(f"+ ({self.motor_y.name})")
+ self.label_Y_min.setText(f"- ({self.motor_y.name})")
+ self.label_X_max.setText(f"+ ({self.motor_x.name})")
+ self.label_X_min.setText(f"- ({self.motor_x.name})")
+
self.init_motor_map() # reinitialize the map with the new limits
@pyqtSlot(int, int)
@@ -418,8 +423,6 @@ class MotorApp(QWidget):
for row in reversed(selected_rows): # Reverse to delete from the end
self.tableWidget_coordinates.removeRow(row.row())
- print(f"deleted {selected_rows}")
-
def save_absolute_coordinates(self):
self.generate_table_coordinate(
self.tableWidget_coordinates,
@@ -501,7 +504,6 @@ class MotorControl(QThread):
self.all_motors = self.get_all_motors()
self.all_motors_names = self.get_all_motors_names()
self.motors_loaded.emit(self.all_motors_names, self.all_motors_names)
- print("motors sent to GUI")
return self.all_motors, self.all_motors_names