Skip to content
AbilityBench

Spatial vision

Contrast sensitivity test that returns a curve, not a score

Faint striped patches appear one at a time and you say which way each one leans; a separate staircase at each spatial frequency follows your answers down to the least contrast you can still call, and the result is plotted as a contrast sensitivity function. Free, no account, and every threshold is printed with the criterion it is true at. Because a browser cannot see your screen luminance, your gamma or the light in the room, the page lists those beside the curve rather than pretending they are not in it.

  • 100% free
  • No signup
  • 3 or 5 frequencies
  • Card-and-distance calibration
  • Michelson %

Striped patches appear one at a time, each one leaning either / or \, and each one fainter or stronger than the last depending on how you have been doing. Say which way it leans. Two frequencies or five, 500 ms per patch, and the answer is a curve rather than a score.

Two measurements the browser cannot take

Cycles per degree is a fact about your retina, and a browser knows neither how big a pixel is nor how far away you are sitting. Hold any bank card, driving license or ID card flat against the screen and stretch the outline until the two match — every card in the world is 85.6 mm wide by international standard, which is what makes it a ruler.

That gives 3.74 CSS pixels per millimeter and 39.1 per degree of visual angle, at a device pixel ratio of 1.00. Measure the distance rather than guessing it — a 20% error in distance is a 20% error on the whole x-axis, and it moves the peak of your curve without changing its shape.

How much of the curve
What each frequency becomes on your screen
c/degStripe pairDevice pixelsUsable
1.526.1 CSS px26.1yes
66.5 CSS px6.5yes
123.3 CSS px3.3no — under 4 device px

A stripe pair drawn across fewer than 4 device pixels is being limited by the panel rather than by you, so 1 of these 3 rows will be dropped from the run instead of being measured and reported as though they meant something. Sitting further back raises every figure in the third column, which is the cheapest way to reach a high frequency.

Five unknowns this threshold was measured through

  • Screen luminance. A clinical chart is read at a specified luminance because sensitivity falls as the field gets dimmer. Your brightness slider is somewhere, and nothing here can read it — so a threshold measured at 10% brightness and one at 100% are two different measurements of the same eye.
  • The transfer function. The modulation above is computed in linear light and encoded assuming your display follows the sRGB curve. A panel in a vivid, dynamic or game preset does not, and it will deliver more contrast than the number says at some levels and less at others.
  • Quantization. One code value up and one down around mid gray is 1.69% Michelson at 8 bits per channel — above the threshold a healthy eye reaches near the peak of its curve. This page dithers to get underneath that step and stops at 0.20%, below which the pattern is mostly the dither.
  • Ambient light. Light falling on the panel adds to both the peaks and the troughs, which lowers Michelson contrast at the eye without changing a single number on this page. A window behind you is a measurable difference.
  • The geometry you typed in. 3.74 px/mm at 60 cm gives 39.1 px per degree, and every frequency on the x-axis is that number divided by a stripe width. An error in either input slides the whole curve sideways.

None of these makes the run pointless — a threshold is still a real number about a real event. It makes it a number about you and this screen together, comparable with your own next run on the same screen and not with anybody’s chart score.

Answer with the left and right arrow keys or the two buttons under the patch. Sit still once you have started: the distance you typed in is in every cycles-per-degree figure below, so leaning in halfway through quietly changes the x-axis.

How to measure your contrast sensitivity function

Calibrate the screen against a card, answer which way the stripes lean, read the curve.

  1. Give the page a ruler and a distance

    Hold a bank card flat against the screen and stretch the dashed outline until the two are the same width — the ID-1 format is 85.6 mm everywhere in the world, which turns a card into a calibration target. Then measure how far your eyes are from the panel and type it in centimeters rather than estimating: a 20% error there slides every point on your curve sideways by 20%, which moves the peak without changing the shape and makes the run look like a different eye.

  2. Answer which way each patch leans

    Patches appear for half a second each, either sloping like a forward slash or like a backslash. Press the right or left arrow key, or use the two buttons under the patch. Guess when you genuinely cannot see it — a staircase is built out of the errors you make near threshold, and refusing to answer starves it of the only data it wants. Keep your head still and your distance constant, because both were measured once at the start.

  3. Read each frequency separately

    The curve plots sensitivity, which is one divided by the threshold contrast, against cycles per degree on log axes. Beside it, a table gives each frequency's threshold, the number of reversals that went into it and the spread of those reversals, which is the precision that particular run earned. A frequency marked as pinned at a limit spent its trials against the floor or ceiling of what a screen can draw, and no threshold is reported for it.

Technical specifications

StimulusA Gabor patch 320 CSS pixels square: a sine grating under a Gaussian envelope with a standard deviation of one sixth the patch, so the stripes fade out before the edge instead of ending on a hard border that would give away the orientation
Task and criterionTwo-alternative forced choice on orientation, plus or minus 45 degrees. A 1-up 2-down staircase steps down after two correct and up after one wrong, which converges where 70.7% of answers are right — that is the criterion the threshold is true at, and a threshold quoted without one is not a claim
Spatial frequencies1.5, 6 and 12 cycles per degree on the quick run; 1.5, 3, 6, 12 and 18 on the full one. They are interleaved rather than blocked, so the tired end of the session lands on all of them equally instead of on whichever went last
Contrast stepsGeometric, halving at the start and shrinking by a square root at each reversal down to a floor ratio of 1.15. Contrast is judged on a ratio scale, so a fixed additive step is enormous near threshold and invisible far from it
Stopping and averaging6 reversals per frequency on the quick run and 8 on the full one, capped at 45 patches. The threshold is the mean of what is left after the first 2 reversals are discarded as the approach, and the standard deviation of those reversals is printed beside it
The quantization floorOne code value above and below mid gray is about 1.7% Michelson contrast on an 8-bit channel, which is above the threshold a healthy eye reaches near the peak of its own curve. The patches are dithered by stochastic rounding to get under that step, and the staircase stops at 0.2% because below that the pattern is mostly the dither
Frequencies that get droppedAny frequency whose stripe pair would fall on fewer than 4 device pixels is removed before the run and listed as removed. Below that the panel is the limit rather than the eye, and the number it produced would be a measurement of pixel pitch
Reference figures printedPeak sensitivity at 2-6 cycles/degree — Campbell & Robson (1968), Application of Fourier analysis to the visibility of gratings, Journal of Physiology — and 30 cycles/degree as the spatial-frequency equivalent of an eye chart's 20/20 line. Neither is converted into a rank for you

Frequently asked questions

Why does my sensitivity go down at the coarsest stripes as well as the finest?

Because the visual system subtracts, and very wide stripes give it nothing to subtract. Retinal ganglion cells respond to the difference between a small center and the ring around it, so a pattern whose light and dark regions are far wider than that receptive field puts the same luminance across both halves and the response falls away. Fine stripes fail for the opposite reason — they wash out inside the optics and the receptor spacing. What is left is a peak in the middle, at 2-6 cycles/degree in healthy young eyes, which Campbell & Robson (1968), Application of Fourier analysis to the visibility of gratings, Journal of Physiology established by measuring exactly this function. A test that shows only small high-contrast letters samples the far right of that curve and calls it vision.

Is this a Pelli-Robson chart?

No, and the difference is more than the stimulus. A Pelli-Robson chart is a wall chart of letter triplets, all the same large size — roughly one cycle per degree at the specified three metres — where contrast falls by a fixed step from triplet to triplet, and the score is the log contrast sensitivity of the last triplet you read. It samples one spatial frequency thoroughly. This page samples several frequencies with a staircase at each, which is the laboratory form of the measurement rather than the clinical one, and it produces a function rather than a single log score. The chart also assumes a stated chart luminance and a stated distance, both of which a browser gives up.

Why does display gamma matter so much here?

Because a sine wave drawn in code values is not a sine wave in light. Every sRGB display maps code values through roughly a 2.2 power before they become photons, so a pattern that looks like a smooth ripple in the numbers arrives at the eye with its dark half stretched and its light half compressed — a different waveform with a different effective contrast and harmonics that were never in the original. This page computes the modulation in linear light and encodes it afterwards, which makes the stated Michelson figure true of the pattern that leaves the code. Whether it stays true depends on whether your panel actually follows the sRGB curve, and in a vivid, dynamic or game preset it does not.

The patches look grainy. Is something broken?

That grain is deliberate and it is what makes the low end measurable. An 8-bit channel cannot represent a contrast smaller than one code value, and one code value around mid gray is already coarser than a good eye's threshold at the peak, so a plain rendering would hit a wall well before it reached you. Stochastic rounding fixes the average: each pixel rounds up with a probability equal to the fraction it is short by, so the mean luminance across any small patch is exactly right and the error becomes high-frequency noise instead of a step. The cost is real — that noise sits on top of the grating and raises the measured threshold slightly — which is why it is said out loud rather than hidden.

Why does the page stop and refuse to give a number sometimes?

Because the staircase spent its trials pinned against a limit of the apparatus, and averaging those would produce a threshold that is really a description of a monitor. It happens at both ends. Someone whose sensitivity at 3 cycles per degree is better than the dithered floor keeps answering correctly while the contrast has nowhere left to go, and someone who cannot see the finest grating at any contrast the page will draw pushes it against the ceiling instead. The engine flags a staircase that clamped, and the table says pinned at a limit rather than printing the floor as though it were your threshold.

Should I take my glasses off?

Wear whatever you normally read that screen with, and say which in your own notes. Uncorrected refractive error is a contrast problem before it is an acuity problem: defocus blurs fine detail first, so it pulls the right-hand end of the curve down long before an eye chart notices, and a run taken without your usual correction is measuring the correction. Contact lenses, an anti-glare coating and a dirty screen all belong on the same list. The comparison this page can support is you against you under the same conditions, so keep the conditions written down with the numbers.

Can I compare my curve with a published one?

Compare the shape, not the height. The position of the peak and the slope of the fall-off are properties of your visual system and survive a rough calibration, so a curve that peaks near the middle and drops away on both sides is the right shape and one that rises monotonically to the right is a sign the distance input is wrong. The absolute sensitivities are a different matter: published curves come from calibrated CRT or laboratory-grade displays at a measured luminance, and the vertical offset between those and a browser run is unknown and probably large. This is arithmetic, not a norm — it is here so a contrast page can place its finest grating on the same axis as the acuity figure a visitor already knows from an eye chart. It says nothing about whether any given person reaches it, and a monitor at an unknown distance cannot present a calibrated one-minute stroke in the first place.

What the contrast sensitivity function is, and what a monitor does to it

Acuity asks one question — how small a detail can you resolve at full contrast — and that question is the far right edge of a much larger answer. Campbell and Robson showed in 1968 that the visual system behaves, over a useful range, like a bank of channels each tuned to a band of spatial frequencies, and that the least contrast needed to see a grating traces a curve with a maximum in the middle. Sensitivity peaks at 2-6 cycles/degree and falls away on both sides, which is why an eye chart can call somebody 20/20 while they still struggle with a face in a dim corridor or the edge of a kerb at dusk: those are low-contrast, low-frequency problems, and no letter chart samples them. 30 cycles/degree is where the 20/20 line itself lands on this axis, by arithmetic rather than by measurement — one minute of arc per stroke, two strokes per cycle, sixty minutes in a degree.

The reason this page hands back a curve and never a chart score is stated by the module this site takes its reference figures from, which refuses to supply the cut scores at all: Clinical charts are printed at a specified luminance and viewed at a specified distance, and the cut scores assume both. A browser has no control of screen luminance, gamma or ambient light, so the same visitor scores differently on two monitors. So the run reports the lowest contrast you actually detected at each frequency, as a raw Michelson percentage, with the number of reversals that produced it and the spread of those reversals. Three of the unknowns behind that number can at least be named precisely. Screen luminance sets the whole curve’s height and is not exposed to any web API. The transfer function decides whether the modulation computed here survives into light, and a display in a picture preset does not follow it. And the panel’s bit depth sets a hard floor: one code value up and one down from mid gray is about 1.7% contrast, coarser than a healthy threshold at the peak, which is the specific technical reason most browser contrast tests quietly measure their own quantization and call it eyesight.

That is also what separates this page from its neighbors here. Contrast is the one visual quantity whose absolute value a browser cannot pin down at all, whereas the optical illusion test asks a question about the ratio of two lengths on the same screen, which survives whatever the panel is doing to luminance, and the color perception test has the same problem in the chromatic dimension rather than the luminance one. If the calibration step here interested you, the same geometry — screen size, viewing distance, angles rather than pixels — is what the peripheral vision test and the stereopsis test are built on, and the depth perception test computes its disparities from the same three inputs.

The ruler this page asks you to hold up

ID-1 card width: 85.6 mm wide by 53.98 mm tall.

ISO/IEC 7810, Identification cards — Physical characteristics, format ID-1 — 85.60 mm by 53.98 mm, with a tolerance small enough to be irrelevant at screen resolution

This is a manufacturing standard rather than a measurement of anything, which is exactly why it works as a ruler: the card in your wallet is the same width as the card in everybody else's. What it cannot fix is the second input — how far your eyes are from the screen — which nothing on the desk can standardize for you.

A reaction time here is the interval between the frame that painted the stimulus and the timestamp the browser attached to your key, both read from the same monotonic clock. What neither can see is the display pipeline behind it, so on a 60 Hz screen roughly 16 ms of every figure below is the machine rather than you. That is the timing floor: two numbers closer together than that are the same number, and this page reports no precision it cannot support.

This is a measurement exercise, not a clinical assessment. It reports what you did on this page against a stated reference and nothing more — it cannot establish a cataract, glaucoma, an uncorrected refractive error or any other cause of reduced contrast sensitivity. Only a qualified professional, working with more than a browser, can make that judgment.

Where your thresholds are worked out

Every number on this page is worked out by JavaScript running in the tab you are reading it in. Your answers, your reaction times and your score are never uploaded, logged or kept — which is also why the test carries on working after you disconnect from the network, and why nothing here can be held back behind an email address.

The card width and the viewing distance you type in stay in this tab with everything else — they are not written to storage, so they have to be set again on a return visit, and a reload during a run loses the staircases. The copy button is the only way anything leaves, and it goes to your clipboard with the calibration attached, because a threshold without the distance it was measured at cannot be compared with your own next run.