Draft: Chromostereopsis illusion (notes + stimulus prototype) - #24
Draft
DominiqueMakowski wants to merge 5 commits into
Draft
DominiqueMakowski wants to merge 5 commits into
DominiqueMakowski wants to merge 5 commits into
Conversation
Placeholder submodule holding a condensed review of chromostereopsis (the illusion that red appears nearer than blue on a flat image), to brainstorm the implementation from. Nothing is exported and the submodule is not imported from the package __init__, since the API is not settled. The notes end with the open design questions: chromostereopsis has no objective depth attribute, so how it maps onto Pyllusion's difference / illusion_strength pair needs deciding before any code is written. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two square panels side by side, each a dithered coloured disc inside a dithered coloured surround with a bare annulus between them, and the colour roles swapped between panels. This suggests a way out of the "no objective depth attribute" problem noted in the previous commit: move the judged attribute from depth to size, so difference = objective size difference between the two discs (as in Delboeuf/Ebbinghaus) and illusion_strength = the signed colour assignment. Rests on chromostereopsis biasing size via size constancy, which is a hypothesis and flagged as such. Rendering it also turned up a luminance confound: with the default geometry the red-surround panel is ~49% brighter than the blue-surround one, since red is ~3x more luminous than blue in sRGB and the disc and surround have different areas. Matching disc area to surround area (radius 0.757 at gap 0.08) brings the panels to within dither noise. Still a prototype, not exported. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
difference is now the objective size difference between the two discs (Delboeuf/Ebbinghaus semantics). illusion_strength stays deferred: only its sign is wired up, selecting which panel gets the color1 disc. Adds _chromostereopsis_parameters() following the package convention, exposing the factors from the literature review as knobs so their effect can be measured: density / density_inner / dither_size / dither_shared for patterning, luminance1 / luminance2 / equiluminant for luminance, background for contrast, color1 / color2 for the pairing, and radius / gap / area_matched for geometry. Luminance scaling is done in linear light rather than on code values, so 0.5 is really half the luminance. The dict also reports derived quantities (per-colour luminance and contrast, areas, and the predicted mean luminance of each panel), computed analytically so a condition's brightness confound can be read off without rendering it; agrees with renders to within dither sampling noise. Two things worth recording: on a black background Michelson contrast is 1 for every colour, so a non-black background is needed to manipulate contrast at all; and mid-grey #808080 is very nearly isoluminant with pure red. Both noted in the docstrings. Dither patterns are now shared between panels by default, leaving colour assignment as the only difference between them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sizes and distances now use Pyllusion's grid units instead of ad-hoc panel-relative ones, with defaults matching Delboeuf: size=0.25 and distance=1 give 75px discs with centres 400px apart on the default 800x600 canvas, verified identical to pyllusion.Delboeuf(). The disc maps to Delboeuf's inner circle (the judged target), the square panel to its outer circle (the context). The "black outline" between square and circle was already there as `gap`; it is now in grid units like everything else and documented as such. Raising it grows the area-matched panel with it. area_matched is replaced by size_panel=None, which computes the area-matched panel side from a closed form (no more scipy solver), so the brightness confound is off by default rather than opt-in. Two fixes found while verifying against Delboeuf: - The disc came out 73px instead of 75 because a panel-relative radius was discretised twice. The panel now takes its radius and gap in pixels, converted once from grid units the same way _coord_circle() does it. - dither_size=10 suited the old 500px panels but is ~10% of a Delboeuf-matched 102px panel, which wrecks the disc. Default is now 2px (~50 cells across, matching the proportions of the reference image), and Dither_Cells_Across is reported so the look can be held constant when rendering at other scales. Noted in the notes: matching Delboeuf makes the stimulus small, and the literature reports the effect as stronger for larger stimuli, so the matched defaults may sit at the weak end. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The discs stay pinned to Delboeuf's geometry; only the panels change. size_panel now takes three forms: None (the default) fills each half, its side equal to the centre-to-centre distance in pixels, so the surround is the background of the stimulus rather than a patch floating on black; "match" gives the area-matched size; a float is anything in between. Resolving the automatic size needs the aspect ratio, since Pyllusion sizes scale with height and positions with width, so the parameters function now takes width/height and the image function forwards its own. Two consequences, both measured and documented rather than hidden: - Filling the halves puts the surround at ~35x the disc area, so the red-surround half is 2.7x brighter than the blue-surround half (0.069 vs 0.025) - worse than the imbalance the area match was introduced to fix. equiluminant=True removes it exactly at any panel size (measured ratio 0.99), because equal-luminance colours make the two panels hold the same coloured area just swapped. For large panels it is effectively required, not optional. - The panels now touch at the midline, putting a hard red|blue edge down the centre. Border contrast is reported in the literature as able to reverse perceived depth, so this is not neutral; size_panel slightly below the automatic value inserts a gutter. Also fixes two things found while verifying against Delboeuf: - `difference` used a symmetric +/- split, but Delboeuf defines it on area (smaller disc stays at size, larger scales by sqrt(1+|d|)). The two now agree exactly at every difference, which is the whole point of matching. difference=0.3 gave a 1.35 diameter ratio, now 1.14 as in Delboeuf. - The disc lost a pixel (74 instead of 75) on even-sized panels, because the centre fell between pixels. The disc centre is now placed on a pixel, giving 75px at every panel size. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Draft work towards a Chromostereopsis illusion. Nothing is exported — the submodule is not
imported from
pyllusion/__init__.py, so the public API is unchanged. Targetsdevso it stacks ontop of #23.
Chromostereopsis is the illusion that, on a flat image, red appears nearer than blue. It is
binocular: covering one eye largely abolishes it, and its strength depends on the viewer's optics
and the display. That is the central constraint here — unlike the size illusions, the image alone
cannot guarantee the effect.
The stimulus
Two square panels side by side, each a dithered coloured disc inside a dithered coloured surround
separated by a bare annulus, with the colour roles swapped between panels: a red disc on blue on one
side, a blue disc on red on the other. The panels are geometrically identical and use the same two
colours, so they differ only in which colour is figure and which is ground.
How it maps onto Pyllusion's two parameters
Chromostereopsis has no objective depth for
differenceto manipulate — a flat PNG has none. Theswapped-panel layout gets around this by moving the judged attribute from depth to size:
difference— the objective size difference between the two discs, defined exactly as inDelboeuf and Ebbinghaus (smaller disc stays at
size, larger scales bysqrt(1 + abs(difference)),so it is an area difference). Verified to produce identical disc sizes to
Delboeufat everyvalue tested.
illusion_strength— deferred by agreement. Only its sign is wired up, selecting which panelgets the
color1disc. What the magnitude should scale is still open.not an established finding, and it is flagged as such in the notes. If it does not hold, the design
falls back to a depth judgement with no ground truth.
Geometry
Matched to Delboeuf so the two can share a battery:
size=0.25anddistance=1give 75px discs withcentres 400px apart on the default 800x600 canvas, measured identical to
pyllusion.Delboeuf(). Onlythe discs are tied to Delboeuf;
size_panelsizes the surround separately and by default fills eachhalf of the image, so the surround acts as the stimulus background.
Parameters exposed for study
The factors from the literature review are exposed as knobs so their effect can be measured:
density/density_inner/dither_size/dither_shared(patterning),luminance1/luminance2/equiluminant(luminance),background(contrast),color1/color2(pairing),size/size_panel/gap/distance(geometry),seed.Luminance scales in linear light, not code values. The parameters dict also reports derived
quantities — per-colour luminance and contrast, areas, and each panel's predicted mean luminance,
computed analytically and agreeing with renders to within dither noise.
Things worth knowing
red-surround half ~2.7x brighter than the blue-surround half.
equiluminant=Trueremoves thisexactly at any panel size, and for large panels should be considered required rather than optional.
is reported as able to reverse perceived depth, so it is not neutral; a slightly smaller
size_panelinserts a gutter.a non-black background.
Chromostereopsis.pyends with a "Sources to verify" section: it wascompiled from a review that has not been checked against primary sources, and one citation is
attributed to two different first authors. Not for user-facing docs as-is.
Still open
The magnitude of
illusion_strength; whether the size-bias assumption holds; whether to offer abar/grating variant to stay comparable with the literature (the published effect sizes used bars, not
discs). No
Chromostereopsisclass yet — the natural time to add one is onceillusion_strengthissettled.
🤖 Generated with Claude Code