feat(gui): Scribbler erase brush, proposal outline, multiplicative brush resize - #635
Conversation
…ush resize The mask-drawing GUI gains the pieces needed to REFINE a mask rather than only draw one from scratch: - Segment '2' (red) is now an ERASE brush. `mask_from()` returns `(proposal | added) & ~erased`; `show_mask()` is unchanged (add segment only) so existing callers keep working. - New `proposal=` argument: an existing boolean mask (e.g. one drawn earlier, or drawn for another waveband on the same grid) is outlined in white over the image and edited with the two brushes instead of redrawn. - '=' / '-' resize the brush multiplicatively (`brush_resize_factor`, default 1.4x per press, at least 1 px) with a 1 px floor (`min_radius`), and echo the radius, so a few presses span detail work to thick outskirts strokes. - Motion events outside the image axes are ignored instead of raising `TypeError` inside `add_patch` (xdata is None there), and the brush circle is redrawn on the motion path so it keeps tracking the cursor on the second GUI opened in one process. - The figure build and the blocking event loop are split (`_build_figure` / `start`, `block=` argument) so the callbacks are unit-testable with the Agg backend; the TkAgg-only window placement is guarded. A circle centred on pixel 0 is no longer dropped by the rasteriser. Tests: test_autogalaxy/gui/test_scribbler.py drives the callbacks headlessly. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…osal in mask_from; test hygiene - Drop origin= from the proposal contour so its outline sits on the imshow pixels (it was flipped vertically under imshow_origin: upper); keep a reference as _proposal_contour and test the outline bounds. - Validate an explicit mask_from(proposal=...) shape via _validate_proposal. - mask_from docstring: add/erase are the first/second segments. - Tests: close figures after each test; assert draw_idle on in-axes motion. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Thanks @samlange04, this is a well-scoped contribution and the headless test harness is exactly what the GUI needed. I checked out the branch, ran the new tests (8 of the 11 go red against unfixed One real bug turned up, and rather than bounce it back I pushed the fix onto your branch (commit adfd768, maintainer edits were enabled):
|
Summary
Scribbler(al.Scribbler/ag.Scribbler) could only add to a mask. Its second colour segment had no meaning, so an overshooting stroke meant starting over, and a mask could never be reopened and adjusted. In sustained use on HST samples two defects also surfaced: moving the mouse outside the image axes raisedTypeErrorinsideadd_patch(xdataisNonethere), and the brush circle stopped following the cursor on the second GUI opened in one process because the motion handler never requested a redraw. Brush resizing by a fixed pixel step made moving between 1 px detail work and thick outskirts strokes take dozens of presses.This PR adds an erase brush, a way to refine an existing mask, multiplicative brush resizing, and fixes the defects above. All changes are backwards compatible: every existing call site (
image=,cmap=,mask_overlay=,show_mask()) behaves as before.Scope agreed in https://github.com/orgs/PyAutoLabs/discussions/23. Companion docs PRs on the same branch name in
autolens_workspaceandautogalaxy_workspace.Changes
2(red) is an ERASE brush. Newmask_from()returns(proposal | added) & ~erased.show_mask()still returns the add segment alone.proposal=argument: an existing boolean mask (drawn earlier, or for another waveband on the same grid) is outlined in white over the image and refined, not redrawn. Shape is validated against the image.=/-resize the brush multiplicatively (brush_resize_factor, default 1.4x per press, always at least 1 px) with amin_radiusfloor of 1 px; the radius is echoed.draw_idle()on the motion path._build_figure()/start(),block=), and the TkAgg-only window placement is guarded, so the callbacks are unit-testable with Agg.if not center[0]treated0.0as missing).No public API is removed or changed;
mask_from(),proposal=,brush_resize_factor=andmin_radius=are additive. Downstream: the workspace GUI scripts switch fromshow_mask()tomask_from()in the companion PRs, butshow_mask()keeps working.Testing
pytest): fulltest_autogalaxy/suite.test_autogalaxy/gui/test_scribbler.pydrives the callbacks headlessly with synthetic events. Covers rasterisation, undo, drag painting, outside-axes handling, add-minus-erase, proposal refinement, explicit-proposal override, shape validation, and multiplicative resize with floor.scripts/imaging/data_preparation/gui/mask.pyfrom the companion workspace branch, checking the brush tracks the cursor,2erases, and the proposal outline shows.Not covered by tests: the interactive Tk window itself.
Related Issues
Discussion: https://github.com/orgs/PyAutoLabs/discussions/23
Developed in an HST lens data-reduction pipeline and ported. Written with AI assistance (Claude Code); all changes reviewed and tested by me.
🤖 Generated with Claude Code