Mark every below-threshold finger sample with an idle dot

The selector now returns candidate locations with an activity flag, so the quiet half of
the finger's sampling grid can be drawn at last: one radius-1 dot per candidate, in the
idle tint and off the flow ramp, on no sample that also carries an arrow. The dots are
`DRAW_IDLE_GRID_DOTS` (on) and `draw_arrow_display(idle_dots=...)`; box and mini keep
their own SDK path.
This commit is contained in:
ykcheungab
2026-10-04 11:09:19 +08:00
parent ca28d788c2
commit aa526c62a9
4 changed files with 548 additions and 32 deletions
+40
View File
@@ -97,6 +97,46 @@ def test_the_location_cap_is_a_location_budget() -> None:
)
def test_the_threshold_decides_the_marker_and_never_the_location() -> None:
"""The two markers are one grid read two ways: only the verdict moves with a threshold.
The 2D canvas draws an arrow for an ``active`` sample and a quiet dot for an inactive
one, both at the candidate location this selector returns. So the candidate set — the
marker set, once each sample carries exactly one marker — is the policy's grid and
nothing else, while ``flow_on_px`` decides how many of them are arrows. A threshold
that moved a location would move the idle grid with it, which is what makes the dots
comparable from frame to frame.
"""
h, w = 30, 30
flow = np.zeros((h, w, 2), dtype=np.float64)
# 6 px displacements of 0.1 .. 1.5 px, each on the 5 px grid (2 + 5k) the policy
# samples at, so each threshold step moves exactly one sample across.
for index, value in enumerate((0.05, 0.1, 0.2, 0.35, 0.5, 0.75)):
flow[2 + 5 * index, 2 + 5 * index] = (value, 0.0)
locations: list[tuple[int, int]] | None = None
arrows: list[int] = []
for on_px in (0.2, 0.4, 0.6, 0.9, 1.2):
policy = VisualPolicy(
flow_on_px=on_px,
flow_off_px=max(0.0, on_px - 0.1),
flow_grid_spacing=5,
flow_edge_margin=0,
).normalised()
samples = select_flow_samples(flow, None, policy)
here = list(zip(samples.rows.tolist(), samples.cols.tolist()))
if locations is None:
locations = here
assert here == locations, (
f"the candidate locations moved with the threshold at {on_px} px"
)
# One marker per sample, and which marker is the threshold's whole decision.
assert samples.active.size == len(here)
assert samples.active_count + int((~samples.active).sum()) == len(here)
arrows.append(samples.active_count)
assert arrows == [5, 4, 3, 2, 1], f"the sweep did not move one sample per step: {arrows}"
def test_paint_domain_background_colours() -> None:
mask = np.zeros((4, 4), dtype=bool)
mask[1:3, 1:3] = True