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
- 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.
- PyAutoArray —
autoarray/plot/array.py: replace the inline block (the
# --- colour normalisation --- stanza) with a call to the helper.
- PyAutoArray —
autoarray/plot/inversion.py: same, passing
array=pixel_values. Drop the now-unused inline LogNorm/Normalize use.
- 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.
- 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.py — norm_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
- 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.
- Call it from
array.py and inversion.py.
- 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.
Overview
Building the matplotlib colour norm from
use_log10/vmin/vmaxis writtenout three times —
autoarray/plot/array.py,autoarray/plot/inversion.py, andautogalaxy/util/plot_utils.py::norm_from(added by PyAutoGalaxy#586). The copieshave already diverged, and one divergence is a live behaviour bug:
inversion.pyhardcodes the
1e-4log floor and never consultsautonervesconfig, so a userwho changes
visualize.general.general.log10_min_valuegets it honoured on arrayplots 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
autoarray/plot/utils.py— the lowest repo thatowns this logic, alongside
auto_mask_edgeand the other shared plot helpers.array.pywins on all of them, and write the decision into the helper'sdocstring.
array.pyandinversion.py.autogalaxy.util.plot_utils.norm_fromdelegate to it rather thanreimplement it, keeping the autogalaxy-facing name so the
Clicker/Scribblercallers added by PyAutoGalaxy#586 keep working.vmaxand the degenerate
vmax <= vmincase with tests in PyAutoArray.The three divergence decisions
array.pyreadslog10_min_valuefrom config and clips;inversion.pyhardcodes1e-4and does not cliparray.pywins. The helper always reads the configured floor (fallback1e-4) and clips. This is the behaviour fix — inversion plots change when the configured floor is not1e-4.array.pyderivesvmaxfrom the clipped array;inversion.pyfrompixel_values, falling back tovmin * 10when there are nonearrayargument means "the values being coloured";inversion.pypassespixel_values.array=Nonecollapses into the same fallback, so inversion's separateelsebranch disappears with no behaviour change.array.pywidens a degeneratevmax <= vmineven when explicitly passed;inversion.pyonly inside itselif pixel_valuesbrancharray.pywins.LogNorm(vmin=10, vmax=1)is unusable whoever supplied the numbers. Inversion plots change when a caller passesvmax <= vminexplicitly.Detailed implementation plan
Work Classification
Library — both repos are libraries. Library-first: PyAutoArray merges before
PyAutoGalaxy.
Affected Repositories
Branch Survey
Branch:
claude/autoarray-shared-log-norm-c12ges(remoteweb-githubsession; no local worktree — the session clones are the working trees).
Implementation Steps
autoarray/plot/utils.py: addnorm_from(array=None, use_log10=False, vmin=None, vmax=None). Lazy matplotlib import, aseverywhere else in this module. Body: read
log10_min_valuefromautonervesconfig insidetry/except(fallback1.0e-4); clip; derivevmin_log/vmax_log; widen unconditionally whenvmax_log <= vmin_logornon-finite; return
LogNorm. Non-log path returnsNormalizewhen eitherlimit is given, else
None. Docstring records the table above.autoarray/plot/array.py: replace the inline block (the# --- colour normalisation ---stanza) with a call to the helper.autoarray/plot/inversion.py: same, passingarray=pixel_values. Drop the now-unused inlineLogNorm/Normalizeuse.autogalaxy/util/plot_utils.py:norm_frombecomes adelegate to
autoarray.plot.utils.norm_from, keeping its signature anddocstring intent. autogalaxy may import autoarray; the reverse is not true.
test_autoarray/plot/test_utils.py: new class covering theconfig floor with
log10_min_valuemonkeypatched away from1e-4, assertingboth call sites honour it; explicit limits; derived
vmax; degeneratevmax <= 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.py—norm_frombecomes a delegatetest_autoarray/plot/test_utils.py— new teststest_autogalaxy/gui/test_plot_norm.py— must stay green (pins #586 behaviour)Constraints
such fix is named in the PR body. Thin test coverage on this path means an
unnoticed change ships silently.
pyauto-heartisnot on
PATHin aweb-githubrun) — the Heart gate still applies at ship.Autonomy
Launched
/start_dev … --auto. Prompt headerAutonomy: supervised;refactorwork-type cap is
safe; effective levelsupervised. Per the autonomycontract 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:
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/vmaxis writtenout three times:
autoarray/plot/array.py:164-188autoarray/plot/inversion.py:92-107autogalaxy/util/plot_utils.py::norm_fromarray.pyThis is not a tidiness task — the copies disagree
array.pyreads the configured floor:inversion.pyhardcodes1e-4and never consultsautonervesconfig, anddoes not clip. So a user who changes
log10_min_valuegets it honoured on arrayplots 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.pyderivesvmaxfrom the clipped array,inversion.pyfrompixel_valuesand falls back tovmin_log * 10.0when there are none. Andinversion.py'svmax_log <= vmin_logguard sits inside itselif pixel_valuesbranch, so an explicitly-passed degenerate
vmaxis not widened there but isin
array.py.Decide which behaviour is correct for each difference before merging them —
do not assume
array.pywins on all three. Write the decision into the helper'sdocstring.
Shape of the fix
autoarray/plot/utils.pysits alongsideauto_mask_edgeand the othershared plot helpers. Give it the
array/use_log10/vmin/vmaxsignatureplus whatever parameter reconciles the
pixel_valuesdifference.array.pyandinversion.py.autogalaxy.util.plot_utils.norm_fromdelegate to it rather thanreimplement — autogalaxy may import autoarray (dependency direction is fine;
the reverse is not). Keep
norm_fromas the autogalaxy-facing name so theClicker/Scribblercallers added in PyAutoGalaxy#586 keep working.Constraints
each such fix is named in the PR body. This is a plotting path with thin test
coverage, so an unnoticed change ships silently.
test_autogalaxy/gui/test_plot_norm.py(added by #586) already pins theautogalaxy behaviour — it must stay green, or its change must be justified.
Verification
Add tests in PyAutoArray covering the config-floor path (
log10_min_valueset tosomething other than
1e-4, asserting both call sites honour it), theexplicit-limits path, the derived-
vmaxpath, and the degeneratevmax <= vmincase. Then the full suites in both repos, on 3.12 and 3.13.