Repository navigation
Description
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, alongsideauto_mask_edgeand the other shared plot helpers. - Decide each of the three divergences deliberately rather than assuming
array.pywins on all of them, and write the decision into the helper's
docstring. - Call the helper from
array.pyandinversion.py. - Make
autogalaxy.util.plot_utils.norm_fromdelegate to it rather than
reimplement it, keeping the autogalaxy-facing name so theClicker/
Scribblercallers added by PyAutoGalaxy#586 keep working. - Cover the config-floor path (both call sites), explicit limits, derived
vmax
and the degeneratevmax <= vmincase 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: addnorm_from(array=None, use_log10=False, vmin=None, vmax=None). Lazy matplotlib import, as
everywhere else in this module. Body: readlog10_min_valuefrom
autonervesconfig insidetry/except(fallback1.0e-4); clip; derive
vmin_log/vmax_log; widen unconditionally whenvmax_log <= vmin_logor
non-finite; returnLogNorm. Non-log path returnsNormalizewhen either
limit is given, elseNone. 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 inlineLogNorm/Normalizeuse. - PyAutoGalaxy —
autogalaxy/util/plot_utils.py:norm_frombecomes a
delegate toautoarray.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 withlog10_min_valuemonkeypatched away from1e-4, asserting
both call sites honour it; explicit limits; derivedvmax; 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_frombecomes a delegatetest_autoarray/plot/test_utils.py— new teststest_autogalaxy/gui/test_plot_norm.py— must stay green (pins fix: apply_over_sampling keeps the sparse operator; cache operated_mapping_matrix_list #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-heartis
not onPATHin aweb-githubrun) — 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.pysits alongsideauto_mask_edgeand the other
shared plot helpers. Give it thearray/use_log10/vmin/vmaxsignature
plus whatever parameter reconciles thepixel_valuesdifference. - Call it from
array.pyandinversion.py. - Make
autogalaxy.util.plot_utils.norm_fromdelegate to it rather than
reimplement — autogalaxy may import autoarray (dependency direction is fine;
the reverse is not). Keepnorm_fromas the autogalaxy-facing name so the
Clicker/Scribblercallers 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 fix: apply_over_sampling keeps the sparse operator; cache operated_mapping_matrix_list #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.
Activity
Jammy2211 commentedon Aug 24, 2026
Implemented and pushed — parked at ship sign-off
Effective autonomy is
supervised(prompt headersupervised,refactorcapsafe), so per the autonomy contract the run stops here: the code is written, tested and pushed, but the PRs are not opened until you sign off. Two questions below.What was done
Branch
claude/autoarray-shared-log-norm-c12gesin both repos.accc253) —norm_fromadded toautoarray/plot/utils.py, called fromplot/array.pyandplot/inversion.py. The config read lands as_conf_log10_min_valuealongside the other_conf_*readers. The three divergence decisions are in the helper's docstring.00a0499) —util/plot_utils.py::norm_fromis now a thin delegate; the autogalaxy-facing name and signature are unchanged, so theClicker/Scribblercallers are untouched.The deliberate behaviour changes
Both land on
plot_inversion_reconstructiononly;plot_arrayand the autogalaxy GUI path are unchanged.1e-4and never readvisualize.general.general.log10_min_value. Inversion plots change when the configured floor is not1e-4— this is the fix the task exists for.vmax <= vminis now widened even when passed explicitly. Its guard sat inside theelif pixel_valuesbranch, so an explicit degenerate pair reached matplotlib unmodified.The third difference —
array.pyderivingvmaxfrom the image,inversion.pyfrompixel_values— was not merged into one behaviour. The helper'sarrayargument means "the values being coloured"; the two call sites genuinely colour different data.array=Nonetakes the same fallback as an all-NaNarray, which is exactly what inversion's separateelsebranch did, so no behaviour changed there.Verification
test_autoarray/plottest_autoarray(full)test_autogalaxy/gui/test_plot_norm.py(pins #586)test_autogalaxy(full, against this PyAutoArray branch)Two caveats, stated plainly:
inversion/inversion/test_{abstract,factory}.pysparse-operator failures in this environment are present on unmodifiedmain(verified by stashing) and are unrelated to plotting. CI is the real check.The new tests were checked for teeth, not just green: reverting
plot_inversion_reconstructionto the hardcoded floor makestest__plot_inversion_reconstructionfail withassert 0.0001 == 0.003.Question 1 — ship sign-off (blocking)
Open the two PRs, PyAutoArray first (library-first: PyAutoArray#488 merges before the PyAutoGalaxy one)?
ready-to-shipin the Mind registry. Merge stays yours either way.One thing the ship gate could not do here:
pyauto-heartis not onPATHin thisweb-githubsession, so the vitals faculty was never consulted. The Heart verdict is unknown, not green. If you would rather the gate ran properly, this needs a session that can reach Heart.Question 2 — an orphaned prompt file (non-blocking)
The prompt this task started from did not exist on
PyAutoMind/main. It was only ever committed toclaude/aplt-output-drift-repos-33n81z(dd7501f1, "file the three aplt-output-drift follow-ups + queue them"), and **no PR was ever opened for that branch** — so all three follow-ups filed that day, plus thequeue.mdentries, are invisible tomain`, the dashboard and the Brain router.I recovered this one file onto the task branch and advanced it
draft/→active/. The other two are still stranded:draft/workspaces/euclid_crlf_line_endings.mdqueue.mdadditions from that commitWant me to open a PR for that PyAutoMind branch, or recover the remaining files the same way? Either works; doing nothing loses them quietly, which is why it is worth a look. This is not blocking the ship decision.
Generated by Claude Code
Jammy2211 commentedon Aug 24, 2026
Shipped
b690c3e0c7bc62d7132c9Library-first order honoured: PyAutoArray merged first, PyAutoGalaxy after. Every leg on every run was
completed+successbefore either merge; both branches verified merged intomainafterwards (rev-list origin/main..branch= 0).What landed
autoarray.plot.utils.norm_fromis now the single colour-norm implementation, called byplot/array.py,plot/inversion.py, and by delegation fromautogalaxy.util.plot_utils.norm_from. The config read lands once as_conf_log10_min_value.Two deliberate behaviour changes, both on
plot_inversion_reconstructiononly —plot_arrayand the autogalaxy GUI path are untouched:log10_min_valueis honoured. It hardcoded1e-4. This is the bug the task existed for.vmax <= vminis widened however it arose, not only in the derived branch.The third difference was deliberately not reconciled:
arraymeans "the values being coloured", soarray.pypassing the image andinversion.pypassingpixel_valuesis correct rather than divergent. All three decisions are in the helper's docstring, as the prompt required.Notes worth keeping
pyauto-heart readinessreturnedstale, score 35 — every reasonstatus unknown(no report.json,install verification not run, five repos unknown). That is Heart with an empty sensor bank in a freshweb-githubcontainer, not a health signal. The merge decision rested on real CI instead, which is the sensor Heart was missing. Flagging it because astaleHeart on a green-CI change is a gate that did not actually gate.mainwhen the task started. It lived only on the unmerged filing branchclaude/aplt-output-drift-repos-33n81z, so/start_devon the given path found nothing until it was recovered from that branch. PR PyAutoMind#311 landed that branch mid-task, which is why the Mind close-out hit a merge conflict —mainwas adding the prompt todraft/as this branch folded it intocomplete/. Resolved by dropping the draft copy and marking itsqueue.mdline# DONE. Nothing warns about a filing branch with no PR.Completion record:
complete/2026/08/shared-log-norm-helper.md. Both merged PRs remain in the pending-release queue; no release was performed.Generated by Claude Code