Skip to content

refactor: share the colour-norm helper; fix inversion log10 floor #488

Description

@Jammy2211

Overview

Building the matplotlib colour norm from use_log10 / vmin / vmax is written
out three times — autoarray/plot/array.py, autoarray/plot/inversion.py, and
autogalaxy/util/plot_utils.py::norm_from (added by PyAutoGalaxy#586). The copies
have already diverged, and one divergence is a live behaviour bug: inversion.py
hardcodes the 1e-4 log floor and never consults autonerves config, so a user
who changes visualize.general.general.log10_min_value gets it honoured on array
plots and silently ignored on inversion plots.

Deduplicating is how the divergence stops recurring; fixing the config floor is
the point. Split out of aplt-output-drift-remaining-repos (PyAutoGalaxy#585).

Plan

  • Add one colour-norm helper to autoarray/plot/utils.py — the lowest repo that
    owns this logic, alongside auto_mask_edge and the other shared plot helpers.
  • Decide each of the three divergences deliberately rather than assuming
    array.py wins on all of them, and write the decision into the helper's
    docstring.
  • Call the helper from array.py and inversion.py.
  • Make autogalaxy.util.plot_utils.norm_from delegate to it rather than
    reimplement it, keeping the autogalaxy-facing name so the Clicker /
    Scribbler callers added by PyAutoGalaxy#586 keep working.
  • Cover the config-floor path (both call sites), explicit limits, derived vmax
    and the degenerate vmax <= vmin case with tests in PyAutoArray.

The three divergence decisions

# Divergence Decision
1 array.py reads log10_min_value from config and clips; inversion.py hardcodes 1e-4 and does not clip array.py wins. The helper always reads the configured floor (fallback 1e-4) and clips. This is the behaviour fix — inversion plots change when the configured floor is not 1e-4.
2 array.py derives vmax from the clipped array; inversion.py from pixel_values, falling back to vmin * 10 when there are none Neither wins — it is not a divergence. The two call sites colour different data. The helper's array argument means "the values being coloured"; inversion.py passes pixel_values. array=None collapses into the same fallback, so inversion's separate else branch disappears with no behaviour change.
3 array.py widens a degenerate vmax <= vmin even when explicitly passed; inversion.py only inside its elif pixel_values branch array.py wins. LogNorm(vmin=10, vmax=1) is unusable whoever supplied the numbers. Inversion plots change when a caller passes vmax <= vmin explicitly.
Detailed implementation plan

Work Classification

Library — both repos are libraries. Library-first: PyAutoArray merges before
PyAutoGalaxy.

Affected Repositories

  • PyAutoArray (primary)
  • PyAutoGalaxy

Branch Survey

Repository Current Branch Dirty?
PyAutoArray main clean
PyAutoGalaxy main clean
PyAutoMind claude/autoarray-shared-log-norm-c12ges prompt file only

Branch: claude/autoarray-shared-log-norm-c12ges (remote web-github
session; no local worktree — the session clones are the working trees).

Implementation Steps

  1. PyAutoArray — autoarray/plot/utils.py: add norm_from(array=None, use_log10=False, vmin=None, vmax=None). Lazy matplotlib import, as
    everywhere else in this module. Body: read log10_min_value from
    autonerves config inside try/except (fallback 1.0e-4); clip; derive
    vmin_log/vmax_log; widen unconditionally when vmax_log <= vmin_log or
    non-finite; return LogNorm. Non-log path returns Normalize when either
    limit is given, else None. Docstring records the table above.
  2. PyAutoArray — autoarray/plot/array.py: replace the inline block (the
    # --- colour normalisation --- stanza) with a call to the helper.
  3. PyAutoArray — autoarray/plot/inversion.py: same, passing
    array=pixel_values. Drop the now-unused inline LogNorm/Normalize use.
  4. PyAutoGalaxy — autogalaxy/util/plot_utils.py: norm_from becomes a
    delegate to autoarray.plot.utils.norm_from, keeping its signature and
    docstring intent. autogalaxy may import autoarray; the reverse is not true.
  5. Tests — test_autoarray/plot/test_utils.py: new class covering the
    config floor with log10_min_value monkeypatched away from 1e-4, asserting
    both call sites honour it; explicit limits; derived vmax; degenerate
    vmax <= vmin; all-NaN input.

Key Files

  • autoarray/plot/utils.py — new shared helper (the reference implementation)
  • autoarray/plot/array.py — call site 1 (was the reference)
  • autoarray/plot/inversion.py — call site 2 (carried the bug)
  • autogalaxy/util/plot_utils.pynorm_from becomes a delegate
  • test_autoarray/plot/test_utils.py — new tests
  • test_autogalaxy/gui/test_plot_norm.py — must stay green (pins #586 behaviour)

Constraints

  • Behaviour-preserving except where a divergence is deliberately fixed; each
    such fix is named in the PR body. Thin test coverage on this path means an
    unnoticed change ships silently.
  • The vitals faculty could not be consulted in this session (pyauto-heart is
    not on PATH in a web-github run) — the Heart gate still applies at ship.

Autonomy

Launched /start_dev … --auto. Prompt header Autonomy: supervised; refactor
work-type cap is safe; effective level supervised. Per the autonomy
contract that means: the plan is written here rather than presented and waited
on, work proceeds, and the run parks at ship sign-off with a question on this
issue.

Original Prompt

Click to expand starting prompt

One shared colour-norm helper — the three copies have already diverged

Type: refactor
Target: autoarray
Repos:

  • PyAutoArray
  • PyAutoGalaxy
    Difficulty: small
    Autonomy: supervised
    Priority: normal
    Status: formalised
    Filed: 2026-08-24

Split out of aplt-output-drift-remaining-repos (PyAutoGalaxy#585, 2026-08-24),
which added the third copy rather than widen that task into a third repo.

The duplication

Building the matplotlib colour norm from use_log10 / vmin / vmax is written
out three times:

Where Notes
autoarray/plot/array.py:164-188 the reference implementation
autoarray/plot/inversion.py:92-107 already divergent — see below
autogalaxy/util/plot_utils.py::norm_from added by PyAutoGalaxy#586, faithful to array.py

This is not a tidiness task — the copies disagree

array.py reads the configured floor:

log10_min = _conf.instance["visualize"]["general"]["general"]["log10_min_value"]   # fallback 1.0e-4
clipped = np.clip(array, log10_min, None)

inversion.py hardcodes 1e-4 and never consults autonerves config, and
does not clip. So a user who changes log10_min_value gets it honoured on array
plots and silently ignored on inversion plots. That is a live behaviour bug, not
a style issue, and it is the thing to fix first — the deduplication is how you
stop it recurring.

They differ in a second way worth preserving deliberately rather than by
accident: array.py derives vmax from the clipped array, inversion.py from
pixel_values and falls back to vmin_log * 10.0 when there are none. And
inversion.py's vmax_log <= vmin_log guard sits inside its elif pixel_values
branch, so an explicitly-passed degenerate vmax is not widened there but is
in array.py.

Decide which behaviour is correct for each difference before merging them
do not assume array.py wins on all three. Write the decision into the helper's
docstring.

Shape of the fix

  1. Add one helper in PyAutoArray — the lowest repo that owns this logic;
    autoarray/plot/utils.py sits alongside auto_mask_edge and the other
    shared plot helpers. Give it the array/use_log10/vmin/vmax signature
    plus whatever parameter reconciles the pixel_values difference.
  2. Call it from array.py and inversion.py.
  3. Make autogalaxy.util.plot_utils.norm_from delegate to it rather than
    reimplement — autogalaxy may import autoarray (dependency direction is fine;
    the reverse is not). Keep norm_from as the autogalaxy-facing name so the
    Clicker/Scribbler callers added in PyAutoGalaxy#586 keep working.

Constraints

  • Behaviour-preserving except where you deliberately fix a divergence, and
    each such fix is named in the PR body. This is a plotting path with thin test
    coverage, so an unnoticed change ships silently.
  • Library-first: PyAutoArray merges before PyAutoGalaxy.
  • test_autogalaxy/gui/test_plot_norm.py (added by #586) already pins the
    autogalaxy behaviour — it must stay green, or its change must be justified.

Verification

Add tests in PyAutoArray covering the config-floor path (log10_min_value set to
something other than 1e-4, asserting both call sites honour it), the
explicit-limits path, the derived-vmax path, and the degenerate vmax <= vmin
case. Then the full suites in both repos, on 3.12 and 3.13.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions