mirror of
https://github.com/bec-project/bec_widgets.git
synced 2025-07-13 19:21:50 +02:00
refactor: moved colormap related static methods to qt_utils colors.py
This commit is contained in:
@ -1,6 +1,32 @@
|
|||||||
|
plot_settings:
|
||||||
|
background_color: "white"
|
||||||
|
num_columns: 2
|
||||||
|
|
||||||
xy_pairs: [["samx", ["gauss_bpm", "gauss_adc1"]],
|
xy_pairs: [["samx", ["gauss_bpm", "gauss_adc1"]],
|
||||||
["samx", ["gauss_adc1", "gauss_adc2"]],
|
["samx", ["gauss_adc1", "gauss_adc2"]]]
|
||||||
["samx", ["gauss_adc2"]],
|
|
||||||
["samx", ["gauss_adc1"]],
|
plot_data:
|
||||||
["samx", ["gauss_bpm", "gauss_adc2"]],
|
- BPM plot:
|
||||||
["samx", ["gauss_bpm", "gauss_adc1", "gauss_adc2"]]]
|
- x:
|
||||||
|
- signal:
|
||||||
|
- name: "samx"
|
||||||
|
- entry: "samx"
|
||||||
|
- label: 'Motor X' # will serve as x label
|
||||||
|
- y:
|
||||||
|
- signal:
|
||||||
|
- name: "gauss_bpm"
|
||||||
|
- entry: "gauss_bpm"
|
||||||
|
- label: 'BPM' # will serve as y label
|
||||||
|
|
||||||
|
- ADC plot:
|
||||||
|
- name: "gauss_adc1"
|
||||||
|
- x:
|
||||||
|
- signal:
|
||||||
|
- name: "samy"
|
||||||
|
- entry: "samy"
|
||||||
|
- label: 'Motor Y'
|
||||||
|
- y:
|
||||||
|
- signal:
|
||||||
|
- name: "gauss_adc"
|
||||||
|
- entry: ["gauss_adc1", "gauss_adc2"]
|
||||||
|
- label: 'ADC'
|
@ -8,12 +8,15 @@ from pyqtgraph import mkBrush, mkColor, mkPen
|
|||||||
from pyqtgraph.Qt import QtCore, uic
|
from pyqtgraph.Qt import QtCore, uic
|
||||||
|
|
||||||
from bec_lib.core import MessageEndpoints
|
from bec_lib.core import MessageEndpoints
|
||||||
from bec_widgets.qt_utils import Crosshair
|
from bec_widgets.qt_utils import Crosshair, Colors
|
||||||
|
|
||||||
|
|
||||||
# TODO implement:
|
# TODO implement:
|
||||||
# - implement scanID database for visualizing previous scans
|
# - implement scanID database for visualizing previous scans
|
||||||
# - change how dap is handled in bec_dispatcher to handle more workers
|
# - change how dap is handled in bec_dispatcher to handle more workers
|
||||||
|
# - YAML config -> plot settings
|
||||||
|
# - YAML config -> xy pairs -> multiple subsignals for different devices
|
||||||
|
# - Internal logic -> if user specify
|
||||||
|
|
||||||
|
|
||||||
class PlotApp(QWidget):
|
class PlotApp(QWidget):
|
||||||
@ -37,14 +40,21 @@ class PlotApp(QWidget):
|
|||||||
update_signal = pyqtSignal()
|
update_signal = pyqtSignal()
|
||||||
update_dap_signal = pyqtSignal()
|
update_dap_signal = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, xy_pairs, parent=None):
|
def __init__(self, plot_settings: dict, xy_pairs: list, plot_data: dict, parent=None):
|
||||||
super(PlotApp, self).__init__(parent)
|
super(PlotApp, self).__init__(parent)
|
||||||
|
|
||||||
|
# YAML config
|
||||||
|
self.plot_settings = plot_settings
|
||||||
|
self.xy_pairs = xy_pairs
|
||||||
|
self.plot_data = plot_data
|
||||||
|
|
||||||
|
# Setting global plot settings
|
||||||
|
self.init_plot_background(self.plot_settings["background_color"])
|
||||||
|
|
||||||
|
# Loading UI
|
||||||
current_path = os.path.dirname(__file__)
|
current_path = os.path.dirname(__file__)
|
||||||
uic.loadUi(os.path.join(current_path, "extreme.ui"), self)
|
uic.loadUi(os.path.join(current_path, "extreme.ui"), self)
|
||||||
|
|
||||||
# xy pairs for setting number of windows
|
|
||||||
self.xy_pairs = xy_pairs
|
|
||||||
|
|
||||||
# Nested dictionary to hold x and y data for multiple plots
|
# Nested dictionary to hold x and y data for multiple plots
|
||||||
self.data = {}
|
self.data = {}
|
||||||
|
|
||||||
@ -55,7 +65,8 @@ class PlotApp(QWidget):
|
|||||||
self.scanID = None
|
self.scanID = None
|
||||||
|
|
||||||
# Initialize the UI
|
# Initialize the UI
|
||||||
self.init_ui(self.spinBox_N_columns.value())
|
self.init_ui(self.plot_settings["num_columns"])
|
||||||
|
self.spinBox_N_columns.setValue(self.plot_settings["num_columns"])
|
||||||
self.splitter.setSizes([400, 100])
|
self.splitter.setSizes([400, 100])
|
||||||
|
|
||||||
# Connect the update signal to the update plot method
|
# Connect the update signal to the update plot method
|
||||||
@ -66,6 +77,26 @@ class PlotApp(QWidget):
|
|||||||
# Change layout of plots when the number of columns is changed in GUI
|
# Change layout of plots when the number of columns is changed in GUI
|
||||||
self.spinBox_N_columns.valueChanged.connect(lambda x: self.init_ui(x))
|
self.spinBox_N_columns.valueChanged.connect(lambda x: self.init_ui(x))
|
||||||
|
|
||||||
|
def init_plot_background(self, background_color: str) -> None:
|
||||||
|
"""
|
||||||
|
Initialize plot settings based on the background color.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
background_color (str): The background color ('white' or 'black').
|
||||||
|
|
||||||
|
This method sets the background and foreground colors for pyqtgraph.
|
||||||
|
If the background is dark ('black'), the foreground will be set to 'white',
|
||||||
|
and vice versa.
|
||||||
|
"""
|
||||||
|
if background_color.lower() == "black":
|
||||||
|
pg.setConfigOption("background", "k")
|
||||||
|
pg.setConfigOption("foreground", "w")
|
||||||
|
elif background_color.lower() == "white":
|
||||||
|
pg.setConfigOption("background", "w")
|
||||||
|
pg.setConfigOption("foreground", "k")
|
||||||
|
else:
|
||||||
|
print(f"Warning: Unknown background color {background_color}. Using default settings.")
|
||||||
|
|
||||||
def init_ui(self, num_columns: int = 3) -> None:
|
def init_ui(self, num_columns: int = 3) -> None:
|
||||||
"""
|
"""
|
||||||
Initialize the UI components, create plots and store their grid positions.
|
Initialize the UI components, create plots and store their grid positions.
|
||||||
@ -79,7 +110,6 @@ class PlotApp(QWidget):
|
|||||||
stretches the last plots to fit the remaining space.
|
stretches the last plots to fit the remaining space.
|
||||||
"""
|
"""
|
||||||
self.glw.clear()
|
self.glw.clear()
|
||||||
|
|
||||||
self.plots = {}
|
self.plots = {}
|
||||||
self.grid_coordinates = [] # List to keep track of grid positions for each plot
|
self.grid_coordinates = [] # List to keep track of grid positions for each plot
|
||||||
|
|
||||||
@ -102,7 +132,9 @@ class PlotApp(QWidget):
|
|||||||
remaining_space -= colspan - 1 # Update remaining space
|
remaining_space -= colspan - 1 # Update remaining space
|
||||||
last_row_cols -= 1 # Update remaining plots
|
last_row_cols -= 1 # Update remaining plots
|
||||||
|
|
||||||
plot = self.glw.addPlot(row=row, col=col, colspan=colspan)
|
plot = self.glw.addPlot(
|
||||||
|
row=row, col=col, colspan=colspan, title=list(self.plot_data[i].keys())[0]
|
||||||
|
)
|
||||||
plot.setLabel("bottom", x)
|
plot.setLabel("bottom", x)
|
||||||
plot.setLabel("left", ", ".join(ys))
|
plot.setLabel("left", ", ".join(ys))
|
||||||
plot.addLegend()
|
plot.addLegend()
|
||||||
@ -126,7 +158,7 @@ class PlotApp(QWidget):
|
|||||||
for idx, ((x, ys), plot) in enumerate(self.plots.items()):
|
for idx, ((x, ys), plot) in enumerate(self.plots.items()):
|
||||||
plot.clear()
|
plot.clear()
|
||||||
self.curves_data[(x, tuple(ys))] = []
|
self.curves_data[(x, tuple(ys))] = []
|
||||||
colors_ys = PlotApp.golden_angle_color(colormap="CET-R2", num=len(ys))
|
colors_ys = Colors.golden_angle_color(colormap="plasma", num=len(ys))
|
||||||
|
|
||||||
row, col = self.grid_coordinates[idx] # Retrieve the grid position for this plot
|
row, col = self.grid_coordinates[idx] # Retrieve the grid position for this plot
|
||||||
|
|
||||||
@ -235,51 +267,6 @@ class PlotApp(QWidget):
|
|||||||
|
|
||||||
self.update_signal.emit()
|
self.update_signal.emit()
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def golden_ratio(num: int) -> list:
|
|
||||||
"""Calculate the golden ratio for a given number of angles.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
num (int): Number of angles
|
|
||||||
"""
|
|
||||||
phi = 2 * np.pi * ((1 + np.sqrt(5)) / 2)
|
|
||||||
angles = []
|
|
||||||
for ii in range(num):
|
|
||||||
x = np.cos(ii * phi)
|
|
||||||
y = np.sin(ii * phi)
|
|
||||||
angle = np.arctan2(y, x)
|
|
||||||
angles.append(angle)
|
|
||||||
return angles
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def golden_angle_color(colormap: str, num: int) -> list:
|
|
||||||
"""
|
|
||||||
Extract num colors for from the specified colormap following golden angle distribution.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
colormap (str): Name of the colormap
|
|
||||||
num (int): Number of requested colors
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
list: List of colors with length <num>
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
ValueError: If the number of requested colors is greater than the number of colors in the colormap.
|
|
||||||
"""
|
|
||||||
|
|
||||||
cmap = pg.colormap.get(colormap)
|
|
||||||
cmap_colors = cmap.color
|
|
||||||
if num > len(cmap_colors):
|
|
||||||
raise ValueError(
|
|
||||||
f"Number of colors requested ({num}) is greater than the number of colors in the colormap ({len(cmap_colors)})"
|
|
||||||
)
|
|
||||||
angles = PlotApp.golden_ratio(len(cmap_colors))
|
|
||||||
color_selection = np.round(np.interp(angles, (-np.pi, np.pi), (0, len(cmap_colors))))
|
|
||||||
colors = [
|
|
||||||
mkColor(tuple((cmap_colors[int(ii)] * 255).astype(int))) for ii in color_selection[:num]
|
|
||||||
]
|
|
||||||
return colors
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import yaml
|
import yaml
|
||||||
@ -297,7 +284,10 @@ if __name__ == "__main__":
|
|||||||
try:
|
try:
|
||||||
with open(args.config, "r") as file:
|
with open(args.config, "r") as file:
|
||||||
config = yaml.safe_load(file)
|
config = yaml.safe_load(file)
|
||||||
|
|
||||||
|
plot_settings = config.get("plot_settings", {})
|
||||||
xy_pairs = config.get("xy_pairs", [])
|
xy_pairs = config.get("xy_pairs", [])
|
||||||
|
plot_data = config.get("plot_data", {})
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
print(f"The file {args.config} was not found.")
|
print(f"The file {args.config} was not found.")
|
||||||
exit(1)
|
exit(1)
|
||||||
@ -316,7 +306,7 @@ if __name__ == "__main__":
|
|||||||
queue = client.queue
|
queue = client.queue
|
||||||
|
|
||||||
app = QApplication([])
|
app = QApplication([])
|
||||||
plotApp = PlotApp(xy_pairs=xy_pairs)
|
plotApp = PlotApp(xy_pairs=xy_pairs, plot_settings=plot_settings, plot_data=plot_data)
|
||||||
|
|
||||||
# Connecting signals from bec_dispatcher
|
# Connecting signals from bec_dispatcher
|
||||||
bec_dispatcher.connect_slot(plotApp.on_scan_segment, MessageEndpoints.scan_segment())
|
bec_dispatcher.connect_slot(plotApp.on_scan_segment, MessageEndpoints.scan_segment())
|
||||||
|
@ -1 +1,2 @@
|
|||||||
from .crosshair import Crosshair
|
from .crosshair import Crosshair
|
||||||
|
from .colors import Colors
|
||||||
|
50
bec_widgets/qt_utils/colors.py
Normal file
50
bec_widgets/qt_utils/colors.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import numpy as np
|
||||||
|
import pyqtgraph as pg
|
||||||
|
from pyqtgraph import mkColor
|
||||||
|
|
||||||
|
|
||||||
|
class Colors:
|
||||||
|
@staticmethod
|
||||||
|
def golden_ratio(num: int) -> list:
|
||||||
|
"""Calculate the golden ratio for a given number of angles.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
num (int): Number of angles
|
||||||
|
"""
|
||||||
|
phi = 2 * np.pi * ((1 + np.sqrt(5)) / 2)
|
||||||
|
angles = []
|
||||||
|
for ii in range(num):
|
||||||
|
x = np.cos(ii * phi)
|
||||||
|
y = np.sin(ii * phi)
|
||||||
|
angle = np.arctan2(y, x)
|
||||||
|
angles.append(angle)
|
||||||
|
return angles
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def golden_angle_color(colormap: str, num: int) -> list:
|
||||||
|
"""
|
||||||
|
Extract num colors for from the specified colormap following golden angle distribution.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
colormap (str): Name of the colormap
|
||||||
|
num (int): Number of requested colors
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: List of colors with length <num>
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If the number of requested colors is greater than the number of colors in the colormap.
|
||||||
|
"""
|
||||||
|
|
||||||
|
cmap = pg.colormap.get(colormap)
|
||||||
|
cmap_colors = cmap.color
|
||||||
|
if num > len(cmap_colors):
|
||||||
|
raise ValueError(
|
||||||
|
f"Number of colors requested ({num}) is greater than the number of colors in the colormap ({len(cmap_colors)})"
|
||||||
|
)
|
||||||
|
angles = Colors.golden_ratio(len(cmap_colors))
|
||||||
|
color_selection = np.round(np.interp(angles, (-np.pi, np.pi), (0, len(cmap_colors))))
|
||||||
|
colors = [
|
||||||
|
mkColor(tuple((cmap_colors[int(ii)] * 255).astype(int))) for ii in color_selection[:num]
|
||||||
|
]
|
||||||
|
return colors
|
Reference in New Issue
Block a user