From 78e2d8d424e739ffef7a3a386674d2e5db772b5e Mon Sep 17 00:00:00 2001 From: detlefarend <detlef@arend-privat.net> Date: Wed, 30 Oct 2024 17:30:23 +0100 Subject: [PATCH] Bug: Missing plot refresh of tasks sharing the Matplotlib figure with the prior task #1076 --- src/mlpro/bf/plot.py | 133 +++++++++++++----- src/mlpro/bf/streams/basics.py | 12 +- .../bf/streams/tasks/windows/ringbuffer.py | 2 +- 3 files changed, 106 insertions(+), 41 deletions(-) diff --git a/src/mlpro/bf/plot.py b/src/mlpro/bf/plot.py index 0d83466a9..e648a982c 100644 --- a/src/mlpro/bf/plot.py +++ b/src/mlpro/bf/plot.py @@ -53,10 +53,14 @@ ## -- - New property Plottable.color ## -- - Class PlotSettings: removed parameter p_plot_depth ## -- 2024-07-08 2.16.1 SY Add MinVal for undefined range in DataPlotting +## -- 2024-10-30 2.17.0 DA Extensions on classes PlotSettings, Plottable: +## -- - Class PlotSettings: new methods register(), unregister(), +## -- is_last_registered() +## -- - Class Plottable: extensions on init_plot(), update_plot() ## ------------------------------------------------------------------------------------------------- """ -Ver. 2.16.1 (2024-07-08) +Ver. 2.17.0 (2024-10-30) This module provides various classes related to data plotting. @@ -168,6 +172,7 @@ def __init__( self, self.id = p_id self.view_autoselect = p_view_autoselect self.kwargs = p_kwargs.copy() + self._registered_obj = [] if ( p_plot_horizon > 0 ) and ( p_data_horizon > 0 ): self.plot_horizon = min(p_plot_horizon, p_data_horizon) @@ -176,23 +181,80 @@ def __init__( self, self.plot_horizon = p_plot_horizon self.data_horizon = p_data_horizon + +## ------------------------------------------------------------------------------------------------- + def register( self, p_plot_obj : type): + """ + Registers the specified plotting object. Internally used in class Plottable. + + Parameters + ---------- + p_plot_obj : type + Plotting object to be registered + """ + + self._registered_obj.append(p_plot_obj) + + +## ------------------------------------------------------------------------------------------------- + def unregister( self, p_plot_obj : type): + """ + Unregisters the specified plotting object. Internally used in class Plottable. + + Parameters + ---------- + p_plot_obj : type + Plotting object to be registered + """ + + try: + self._registered_obj.remove(p_plot_obj) + except: + pass + + +## ------------------------------------------------------------------------------------------------- + def is_last_registered( self, p_plot_obj : type ) -> bool: + """ + Checks whether the specified plot object was the last one registered. Internally used in + class Plottable. + + Parameters + ---------- + p_plot_obj : type + Plotting object to be registered + + Returns + ------- + bool + True, if the specified plotting object was the last one registering. False otherwise. + """ + + try: + return p_plot_obj == self._registered_obj[-1] + except: + return False + ## ------------------------------------------------------------------------------------------------- def copy(self): - return self.__class__( p_view = self.view, - p_axes = self.axes, - p_pos_x = self.pos_x, - p_pos_y = self.pos_y, - p_size_x = self.size_x, - p_size_y = self.size_y, - p_step_rate = self.step_rate, - p_plot_horizon = self.plot_horizon, - p_data_horizon = self.data_horizon, - p_detail_level = self.detail_level, - p_force_fg = self.force_fg, - p_id = self.id, - p_view_autoselect = self.view_autoselect, - p_kwargs = self.kwargs ) + duplicate = self.__class__( p_view = self.view, + p_axes = self.axes, + p_pos_x = self.pos_x, + p_pos_y = self.pos_y, + p_size_x = self.size_x, + p_size_y = self.size_y, + p_step_rate = self.step_rate, + p_plot_horizon = self.plot_horizon, + p_data_horizon = self.data_horizon, + p_detail_level = self.detail_level, + p_force_fg = self.force_fg, + p_id = self.id, + p_view_autoselect = self.view_autoselect, + p_kwargs = self.kwargs ) + + #duplicate._registered_obj = self._registered_obj.copy() + return duplicate @@ -322,26 +384,27 @@ def init_plot( self, plt.ioff() - # 2 Prepare internal data structures - - # 2.1 Plot settings per view - self.set_plot_settings( p_plot_settings=p_plot_settings ) - - # 2.2 Dictionary with methods for initialization and update of a plot per view - self._plot_methods = { PlotSettings.C_VIEW_2D : [ self._init_plot_2d, self._update_plot_2d, self._remove_plot_2d ], - PlotSettings.C_VIEW_3D : [ self._init_plot_3d, self._update_plot_3d, self._remove_plot_3d ], - PlotSettings.C_VIEW_ND : [ self._init_plot_nd, self._update_plot_nd, self._remove_plot_nd ] } + # 2 Prepare internal data structures + # 2.1 Dictionary with methods for initialization and update of a plot per view + self._plot_methods = { PlotSettings.C_VIEW_2D : ( self._init_plot_2d, self._update_plot_2d, self._remove_plot_2d ), + PlotSettings.C_VIEW_3D : ( self._init_plot_3d, self._update_plot_3d, self._remove_plot_3d ), + PlotSettings.C_VIEW_ND : ( self._init_plot_nd, self._update_plot_nd, self._remove_plot_nd ) } - # 3 Setup the Matplotlib host figure if no one is provided as parameter + # 2.2 Plot settings per view + self.set_plot_settings( p_plot_settings=p_plot_settings ) + + # 2.3 Setup the Matplotlib host figure if no one is provided as parameter if p_figure is None: self._figure : Figure = self._init_figure() self._plot_own_figure = True else: self._figure : Figure = p_figure + + self._plot_settings.register( p_plot_obj = self ) - # 4 Call of all initialization methods of the required views + # 3 Call of all initialization methods of the required views view = self._plot_settings.view try: plot_method = self._plot_methods[view][0] @@ -354,13 +417,13 @@ def init_plot( self, raise ImplementationError('Please set attribute "axes" in your custom _init_plot_' + view + ' method') - # # 5 In standalone mode: refresh figure + # 4 In standalone mode: refresh figure if self._plot_own_figure: self._figure.canvas.draw() self._figure.canvas.flush_events() - # 6 Marker to ensure that initialization runs only once + # 5 Marker to ensure that initialization runs only once self._plot_initialized = True self._plot_first_time = True @@ -471,10 +534,10 @@ def refresh_plot(self, p_force:bool=False): # 1 Object has own figure or refresh is forced by caller? - if not self._plot_own_figure and not p_force: return +# if not self._plot_own_figure and not p_force: return - if self._plot_own_figure: - self._plot_step_counter = mod(self._plot_step_counter+1, self._plot_settings.step_rate) +# if self._plot_own_figure: + self._plot_step_counter = mod(self._plot_step_counter+1, self._plot_settings.step_rate) # 2 Refresh plot @@ -589,8 +652,9 @@ def update_plot(self, **p_kwargs): self._plot_methods[view][1](p_settings=self._plot_settings, **p_kwargs) - # 4 Update content of own(!) figure after self._plot_step_rate calls - self.refresh_plot(p_force=False) + # 4 The last plotting object for the figure refreshs the plot + if self._plot_settings.is_last_registered( p_plot_obj = self ): + self.refresh_plot(p_force=True) ## ------------------------------------------------------------------------------------------------- @@ -665,6 +729,8 @@ def remove_plot(self, p_refresh:bool = True): # 3 Optionally refresh if p_refresh: self.refresh_plot(p_force=False) + # 4 Clear internal plot parameters + self._plot_settings.unregister( p_plot_obj = self ) self._plot_first_time = True @@ -695,6 +761,7 @@ def _remove_plot_nd(self): pass +## ------------------------------------------------------------------------------------------------- color = property( fget = get_plot_color, fset = set_plot_color ) plot_detail_level = property( fget = get_plot_detail_level, fset = assign_plot_detail_level ) diff --git a/src/mlpro/bf/streams/basics.py b/src/mlpro/bf/streams/basics.py index 5a0572c34..272cc3c29 100644 --- a/src/mlpro/bf/streams/basics.py +++ b/src/mlpro/bf/streams/basics.py @@ -70,10 +70,11 @@ ## -- visualization 2D,3D,ND ## -- 2024-09-11 2.1.0 DA Class Instance: new parent KWArgs ## -- 2024-10-29 2.2.0 DA Changed definiton of InstType, InstTypeNew, InstTypeDel +## -- 2024-10-30 2.3.0 DA Refactoring of StreamTask.update_plot() ## ------------------------------------------------------------------------------------------------- """ -Ver. 2.2.0 (2024-10-29) +Ver. 2.3.0 (2024-10-30) This module provides classes for standardized data stream processing. @@ -1096,21 +1097,18 @@ def update_plot( self, else: inst = p_inst - if len(inst) == 0: return - try: self._plot_view_finalized except: - if self._plot_settings.view_autoselect: + if self._plot_settings.view_autoselect and ( len(inst) > 0 ): self._finalize_plot_view(p_inst_ref=next(iter(inst.values()))[1]) - - self._plot_view_finalized = True + self._plot_view_finalized = True Task.update_plot(self, p_inst=inst, **p_kwargs) self._plot_num_inst += len(inst) - + ## ------------------------------------------------------------------------------------------------- def _update_plot_2d( self, p_settings : PlotSettings, diff --git a/src/mlpro/bf/streams/tasks/windows/ringbuffer.py b/src/mlpro/bf/streams/tasks/windows/ringbuffer.py index 1524ef9fc..580bbf014 100644 --- a/src/mlpro/bf/streams/tasks/windows/ringbuffer.py +++ b/src/mlpro/bf/streams/tasks/windows/ringbuffer.py @@ -124,7 +124,7 @@ def _run(self, p_inst : InstDict ): # 1 Main processing loop - for inst_id, (inst_type, inst) in sorted(inst.items()): + for inst_id, (inst_type, inst) in sorted(inst.items()): if inst_type != InstTypeNew: # Obsolete instances need to be removed from the buffer (not yet implemented)