spacr.qt.wand_rescue¶
Rescues for a magic-wand flood that runs away.
A flood fill from one click is the fastest way to take a whole object, and it has one failure mode that matters: the object touches something bright that is not it – a debris streak, a saturated membrane seam, the rim of a well – and the flood walks out along that seam and swallows the field. The tolerance that takes the object correctly and the tolerance that escapes are often the same number, so “lower the tolerance” is not an answer: it shrinks the object as well.
Three independent rescues are offered here, in the order they run. Each catches the runaway at a different point, and each can be turned off:
Directional runaway detection and trimming —
trim_directional_runaway()measures flood width along scanlines extending from the seed. A sustained abrupt expansion marks a leak, and pixels beyond that position are removed. Detection at this stage enables the following two refinements.Intensity-constrained reflooding —
wand_region()performs a binary search for the highest tested tolerance that does not trigger runaway detection. The resulting connected region replaces the straight-line directional cut.Gradient-based boundary refinement —
taper_region_to_intensity()applies a watershed to a smoothed intensity gradient within a configurable band, moving the provisional boundary toward a nearby image edge.
Separately, cap_region_from_seed() bounds a flood that is simply too
big, keeping the pixels geodesically nearest the click so the result is
a bounded piece of the thing that was clicked rather than an arbitrary
prefix of a scan order.
Ported from the standalone curation tool
(plaque_assay_model/tools/curate_masks_qt.py), where these were built
against crystal-violet plaque scans. The failure is not specific to that
stain: a nucleus touching a bright fibre and a plaque touching a well rim
are the same flood escaping down the same kind of seam.
Nothing here imports Qt, so the geometry is testable without a GUI.
Functions¶
|
Keep at most |
|
Boolean flood from |
|
|
|
Move a geometric edge onto the nearest real intensity edge. |
|
Cut a flood where it suddenly widens, and say where it was cut. |
|
Flood from one click and apply whichever rescues are switched on. |
Module Contents¶
- spacr.qt.wand_rescue.cap_region_from_seed(region: numpy.ndarray, seed_yx: Tuple[int, int], max_pixels: int) numpy.ndarray[source]¶
Keep at most
max_pixelsofregion, nearest the click.Nearest is measured through the region – breadth-first growth that may only step on flooded pixels – so the kept piece cannot jump a gap to a bright patch that merely happens to be close in a straight line. Eight-connected, because the piece being salvaged is a shape to keep whole, not a flood to grow.
Returns
regionunchanged when it already fits, and an empty mask when the seed is not inside it.- Parameters:
region – boolean mask of the flood, shape (H, W).
seed_yx –
(row, column)of the click in pixels.max_pixels – pixel budget; values below 1 are treated as 1.
- spacr.qt.wand_rescue.flood_region(image: numpy.ndarray, seed_x: int, seed_y: int, tolerance: float) numpy.ndarray[source]¶
Boolean flood from
(seed_x, seed_y), uncapped.A pixel joins when its distance from the seed’s value is at most
tolerance– absolute difference on a grey image, Euclidean distance across channels on a colour one – and it is reachable from the seed through four-connected steps. Both rules arespacr.qt.mask_engine.magic_wand()’s, so the region this returns is the region that wand would fill given no pixel budget.Uncapped on purpose: the runaway detector has to see how far the leak went to recognise it as one. A flood truncated at the budget looks like a large compact object, which is exactly what a leak is not.
- Parameters:
image – 2-D greyscale image, or a colour image with channels last; pixel values are compared as float32.
seed_x – column of the click in pixels; a seed outside the image yields an all-False mask.
seed_y – row of the click in pixels.
tolerance – largest distance from the seed’s value a pixel may have and still join, in the image’s intensity units; negative values count as 0.
- spacr.qt.wand_rescue.magic_wand(image: numpy.ndarray, mask: numpy.ndarray, seed_x: int, seed_y: int, tolerance: float, max_pixels: int = 100000, action: str = 'add', **settings) Tuple[numpy.ndarray, Dict[str, object]][source]¶
wand_region(), written into a copy ofmask.Writes 255 where the region landed for
action="add"and 0 foraction="erase", matchingspacr.qt.mask_engine.magic_wand(), and returns the report beside the new mask. A rejected flood leaves the mask untouched.- Parameters:
image – 2-D greyscale image, or a colour image with channels last; pixel values are compared as float32. None returns
maskunchanged with a rejected report.mask – 2-D mask of shape (H, W) that is copied and written into; None is returned as is with a rejected report.
seed_x – column of the click in pixels.
seed_y – row of the click in pixels.
tolerance – flood tolerance in the image’s intensity units, as in
flood_region().
- spacr.qt.wand_rescue.taper_region_to_intensity(image: numpy.ndarray, flooded_region: numpy.ndarray, provisional: numpy.ndarray, seed_yx: Tuple[int, int], sigma: float = 2.0, margin: int = 8, foreground_erode: int = 3) numpy.ndarray[source]¶
Move a geometric edge onto the nearest real intensity edge.
A directional cut is a straight line and a geodesic cap is a circular arc; objects are neither.
provisionalis whichever of those the earlier rescues produced. Its interior, inset byforeground_erode, is marked as certainly object; the part offlooded_regionthat was thrown away, at leastmargindeep, is marked as certainly not. A watershed on thesigma-smoothed intensity gradient decides the band between them, so the final boundary follows an intensity change – up or down – instead of the cut.The result never leaves the original flood, and always keeps the connected piece the click is in. If the discarded part is thinner than
marginthere is no room for a band, so its deepest quarter is used rather than giving up and leaving the straight edge.- Parameters:
image – greyscale image, or a colour image with channels last that is averaged to grey; its smoothed gradient drives the watershed.
flooded_region – boolean mask of the original, uncapped flood; the result never leaves it.
provisional – boolean mask produced by the earlier rescue (a straight cut or a cap); it is clipped to
flooded_regionand returned as is when the seed is outside it.seed_yx –
(row, column)of the click in pixels.
- spacr.qt.wand_rescue.trim_directional_runaway(region: numpy.ndarray, seed_yx: Tuple[int, int], ratio: float = 2.0, warmup: int = 12, min_baseline: int = 8, confirm: int = 2) Tuple[numpy.ndarray, Dict[str, int]][source]¶
Cut a flood where it suddenly widens, and say where it was cut.
Walking up, down, left and right from the clicked scanline, the flood’s width is a profile. An object’s own profile changes gradually; a leak into a seam appears as a step. A leak is called when
confirmconsecutive scanlines are all at leastratiotimes wider than the widest scanline established strictly before them, and everything from that scanline outward is removed.The three guards exist because a naive step detector fires on the object itself:
warmupignores the scanlines nearest the click, where a one-pixel-wide start doubling to two pixels is a ratio of 2.0 and means nothing;min_baselinerefuses to judge until the object has reached a real width, for the same reason;confirmrequires the expansion to persist, so a single noisy row cannot cut the object in half.
- Parameters:
region – boolean mask of the flood, shape (H, W).
seed_yx –
(row, column)of the click in pixels; the width profiles are walked outward from it, and a seed outside the mask’s bounds returns an untouched copy with no cuts.
- Returns:
(trimmed, cuts).cutsmaps each direction that leaked to the image coordinate the cut was made at, and is empty when nothing leaked – which is how a caller knows the flood was clean.
- spacr.qt.wand_rescue.wand_region(image: numpy.ndarray, seed_x: int, seed_y: int, tolerance: float, max_pixels: int = 100000, **settings) Tuple[numpy.ndarray, Dict[str, object]][source]¶
Flood from one click and apply whichever rescues are switched on.
Runs the three rescues in order – detect the runaway, replace the straight cut with an intensity border, taper what is left onto the local gradient – and then applies the pixel budget. Every step is optional and each is inert on a flood that did not run away: with no leak detected, this returns exactly
flood_region()’s answer.settingsaccepts the keys ofRESCUE_DEFAULTS; anything missing takes the default.- Parameters:
image – 2-D greyscale image, or a colour image with channels last; pixel values are compared as float32.
seed_x – column of the click in pixels.
seed_y – row of the click in pixels.
tolerance – flood tolerance in the image’s intensity units, as in
flood_region(); it is also the upper bound of the intensity-border search.
- Returns:
(region, report).reportnames what happened –cuts(the directions that leaked),intensity_borderand therefined_toleranceit settled on,tapered,capped, and the pixel counts before and after – so the caller can tell the user why the wand took what it took, and write it in the ledger.