diff --git a/fastplotlib/__init__.py b/fastplotlib/__init__.py index ca872f4e4..f0f3cc7b1 100644 --- a/fastplotlib/__init__.py +++ b/fastplotlib/__init__.py @@ -19,9 +19,7 @@ adapters = [a.request_adapter_info() for a in enumerate_adapters()] if len(adapters) < 1: - raise IndexError( - "No WGPU adapters found, fastplotlib will not work." - ) + raise IndexError("No WGPU adapters found, fastplotlib will not work.") with open(Path(__file__).parent.joinpath("VERSION"), "r") as f: __version__ = f.read().split("\n")[0] diff --git a/fastplotlib/graphics/_base.py b/fastplotlib/graphics/_base.py index 4f342faa2..91ccb143e 100644 --- a/fastplotlib/graphics/_base.py +++ b/fastplotlib/graphics/_base.py @@ -315,7 +315,7 @@ def link( feature=feature, new_data=new_data, callback=callback, - bidirectional=False # else infinite recursion, otherwise target will call + bidirectional=False, # else infinite recursion, otherwise target will call # this instance .link(), and then it will happen again etc. ) diff --git a/fastplotlib/graphics/_features/__init__.py b/fastplotlib/graphics/_features/__init__.py index a1769b010..fb25db287 100644 --- a/fastplotlib/graphics/_features/__init__.py +++ b/fastplotlib/graphics/_features/__init__.py @@ -3,7 +3,12 @@ from ._sizes import PointsSizesFeature from ._present import PresentFeature from ._thickness import ThicknessFeature -from ._base import GraphicFeature, GraphicFeatureIndexable, FeatureEvent, to_gpu_supported_dtype +from ._base import ( + GraphicFeature, + GraphicFeatureIndexable, + FeatureEvent, + to_gpu_supported_dtype, +) from ._selection_features import LinearSelectionFeature, LinearRegionSelectionFeature from ._deleted import Deleted diff --git a/fastplotlib/graphics/_features/_colors.py b/fastplotlib/graphics/_features/_colors.py index b6723b34b..ad9e673ef 100644 --- a/fastplotlib/graphics/_features/_colors.py +++ b/fastplotlib/graphics/_features/_colors.py @@ -1,7 +1,13 @@ import numpy as np import pygfx -from ...utils import make_colors, get_cmap_texture, make_pygfx_colors, parse_cmap_values, quick_min_max +from ...utils import ( + make_colors, + get_cmap_texture, + make_pygfx_colors, + parse_cmap_values, + quick_min_max, +) from ._base import ( GraphicFeature, GraphicFeatureIndexable, @@ -426,4 +432,4 @@ def vmax(self) -> float: @vmax.setter def vmax(self, value: float): """Maximum contrast limit.""" - self._parent._material.clim = (self._parent._material.clim[0], value) \ No newline at end of file + self._parent._material.clim = (self._parent._material.clim[0], value) diff --git a/fastplotlib/graphics/_features/_sizes.py b/fastplotlib/graphics/_features/_sizes.py index e951064e4..8fceb8df3 100644 --- a/fastplotlib/graphics/_features/_sizes.py +++ b/fastplotlib/graphics/_features/_sizes.py @@ -34,12 +34,16 @@ def __getitem__(self, item): def _fix_sizes(self, sizes, parent): graphic_type = parent.__class__.__name__ - + n_datapoints = parent.data().shape[0] if not isinstance(sizes, (list, tuple, np.ndarray)): - sizes = np.full(n_datapoints, sizes, dtype=np.float32) # force it into a float to avoid weird gpu errors - elif not isinstance(sizes, np.ndarray): # if it's not a ndarray already, make it one - sizes = np.array(sizes, dtype=np.float32) # read it in as a numpy.float32 + sizes = np.full( + n_datapoints, sizes, dtype=np.float32 + ) # force it into a float to avoid weird gpu errors + elif not isinstance( + sizes, np.ndarray + ): # if it's not a ndarray already, make it one + sizes = np.array(sizes, dtype=np.float32) # read it in as a numpy.float32 if (sizes.ndim != 1) or (sizes.size != parent.data().shape[0]): raise ValueError( f"sequence of `sizes` must be 1 dimensional with " @@ -49,7 +53,9 @@ def _fix_sizes(self, sizes, parent): sizes = to_gpu_supported_dtype(sizes) if any(s < 0 for s in sizes): - raise ValueError("All sizes must be positive numbers greater than or equal to 0.0.") + raise ValueError( + "All sizes must be positive numbers greater than or equal to 0.0." + ) if sizes.ndim == 1: if graphic_type == "ScatterGraphic": diff --git a/fastplotlib/graphics/image.py b/fastplotlib/graphics/image.py index 3d629c10f..1cad33f22 100644 --- a/fastplotlib/graphics/image.py +++ b/fastplotlib/graphics/image.py @@ -345,11 +345,7 @@ def col_chunk_index(self, index: int): class HeatmapGraphic(Graphic, Interaction, _AddSelectorsMixin): - feature_events = { - "data", - "cmap", - "present" - } + feature_events = {"data", "cmap", "present"} def __init__( self, diff --git a/fastplotlib/graphics/scatter.py b/fastplotlib/graphics/scatter.py index 63689fad9..f6104aeb7 100644 --- a/fastplotlib/graphics/scatter.py +++ b/fastplotlib/graphics/scatter.py @@ -90,7 +90,9 @@ def __init__( super(ScatterGraphic, self).__init__(*args, **kwargs) world_object = pygfx.Points( - pygfx.Geometry(positions=self.data(), sizes=self.sizes(), colors=self.colors()), + pygfx.Geometry( + positions=self.data(), sizes=self.sizes(), colors=self.colors() + ), material=pygfx.PointsMaterial(color_mode="vertex", vertex_sizes=True), ) diff --git a/fastplotlib/graphics/selectors/_base_selector.py b/fastplotlib/graphics/selectors/_base_selector.py index e6796f270..6c1f8c6ae 100644 --- a/fastplotlib/graphics/selectors/_base_selector.py +++ b/fastplotlib/graphics/selectors/_base_selector.py @@ -44,7 +44,7 @@ def __init__( hover_responsive: Tuple[WorldObject, ...] = None, arrow_keys_modifier: str = None, axis: str = None, - name: str = None + name: str = None, ): if edges is None: edges = tuple() diff --git a/fastplotlib/graphics/selectors/_linear.py b/fastplotlib/graphics/selectors/_linear.py index 16ccab1b4..ff617c5e3 100644 --- a/fastplotlib/graphics/selectors/_linear.py +++ b/fastplotlib/graphics/selectors/_linear.py @@ -28,10 +28,10 @@ def limits(self, values: Tuple[float, float]): # check that `values` is an iterable of two real numbers # using `Real` here allows it to work with builtin `int` and `float` types, and numpy scaler types if len(values) != 2 or not all(map(lambda v: isinstance(v, Real), values)): - raise TypeError( - "limits must be an iterable of two numeric values" - ) - self._limits = tuple(map(round, values)) # if values are close to zero things get weird so round them + raise TypeError("limits must be an iterable of two numeric values") + self._limits = tuple( + map(round, values) + ) # if values are close to zero things get weird so round them self.selection._limits = self._limits # TODO: make `selection` arg in graphics data space not world space @@ -267,11 +267,7 @@ def make_ipywidget_slider(self, kind: str = "IntSlider", **kwargs): return slider - def add_ipywidget_handler( - self, - widget, - step: Union[int, float] = None - ): + def add_ipywidget_handler(self, widget, step: Union[int, float] = None): """ Bidirectionally connect events with a ipywidget slider @@ -285,7 +281,10 @@ def add_ipywidget_handler( """ - if not isinstance(widget, (ipywidgets.IntSlider, ipywidgets.FloatSlider, ipywidgets.FloatLogSlider)): + if not isinstance( + widget, + (ipywidgets.IntSlider, ipywidgets.FloatSlider, ipywidgets.FloatLogSlider), + ): raise TypeError( f"`widget` must be one of: ipywidgets.IntSlider, ipywidgets.FloatSlider, or ipywidgets.FloatLogSlider\n" f"You have passed a: <{type(widget)}" diff --git a/fastplotlib/graphics/selectors/_linear_region.py b/fastplotlib/graphics/selectors/_linear_region.py index 2a7547d5b..4ffbd2cc2 100644 --- a/fastplotlib/graphics/selectors/_linear_region.py +++ b/fastplotlib/graphics/selectors/_linear_region.py @@ -27,10 +27,10 @@ def limits(self, values: Tuple[float, float]): # check that `values` is an iterable of two real numbers # using `Real` here allows it to work with builtin `int` and `float` types, and numpy scaler types if len(values) != 2 or not all(map(lambda v: isinstance(v, Real), values)): - raise TypeError( - "limits must be an iterable of two numeric values" - ) - self._limits = tuple(map(round, values)) # if values are close to zero things get weird so round them + raise TypeError("limits must be an iterable of two numeric values") + self._limits = tuple( + map(round, values) + ) # if values are close to zero things get weird so round them self.selection._limits = self._limits def __init__( @@ -243,7 +243,7 @@ def __init__( hover_responsive=self.edges, arrow_keys_modifier=arrow_keys_modifier, axis=axis, - name=name + name=name, ) def get_selected_data( @@ -417,11 +417,7 @@ def make_ipywidget_slider(self, kind: str = "IntRangeSlider", **kwargs): return slider - def add_ipywidget_handler( - self, - widget, - step: Union[int, float] = None - ): + def add_ipywidget_handler(self, widget, step: Union[int, float] = None): """ Bidirectionally connect events with a ipywidget slider @@ -434,7 +430,9 @@ def add_ipywidget_handler( step size, if ``None`` 100 steps are created """ - if not isinstance(widget, (ipywidgets.IntRangeSlider, ipywidgets.FloatRangeSlider)): + if not isinstance( + widget, (ipywidgets.IntRangeSlider, ipywidgets.FloatRangeSlider) + ): raise TypeError( f"`widget` must be one of: ipywidgets.IntRangeSlider or ipywidgets.FloatRangeSlider\n" f"You have passed a: <{type(widget)}" diff --git a/fastplotlib/graphics/selectors/_mesh_positions.py b/fastplotlib/graphics/selectors/_mesh_positions.py index 07ff60498..a22b22b17 100644 --- a/fastplotlib/graphics/selectors/_mesh_positions.py +++ b/fastplotlib/graphics/selectors/_mesh_positions.py @@ -1,2 +1 @@ import numpy as np - diff --git a/fastplotlib/graphics/selectors/_polygon.py b/fastplotlib/graphics/selectors/_polygon.py index b347da0f4..44d378329 100644 --- a/fastplotlib/graphics/selectors/_polygon.py +++ b/fastplotlib/graphics/selectors/_polygon.py @@ -16,7 +16,6 @@ def __init__( parent: Graphic = None, name: str = None, ): - self.parent = parent group = pygfx.Group() @@ -47,7 +46,9 @@ def _add_plot_area_hook(self, plot_area): self._plot_area.renderer.add_event_handler(self._add_segment, "click") # pointer move to change endpoint of segment - self._plot_area.renderer.add_event_handler(self._move_segment_endpoint, "pointer_move") + self._plot_area.renderer.add_event_handler( + self._move_segment_endpoint, "pointer_move" + ) # click to finish existing segment self._plot_area.renderer.add_event_handler(self._finish_segment, "click") @@ -69,7 +70,9 @@ def _add_segment(self, ev): new_line = pygfx.Line( geometry=pygfx.Geometry(positions=data.astype(np.float32)), - material=pygfx.LineMaterial(thickness=self.edge_width, color=pygfx.Color(self.edge_color)) + material=pygfx.LineMaterial( + thickness=self.edge_width, color=pygfx.Color(self.edge_color) + ), ) self.world_object.add(new_line) @@ -86,7 +89,9 @@ def _move_segment_endpoint(self, ev): return # change endpoint - self.world_object.children[-1].geometry.positions.data[1] = np.array([world_pos]).astype(np.float32) + self.world_object.children[-1].geometry.positions.data[1] = np.array( + [world_pos] + ).astype(np.float32) self.world_object.children[-1].geometry.positions.update_range() def _finish_segment(self, ev): @@ -114,14 +119,15 @@ def _finish_polygon(self, ev): return # make new line to connect first and last vertices - data = np.vstack([ - world_pos, - self.world_object.children[0].geometry.positions.data[0] - ]) + data = np.vstack( + [world_pos, self.world_object.children[0].geometry.positions.data[0]] + ) new_line = pygfx.Line( geometry=pygfx.Geometry(positions=data.astype(np.float32)), - material=pygfx.LineMaterial(thickness=self.edge_width, color=pygfx.Color(self.edge_color)) + material=pygfx.LineMaterial( + thickness=self.edge_width, color=pygfx.Color(self.edge_color) + ), ) self.world_object.add(new_line) @@ -130,7 +136,7 @@ def _finish_polygon(self, ev): self._add_segment: "click", self._move_segment_endpoint: "pointer_move", self._finish_segment: "click", - self._finish_polygon: "double_click" + self._finish_polygon: "double_click", } for handler, event in handlers.items(): diff --git a/fastplotlib/graphics/selectors/_sync.py b/fastplotlib/graphics/selectors/_sync.py index 9414a2e20..ce903aab8 100644 --- a/fastplotlib/graphics/selectors/_sync.py +++ b/fastplotlib/graphics/selectors/_sync.py @@ -3,7 +3,9 @@ class Synchronizer: - def __init__(self, *selectors: LinearSelector, key_bind: Union[str, None] = "Shift"): + def __init__( + self, *selectors: LinearSelector, key_bind: Union[str, None] = "Shift" + ): """ Synchronize the movement of `Selectors`. Selectors will move in sync only when the selected `"key_bind"` is used during the mouse movement event. Valid key binds are: ``"Control"``, ``"Shift"`` and ``"Alt"``. diff --git a/fastplotlib/graphics/text.py b/fastplotlib/graphics/text.py index a159d9560..a8a873287 100644 --- a/fastplotlib/graphics/text.py +++ b/fastplotlib/graphics/text.py @@ -17,7 +17,7 @@ def __init__( screen_space: bool = True, anchor: str = "middle-center", *args, - **kwargs + **kwargs, ): """ Create a text Graphic @@ -144,4 +144,3 @@ def outline_color(self, color: Union[str, np.ndarray]): raise ValueError("Outline color must be of type str or np.ndarray") self.world_object.material.outline_color = color - diff --git a/fastplotlib/layouts/_defaults.py b/fastplotlib/layouts/_defaults.py index b28b04f64..8b1378917 100644 --- a/fastplotlib/layouts/_defaults.py +++ b/fastplotlib/layouts/_defaults.py @@ -1,3 +1 @@ - - diff --git a/fastplotlib/layouts/_frame/_frame.py b/fastplotlib/layouts/_frame/_frame.py index abd79759e..2b76b8124 100644 --- a/fastplotlib/layouts/_frame/_frame.py +++ b/fastplotlib/layouts/_frame/_frame.py @@ -27,15 +27,14 @@ def __call__(self, *args, **kwargs): JupyterOutputContext = UnavailableOutputContext( "Jupyter", "You must install fastplotlib using the `'notebook'` option to use this context:\n" - 'pip install "fastplotlib[notebook]"' + 'pip install "fastplotlib[notebook]"', ) if CANVAS_OPTIONS_AVAILABLE["qt"]: from ._qt_output import QOutputContext else: QtOutput = UnavailableOutputContext( - "Qt", - "You must install `PyQt6` to use this output context" + "Qt", "You must install `PyQt6` to use this output context" ) @@ -45,6 +44,7 @@ class Frame: Gives them their `show()` call that returns the appropriate output context. """ + def __init__(self): self._output = None @@ -83,13 +83,13 @@ def start_render(self): self.canvas.set_logical_size(*self._starting_size) def show( - self, - autoscale: bool = True, - maintain_aspect: bool = None, - toolbar: bool = True, - sidecar: bool = False, - sidecar_kwargs: dict = None, - add_widgets: list = None, + self, + autoscale: bool = True, + maintain_aspect: bool = None, + toolbar: bool = True, + sidecar: bool = False, + sidecar_kwargs: dict = None, + add_widgets: list = None, ): """ Begins the rendering event loop and shows the plot in the desired output context (jupyter, qt or glfw). @@ -168,9 +168,7 @@ def show( elif self.canvas.__class__.__name__ == "QWgpuCanvas": self._output = QOutputContext( - frame=self, - make_toolbar=toolbar, - add_widgets=add_widgets + frame=self, make_toolbar=toolbar, add_widgets=add_widgets ) else: # assume GLFW, the output context is just the canvas diff --git a/fastplotlib/layouts/_frame/_ipywidget_toolbar.py b/fastplotlib/layouts/_frame/_ipywidget_toolbar.py index 552ee9827..72976a445 100644 --- a/fastplotlib/layouts/_frame/_ipywidget_toolbar.py +++ b/fastplotlib/layouts/_frame/_ipywidget_toolbar.py @@ -28,6 +28,7 @@ class IpywidgetToolBar(HBox, ToolBar): """Basic toolbar using ipywidgets""" + def __init__(self, plot): ToolBar.__init__(self, plot) @@ -82,7 +83,7 @@ def __init__(self, plot): disabled=False, icon="draw-polygon", layout=Layout(width="auto"), - tooltip="add PolygonSelector" + tooltip="add PolygonSelector", ) widgets = [ @@ -109,7 +110,9 @@ def __init__(self, plot): widgets.append(image) if hasattr(self.plot, "_subplots"): - positions = list(product(range(self.plot.shape[0]), range(self.plot.shape[1]))) + positions = list( + product(range(self.plot.shape[0]), range(self.plot.shape[1])) + ) values = list() for pos in positions: if self.plot[pos].name is not None: @@ -150,7 +153,9 @@ def _get_subplot_dropdown_value(self) -> str: return self._dropdown.value def auto_scale_handler(self, obj): - self.current_subplot.auto_scale(maintain_aspect=self.current_subplot.camera.maintain_aspect) + self.current_subplot.auto_scale( + maintain_aspect=self.current_subplot.camera.maintain_aspect + ) def center_scene_handler(self, obj): self.current_subplot.center_scene() @@ -230,7 +235,7 @@ def __init__(self, iw): icon="adjust", description="reset", layout=Layout(width="auto"), - tooltip="reset vmin/vmax and reset histogram using current frame" + tooltip="reset vmin/vmax and reset histogram using current frame", ) self.sliders: Dict[str, IntSlider] = dict() @@ -250,7 +255,9 @@ def __init__(self, iw): orientation="horizontal", ) - slider.observe(partial(self.iw._slider_value_changed, dim), names="value") + slider.observe( + partial(self.iw._slider_value_changed, dim), names="value" + ) self.sliders[dim] = slider @@ -287,7 +294,7 @@ def __init__(self, iw): self.reset_vminvmax_hlut_button, self.play_button, self.step_size_setter, - self.speed_text + self.speed_text, ] self.play_button.interval = 10 diff --git a/fastplotlib/layouts/_frame/_jupyter_output.py b/fastplotlib/layouts/_frame/_jupyter_output.py index 25f5e2a2e..786041bcf 100644 --- a/fastplotlib/layouts/_frame/_jupyter_output.py +++ b/fastplotlib/layouts/_frame/_jupyter_output.py @@ -13,13 +13,14 @@ class JupyterOutputContext(VBox): Basically vstacks plot canvas, toolbar, and other widgets. Uses sidecar if desired. """ + def __init__( - self, - frame, - make_toolbar: bool, - use_sidecar: bool, - sidecar_kwargs: dict, - add_widgets: List[Widget], + self, + frame, + make_toolbar: bool, + use_sidecar: bool, + sidecar_kwargs: dict, + add_widgets: List[Widget], ): """ diff --git a/fastplotlib/layouts/_frame/_qt_output.py b/fastplotlib/layouts/_frame/_qt_output.py index b4c7cffd9..e8be2d050 100644 --- a/fastplotlib/layouts/_frame/_qt_output.py +++ b/fastplotlib/layouts/_frame/_qt_output.py @@ -9,11 +9,12 @@ class QOutputContext(QtWidgets.QWidget): Basically vstacks plot canvas, toolbar, and other widgets. """ + def __init__( - self, - frame, - make_toolbar, - add_widgets, + self, + frame, + make_toolbar, + add_widgets, ): """ diff --git a/fastplotlib/layouts/_frame/_qt_toolbar.py b/fastplotlib/layouts/_frame/_qt_toolbar.py index 9d4e0b48f..4ee073701 100644 --- a/fastplotlib/layouts/_frame/_qt_toolbar.py +++ b/fastplotlib/layouts/_frame/_qt_toolbar.py @@ -11,8 +11,11 @@ from ._qtoolbar_template import Ui_QToolbar -class QToolbar(ToolBar, QtWidgets.QWidget): # inheritance order MUST be Toolbar first, QWidget second! Else breaks +class QToolbar( + ToolBar, QtWidgets.QWidget +): # inheritance order MUST be Toolbar first, QWidget second! Else breaks """Toolbar for Qt context""" + def __init__(self, output_context, plot): QtWidgets.QWidget.__init__(self, parent=output_context) ToolBar.__init__(self, plot) @@ -49,7 +52,9 @@ def __init__(self, output_context, plot): self.setMaximumHeight(35) # set the initial values for buttons - self.ui.maintain_aspect_button.setChecked(self.current_subplot.camera.maintain_aspect) + self.ui.maintain_aspect_button.setChecked( + self.current_subplot.camera.maintain_aspect + ) self.ui.panzoom_button.setChecked(self.current_subplot.controller.enabled) if copysign(1, self.current_subplot.camera.local.scale_y) == -1: @@ -70,7 +75,9 @@ def update_current_subplot(self, ev): # set buttons w.r.t. current subplot self.ui.panzoom_button.setChecked(subplot.controller.enabled) - self.ui.maintain_aspect_button.setChecked(subplot.camera.maintain_aspect) + self.ui.maintain_aspect_button.setChecked( + subplot.camera.maintain_aspect + ) if copysign(1, subplot.camera.local.scale_y) == -1: self.ui.y_direction_button.setText("v") @@ -81,7 +88,9 @@ def _get_subplot_dropdown_value(self) -> str: return self.ui.current_subplot.text() def auto_scale_handler(self, *args): - self.current_subplot.auto_scale(maintain_aspect=self.current_subplot.camera.maintain_aspect) + self.current_subplot.auto_scale( + maintain_aspect=self.current_subplot.camera.maintain_aspect + ) def center_scene_handler(self, *args): self.current_subplot.center_scene() @@ -128,6 +137,7 @@ class SliderInterface: This interface makes a QSlider behave somewhat like a ipywidget IntSlider, enough for ImageWidget to function. """ + def __init__(self, qslider): self.qslider = qslider @@ -176,7 +186,9 @@ def __init__(self, image_widget): self.reset_vmin_vmax_hlut_button = QtWidgets.QPushButton(self) self.reset_vmin_vmax_hlut_button.setText("reset histogram-lut") - self.reset_vmin_vmax_hlut_button.clicked.connect(self.image_widget.reset_vmin_vmax_frame) + self.reset_vmin_vmax_hlut_button.clicked.connect( + self.image_widget.reset_vmin_vmax_frame + ) hlayout_buttons.addWidget(self.reset_vmin_vmax_hlut_button) self.vlayout.addLayout(hlayout_buttons) @@ -187,7 +199,9 @@ def __init__(self, image_widget): if self.image_widget.ndim > 2: # create a slider, spinbox and dimension label for each dimension in the ImageWidget for dim in self.image_widget.slider_dims: - hlayout = QtWidgets.QHBoxLayout() # horizontal stack for label, slider, spinbox + hlayout = ( + QtWidgets.QHBoxLayout() + ) # horizontal stack for label, slider, spinbox # max value for current dimension max_val = self.image_widget._dims_max_bounds[dim] - 1 @@ -213,7 +227,9 @@ def __init__(self, image_widget): spinbox.valueChanged.connect(slider.setValue) # connect slider to change the index within the dimension - slider.valueChanged.connect(partial(self.image_widget._slider_value_changed, dim)) + slider.valueChanged.connect( + partial(self.image_widget._slider_value_changed, dim) + ) # slider dimension label slider_label = QtWidgets.QLabel(self) diff --git a/fastplotlib/layouts/_gridplot.py b/fastplotlib/layouts/_gridplot.py index 459aca5fd..98e5643f3 100644 --- a/fastplotlib/layouts/_gridplot.py +++ b/fastplotlib/layouts/_gridplot.py @@ -84,7 +84,9 @@ def __init__( if names is not None: if len(list(chain(*names))) != self.shape[0] * self.shape[1]: - raise ValueError("must provide same number of subplot `names` as specified by gridplot shape") + raise ValueError( + "must provide same number of subplot `names` as specified by gridplot shape" + ) self.names = to_array(names).reshape(self.shape) else: @@ -111,7 +113,9 @@ def __init__( if controller_ids is None: # individual controller for each subplot - controller_ids = np.arange(self.shape[0] * self.shape[1]).reshape(self.shape) + controller_ids = np.arange(self.shape[0] * self.shape[1]).reshape( + self.shape + ) elif isinstance(controller_ids, str): if controller_ids == "sync": @@ -129,7 +133,9 @@ def __init__( # list of str of subplot names, convert this to integer ids if all([isinstance(item, str) for item in ids_flat]): if self.names is None: - raise ValueError("must specify subplot `names` to use list of str for `controller_ids`") + raise ValueError( + "must specify subplot `names` to use list of str for `controller_ids`" + ) # make sure each controller_id str is a subplot name if not all([n in self.names for n in ids_flat]): @@ -148,7 +154,9 @@ def __init__( # set id based on subplot position for each synced sublist for i, sublist in enumerate(controller_ids): for name in sublist: - ids_init[self.names == name] = -(i + 1) # use negative numbers because why not + ids_init[self.names == name] = -( + i + 1 + ) # use negative numbers because why not controller_ids = ids_init @@ -163,11 +171,15 @@ def __init__( ) if controller_ids.shape != self.shape: - raise ValueError("Number of controller_ids does not match the number of subplots") + raise ValueError( + "Number of controller_ids does not match the number of subplots" + ) if controller_types is None: # `create_controller()` will auto-determine controller for each subplot based on defaults - controller_types = np.array(["default"] * self.shape[0] * self.shape[1]).reshape(self.shape) + controller_types = np.array( + ["default"] * self.shape[0] * self.shape[1] + ).reshape(self.shape) # validate controller types types_flat = list(chain(*controller_types)) @@ -180,7 +192,9 @@ def __init__( if controller_type is None: continue - if (controller_type not in valid_str) and (not isinstance(controller_type, valid_instances)): + if (controller_type not in valid_str) and ( + not isinstance(controller_type, valid_instances) + ): raise ValueError( f"You have passed an invalid controller type, valid controller_types arguments are:\n" f"{valid_str} or instances of {[c.__name__ for c in valid_instances]}" diff --git a/fastplotlib/layouts/_plot_area.py b/fastplotlib/layouts/_plot_area.py index d4fc62e1f..08a09baa7 100644 --- a/fastplotlib/layouts/_plot_area.py +++ b/fastplotlib/layouts/_plot_area.py @@ -63,7 +63,7 @@ def __init__( name: str, optional name this ``subplot`` or ``plot`` - + """ self._parent: PlotArea = parent @@ -163,9 +163,13 @@ def camera(self, new_camera: Union[str, pygfx.PerspectiveCamera]): self._camera.fov = 50 else: - raise ValueError("camera must be one of '2d', '3d' or a pygfx.PerspectiveCamera instance") + raise ValueError( + "camera must be one of '2d', '3d' or a pygfx.PerspectiveCamera instance" + ) else: - raise ValueError("camera must be one of '2d', '3d' or a pygfx.PerspectiveCamera instance") + raise ValueError( + "camera must be one of '2d', '3d' or a pygfx.PerspectiveCamera instance" + ) # in the future we can think about how to allow changing the controller @property @@ -188,9 +192,7 @@ def controller(self, new_controller: Union[str, pygfx.Controller]): for camera in cameras_list: new_controller.add_camera(camera) - new_controller.register_events( - self.viewport - ) + new_controller.register_events(self.viewport) # TODO: monkeypatch until we figure out a better # pygfx plans on refactoring viewports anyways @@ -246,7 +248,7 @@ def name(self, name: str): if name is None: self._name = None return - + if not isinstance(name, str): raise TypeError("PlotArea `name` must be of type ") self._name = name @@ -473,9 +475,9 @@ def _add_or_insert_graphic( if isinstance(graphic, BaseSelector): # store in SELECTORS dict loc = graphic.loc - SELECTORS[ - loc - ] = graphic # add hex id string for referencing this graphic instance + SELECTORS[loc] = ( + graphic # add hex id string for referencing this graphic instance + ) # don't manage garbage collection of LineSliders for now if action == "insert": self._selectors.insert(index, loc) @@ -484,9 +486,9 @@ def _add_or_insert_graphic( else: # store in GRAPHICS dict loc = graphic.loc - GRAPHICS[ - loc - ] = graphic # add hex id string for referencing this graphic instance + GRAPHICS[loc] = ( + graphic # add hex id string for referencing this graphic instance + ) if action == "insert": self._graphics.insert(index, loc) @@ -566,10 +568,10 @@ def center_scene(self, *, zoom: float = 1.35): camera.zoom = zoom def auto_scale( - self, - *, # since this is often used as an event handler, don't want to coerce maintain_aspect = True - maintain_aspect: Union[None, bool] = None, - zoom: float = 0.8 + self, + *, # since this is often used as an event handler, don't want to coerce maintain_aspect = True + maintain_aspect: Union[None, bool] = None, + zoom: float = 0.8, ): """ Auto-scale the camera w.r.t to the scene @@ -737,7 +739,7 @@ def __contains__(self, item: Union[str, Graphic]): return False raise TypeError("PlotArea `in` operator accepts only `Graphic` or `str` types") - + def __str__(self): if self.name is None: name = "unnamed" diff --git a/fastplotlib/legends/legend.py b/fastplotlib/legends/legend.py index e29665e0f..291c25ff3 100644 --- a/fastplotlib/legends/legend.py +++ b/fastplotlib/legends/legend.py @@ -13,9 +13,9 @@ class LegendItem: def __init__( - self, - label: str, - color: pygfx.Color, + self, + label: str, + color: pygfx.Color, ): """ @@ -31,11 +31,7 @@ def __init__( class LineLegendItem(LegendItem): def __init__( - self, - parent, - graphic: LineGraphic, - label: str, - position: Tuple[int, int] + self, parent, graphic: LineGraphic, label: str, position: Tuple[int, int] ): """ @@ -55,7 +51,9 @@ def __init__( pass else: - raise ValueError("Must specify `label` or Graphic must have a `name` to auto-use as the label") + raise ValueError( + "Must specify `label` or Graphic must have a `name` to auto-use as the label" + ) # for now only support lines with a single color if np.unique(graphic.colors(), axis=0).shape[0] > 1: @@ -70,17 +68,13 @@ def __init__( graphic.colors.add_event_handler(self._update_color) # construct Line WorldObject - data = np.array( - [[0, 0, 0], - [3, 0, 0]], - dtype=np.float32 - ) + data = np.array([[0, 0, 0], [3, 0, 0]], dtype=np.float32) material = pygfx.LineMaterial self._line_world_object = pygfx.Line( geometry=pygfx.Geometry(positions=data), - material=material(thickness=8, color=self._color) + material=material(thickness=8, color=self._color), ) # self._line_world_object.world.x = position[0] @@ -96,7 +90,7 @@ def __init__( color="w", outline_color="w", outline_thickness=0, - ) + ), ) self.world_object = pygfx.Group() @@ -109,7 +103,9 @@ def __init__( self.world_object.world.y = position[1] self.world_object.world.z = 2 - self.world_object.add_event_handler(partial(self._highlight_graphic, graphic), "click") + self.world_object.add_event_handler( + partial(self._highlight_graphic, graphic), "click" + ) @property def label(self) -> str: @@ -123,7 +119,9 @@ def label(self, text: str): def _update_color(self, ev: FeatureEvent): new_color = ev.pick_info["new_data"] if np.unique(new_color, axis=0).shape[0] > 1: - raise ValueError("LegendError: LineGraphic colors no longer appropriate for legend") + raise ValueError( + "LegendError: LineGraphic colors no longer appropriate for legend" + ) self._color = new_color[0] self._line_world_object.material.color = pygfx.Color(self._color) @@ -142,12 +140,12 @@ def _highlight_graphic(self, graphic, ev): class Legend(Graphic): def __init__( - self, - plot_area, - highlight_color: Union[str, tuple, np.ndarray] = "w", - max_rows: int = 5, - *args, - **kwargs + self, + plot_area, + highlight_color: Union[str, tuple, np.ndarray] = "w", + max_rows: int = 5, + *args, + **kwargs, ): """ @@ -166,7 +164,7 @@ def __init__( self._graphics: List[Graphic] = list() # hex id of Graphic, i.e. graphic.loc are the keys - self._items: OrderedDict[str: LegendItem] = OrderedDict() + self._items: OrderedDict[str:LegendItem] = OrderedDict() super().__init__(*args, **kwargs) @@ -176,7 +174,9 @@ def __init__( self._mesh = pygfx.Mesh( pygfx.box_geometry(50, 10, 1), - pygfx.MeshBasicMaterial(color=pygfx.Color([0.1, 0.1, 0.1, 1]), wireframe_thickness=10) + pygfx.MeshBasicMaterial( + color=pygfx.Color([0.1, 0.1, 0.1, 1]), wireframe_thickness=10 + ), ) self.world_object.add(self._mesh) @@ -235,7 +235,9 @@ def add_graphic(self, graphic: Graphic, label: str = None): # get x position offset for this new column of LegendItems # start by getting the LegendItems in the previous column - prev_column_items: List[LegendItem] = list(self._items.values())[-self._max_rows:] + prev_column_items: List[LegendItem] = list(self._items.values())[ + -self._max_rows : + ] # x position of LegendItems in previous column x_pos = prev_column_items[-1].world_object.world.x max_width = 0 @@ -267,7 +269,7 @@ def add_graphic(self, graphic: Graphic, label: str = None): self._graphics.append(graphic) self._items[graphic.loc] = legend_item - + graphic.deleted.add_event_handler(partial(self.remove_graphic, graphic)) self._col_counter = new_col_ix diff --git a/fastplotlib/utils/__init__.py b/fastplotlib/utils/__init__.py index addea140d..305af90a8 100644 --- a/fastplotlib/utils/__init__.py +++ b/fastplotlib/utils/__init__.py @@ -10,6 +10,4 @@ class _Config: party_parrot: bool -config = _Config( - party_parrot=False -) +config = _Config(party_parrot=False) diff --git a/fastplotlib/utils/_gpu_info.py b/fastplotlib/utils/_gpu_info.py index 9387f3ece..93e95d281 100644 --- a/fastplotlib/utils/_gpu_info.py +++ b/fastplotlib/utils/_gpu_info.py @@ -11,6 +11,7 @@ NOTEBOOK = False else: from IPython.display import display + if ip.has_trait("kernel") and (JupyterWgpuCanvas is not False): NOTEBOOK = True else: @@ -21,17 +22,14 @@ def _notebook_print_banner(): if NOTEBOOK is False: return - logo_path = Path(__file__).parent.parent.joinpath("assets", "fastplotlib_face_logo.png") + logo_path = Path(__file__).parent.parent.joinpath( + "assets", "fastplotlib_face_logo.png" + ) with open(logo_path, "rb") as f: logo_data = f.read() - image = Image( - value=logo_data, - format="png", - width=300, - height=55 - ) + image = Image(value=logo_data, format="png", width=300, height=55) display(image) @@ -39,7 +37,9 @@ def _notebook_print_banner(): adapters = [a for a in enumerate_adapters()] adapters_info = [a.request_adapter_info() for a in adapters] - ix_default = adapters_info.index(get_default_device().adapter.request_adapter_info()) + ix_default = adapters_info.index( + get_default_device().adapter.request_adapter_info() + ) if len(adapters) > 0: print("Available devices:") diff --git a/fastplotlib/utils/generate_add_methods.py b/fastplotlib/utils/generate_add_methods.py index 3fe16260c..9cb87baab 100644 --- a/fastplotlib/utils/generate_add_methods.py +++ b/fastplotlib/utils/generate_add_methods.py @@ -5,11 +5,8 @@ # so that fastplotlib will import # hacky but it works current_module = pathlib.Path(__file__).parent.parent.resolve() -with open(current_module.joinpath('layouts/graphic_methods_mixin.py'), 'w') as f: - f.write( - f"class GraphicMethodsMixin:\n" - f" pass" - ) +with open(current_module.joinpath("layouts/graphic_methods_mixin.py"), "w") as f: + f.write(f"class GraphicMethodsMixin:\n" f" pass") from fastplotlib import graphics @@ -24,21 +21,23 @@ def generate_add_graphics_methods(): # clear file and regenerate from scratch - f = open(current_module.joinpath('layouts/graphic_methods_mixin.py'), 'w') + f = open(current_module.joinpath("layouts/graphic_methods_mixin.py"), "w") - f.write('# This is an auto-generated file and should not be modified directly\n\n') + f.write("# This is an auto-generated file and should not be modified directly\n\n") - f.write('from typing import *\n\n') - f.write('import numpy\n') - f.write('import weakref\n\n') - f.write('from ..graphics import *\n') - f.write('from ..graphics._base import Graphic\n\n') + f.write("from typing import *\n\n") + f.write("import numpy\n") + f.write("import weakref\n\n") + f.write("from ..graphics import *\n") + f.write("from ..graphics._base import Graphic\n\n") f.write("\nclass GraphicMethodsMixin:\n") f.write(" def __init__(self):\n") f.write(" pass\n\n") - f.write(" def _create_graphic(self, graphic_class, *args, **kwargs) -> Graphic:\n") + f.write( + " def _create_graphic(self, graphic_class, *args, **kwargs) -> Graphic:\n" + ) f.write(" if 'center' in kwargs.keys():\n") f.write(" center = kwargs.pop('center')\n") f.write(" else:\n") @@ -55,19 +54,23 @@ def generate_add_graphics_methods(): method_name = class_name.type class_args = inspect.getfullargspec(class_name)[0][1:] - class_args = [arg + ', ' for arg in class_args] + class_args = [arg + ", " for arg in class_args] s = "" for a in class_args: s += a - f.write(f" def add_{method_name}{inspect.signature(class_name.__init__)} -> {class_name.__name__}:\n") + f.write( + f" def add_{method_name}{inspect.signature(class_name.__init__)} -> {class_name.__name__}:\n" + ) f.write(' """\n') - f.write(f' {class_name.__init__.__doc__}\n') + f.write(f" {class_name.__init__.__doc__}\n") f.write(' """\n') - f.write(f" return self._create_graphic({class_name.__name__}, {s}*args, **kwargs)\n\n") + f.write( + f" return self._create_graphic({class_name.__name__}, {s}*args, **kwargs)\n\n" + ) f.close() -if __name__ == '__main__': +if __name__ == "__main__": generate_add_graphics_methods() diff --git a/fastplotlib/utils/mesh_masks.py b/fastplotlib/utils/mesh_masks.py index 600e5ab6d..c44588b6c 100644 --- a/fastplotlib/utils/mesh_masks.py +++ b/fastplotlib/utils/mesh_masks.py @@ -125,4 +125,4 @@ x_right = (x_right, 0) x_left = (x_left, 0) y_top = (y_top, 1) -y_bottom = (y_bottom, 1) \ No newline at end of file +y_bottom = (y_bottom, 1) diff --git a/fastplotlib/widgets/histogram_lut.py b/fastplotlib/widgets/histogram_lut.py index 64feb8df6..31f6ab8e9 100644 --- a/fastplotlib/widgets/histogram_lut.py +++ b/fastplotlib/widgets/histogram_lut.py @@ -13,12 +13,12 @@ # TODO: This is a widget, we can think about a BaseWidget class later if necessary class HistogramLUT(Graphic): def __init__( - self, - data: np.ndarray, - image_graphic: ImageGraphic, - nbins: int = 100, - flank_divisor: float = 5.0, - **kwargs + self, + data: np.ndarray, + image_graphic: ImageGraphic, + nbins: int = 100, + flank_divisor: float = 5.0, + **kwargs, ): """ @@ -58,11 +58,14 @@ def __init__( size=size, origin=origin, axis="y", - edge_thickness=8 + edge_thickness=8, ) # there will be a small difference with the histogram edges so this makes them both line up exactly - self.linear_region.selection = (image_graphic.cmap.vmin, image_graphic.cmap.vmax) + self.linear_region.selection = ( + image_graphic.cmap.vmin, + image_graphic.cmap.vmax, + ) self._vmin = self.image_graphic.cmap.vmin self._vmax = self.image_graphic.cmap.vmax @@ -105,9 +108,7 @@ def __init__( self._text_vmax.position_x = -120 self._text_vmax.position_y = self.linear_region.selection()[1] - self.linear_region.selection.add_event_handler( - self._linear_region_handler - ) + self.linear_region.selection.add_event_handler(self._linear_region_handler) self.image_graphic.cmap.add_event_handler(self._image_cmap_handler) @@ -168,12 +169,16 @@ def _calculate_histogram(self, data): flank_size = flank_nbins * bin_width flank_left = np.arange(edges[0] - flank_size, edges[0], bin_width) - flank_right = np.arange(edges[-1] + bin_width, edges[-1] + flank_size, bin_width) + flank_right = np.arange( + edges[-1] + bin_width, edges[-1] + flank_size, bin_width + ) edges_flanked = np.concatenate((flank_left, edges, flank_right)) np.unique(np.diff(edges_flanked)) - hist_flanked = np.concatenate((np.zeros(flank_nbins), hist, np.zeros(flank_nbins))) + hist_flanked = np.concatenate( + (np.zeros(flank_nbins), hist, np.zeros(flank_nbins)) + ) # scale 0-100 to make it easier to see # float32 data can produce unnecessarily high values @@ -181,7 +186,7 @@ def _calculate_histogram(self, data): if edges_flanked.size > hist_scaled.size: # we don't care about accuracy here so if it's off by 1-2 bins that's fine - edges_flanked = edges_flanked[:hist_scaled.size] + edges_flanked = edges_flanked[: hist_scaled.size] return hist, edges, hist_scaled, edges_flanked @@ -209,7 +214,10 @@ def vmin(self, value: float): # must use world coordinate values directly from selection() # otherwise the linear region bounds jump to the closest bin edges - self.linear_region.selection = (value * self._scale_factor, self.linear_region.selection()[1]) + self.linear_region.selection = ( + value * self._scale_factor, + self.linear_region.selection()[1], + ) self.image_graphic.cmap.vmin = value self._block_events(False) @@ -230,7 +238,10 @@ def vmax(self, value: float): # must use world coordinate values directly from selection() # otherwise the linear region bounds jump to the closest bin edges - self.linear_region.selection = (self.linear_region.selection()[0], value * self._scale_factor) + self.linear_region.selection = ( + self.linear_region.selection()[0], + value * self._scale_factor, + ) self.image_graphic.cmap.vmax = value self._block_events(False) @@ -280,9 +291,7 @@ def image_graphic(self, graphic): ) # cleanup events from current image graphic - self._image_graphic.cmap.remove_event_handler( - self._image_cmap_handler - ) + self._image_graphic.cmap.remove_event_handler(self._image_cmap_handler) self._image_graphic = graphic diff --git a/fastplotlib/widgets/image.py b/fastplotlib/widgets/image.py index a3f6335c9..0da1bb520 100644 --- a/fastplotlib/widgets/image.py +++ b/fastplotlib/widgets/image.py @@ -40,6 +40,7 @@ def _is_arraylike(obj) -> bool: class _WindowFunctions: """Stores window function and window size""" + def __init__(self, image_widget, func: callable, window_size: int): self._image_widget = image_widget self._func = None @@ -131,7 +132,9 @@ def cmap(self) -> List[str]: def cmap(self, names: Union[str, List[str]]): if isinstance(names, list): if not all([isinstance(n, str) for n in names]): - raise TypeError(f"Must pass cmap name as a `str` of list of `str`, you have passed:\n{names}") + raise TypeError( + f"Must pass cmap name as a `str` of list of `str`, you have passed:\n{names}" + ) if not len(names) == len(self.managed_graphics): raise IndexError( @@ -558,7 +561,9 @@ def __init__( # user specified kwargs will overwrite the defaults grid_plot_kwargs_default.update(grid_plot_kwargs) - self._gridplot: GridPlot = GridPlot(shape=grid_shape, **grid_plot_kwargs_default) + self._gridplot: GridPlot = GridPlot( + shape=grid_shape, **grid_plot_kwargs_default + ) for data_ix, (d, subplot) in enumerate(zip(self.data, self.gridplot)): if self._names is not None: @@ -574,11 +579,7 @@ def __init__( subplot.set_title(name) if histogram_widget: - hlut = HistogramLUT( - data=d, - image_graphic=ig, - name="histogram_lut" - ) + hlut = HistogramLUT(data=d, image_graphic=ig, name="histogram_lut") subplot.docks["right"].add_graphic(hlut) subplot.docks["right"].size = 80 @@ -596,7 +597,7 @@ def frame_apply(self) -> Union[dict, None]: def frame_apply(self, frame_apply: Dict[int, callable]): if frame_apply is None: frame_apply = dict() - + self._frame_apply = frame_apply # force update image graphic self.current_index = self.current_index @@ -901,7 +902,9 @@ def set_data( max_lengths["z"] = min(max_lengths["z"], new_array.shape[1] - 1) # set histogram widget - subplot.docks["right"]["histogram_lut"].set_data(new_array, reset_vmin_vmax=reset_vmin_vmax) + subplot.docks["right"]["histogram_lut"].set_data( + new_array, reset_vmin_vmax=reset_vmin_vmax + ) # set slider maxes # TODO: maybe make this stuff a property, like ndims, n_frames etc. and have it set the sliders @@ -912,7 +915,9 @@ def set_data( # force graphics to update self.current_index = self.current_index - def show(self, toolbar: bool = True, sidecar: bool = False, sidecar_kwargs: dict = None): + def show( + self, toolbar: bool = True, sidecar: bool = False, sidecar_kwargs: dict = None + ): """ Show the widget. @@ -931,7 +936,7 @@ def show(self, toolbar: bool = True, sidecar: bool = False, sidecar_kwargs: dict toolbar=toolbar, sidecar=sidecar, sidecar_kwargs=sidecar_kwargs, - add_widgets=[self._image_widget_toolbar] + add_widgets=[self._image_widget_toolbar], ) return self._output