Spaces:
Running on Zero
Running on Zero
File size: 6,513 Bytes
99b82f1 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | """Ink Lens: the control-set threshold and the region status (numpy only).
The rule, frozen in docs/prereg/2026-10-20-ink-lens-gate.md before any map of it existed:
- F and R are the mean ink probabilities (0 to 1) of the region with its layers as stored and reversed, each averaged
over the same depth windows and readers.
- Papyrus: pixels whose middle CT layer is above the region's 5th percentile of CT and above 0 (so air and the
masked outside of a segment do not lower the threshold).
- T is the 99th percentile of R over the papyrus pixels: the level that only 1 % of the control's own pixels pass.
- Kept ink: papyrus pixels with F > T. Excess: the fraction of papyrus pixels with F > T, divided by 0.01.
- Status: ink when excess >= 3, weak when 1.5 <= excess < 3, none below 1.5, too_small under 100,000 papyrus pixels
(about 3 mm square at 9.6 um), unreadable when a map is not finite, constant, or identical to its control.
A region with no text, or where the model reads the reversed volume as well as the stored one, has an excess near 1
and the status none: the check can fail. Everything it keeps is model output, not a reading.
"""
from __future__ import annotations
RULE_ID = "ink-lens-gate-v1"
CT_PERCENTILE = 5.0
CONTROL_PERCENTILE = 99.0
BASE_RATE = 0.01
INK_EXCESS = 3.0
WEAK_EXCESS = 1.5
MIN_PAPYRUS = 100_000
STATUSES = ("ink", "weak", "none", "too_small", "unreadable")
STATUS_TEXT = {
"ink": "ink above the control: at least 3 times as many pixels pass the control's 99th percentile as the control",
"weak": "some ink above the control: 1.5 to 3 times the control's share; look before trusting it",
"none": "no ink above the control: the stored order passes the control's 99th percentile no more than 1.5 times "
"as often as the control itself",
"too_small": "too little papyrus in the region to judge (under 100,000 pixels, about 3 mm square)",
"unreadable": "the maps cannot be judged (not finite, constant, or the same as the control)",
}
class GateError(ValueError):
"""Maps the gate cannot take at all (wrong shapes); the message says why."""
def papyrus(ct_middle, percentile=CT_PERCENTILE):
"""Boolean mask of papyrus pixels in a region: middle CT layer above its own percentile and above 0."""
import numpy as np
ct = np.asarray(ct_middle)
if ct.ndim != 2:
raise GateError(f"the CT layer is 2-D, not {ct.shape}")
nz = ct[ct > 0]
if nz.size == 0:
return np.zeros(ct.shape, bool)
floor = max(float(np.percentile(ct, percentile)), 0.0)
return ct > floor
def _unreadable(F, R, mask):
import numpy as np
reasons = []
for name, m in (("stored-order map", F), ("control map", R)):
if not np.all(np.isfinite(m)):
reasons.append(f"the {name} has values that are not finite")
elif mask.any() and float(m[mask].max()) - float(m[mask].min()) < 1e-6:
reasons.append(f"the {name} is constant on the papyrus")
if not reasons and mask.any() and np.array_equal(F[mask], R[mask]):
reasons.append("the stored-order map is identical to its control (a broken run, or the control was not run)")
return reasons
def decide(F, R, ct_middle, min_papyrus=MIN_PAPYRUS):
"""The gate on one region: threshold, excess, status and counts, as a JSON-able dict."""
import numpy as np
F = np.asarray(F, dtype=np.float32)
R = np.asarray(R, dtype=np.float32)
if F.shape != R.shape or F.ndim != 2:
raise GateError(f"the map {F.shape} and its control {R.shape} must be 2-D and the same shape")
mask = papyrus(ct_middle)
if mask.shape != F.shape:
raise GateError(f"the CT layer {mask.shape} and the maps {F.shape} differ in shape")
n = int(mask.sum())
out = {"rule": RULE_ID, "papyrus_px": n, "region_px": int(mask.size), "threshold": None, "excess": None,
"forward_above": None, "control_above": None, "status": None}
reasons = _unreadable(F, R, mask)
if reasons:
out.update(status="unreadable", reasons=reasons)
elif n < min_papyrus:
out.update(status="too_small")
else:
T = float(np.percentile(R[mask], CONTROL_PERCENTILE))
f_above = float((F[mask] > T).mean())
r_above = float((R[mask] > T).mean())
excess = f_above / BASE_RATE
status = "ink" if excess >= INK_EXCESS else "weak" if excess >= WEAK_EXCESS else "none"
out.update(threshold=round(T, 6), excess=round(excess, 4), forward_above=round(f_above, 6),
control_above=round(r_above, 6), status=status)
out["status_text"] = STATUS_TEXT[out["status"]]
out["note"] = "Model output, not a reading."
return out
def kept(F, decision, ct_middle):
"""F where it passes the control's threshold on papyrus, else 0 (float32). Nothing passes unless the status is
ink or weak."""
import numpy as np
F = np.asarray(F, dtype=np.float32)
out = np.zeros_like(F)
if decision.get("status") not in ("ink", "weak"):
return out
m = papyrus(ct_middle) & (F > float(decision["threshold"]))
out[m] = F[m]
return out
def to_u8(kept_map):
"""The kept map as uint8 for an overlay: 0 where nothing is kept (transparent in VC3D), else 1 to 255."""
import numpy as np
k = np.asarray(kept_map, dtype=np.float32)
out = np.zeros(k.shape, np.uint8)
on = k > 0
out[on] = np.clip(np.rint(k[on] * 254) + 1, 1, 255).astype(np.uint8)
return out
def label_shares(kept_map, ink, supervised, inner=64):
"""The preregistered label check: the share of labeled ink pixels kept and the share of labeled background
pixels kept, inside the supervision mask with an inner edge left out."""
import numpy as np
k = np.asarray(kept_map) > 0
m = np.asarray(supervised, bool).copy()
if inner:
m[:inner] = m[-inner:] = False
m[:, :inner] = m[:, -inner:] = False
ink = np.asarray(ink, bool)
ink_px, bg_px = int((m & ink).sum()), int((m & ~ink).sum())
ink_share = float((k & m & ink).sum()) / ink_px if ink_px else None
bg_share = float((k & m & ~ink).sum()) / bg_px if bg_px else None
passes = None if ink_share is None or bg_share is None else ink_share > bg_share
return {"ink_px": ink_px, "background_px": bg_px, "ink_kept_share": None if ink_share is None else round(ink_share, 5),
"background_kept_share": None if bg_share is None else round(bg_share, 5), "ink_share_larger": passes,
"inner": inner}
|