Z-slice segment-counting line (negative result) - #5
Open
trisha-ant wants to merge 7 commits into
Open
Conversation
…l kwarg Max-intensity projections collapse depth, so overlapping body segments fuse into one band — the key discriminative feature for fold stages is lost. This is the harness half of the z-slice subagent work: expose a single non-projected XY slice (z=Z//2) so a perception variant can count discrete body-segment profiles at one plane. - testset.py: new _create_slice_image() helper (same crop as the three-view projection); TestCase gains midplane_b64; iter_embryo populates it. Also collapses the duplicated load-volumes-or-not branch into one path. - run.py: introspect perceive_fn signature once and only pass midplane_b64 when the function declares it (or **kwargs), so all existing variants are called unchanged. - program.md: document the new optional kwarg.
…old stages
Max-intensity projections fuse overlapping body segments, which is the
main reason 1.5fold/2fold/pretzel are hard to tell apart. This variant
runs the scientific classifier, and when that lands on a fold stage it
sends the midplane z-slice to a subagent that counts discrete
body-segment cross-sections (≤1 → 1.5fold, 2 → 2fold, ≥3 → pretzel)
and overrides.
Trigger is deliberately the simplest baseline (primary ∈ fold stages)
so the subagent only ever moves between fold stages, never into or out
of them — count=1 is ambiguous between comma and 1.5fold, so triggering
on comma would systematically push comma frames forward.
Override is recorded in reasoning ("(overrode 2fold)") and in
verification_triggered/phase_count for auditability.
…stages Midplane slice rarely shows >=3 separable cross-sections on pretzel embryos (body folds sit at different z-depths), so the segment-counting subagent systematically downgrades correct pretzel calls to 2fold/1.5fold. Per-stage vs scientific (n=233): 1.5fold: 51.2% (-14.7) 2fold: 59.3% (-1.7) pretzel: 6.8% (-81.2) adjacent: 89.3% (-10.3)
Same segment-counting subagent as zslice, but only applies the override when the count maps to a *later* stage than the primary (1.5fold->2fold->pretzel). Trigger excludes pretzel since there is nothing to upgrade to, so the worst case on pretzel is parity with scientific. Targets the 1.5fold/2fold gap without the catastrophic pretzel downgrade observed in the symmetric variant.
…gent has no signal
Overall 72.1% vs scientific 77.3% (n=233). Asymmetric design successfully
caps the pretzel catastrophe (85% vs 7% for symmetric zslice), but
attribution shows the override itself only fired 7 times and was wrong every
time (5x 1.5fold->2fold on GT=1.5fold, 2x 2fold->pretzel on GT=2fold).
Segment-count distribution across 88 triggers: {1:40, 2:46, 3:2} -- the
subagent returns <=2 in 98% of cases regardless of true stage, because a
single midplane slice genuinely doesn't show >2 separable cross-sections.
The large per-stage swings (1.5fold -46, 2fold +19) are run-to-run variance
in the primary classifier, not the override.
Conclusion: midplane segment-counting carries no signal; multi-plane
sampling or a different subagent task is the remaining path.
Single-midplane segment-counting (zslice, zslice_asym) returns <=2 in 98% of
cases because body folds sit at different depths and one plane rarely
intersects 3+ at separable locations. This variant samples slices at z =
{0.25, 0.40, 0.50, 0.60, 0.75} of stack depth, runs the count subagent on
each concurrently, and overrides with the *maximum*. Symmetric override on
the full FOLD_STAGES trigger so the hypothesis is tested cleanly.
Harness: TestCase gains zslices_b64 (list of 5 base64 JPEGs via
_create_zslice_stack), and run.py whitelists it as an optional kwarg.
Existing variants unaffected (kwarg routing is per-signature).
…ration Overall 42.5% vs scientific 77.3% (n=233). Max-count >=3 fires at 56% on GT=2fold and 55% on GT=pretzel -- zero discriminative power between the two stages we need to separate. The max over 5 planes amplifies per-slice noise (~20% false-3 rate -> ~67% any-slice-says-3). Override attribution: 111 fired, 26 helpful / 85 harmful (net -25.3%). Per-depth mean count on pretzel: z=0.25:2.19, 0.40:1.99, 0.50:1.83, 0.60:1.87, 0.75:2.12 -- quarter-planes show marginally more structure than midplane but no depth reliably hits 3. Closes the segment-counting line: neither single-plane (zslice/zslice_asym) nor multi-plane works because the subagent cannot reliably distinguish 2 vs 3 fluorescent-nuclei blobs in any optical section.
trisha-ant
marked this pull request as ready for review
April 23, 2026 15:53
trisha-ant
changed the base branch from
trisha/gently-zslice-asym
to
trisha/gently-remove-confidence
April 28, 2026 13:35
This was referenced Apr 28, 2026
trisha-ant
force-pushed
the
trisha/gently-zslice-multi
branch
from
April 28, 2026 14:55
79a881e to
9bfdba6
Compare
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.
Stacked on #1. This PR contains the full z-slice segment-counting line of inquiry (harness + three variants, previously split across #2–#4, now consolidated here). All results in this PR are on
claude-opus-4-6; the Opus 4.7 re-test of zslice_multi is in #6. Retrospective on the full arc below.Retrospective: z-slice segment-counting (PRs #3–#5)
Hypothesis
Max-intensity projections collapse depth, fusing overlapping body segments into one bright band and destroying the discriminative feature for fold stages — how many times the body folds back on itself. A biologist resolves this by scrolling z-slices and counting body-segment cross-sections at a single plane: ≤1 → 1.5fold, 2 → 2fold, ≥3 → pretzel. The hypothesis was that a VLM subagent, given a non-projected z-slice, could perform this count and use it to override the projection-based classifier on fold stages.
What was tried
Three variants, each refining the previous after a negative result:
zslicezslice_asymzslice_multiAll three run
perceive_scientificfirst and only invoke the subagent when the primary lands on a fold stage, so the subagent never moves predictions into or out of the fold region.Results
n=233 hard-stage frames (1.5fold + 2fold + pretzel), 4 embryos. Baseline is
scientificre-run on the same subset.Override attribution (isolating the subagent's contribution from primary-classifier variance):
Why it did not work
The subagent cannot count. The core assumption — that a VLM can reliably distinguish 2 vs 3 fluorescent-nuclei blobs in an optical section — is false at this image resolution and contrast.
Single midplane returns ≤2 almost always. Across 88 zslice_asym triggers, the count distribution was
{1:40, 2:46, 3:2}— 98% ≤2 regardless of true stage. A pretzel embryo's folds sit at different z-depths, so one plane rarely intersects 3+ segments at separable locations; what does intersect tends to merge into one blob. The symmetriczslicevariant therefore systematically downgraded correct pretzel calls to 2fold/1.5fold (88%→7%).Asymmetric gating works as damage control but reveals zero signal.
zslice_asymrestored pretzel to 85% by refusing to downgrade, but the override only fired 7 times in 88 triggers and was wrong all 7. The large per-stage swings vsscientific(1.5fold −46pp, 2fold +19pp) are run-to-run variance in the primary classifier, not the override.Multi-plane max amplifies noise instead of recovering signal. Taking the max over 5 noisy estimators with a ~20% per-slice false-3 rate yields a ~67% any-slice-says-3 rate. The decisive measurement: max≥3 fires at 56% on GT=2fold vs 55% on GT=pretzel — statistically identical. No depth reliably hits 3 (per-depth mean count on pretzel: 1.83–2.19 across the five planes).
Root cause: in fluorescent-nuclei light-sheet data, body segments at fold stages are dense clusters of bright dots without clear membrane boundaries. The VLM perceives a single z-slice as 1–2 diffuse bright regions; it cannot resolve the gap structure that a human counts after contrast adjustment and scrolling. The count task needs either a different imaging modality (membrane marker) or classical image processing (threshold + connected components) rather than a VLM.
(We later re-tested zslice_multi on
claude-opus-4-7to see if its improved vision rescues segment-counting — it does not; max≥3 still has no 2fold/pretzel separation. See #6 for that result alongside the rest of the 4.7 work.)What's salvageable
midplane_b64/zslices_b64optional kwargs are general infrastructure. Keep, even though the consumer didn't pan out; future variants may use raw slices for other purposes.What's in this PR
Harness (
benchmark/testset.py,run.py):_create_slice_image(volume, z, max_dim)— render one XY z-slice as base64 JPEG, same crop as the projectionTestCasegainsmidplane_b64: str(z=Z//2) andzslices_b64: list[str](z = {0.25, 0.40, 0.50, 0.60, 0.75})run.py:_accepted_optional_kwargs()introspects each variant's signature and passes optional z-slice kwargs only if declared — zero churn to existing variantsVariants (
perception/):zslice.py— scientific + midplane segment-count subagent, symmetric overridezslice_asym.py— upgrade-only override on 1.5fold/2foldzslice_multi.py— max segment-count across 5 z-planes, symmetric overrideDocs:
program.md× 2 — document optional z-slice kwargs.Verification
_accepted_optional_kwargsreturns{'midplane_b64'}for zslice/zslice_asym,{'zslices_b64'}for zslice_multi,set()for all othersreasoningfield in saved predictions