feat(gui): Scribbler radial-subtracted side-by-side panels; cross-grid mask regrid - #641
Open
samlange04 wants to merge 4 commits into
Open
samlange04 wants to merge 4 commits into
samlange04 wants to merge 4 commits into
Conversation
…d mask regrid New `autogalaxy/gui/display_util.py` (pure numpy, unit-tested): - `radial_median_subtract(array, exclude=None)`: subtract the azimuthally-averaged radial median profile, a display transform that lifts lensed arcs and companions out from under a smooth galaxy's light. `exclude` keeps a known contaminant out of the ring statistics so it cannot stamp a dark annulus at its own radius. - `mask_boundary`: 1-px inner outline of a mask. - `composite_panels` / `fold_panels`: lay panels side by side with a gutter, and fold a scribble drawn on the composite back onto one grid. - `mask_regridded_from` / `mask_2d_regridded_from`: nearest-neighbour transfer of a boolean mask between uniform grids of different pixel scale / size by scaled coordinates (no WCS), so a mask drawn on one waveband can seed another. Long-term home is autoarray.mask; kept here while the GUI is its only caller. `Scribbler(subtract_radial=True, side_by_side=True)` displays [radial-subtracted | as-observed], each panel colour-scaled independently, with any `proposal` outlined on both. Strokes on either panel fold onto the image's own grid, so `get_scribble_masks()` / `mask_from()` always return image-shaped arrays. The single-panel display path is unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…bler `Scribbler(positions=..., position_marker_size=6)` draws each (y, x) scaled coordinate (a list or `Grid2DIrregular`, e.g. the multiple-image positions from the positions GUI) as four dark ticks on every panel while the mask is painted. A cross rather than a ring: the proposal outline and the mask-overlay edge are closed boundaries, and a third closed shape reads as one more region being masked. The arms stop 2 px short of the centre so the marked pixel is never covered, and they are dark because a bright marker cannot be told from the arc flux it points at. Display only: the mask is read back from brush positions. `positions_pixels()` exposes the (row, column) conversion, which honours the image's pixel scales and origin. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Extends the single-panel outline-bounds test to the radial-subtracted side-by-side display: the asymmetric proposal must be outlined over the same pixels in the left panel and one image-width-plus-gap to the right. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ses, blank gutter, titles Manual testing of the side-by-side Scribbler on the COSMOS-Web ring and the simulated `simple` dataset found display defects, some of them inherited from the erase-brush PR: - The display was scaled linearly from min to max, so a galaxy core thousands of sigma above the sky collapsed the whole field onto one colour and the radial-subtracted panel showed nothing. Every display (single panel too) is now mapped through an arcsinh stretch about its own sky level (`display_util.arcsinh_stretch`, MAD-scaled, clipped at the 0.5 / 99.5 percentiles); `vmin` / `vmax` set the data values at the ends of the stretch instead, and `stretch="linear"` restores the previous scaling (and `norm="log"`). A legacy `Cmap`-style object keeps its own norm. - The brushes painted green and red, which vanish against a jet-coloured image and are not told apart by every kind of colour vision. Add now paints WHITE and erase BLACK: no colour map contains either. The cursor ring takes the active brush's colour with a thin halo of the other, so it is visible on any background and shows the mode; `brush_color=` overrides the ring colour. - The position crosses had 6 px arms with an open centre regardless of image size, which on a 100 px image was far too large and left the marked pixel ambiguous. They are now closed black crosses with a white halo whose default arm scales with the image (1.5% of the shorter side, at least 3 px). The proposal outline gets the same black-on-white treatment, since plain white was lost on the brighter arcsinh sky. - The gutter between panels was filled with 0.0 and so painted in the colour map's lowest colour, reading as the left image running past its own edge. `composite_panels` now fills the gutter with NaN so it is left unpainted. - With several GUI windows in one session it was not obvious what each one was for. New `title=` goes on the first line of the window (e.g. "Mask extra galaxies: F277W, proposing from F150W"), and the window now lists the markers it is actually showing (proposal outline, black x = overlaid mask edge, + = marked positions) above the keys. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Collaborator
Author
|
Pushed 99643eb after a manual test of the GUI against current
The key legend and the workspace prose in PyAutoLabs/autolens_workspace#583 and PyAutoLabs/autogalaxy_workspace#254 say white / black now; the wave-1 docs PRs (#579 / #251) still say green / red and will be right once those two merge on top of them. If you would rather keep the green / red pair for #635 compatibility and only change the ring, say so and I will split it out. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Follow-up to #635 (Scribbler erase brush and refine-existing-mask). Two additions that came out of masking HST and JWST lens samples, plus a set of display fixes that a manual test of the GUI turned up (see Manual test below):
Scribbler(subtract_radial=True)shows both at once, and you can paint on either panel.cosmos_web_ringF150W 0.03"/419 px vs F277W 0.06"/209 px). feat(gui): Scribbler erase brush, proposal outline, multiplicative brush resize #635 lets a mask be reopened as a proposal, but only on the same grid. This adds nearest-neighbour transfer between uniform grids.It also adds marked positions (small crosses) on the display. Every new argument defaults to the behaviour described here;
stretch="linear"restores the previous colour scaling.Scope agreed in https://github.com/orgs/PyAutoLabs/discussions/23. Companion docs PRs: PyAutoLabs/autolens_workspace#583 and PyAutoLabs/autogalaxy_workspace#254 (same branch name, stacked on autolens_workspace#579 / autogalaxy_workspace#251).
Changes
New
autogalaxy/gui/display_util.py(pure numpy):radial_median_subtract(array, exclude=None): subtracts the per-integer-radius median profile about the array centre.excludedrops known-bad pixels from the ring statistics, so a bright contaminant cannot stamp a dark annulus at its own radius. This is a display transform only; the mask is read back from brush positions.arcsinh_stretch(array, clip_percentiles=(0.5, 99.5), vmin=None, vmax=None): maps an image onto [0, 1] through an arcsinh stretch about its median, scaled by a MAD estimate of the noise, so sky-level structure and a core thousands of sigma brighter are visible at once.vmin/vmaxreplace the percentile limits with data values. Falls back to the full range when the percentiles coincide (a flat image with a few outliers).mask_boundary(mask): 1 px inner outline.composite_panels/fold_panels: lay panels side by side with a gutter, and fold a scribble on the composite back onto one grid by OR. The gutter is NaN soimshowleaves it unpainted.mask_regridded_from/mask_2d_regridded_from: nearest-neighbour transfer of a boolean mask between uniform grids by scaled (arcsec) coordinates.grid_pixel_centres_2d_slim_fromtruncates toward zero, which misplaces pixels on the negative edge.autoarray.mask; it lives here while the GUI is its only caller.Scribbler(subtract_radial=True, side_by_side=True, panel_gap=6):[radial-subtracted | as-observed], each panel stretched independently througharcsinh_stretch.mask_overlayedge is drawn on both panels. The outline uses the no-origin=contour from adfd768, so it lines up withimshowon each panel.get_scribble_masks()andmask_from()always return image-shaped arrays.Scribbler(stretch="arcsinh")(default), single panel too:arcsinh_stretch;vmin/vmaxset the data values at its ends.stretch="linear"restores matplotlib's own scaling withnorm(andnorm="log"). A legacyCmap-style object keeps its own norm.Brushes and markers:
brush_color=overrides it.Scribbler(positions=..., position_marker_size=None): (y, x) scaled coordinates (a list or aGrid2DIrregular, e.g. from the positions GUI) are marked with a small closed cross, black on a white halo, on every panel. Default arm length scales with the image (1.5% of the shorter side, at least 3 px); the old 6 px open-centred cross was far too large on a 100 px image and left the marked pixel ambiguous. Display only; the crosses never enter the mask.positions_pixels()exposes the conversion to pixels, honouring pixel scales and origin.Scribbler(title=...): the first line of the window says what is being masked and where the proposal came from (e.g."Mask extra galaxies: F277W, proposing from F150W"). Below it the window lists the markers it is actually showing (outline = the mask being refined | black x = edge of the overlaid mask | + = marked positions) and then the keys, so a user with several windows open can tell them apart and never has to remember which marker is which.Tests
test_autogalaxy/gui/test_display_util.py(new):excludekeeps a contaminant out of the ring median.arcsinh_stretch: unit interval, a 5-sigma pixel stays well clear of the floor where a linear scale puts it at 5e-4, explicit limits replace the percentiles, non-finite pixels map to 0, flat images do not divide by zero.Mask2Dwrapper.Added to
test_scribbler.py:vmin/vmax,stretch="linear"reproduces the old scaling, unknown stretches are rejected.brush_coloris kept.test_autogalaxy/gui: 56 passed.Manual test
Run by hand on 2026-10-03 (this branch merged with current
main), with the companion autolens_workspace scriptsgui/mask_extra_galaxies_multi.py(COSMOS-Web ring, F150W then F277W),gui/mask_arcs.pyandgui/mask.py(simulatedsimpledatasets). The first pass found the defects fixed in the last commit: a linear min-to-max scale left the radial-subtracted panel blank (99% of the pixels within 1% of the range), the blue brush ring and the green / red strokes vanished against jet, the crosses were far too big, the zero-filled gutter painted jet blue as if the left image overflowed, and the white outline was lost on the brighter arcsinh sky. The second pass, with all fixes in, exercised both brushes, undo and resize, the side-by-side display, the F150W to F277W regrid with each of the apply / drawn-only / skip answers, the arc GUI with positions and overlay, the window titles, and closing a window unpainted.Origin
Developed in the HST lens data-reduction pipeline (
make_masks.py,make_arc_masks.py) and ported. PyAutoMind prompt:draft/feature/autogalaxy/scribbler_radial_panels_regrid.md.🤖 Generated with Claude Code