diff --git a/src/quantem/core/visualization/show_params.py b/src/quantem/core/visualization/show_params.py index d0399db9..87bf2fb2 100644 --- a/src/quantem/core/visualization/show_params.py +++ b/src/quantem/core/visualization/show_params.py @@ -248,11 +248,23 @@ class Scalebar: Font size of the scale bar label in points. Default 12. bold : bool Whether to render the label in bold. Default True. + box : bool + Draw a translucent box behind the bar and label. Default False. + box_color : str + Fill colour of the box. Default ``"black"``. + box_alpha : float + Opacity of the box, between 0 and 1. Default 0.5. + outline_color : str or None + Colour of an outline drawn around the bar and label (matplotlib + path effects). ``None`` draws no outline. Default ``None``. + outline_width : float + Width of the outline in points. Default 2.0. Examples -------- >>> ShowParams.Scalebar(sampling=0.5, units="Å") >>> ShowParams.Scalebar(sampling=0.02, units="1/Å", color="black", fontsize=16) + >>> ShowParams.Scalebar(sampling=0.5, units="Å", outline_color="black") """ sampling: float = 1.0 @@ -264,6 +276,11 @@ class Scalebar: loc: Literal["lower right", "lower left", "upper right", "upper left"] = "lower right" fontsize: int = 12 bold: bool = False + box: bool = False + box_color: str = "black" + box_alpha: float = 0.5 + outline_color: str | None = None + outline_width: float = 2.0 def __post_init__(self) -> None: validate_gt(self.sampling, 0, "sampling") @@ -271,6 +288,9 @@ def __post_init__(self) -> None: validate_gt(self.length, 0, "length") validate_gt(self.width_px, 0, "width_px") validate_gt(self.fontsize, 0, "fontsize") + validate_gt(self.outline_width, 0, "outline_width") + if not 0 <= self.box_alpha <= 1: + raise ValueError(f"box_alpha must be between 0 and 1, got {self.box_alpha}") def to_config(self) -> ScalebarConfig: """Convert to a ``ScalebarConfig``.""" diff --git a/src/quantem/core/visualization/visualization.py b/src/quantem/core/visualization/visualization.py index e252caa6..e3941714 100644 --- a/src/quantem/core/visualization/visualization.py +++ b/src/quantem/core/visualization/visualization.py @@ -180,6 +180,11 @@ def _show_2d_array( scalebar_config.loc, scalebar_config.fontsize, scalebar_config.bold, + box=scalebar_config.box, + box_color=scalebar_config.box_color, + box_alpha=scalebar_config.box_alpha, + outline_color=scalebar_config.outline_color, + outline_width=scalebar_config.outline_width, ) for spine in ax.spines.values(): # fixes asymmetry of bbox for some reason @@ -297,6 +302,11 @@ def _show_2d_combined( scalebar_config.loc, scalebar_config.fontsize, scalebar_config.bold, + box=scalebar_config.box, + box_color=scalebar_config.box_color, + box_alpha=scalebar_config.box_alpha, + outline_color=scalebar_config.outline_color, + outline_width=scalebar_config.outline_width, ) return fig, ax diff --git a/src/quantem/core/visualization/visualization_utils.py b/src/quantem/core/visualization/visualization_utils.py index 1be768db..04fd59b3 100644 --- a/src/quantem/core/visualization/visualization_utils.py +++ b/src/quantem/core/visualization/visualization_utils.py @@ -5,7 +5,7 @@ import matplotlib.pyplot as plt import numpy as np from colorspacious import cspace_convert -from matplotlib import cm, colors, legend, ticker +from matplotlib import cm, colors, legend, patheffects, ticker from matplotlib.axes import Axes from matplotlib.colorbar import Colorbar from matplotlib.figure import Figure @@ -144,6 +144,18 @@ class ScalebarConfig: Font size of the scale bar label in points. bold : bool, default=True Whether to render the scale bar label in bold. + box : bool, default=False + Draw a translucent box behind the bar and label. + box_color : str, default="black" + Fill color of the box. + box_alpha : float, default=0.5 + Opacity of the box. + outline_color : str, optional + Color of an outline drawn around the bar and label (via matplotlib + path effects) so the scale bar stays readable on any background. + If None, no outline is drawn. + outline_width : float, default=2.0 + Width of the outline in points. """ sampling: float = 1.0 @@ -155,6 +167,11 @@ class ScalebarConfig: loc: str | int = "lower right" fontsize: int = 12 bold: bool = True + box: bool = False + box_color: str = "black" + box_alpha: float = 0.5 + outline_color: str | None = None + outline_width: float = 2.0 SCALEBAR_KWARGS = [ @@ -350,6 +367,8 @@ def add_scalebar_to_ax( box: bool = False, box_color: str = "black", box_alpha: float = 0.5, + outline_color: str | None = None, + outline_width: float = 2.0, ) -> None: """Add a scale bar to a matplotlib axis. @@ -381,13 +400,16 @@ def add_scalebar_to_ax( box : bool, default=False Draw a translucent box behind the bar and label so it stays readable on any image (e.g. white bar on a black box, or black bar - on a white box). Padding follows ``pad_px``. ``box``, ``box_color`` - and ``box_alpha`` are only available when calling this function - directly; ScalebarConfig and show_2d do not pass them. + on a white box). Padding follows ``pad_px``. box_color : str, default="black" Fill color of the box. box_alpha : float, default=0.5 Opacity of the box. + outline_color : str, optional + Color of an outline drawn around the bar and label using + matplotlib path effects. If None, no outline is drawn. + outline_width : float, default=2.0 + Width of the outline in points. """ from matplotlib.font_manager import FontProperties @@ -426,6 +448,14 @@ def add_scalebar_to_ax( ) if box: bar.patch.set(facecolor=box_color, edgecolor="none", alpha=box_alpha) + if outline_color is not None: + effects = [ + patheffects.withStroke(linewidth=outline_width, foreground=outline_color) + ] + for artist in bar.size_bar.get_children(): + artist.set_path_effects(effects) + for artist in bar.txt_label.get_children(): + artist.set_path_effects(effects) ax.add_artist(bar)