diff --git a/bec_widgets/line_plot.py b/bec_widgets/line_plot.py index d8546bfa..53485989 100644 --- a/bec_widgets/line_plot.py +++ b/bec_widgets/line_plot.py @@ -42,15 +42,9 @@ class BasicPlot(QtWidgets.QWidget): current_path = os.path.dirname(__file__) uic.loadUi(os.path.join(current_path, "line_plot.ui"), self) - self.splitter_H.setSizes([1, 1]) - self._idle_time = 100 - self.title = "" - self.label_bottom = "" - self.label_left = "" self.producer = RedisConnector(["localhost:6379"]).producer() - self.scan_motors = [] self.y_value_list = y_value_list self.previous_y_value_list = None self.plotter_data_x = [] @@ -58,15 +52,6 @@ class BasicPlot(QtWidgets.QWidget): self.plotter_scan_id = None - ########################## - # Buttons - ########################## - self.init_ui() - - self.hook_crosshair() - - self.pushButton_generate.clicked.connect(self.generate_data) - self._current_proj = None self._current_metadata_ep = "px_stream/projection_{}/metadata" @@ -75,6 +60,21 @@ class BasicPlot(QtWidgets.QWidget): self.data_retriever = threading.Thread(target=self.on_projection, daemon=True) self.data_retriever.start() + # self.comboBox.currentIndexChanged.connect(lambda : print(f'current comboText: {self.comboBox.currentText()}')) + # self.comboBox.currentIndexChanged.connect(lambda: print(f'current comboIndex: {self.comboBox.currentIndex()}')) + # + # self.doubleSpinBox.valueChanged.connect(lambda : print('Spin Changed')) + + # self.splitterH_main.setSizes([1, 1]) + + ########################## + # UI + ########################## + self.init_ui() + self.init_curves() + self.hook_crosshair() + self.pushButton_generate.clicked.connect(self.generate_data) + def init_ui(self): """Setup all ui elements""" ########################## @@ -89,16 +89,16 @@ class BasicPlot(QtWidgets.QWidget): # ROI selector - so far from [-1,1] #TODO update to scale with xrange self.roi_selector = pg.LinearRegionItem([-1, 1]) - # self.glw_plot.nextRow() #TODO update of cursor - # self.label_plot_moved = pg.LabelItem(justify="center") - # self.glw_plot.addItem(self.label_plot_moved) - # self.label_plot_moved.setText("Actual coordinates (X, Y)") - # - # # Label for coordinates clicked - # self.glw_plot.nextRow() - # self.label_plot_clicked = pg.LabelItem(justify="center") - # self.glw_plot.addItem(self.label_plot_clicked) - # self.label_plot_clicked.setText("Clicked coordinates (X, Y)") + self.glw_plot.nextRow() # TODO update of cursor + self.label_plot_moved = pg.LabelItem(justify="center") + self.glw_plot.addItem(self.label_plot_moved) + self.label_plot_moved.setText("Actual coordinates (X, Y)") + + # Label for coordinates clicked + self.glw_plot.nextRow() + self.label_plot_clicked = pg.LabelItem(justify="center") + self.glw_plot.addItem(self.label_plot_clicked) + self.label_plot_clicked.setText("Clicked coordinates (X, Y)") # 1D PlotItem self.glw_plot.nextRow() @@ -107,9 +107,47 @@ class BasicPlot(QtWidgets.QWidget): self.glw_plot.addItem(self.plot) self.plot.addLegend() - # check if roi selector is in the plot - if self.roi_selector not in self.plot.items: - self.plot.addItem(self.roi_selector) + ########################## + # 2D Plot + ########################## + + # Label for coordinates moved + self.label_image_moved = pg.LabelItem(justify="center") + self.glw_image.addItem(self.label_image_moved) + self.label_image_moved.setText("Actual coordinates (X, Y)") + + # Label for coordinates clicked + self.glw_image.nextRow() + self.label_image_clicked = pg.LabelItem(justify="center") + self.glw_image.addItem(self.label_image_clicked) + self.label_image_clicked.setText("Clicked coordinates (X, Y)") + + # TODO try to lock aspect ratio with view + + # # Create a window + # win = pg.GraphicsLayoutWidget() + # win.show() + # + # # Create a ViewBox + # view = win.addViewBox() + # + # # Lock the aspect ratio + # view.setAspectLocked(True) + + # # Create an ImageItem + # image_item = pg.ImageItem(np.random.random((100, 100))) + # + # # Add the ImageItem to the ViewBox + # view.addItem(image_item) + + # 2D ImageItem + self.glw_image.nextRow() + self.plot_image = pg.PlotItem() + self.glw_image.addItem(self.plot_image) + + def init_curves(self): + # init of 1D plot + self.plot.clear() self.curves = [] self.pens = [] @@ -128,31 +166,36 @@ class BasicPlot(QtWidgets.QWidget): self.pens.append(pen) self.brushs.append(brush) - ########################## - # 2D Plot - ########################## + # check if roi selector is in the plot + if self.roi_selector not in self.plot.items: + self.plot.addItem(self.roi_selector) - # Label for coordinates moved - self.label_image_moved = pg.LabelItem(justify="center") - self.glw_image.addItem(self.label_image_moved) - self.label_image_moved.setText("Actual coordinates (X, Y)") + # init of 2D plot + self.plot_image.clear() - # Label for coordinates clicked - self.glw_image.nextRow() - self.label_image_clicked = pg.LabelItem(justify="center") - self.glw_image.addItem(self.label_image_clicked) - self.label_image_clicked.setText("Clicked coordinates (X, Y)") - - # 2D ImageItem - self.glw_image.nextRow() - self.plot_image = pg.PlotItem() self.img = pg.ImageItem() - self.glw_image.addItem(self.plot_image) self.plot_image.addItem(self.img) - def hook_crosshair(self): + # hooking signals + self.hook_crosshair() + self.init_table() + + def splitter_sizes(self): ... - # self.crosshair_plot = Crosshair(self.plot, precision=2) + + def hook_crosshair(self): + self.crosshair_1d = Crosshair(self.plot, precision=4) + + self.crosshair_1d.coordinatesChanged1D.connect( + lambda x, y: self.label_plot_moved.setText(f"Moved : ({x}, {y})") + ) + self.crosshair_1d.coordinatesClicked1D.connect( + lambda x, y: self.label_plot_clicked.setText(f"Moved : ({x}, {y})") + ) + + self.crosshair_1d.coordinatesChanged1D.connect( + lambda x, y: self.update_table(table_widget=self.cursor_table, x=x, y_values=y) + ) self.crosshair_2D = Crosshair(self.plot_image) @@ -170,21 +213,21 @@ class BasicPlot(QtWidgets.QWidget): def gauss(x, mu, sigma): return (1 / (sigma * np.sqrt(2 * np.pi))) * np.exp(-0.5 * ((x - mu) / sigma) ** 2) - mu = 0 # mean - sigma = 1 # standard deviation - self.plotter_data_x = np.linspace(0, 10, 1000) self.plotter_data_y = [ - gauss(self.plotter_data_x, mu, sigma), + gauss(self.plotter_data_x, 1, 1), + gauss(self.plotter_data_x, 1.5, 3), np.sin(self.plotter_data_x), np.cos(self.plotter_data_x), np.sin(2 * self.plotter_data_x), ] # List of y-values for multiple curves - self.y_value_list = ["gauss"] # ["Sine"]#, "Cosine", "Sine2x"] + self.y_value_list = ["Gauss (1,1)", "Gauss (1.5,3)"] # ["Sine"]#, "Cosine", "Sine2x"] # Curves color_list = ["#384c6b", "#e28a2b", "#5E3023", "#e41a1c", "#984e83", "#4daf4a"] + self.init_curves() + for ii in range(len(self.y_value_list)): self.curves[ii].setData(self.plotter_data_x, self.plotter_data_y[ii]) @@ -208,11 +251,22 @@ class BasicPlot(QtWidgets.QWidget): self.producer.set_and_publish("px_stream/gui_event", msg=msg) self.roi_signal.emit(region) + def init_table(self): + # Init number of rows in table according to n of devices + self.cursor_table.setRowCount(len(self.y_value_list)) + # self.table.setHorizontalHeaderLabels(["(X, Y) - Moved", "(X, Y) - Clicked"]) #TODO can be dynamic + self.cursor_table.setVerticalHeaderLabels(self.y_value_list) + self.cursor_table.resizeColumnsToContents() + + def update_table(self, table_widget, x, y_values): + for i, y in enumerate(y_values): + table_widget.setItem(i, 1, QTableWidgetItem(str(x))) + table_widget.setItem(i, 2, QTableWidgetItem(str(y))) + table_widget.resizeColumnsToContents() + def update(self): """Update the plot with the new data.""" - print("updated") - # check if QTable was initialised and if list of devices was changed # if self.y_value_list != self.previous_y_value_list: # self.setup_cursor_table() @@ -307,7 +361,6 @@ class BasicPlot(QtWidgets.QWidget): @pyqtSlot(dict, dict) def on_dap_update(self, data: dict, metadata: dict): self.img.setImage(data["z"]) - # time.sleep(0,1) @pyqtSlot(dict) def new_proj(self, data): @@ -337,6 +390,7 @@ if __name__ == "__main__": default=["gauss_bpm"], ) # default = ["gauss_bpm", "bpm4i", "bpm5i", "bpm6i", "xert"], + # dispatcher = bec_dispatcher value = parser.parse_args() print(f"Plotting signals for: {', '.join(value.signals)}") client = bec_dispatcher.client @@ -347,6 +401,8 @@ if __name__ == "__main__": # bec_dispatcher.connect(plot) bec_dispatcher.connect_proj_id(plot.new_proj) bec_dispatcher.connect_dap_slot(plot.on_dap_update, "px_dap_worker") + plot.roi_signal.connect(lambda x: print(f"signal from ROI {x}")) + plot.roi_signal.connect(lambda x: bec_dispatcher.getStuff(x)) plot.show() # client.callbacks.register("scan_segment", plot, sync=False) app.exec_() diff --git a/bec_widgets/line_plot.ui b/bec_widgets/line_plot.ui index 7ef2ca4d..8f4c0731 100644 --- a/bec_widgets/line_plot.ui +++ b/bec_widgets/line_plot.ui @@ -13,24 +13,17 @@ Line Plot - - - - - Generate 1D and 2D data without stream - - - - - + + + Qt::Horizontal - 0 - 0 + 1 + 1 @@ -39,30 +32,112 @@ - - - Qt::ElideMiddle - - - - Display - - - - - Device - - - - - X - - - - - Y - - + + + + + + Generate 1D and 2D data without stream + + + + + + + + 0 + 0 + + + + 1st angle of azimutal segment (deg) + + + + + + + 0 + 0 + + + + 360.000000000000000 + + + 0.250000000000000 + + + + + + + + 0 + 0 + + + + + f1amp + + + + + f2amp + + + + + f2 phase + + + + + + + + + + + + + Precision + + + + + + + 4 + + + + + + + + + Qt::ElideMiddle + + + + Display + + + + + X + + + + + Y + + + + + diff --git a/bec_widgets/qt_utils/crosshair.py b/bec_widgets/qt_utils/crosshair.py index f558ddd2..1185da7b 100644 --- a/bec_widgets/qt_utils/crosshair.py +++ b/bec_widgets/qt_utils/crosshair.py @@ -55,10 +55,19 @@ class Crosshair(QObject): curves.append((item.xData, item.yData)) elif isinstance(item, pg.ImageItem): # 2D plot return item.image, None + return curves + # if curves: + # return curves + # else: + # return None + def snap_to_data(self, x, y): data = self.get_data() + # if data is None: #TODO hadle if data are None + # return x, y + if isinstance(data, list): # 1D plot y_values = [] for x_data, y_data in data: @@ -92,14 +101,16 @@ class Crosshair(QObject): pos = event[0] if self.plot_item.vb.sceneBoundingRect().contains(pos): mouse_point = self.plot_item.vb.mapSceneToView(pos) + self.v_line.setPos(mouse_point.x()) + self.h_line.setPos(mouse_point.y()) + x, y = mouse_point.x(), mouse_point.y() if self.is_log_x: x = 10**x if self.is_log_y: y = 10**y x, y_values = self.snap_to_data(x, y) - self.v_line.setPos(mouse_point.x()) - self.h_line.setPos(mouse_point.y()) + if isinstance(y_values, list): # 1D plot self.coordinatesChanged1D.emit( round(x, self.precision), [round(y_val, self.precision) for y_val in y_values] @@ -136,6 +147,8 @@ class Crosshair(QObject): self.marker_2d.setPos([x, y_values]) def check_log(self): - # Check if plot uses log scale + """ + Check if x or y axis is in log scale + """ self.is_log_x = self.plot_item.ctrl.logXCheck.isChecked() self.is_log_y = self.plot_item.ctrl.logYCheck.isChecked() diff --git a/bec_widgets/qt_utils/utils_example.py b/bec_widgets/qt_utils/utils_example.py index 119a0e64..cca82215 100644 --- a/bec_widgets/qt_utils/utils_example.py +++ b/bec_widgets/qt_utils/utils_example.py @@ -16,6 +16,7 @@ from crosshair import Crosshair class ExampleApp(QWidget): def __init__(self): + """Example application for using the Crosshair class""" super().__init__() # Layout @@ -128,6 +129,7 @@ class ExampleApp(QWidget): self.column2.addWidget(self.plot_widget_2d) def update_table(self, table_widget, x, y_values, column): + """Update the table with the new coordinates""" for i, y in enumerate(y_values): table_widget.setItem(i, column, QTableWidgetItem(f"({x}, {y})")) table_widget.resizeColumnsToContents()