From 4ef6ae90f2afd5e2442465c11ce5165517cd4218 Mon Sep 17 00:00:00 2001 From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com> Date: Tue, 12 Mar 2024 22:21:21 +0100 Subject: [PATCH] fix(cli): find_widget_by_id for BECImageShow changed to be compatible with RPC logic --- bec_widgets/cli/client.py | 66 ++++++++++++++- bec_widgets/cli/client_utils.py | 1 - bec_widgets/cli/server.py | 8 +- bec_widgets/widgets/figure/figure.py | 1 + bec_widgets/widgets/plots/image.py | 119 ++++++++++++++++++++------- 5 files changed, 157 insertions(+), 38 deletions(-) diff --git a/bec_widgets/cli/client.py b/bec_widgets/cli/client.py index 9afea48d..0404af11 100644 --- a/bec_widgets/cli/client.py +++ b/bec_widgets/cli/client.py @@ -555,7 +555,7 @@ class BECImageShow(RPCBase): Args: vmin(float): Minimum value of the color bar. vmax(float): Maximum value of the color bar. - name(str): The name of the image. + name(str): The name of the image. If None, apply to all images. """ @rpc_call @@ -565,7 +565,7 @@ class BECImageShow(RPCBase): If name is not specified, then set color map for all images. Args: cmap(str): The color map of the image. - name(str): The name of the image. + name(str): The name of the image. If None, apply to all images. """ @@ -585,7 +585,67 @@ class BECImageItem(RPCBase): @rpc_call def set(self, **kwargs): """ - None + Set the properties of the image. + Args: + **kwargs: Keyword arguments for the properties to be set. + Possible properties: + - downsample + - color_map + - monitor + - opacity + - vrange + - fft + - log + - rot + - transpose + """ + + @rpc_call + def set_fft(self, enable: "bool" = False): + """ + Set the FFT of the image. + Args: + enable(bool): Whether to perform FFT on the monitor data. + """ + + @rpc_call + def set_log(self, enable: "bool" = False): + """ + Set the log of the image. + Args: + enable(bool): Whether to perform log on the monitor data. + """ + + @rpc_call + def set_rotation(self, deg_90: "int" = 0): + """ + Set the rotation of the image. + Args: + deg_90(int): The rotation angle of the monitor data before displaying. + """ + + @rpc_call + def set_transpose(self, enable: "bool" = False): + """ + Set the transpose of the image. + Args: + enable(bool): Whether to transpose the image. + """ + + @rpc_call + def set_opacity(self, opacity: "float" = 1.0): + """ + Set the opacity of the image. + Args: + opacity(float): The opacity of the image. + """ + + @rpc_call + def set_autorange(self, autorange: "bool" = True): + """ + Set the autorange of the color bar. + Args: + autorange(bool): Whether to autorange the color bar. """ @rpc_call diff --git a/bec_widgets/cli/client_utils.py b/bec_widgets/cli/client_utils.py index 8c6b20ec..a0d14b93 100644 --- a/bec_widgets/cli/client_utils.py +++ b/bec_widgets/cli/client_utils.py @@ -132,7 +132,6 @@ class RPCBase: print(f"RPCBase: {rpc_msg}") # pylint: disable=protected-access receiver = self._root._gui_id - # self._client.connector.send(MessageEndpoints.gui_instructions(receiver), rpc_msg) self._client.connector.set_and_publish(MessageEndpoints.gui_instructions(receiver), rpc_msg) if not wait_for_rpc_response: diff --git a/bec_widgets/cli/server.py b/bec_widgets/cli/server.py index 6dd54b3c..b6ee4024 100644 --- a/bec_widgets/cli/server.py +++ b/bec_widgets/cli/server.py @@ -62,9 +62,9 @@ class BECWidgetsCLIServer: item = widget.find_widget_by_id(gui_id) if item: return item - raise NotImplementedError( - f"gui_id lookup for widget of type {widget.__class__.__name__} not implemented" - ) + # raise NotImplementedError( + # f"gui_id lookup for widget of type {widget.__class__.__name__} not implemented" + # ) raise ValueError(f"Object with gui_id {gui_id} not found") @@ -94,7 +94,7 @@ class BECWidgetsCLIServer: "config": obj.config.model_dump(), "__rpc__": True, } - return obj + return obpyj if __name__ == "__main__": # pragma: no cover diff --git a/bec_widgets/widgets/figure/figure.py b/bec_widgets/widgets/figure/figure.py index 33c12e4c..7d0c687d 100644 --- a/bec_widgets/widgets/figure/figure.py +++ b/bec_widgets/widgets/figure/figure.py @@ -607,6 +607,7 @@ class BECFigureMainWindow(QMainWindow): def closeEvent(self, event): self.figure.cleanup() + self.figure.client.shutdown() if self.safe_close == True: print("Safe close") event.accept() diff --git a/bec_widgets/widgets/plots/image.py b/bec_widgets/widgets/plots/image.py index 24fc8f0d..37845b01 100644 --- a/bec_widgets/widgets/plots/image.py +++ b/bec_widgets/widgets/plots/image.py @@ -31,6 +31,7 @@ class ProcessingConfig(BaseModel): class ImageItemConfig(ConnectionConfig): + parent_id: Optional[str] = Field(None, description="The parent plot of the image.") monitor: Optional[str] = Field(None, description="The name of the monitor.") source: Optional[str] = Field(None, description="The source of the curve.") color_map: Optional[str] = Field("magma", description="The color map of the image.") @@ -42,6 +43,7 @@ class ImageItemConfig(ConnectionConfig): color_bar: Optional[Literal["simple", "full"]] = Field( "simple", description="The type of the color bar." ) + autorange: Optional[bool] = Field(True, description="Whether to autorange the color bar.") processing: ProcessingConfig = Field( default_factory=ProcessingConfig, description="The post processing of the image." ) @@ -57,6 +59,12 @@ class ImageConfig(WidgetConfig): class BECImageItem(BECConnector, pg.ImageItem): USER_ACCESS = [ "set", + "set_fft", + "set_log", + "set_rotation", + "set_transpose", + "set_opacity", + "set_autorange", "set_color_map", "set_auto_downsample", "set_monitor", @@ -89,13 +97,30 @@ class BECImageItem(BECConnector, pg.ImageItem): self.set(**kwargs) def apply_config(self): + """ + Apply current configuration. + """ self.set_color_map(self.config.color_map) self.set_auto_downsample(self.config.downsample) if self.config.vrange is not None: self.set_vrange(vrange=self.config.vrange) - # self.set_color_bar(self.config.color_bar) def set(self, **kwargs): + """ + Set the properties of the image. + Args: + **kwargs: Keyword arguments for the properties to be set. + Possible properties: + - downsample + - color_map + - monitor + - opacity + - vrange + - fft + - log + - rot + - transpose + """ method_map = { "downsample": self.set_auto_downsample, "color_map": self.set_color_map, @@ -114,23 +139,58 @@ class BECImageItem(BECConnector, pg.ImageItem): print(f"Warning: '{key}' is not a recognized property.") def set_fft(self, enable: bool = False): + """ + Set the FFT of the image. + Args: + enable(bool): Whether to perform FFT on the monitor data. + """ self.config.processing.fft = enable def set_log(self, enable: bool = False): + """ + Set the log of the image. + Args: + enable(bool): Whether to perform log on the monitor data. + """ self.config.processing.log = enable if enable and self.color_bar and self.config.color_bar == "full": self.color_bar.autoHistogramRange() def set_rotation(self, deg_90: int = 0): + """ + Set the rotation of the image. + Args: + deg_90(int): The rotation angle of the monitor data before displaying. + """ self.config.processing.rotation = deg_90 def set_transpose(self, enable: bool = False): + """ + Set the transpose of the image. + Args: + enable(bool): Whether to transpose the image. + """ self.config.processing.transpose = enable def set_opacity(self, opacity: float = 1.0): + """ + Set the opacity of the image. + Args: + opacity(float): The opacity of the image. + """ self.setOpacity(opacity) self.config.opacity = opacity + def set_autorange(self, autorange: bool = True): + """ + Set the autorange of the color bar. + Args: + autorange(bool): Whether to autorange the color bar. + """ + self.config.autorange = autorange + if self.color_bar is not None: + self.color_bar.autoHistogramRange() + def set_color_map(self, cmap: str = "magma"): """ Set the color map of the image. @@ -173,6 +233,7 @@ class BECImageItem(BECConnector, pg.ImageItem): vmin, vmax = vrange self.setLevels([vmin, vmax]) self.config.vrange = (vmin, vmax) + self.config.autorange = False if self.color_bar is not None: if self.config.color_bar == "simple": self.color_bar.setLevels(low=vmin, high=vmax) @@ -215,24 +276,6 @@ class BECImageItem(BECConnector, pg.ImageItem): else: raise ValueError("style should be 'simple' or 'full'") - def _update_color_bar_for_log(self): - """ - Update the color bar to reflect a logarithmic scale. - """ - ... - # if self.config.vrange: - # vmin, vmax = self.config.vrange - # offset = 1e-6 - # vmin_log, vmax_log = np.log10(vmin + offset), np.log10(vmax + offset) - # if self.config.color_bar == "simple": - # self.color_bar.setLevels(low=vmin_log, high=vmax_log) - # elif self.config.color_bar == "full": - # self.color_bar.setImageItem(self) - # self.color_bar.setLevels(min=vmin_log, max=vmax_log) - # self.color_bar.setHistogramRange( - # vmin_log - 0.1 * vmin_log, vmax_log + 0.1 * vmax_log - # ) - class BECImageShow(BECPlotBase): USER_ACCESS = [ @@ -286,6 +329,20 @@ class BECImageShow(BECPlotBase): Args: item_id(str): The gui_id of the widget. + Returns: + BECImageItem: The widget with the given gui_id. + """ + for source, images in self._images.items(): + for monitor, image_item in images.items(): + if image_item.gui_id == item_id: + return image_item + + def find_image_by_monitor(self, item_id: str) -> BECImageItem: + """ + Find the widget by its gui_id. + Args: + item_id(str): The gui_id of the widget. + Returns: BECImageItem: The widget with the given gui_id. """ @@ -294,7 +351,7 @@ class BECImageShow(BECPlotBase): if key == item_id and isinstance(value, BECImageItem): return value elif isinstance(value, dict): - result = self.find_widget_by_id(item_id) + result = self.find_image_by_monitor(item_id) if result is not None: return result @@ -344,6 +401,7 @@ class BECImageShow(BECPlotBase): """ if isinstance(config, dict): config = ImageItemConfig(**config) + config.parent_id = self.gui_id name = config.monitor if config.monitor is not None else config.gui_id image = self._add_image_object(source=config.source, name=name, config=config) return image @@ -468,7 +526,7 @@ class BECImageShow(BECPlotBase): image_id (str, optional): The ID of the specific image to apply the setting to. If None, applies to all images. """ if image_id: - image = self.find_widget_by_id(image_id) + image = self.find_image_by_monitor(image_id) if image: getattr(image, setting_method_name)(*args, **kwargs) else: @@ -610,7 +668,7 @@ class BECImageShow(BECPlotBase): data(np.ndarray): The data to be updated. """ image_to_update = self._images["device_monitor"][device] - image_to_update.updateImage(data) + image_to_update.updateImage(data, autoLevels=image_to_update.config.autorange) def _connect_device_monitor(self, monitor: str): """ @@ -618,7 +676,7 @@ class BECImageShow(BECPlotBase): Args: monitor(str): The name of the monitor. """ - image_item = self.find_widget_by_id(monitor) + image_item = self.find_image_by_monitor(monitor) try: previous_monitor = image_item.config.monitor except AttributeError: @@ -637,6 +695,7 @@ class BECImageShow(BECPlotBase): def _add_image_object( self, source: str, name: str, config: ImageItemConfig, data=None ) -> BECImageItem: # TODO fix types + config.parent_id = self.gui_id image = BECImageItem(config=config, parent_image=self) self.plot_item.addItem(image) self._images[source][name] = image @@ -668,13 +727,13 @@ class BECImageShow(BECPlotBase): Clean up the widget. """ print(f"Cleaning up {self.gui_id}") - for monitor in self._images["device_monitor"]: - self.bec_dispatcher.disconnect_slot( - self.on_image_update, MessageEndpoints.device_monitor(monitor) - ) - if self.thread.isRunning(): - self.thread.quit() - self.thread.wait() + # for monitor in self._images["device_monitor"]: + # self.bec_dispatcher.disconnect_slot( + # self.on_image_update, MessageEndpoints.device_monitor(monitor) + # ) + # if self.thread is not None and self.thread.isRunning(): + # self.thread.quit() + # self.thread.wait() class ImageProcessor: