From 8553dc8c1938223a64fe02bc1463d0f5b5bd2b9e Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Tue, 7 Mar 2023 23:48:53 -0500 Subject: [PATCH 1/3] fix frame_apply. new: grid_plot_kwargs, reset_vmin_vmax() --- fastplotlib/widgets/image.py | 54 ++++++++++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/fastplotlib/widgets/image.py b/fastplotlib/widgets/image.py index 32255629e..cbae36826 100644 --- a/fastplotlib/widgets/image.py +++ b/fastplotlib/widgets/image.py @@ -170,6 +170,7 @@ def __init__( vmin_vmax_sliders: bool = False, grid_shape: Tuple[int, int] = None, names: List[str] = None, + grid_plot_kwargs: dict = None, **kwargs ): """ @@ -471,12 +472,12 @@ def __init__( if vmin_vmax_sliders: data_range = np.ptp(minmax) - data_range_30p = np.ptp(minmax) * 0.3 + data_range_40p = np.ptp(minmax) * 0.3 minmax_slider = FloatRangeSlider( value=minmax, - min=minmax[0] - data_range_30p, - max=minmax[1] + data_range_30p, + min=minmax[0] - data_range_40p, + max=minmax[1] + data_range_40p, step=data_range / 150, description=f"min-max", readout=True, @@ -494,11 +495,15 @@ def __init__( kwargs["vmin"], kwargs["vmax"] = minmax frame = self._process_indices(self.data[0], slice_indices=self._current_index) + frame = self._process_frame_apply(frame, 0) self.image_graphics: List[ImageGraphic] = [self.plot.add_image(data=frame, name="image", **kwargs)] elif self._plot_type == "grid": - self._plot: GridPlot = GridPlot(shape=grid_shape, controllers="sync") + if grid_plot_kwargs is None: + grid_plot_kwargs = {"controllers": "sync"} + + self._plot: GridPlot = GridPlot(shape=grid_shape, **grid_plot_kwargs) self.image_graphics = list() for data_ix, (d, subplot) in enumerate(zip(self.data, self.plot)): @@ -513,12 +518,12 @@ def __init__( if vmin_vmax_sliders: data_range = np.ptp(minmax) - data_range_30p = np.ptp(minmax) * 0.4 + data_range_40p = np.ptp(minmax) * 0.4 minmax_slider = FloatRangeSlider( value=minmax, - min=minmax[0] - data_range_30p, - max=minmax[1] + data_range_30p, + min=minmax[0] - data_range_40p, + max=minmax[1] + data_range_40p, step=data_range / 150, description=f"mm: {name_slider}", readout=True, @@ -539,6 +544,7 @@ def __init__( _kwargs = kwargs frame = self._process_indices(d, slice_indices=self._current_index) + frame = self._process_frame_apply(frame, data_ix) ig = ImageGraphic(frame, name="image", **_kwargs) subplot.add_graphic(ig) subplot.name = name @@ -767,11 +773,17 @@ def _get_window_indices(self, data_ix, dim, indices_dim): return indices_dim def _process_frame_apply(self, array, data_ix) -> np.ndarray: + if callable(self.frame_apply): + return self.frame_apply(array) + if data_ix not in self.frame_apply.keys(): return array - if self.frame_apply[data_ix] is not None: + + elif self.frame_apply[data_ix] is not None: return self.frame_apply[data_ix](array) + return array + def _slider_value_changed( self, dimension: str, @@ -801,6 +813,32 @@ def _set_slider_layout(self, *args): for mm in self.vmin_vmax_sliders: mm.layout = Layout(width=f"{w}px") + def _get_vmin_vmax_range(self, data: np.ndarray) -> Tuple[int, int]: + minmax = quick_min_max(data) + + data_range = np.ptp(minmax) + data_range_40p = np.ptp(minmax) * 0.4 + + _range = ( + minmax, + data_range, + minmax[0] - data_range_40p, + minmax[1] + data_range_40p + ) + + return _range + + def reset_vmin_vmax(self): + """ + Reset the vmin and vmax w.r.t. the currently displayed image(s) + """ + for i, ig in enumerate(self.image_graphics): + mm = self._get_vmin_vmax_range(ig.data()) + self.vmin_vmax_sliders[i].min = mm[2] + self.vmin_vmax_sliders[i].max = mm[3] + self.vmin_vmax_sliders[i].step = mm[1] / 150 + self.vmin_vmax_sliders[i].value = mm[0] + def show(self): """ Show the widget From 72bdc8825c9a7a23a129183d5b275e9ad158c351 Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Wed, 8 Mar 2023 02:21:37 -0500 Subject: [PATCH 2/3] add grid_plot_kwargs to ImageWidget docstring --- fastplotlib/widgets/image.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fastplotlib/widgets/image.py b/fastplotlib/widgets/image.py index cbae36826..b84375e01 100644 --- a/fastplotlib/widgets/image.py +++ b/fastplotlib/widgets/image.py @@ -228,6 +228,9 @@ def __init__( grid_shape: Optional[Tuple[int, int]] manually provide the shape for a gridplot, otherwise a square gridplot is approximated. + grid_plot_kwargs: dict, optional + passed to `GridPlot` + names: Optional[str] gives names to the subplots From 516142986114ee8834aa50ad39903790055ae350 Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Wed, 8 Mar 2023 02:22:30 -0500 Subject: [PATCH 3/3] better init of controllers for GridPlot, now allows controller objects or ints for controllers arg --- fastplotlib/layouts/_gridplot.py | 42 ++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/fastplotlib/layouts/_gridplot.py b/fastplotlib/layouts/_gridplot.py index 3abb4fa63..1ea21e9a1 100644 --- a/fastplotlib/layouts/_gridplot.py +++ b/fastplotlib/layouts/_gridplot.py @@ -71,8 +71,9 @@ def __init__( # create the array representing the views for each subplot in the grid cameras = np.array([cameras] * self.shape[0] * self.shape[1]).reshape(self.shape) - if controllers == "sync": - controllers = np.zeros(self.shape[0] * self.shape[1], dtype=int).reshape(self.shape) + if isinstance(controllers, str): + if controllers == "sync": + controllers = np.zeros(self.shape[0] * self.shape[1], dtype=int).reshape(self.shape) if controllers is None: controllers = np.arange(self.shape[0] * self.shape[1]).reshape(self.shape) @@ -82,13 +83,31 @@ def __init__( if controllers.shape != self.shape: raise ValueError + self._controllers = np.empty(shape=cameras.shape, dtype=object) + cameras = to_array(cameras) if cameras.shape != self.shape: raise ValueError - if not np.all(np.sort(np.unique(controllers)) == np.arange(np.unique(controllers).size)): - raise ValueError("controllers must be consecutive integers") + # create controllers if the arguments were integers + if np.issubdtype(controllers.dtype, np.integer): + if not np.all(np.sort(np.unique(controllers)) == np.arange(np.unique(controllers).size)): + raise ValueError("controllers must be consecutive integers") + + for controller in np.unique(controllers): + cam = np.unique(cameras[controllers == controller]) + if cam.size > 1: + raise ValueError( + f"Controller id: {controller} has been assigned to multiple different camera types") + + self._controllers[controllers == controller] = create_controller(cam[0]) + # else assume it's a single pygfx.Controller instance or a list of controllers + else: + if isinstance(controllers, pygfx.Controller): + self._controllers = np.array([controllers] * shape[0] * shape[1]).reshape(shape) + else: + self._controllers = np.array(controllers).reshape(shape) if canvas is None: canvas = WgpuCanvas() @@ -111,18 +130,9 @@ def __init__( self._subplots: np.ndarray[Subplot] = np.ndarray(shape=(nrows, ncols), dtype=object) # self.viewports: np.ndarray[Subplot] = np.ndarray(shape=(nrows, ncols), dtype=object) - self._controllers: List[pygfx.PanZoomController] = [ - pygfx.PanZoomController() for i in range(np.unique(controllers).size) - ] - - self._controllers = np.empty(shape=cameras.shape, dtype=object) - - for controller in np.unique(controllers): - cam = np.unique(cameras[controllers == controller]) - if cam.size > 1: - raise ValueError(f"Controller id: {controller} has been assigned to multiple different camera types") - - self._controllers[controllers == controller] = create_controller(cam[0]) + # self._controllers: List[pygfx.PanZoomController] = [ + # pygfx.PanZoomController() for i in range(np.unique(controllers).size) + # ] for i, j in self._get_iterator(): position = (i, j)