diff --git a/autoarray/plot/array.py b/autoarray/plot/array.py index b576128ed..3d40c0209 100644 --- a/autoarray/plot/array.py +++ b/autoarray/plot/array.py @@ -61,6 +61,7 @@ def plot_array( cb_unit: Optional[str] = None, line_colors: Optional[List] = None, origin_imshow: Optional[str] = None, + arcsec_after_decimal: Optional[bool] = None, # --- figure control (used only when ax is None) ----------------------------- figsize: Optional[Tuple[int, int]] = None, output_path: Optional[str] = None, @@ -126,6 +127,11 @@ def plot_array( When ``True`` a ``LogNorm`` is applied. origin_imshow Passed directly to ``imshow`` (``"upper"`` or ``"lower"``). + arcsec_after_decimal + Per-call override of the ``ticks.symbol_over_decimal`` config flag. + When ``True`` the arcsecond tick labels place the ``″`` symbol over the + decimal point (``3.″8``) instead of suffixing it (``3.8"``); when ``False`` + the suffix form is forced. ``None`` (the default) reads the config. figsize Figure size in inches. output_path @@ -320,7 +326,7 @@ def plot_array( ) if extent is not None: - apply_extent(ax, extent) + apply_extent(ax, extent, symbol_over_decimal=arcsec_after_decimal) # --- output ---------------------------------------------------------------- if owns_figure: diff --git a/autoarray/plot/grid.py b/autoarray/plot/grid.py index d3627670b..245d06b55 100644 --- a/autoarray/plot/grid.py +++ b/autoarray/plot/grid.py @@ -37,6 +37,7 @@ def plot_grid( buffer: float = 0.1, extent: Optional[Tuple[float, float, float, float]] = None, force_symmetric_extent: bool = True, + arcsec_after_decimal: Optional[bool] = None, # --- figure control (used only when ax is None) ----------------------------- figsize: Optional[Tuple[int, int]] = None, output_path: Optional[str] = None, @@ -77,6 +78,11 @@ def plot_grid( force_symmetric_extent When ``True`` (and *extent* is auto-computed) the limits are made symmetric about the origin so the plot is centred. + arcsec_after_decimal + Per-call override of the ``ticks.symbol_over_decimal`` config flag. + When ``True`` the arcsecond tick labels place the ``″`` symbol over the + decimal point (``3.″8``) instead of suffixing it (``3.8"``); when ``False`` + the suffix form is forced. ``None`` (the default) reads the config. figsize Figure size in inches ``(width, height)``. output_path @@ -178,7 +184,7 @@ def plot_grid( y_abs = max(abs(extent[2]), abs(extent[3])) extent = [-x_abs, x_abs, -y_abs, y_abs] - apply_extent(ax, extent) + apply_extent(ax, extent, symbol_over_decimal=arcsec_after_decimal) # --- output ---------------------------------------------------------------- if owns_figure: diff --git a/autoarray/plot/inversion.py b/autoarray/plot/inversion.py index 1078b91ba..94e22d1ef 100644 --- a/autoarray/plot/inversion.py +++ b/autoarray/plot/inversion.py @@ -34,6 +34,7 @@ def plot_inversion_reconstruction( use_log10: bool = False, zoom_to_brightest: bool = True, zoom_extent_scale: float = 1.0, + arcsec_after_decimal: Optional[bool] = None, # --- overlays --------------------------------------------------------------- lines: Optional[List[np.ndarray]] = None, line_colors: Optional[List] = None, @@ -73,6 +74,11 @@ def plot_inversion_reconstruction( Apply ``LogNorm``. zoom_to_brightest Pass through to ``mapper.extent_from``. + arcsec_after_decimal + Per-call override of the ``ticks.symbol_over_decimal`` config flag. + When ``True`` the arcsecond tick labels place the ``″`` symbol over the + decimal point (``3.″8``) instead of suffixing it (``3.8"``); when ``False`` + the suffix form is forced. ``None`` (the default) reads the config. lines Line overlays (e.g. critical curves). grid @@ -163,7 +169,7 @@ def plot_inversion_reconstruction( if grid is not None: ax.scatter(grid[:, 1], grid[:, 0], s=1, c="w", alpha=0.5) - apply_extent(ax, extent) + apply_extent(ax, extent, symbol_over_decimal=arcsec_after_decimal) apply_labels( ax, diff --git a/autoarray/plot/utils.py b/autoarray/plot/utils.py index 9587517e3..bfbbc1904 100644 --- a/autoarray/plot/utils.py +++ b/autoarray/plot/utils.py @@ -1106,7 +1106,7 @@ def _round_ticks(values: np.ndarray, sig: int = 2) -> np.ndarray: return rounded -def _arcsec_labels(ticks) -> List[str]: +def _arcsec_labels(ticks, symbol_over_decimal: Optional[bool] = None) -> List[str]: """Format tick values as arcsecond coordinate strings. Whole-number tick sets render without a decimal point, so @@ -1117,6 +1117,16 @@ def _arcsec_labels(ticks) -> List[str]: decimal labels keep the suffix form ``3.8"``. When ``ticks.symbol_over_decimal`` is true, labels use the double-prime arcsecond symbol and decimal labels place it over the decimal point, e.g. ``3.″8``. + + Parameters + ---------- + ticks + The tick values to label. + symbol_over_decimal + Per-call override of the ``ticks.symbol_over_decimal`` config flag. + ``True`` forces the symbol-over-decimal form and ``False`` forces the + suffix form, whatever the config says. ``None`` (the default) reads + the config, so callers that do not pass it behave exactly as before. """ minus_in_math = _conf_ticks_flag("minus_in_math", False) @@ -1132,7 +1142,9 @@ def _fmt_minus(label: str) -> str: label if "." in label else f"{float(v):.1f}" for label, v in zip(labels, ticks) ] - if _conf_ticks_flag("symbol_over_decimal", False): + if symbol_over_decimal is None: + symbol_over_decimal = _conf_ticks_flag("symbol_over_decimal", False) + if symbol_over_decimal: symbol_labels = [] for label in labels: if "." in label: @@ -1147,6 +1159,7 @@ def _fmt_minus(label: str) -> str: def apply_extent( ax, extent: Tuple[float, float, float, float], + symbol_over_decimal: Optional[bool] = None, ) -> None: """ Apply axis limits and inward-pulled, rounded, arcsecond-labelled ticks to *ax*. @@ -1154,6 +1167,10 @@ def apply_extent( Tick count and inward factor are read from ``visualize/general.yaml`` (``ticks.number_of_ticks_2d`` and ``ticks.extent_factor_2d``), defaulting to 3 ticks and factor 0.75. + + ``symbol_over_decimal`` is a per-call override of the + ``ticks.symbol_over_decimal`` config flag, forwarded to + :func:`_arcsec_labels`; ``None`` (the default) reads the config. """ factor = _conf_ticks("extent_factor_2d", 0.75) n = int(_conf_ticks("number_of_ticks_2d", 3)) @@ -1166,8 +1183,8 @@ def apply_extent( yticks = _round_ticks(_inward_ticks(ymin, ymax, factor, n)) ax.set_xticks(xticks) ax.set_yticks(yticks) - ax.set_xticklabels(_arcsec_labels(xticks)) - ax.set_yticklabels(_arcsec_labels(yticks)) + ax.set_xticklabels(_arcsec_labels(xticks, symbol_over_decimal=symbol_over_decimal)) + ax.set_yticklabels(_arcsec_labels(yticks, symbol_over_decimal=symbol_over_decimal)) # The y-tick labels are rotated 90 degrees elsewhere; without an explicit # centre alignment a rotated label anchors at its right edge and visually diff --git a/test_autoarray/plot/test_utils.py b/test_autoarray/plot/test_utils.py index 2196eed8e..c8a08912f 100644 --- a/test_autoarray/plot/test_utils.py +++ b/test_autoarray/plot/test_utils.py @@ -91,6 +91,65 @@ def test_arcsec_labels_minus_in_math(): ticks["minus_in_math"] = original_minus +def test_arcsec_labels_symbol_over_decimal_argument_overrides_config(): + # The per-call argument alone switches the format on, with the config flag + # left at its default (false) and `conf.instance` never mutated. + assert _arcsec_labels([-2.1, -0.044, 2.0], symbol_over_decimal=True) == [ + "-2.″1", + "-0.″044", + "2.″0", + ] + assert _arcsec_labels([3.8], symbol_over_decimal=True) == ["3.″8"] + assert _arcsec_labels([-1.0, 0.0, 1.0], symbol_over_decimal=True) == [ + "-1″", + "0″", + "1″", + ] + + +def test_arcsec_labels_symbol_over_decimal_argument_false_beats_config(): + # The override wins in both directions: an explicit False forces the suffix + # form even when the config flag is on. + ticks = conf.instance["visualize"]["general"]["ticks"] + original = ticks.get("symbol_over_decimal", False) + try: + ticks["symbol_over_decimal"] = True + + assert _arcsec_labels([-2.1, -0.044, 2.0], symbol_over_decimal=False) == [ + '-2.1"', + '-0.044"', + '2.0"', + ] + assert _arcsec_labels([3.8], symbol_over_decimal=False) == ['3.8"'] + finally: + ticks["symbol_over_decimal"] = original + + +def test_arcsec_labels_symbol_over_decimal_none_reads_config(): + # None (the default) leaves the config in charge, so every existing caller + # is unchanged. + ticks = conf.instance["visualize"]["general"]["ticks"] + original = ticks.get("symbol_over_decimal", False) + try: + ticks["symbol_over_decimal"] = True + + assert _arcsec_labels([-2.1, -0.044, 2.0], symbol_over_decimal=None) == [ + "-2.″1", + "-0.″044", + "2.″0", + ] + + ticks["symbol_over_decimal"] = False + + assert _arcsec_labels([-2.1, -0.044, 2.0], symbol_over_decimal=None) == [ + '-2.1"', + '-0.044"', + '2.0"', + ] + finally: + ticks["symbol_over_decimal"] = original + + class TestNormFrom: """The one colour-norm helper `plot_array`, `plot_inversion_reconstruction` and `autogalaxy.util.plot_utils.norm_from` all build their norms with.