Measuring What Your Eyes Can’t Ignore

Running Rosenholtz’s Feature Congestion on live web pages in the desktop app, from the command line, or via MCP.

A score of 71 out of 100. That’s what Feature Congestion gives arngren.net, the internet’s poster child for visual chaos. Dozens of product images, mixed font sizes, competing colors, text overlapping images, no grid, no hierarchy. Your eyes don’t know where to land.

Your visual system already knew that. Before you read a single word, your brain’s pre-attentive channels were already overwhelmed. The complexity readout in Scrutinizer is an implementation of Rosenholtz et al.’s Feature Congestion measure (2007), an attempt to put a number on that feeling of “this is too much.”

Arngren.net scoring 71/100 in Scrutinizer's ComplexityHUD
Arngren.net: Complexity 71/100. The peripheral vision simulation is already working hard to simplify this page.

What Feature Congestion Measures

Feature Congestion was introduced by Ruth Rosenholtz and colleagues at MIT (Rosenholtz, Li, Mansfield & Jin 2005, CHI) and revised in Rosenholtz, Li & Nakano (2007, Journal of Vision). In their account, clutter depends on how much the content on a page varies locally.

A page of evenly spaced, same-sized, same-colored items has low clutter. A page where every neighboring element differs in size, color, and orientation has high clutter. Scrutinizer’s simplified Feature Congestion measures this as the local variance of low-level visual features across three channels:

These L, a, b channels come from Oklab, a perceptually uniform color space where equal numerical distances correspond to equal perceived color differences. The original measure uses CIELab color covariance, luminance contrast and orientation, each at three scales.

High combined local variance across the three channels is congestion: multiple visual features competing for attention in the same patch of screen.

The score is the 90th percentile of congestion across the page, square-root scaled to spread across 0–100. P90 measures how cluttered the busy parts of the page are and ignores the whitespace margins that would drag a mean score down.


Congestion vs. Saliency

Scrutinizer computes two separate heatmaps, both from established vision science. They look superficially similar but measure different things.

Saliency: “What pops out?”

The saliency map uses center-surround contrast (Difference-of-Gaussians, inspired by Itti, Koch & Niebur 1998). It detects things that are different from their local context. A single red button on a gray page is highly salient. Saliency is relative: the same red button on a red page would score low.

Congestion: “How much is going on here?”

The congestion map measures local feature variance (Rosenholtz et al. 2007). It measures how much variation exists within each local neighborhood, independent of contrast with the surroundings. A dense grid of different-colored products is highly congested even though no single item particularly pops out. Congestion is absolute.

Consider a page with a clean hero section and a dense product grid below the fold. The saliency map lights up the hero headline (it contrasts with the clean background). The congestion map lights up the product grid (high variance in color, size, and text density within every neighborhood).

As of v1.9, the desktop app includes a split-screen comparison mode (Simulation → Utility → Congestion Report → Saliency vs Congestion) that renders both maps simultaneously: saliency in a cool indigo–to–white palette on the left, congestion in the blue–yellow–red heatmap on the right. Seeing them side by side on the same page makes the distinction immediate.

Split-screen view: saliency (purple, left) vs congestion (blue-yellow, right) on Techmeme
Saliency vs Congestion on Techmeme. Left: saliency picks out images, logos, and high-contrast headlines. Right: congestion lights up everywhere text is dense, even where nothing individually pops.
Congestion heatmap on arngren.net showing near-universal high congestion
Arngren.net congestion heatmap. Nearly the entire page glows yellow. Every region has high local feature variance: mixed images, text, colors, and sizes competing in every neighborhood.

Under the Hood

Scrutinizer runs two separate analysis passes: saliency (“what pops out?”) runs continuously to drive the peripheral simulation, and congestion (“how cluttered?”) runs on demand when you open the ComplexityHUD. Both analyze the page image in Oklab color space, which measures color differences the way humans perceive them.

The score blends color/luminance variation (70%) with edge density (30%), using the 90th percentile so the score reflects the busy parts of the page. Validated at Spearman ρ=0.93 (a rank-order correlation, where 1.0 means perfect agreement in how pages are ordered from least to most cluttered) against visual-clutter, a third-party Python port of the Rosenholtz MATLAB toolbox, on 10 images (details in the v1.8 release notes and the congestion journey doc).


Why Scrutinizer’s scores match the research

Scrutinizer’s first validation script scaled the analysis window with image resolution (σ = 2.5 × maxDim/256). On downscaled screenshots that window covered 10–42 native pixels, far more page area than visual-clutter’s σ = 3 px on native-resolution images, so text and buttons blurred together and rank agreement fell to between 0.53 and 0.68.

Scrutinizer uses a fixed analysis window (σ=2.5 pixels, where σ is the standard deviation of the Gaussian kernel that defines the neighborhood size) matched to the feature scale that matters on web pages, roughly the size of a text character. At 512–1024 px captures it pools over a 2–4× smaller page area, closer to the reference, and gives consistent rankings across capture resolutions (ρ = 0.89–0.93 against the same Python port). The full validation pipeline is documented in the congestion journey doc.

Update (2026-10-01): This section previously said the original algorithm scales its window with resolution and that fixed σ works because web captures at different sizes are not scaled copies of each other. The sweep inputs were downscaled copies of the same screenshots, and the change that mattered was a 2–4× smaller pooling neighborhood. The comparison is against visual-clutter, a third-party Python port of the MIT MATLAB code. See the correction note in the congestion journey doc.


What the Ratings Mean

ScoreRatingWhat it looks like
0–25LowApple product hero pages, single-focus layouts
26–50MediumBlog posts, documentation pages, clean e-commerce
51–75HighNews aggregators, dense product grids, data dashboards, arngren.net (~71)
76–100ExtremeAbove arngren.net: multiple competing visual systems

The spatial breakdown (Spatial tab in the HUD) shows which quadrant of the viewport is hottest, which helps identify whether clutter is concentrated in a sidebar, header, or distributed evenly.


CLI & MCP: Scoring Without the GUI

The desktop app is for exploration: moving your mouse, watching the peripheral simulation, toggling the heatmap. But if you want to score pages at scale, or integrate congestion into a CI pipeline, Scrutinizer ships two headless interfaces that run the same analysis engine.

scrutinizer-audit: Command-Line Interface

A headless CLI that crawls pages with Playwright, captures screenshots, and runs Feature Congestion analysis. Lighthouse for visual clutter.

# Single page
scrutinizer-audit https://example.com

# Multiple pages, both viewports
scrutinizer-audit https://apple.com https://amazon.com --viewport desktop,mobile

# CI gate: fail if any page exceeds score 50
scrutinizer-audit --sitemap https://example.com/sitemap.xml --fail-above 50 --json

# Before/after comparison
scrutinizer-audit https://example.com --output before.json
scrutinizer-audit https://example.com --output after.json
scrutinizer-audit --compare before.json after.json

The CLI supports sitemap parsing (--sitemap), URL lists from files (--file), heatmap PNG export (--heatmaps), configurable analysis resolution (--max-dim), scroll positions (--scroll above-fold,first-scroll), and HTML/JSON report output.

MCP Server: AI Agent Integration

For AI-assisted design review, Scrutinizer exposes its analysis as an MCP (Model Context Protocol) server. Claude Code, or any MCP-compatible agent, can call congestion analysis directly during a conversation.

# Register with Claude Code
claude mcp add scrutinizer-audit -- node cli/mcp/server.js

Four tools are exposed:

The three analysis tools launch headless Chromium, capture the page at the requested viewport, and run the same σ=2.5 congestion pipeline. The agent can reason about the scores (“the product grid scores 62, try removing the sidebar ads and re-measure”) without the user leaving the conversation.

Both CLI and MCP share the same analysis core: lib/analyzer.js reads a PNG buffer, computes Oklab channel variance with fixed σ=2.5, and returns the same congestion/edge density statistics as the desktop app’s ComplexityHUD.


Scores in the Wild

To ground the rating scale, here are scores from scrutinizer-audit run against live pages. Desktop viewport (1440 × 900), above-fold capture, σ=2.5.

PageScoreRatingCong p90Edge p90
example.com (near-empty page)0Low0.0000.000
wikipedia.org31Medium0.0840.116
persci.mit.edu/gallery38Medium0.1730.070
apple.com46Medium0.2620.082
persci.mit.edu53High0.2520.359
persci.mit.edu/people/rosenholtz53High0.2740.305
arngren.net~71High——

Rosenholtz’s own page at MIT’s Perceptual Science Group scores 53, in the High band. Older academic sites tend to land here: dense text, heterogeneous formatting, small images mixed with body copy. The gallery page fares better (38) because image grids, despite being visually busy, have more uniform local neighborhoods than text-heavy layouts.

The floor is 0 (example.com is a near-empty IANA placeholder). The practical range for real sites runs from the low 20s up to arngren.net territory at 71. Most production sites cluster in 30–55.


Clutter and Task Performance

Visual clutter has measurable effects on task performance. Rosenholtz’s 2007 paper showed that Feature Congestion predicts visual search time: the higher the congestion, the longer it takes to find a target. For web pages, the question is whether your users can find what they’re looking for.

UX practitioners already knew this empirically. NNGroup’s eye-tracking research found that users scan web pages in an F-shaped pattern, sweeping the top, then progressively less of each subsequent line (Pernice 2017, building on Nielsen’s 2006 eye-tracking studies). The F-pattern is what happens when scanning works, when enough visual structure exists that the eye can sample efficiently and move on. Feature Congestion quantifies when it fails: when local feature variation is so high that no region stands out, the efficient scan breaks down into effortful search.

In information foraging terms (Pirolli & Card 1999), congestion measures where information scent degrades at the pre-attentive level. Users follow scent (visual cues that signal relevant content), but scent requires peripheral sampling to work. When congestion is high, the peripheral summary statistics that support rapid scanning become noisy, and the user needs more fixations to extract the same navigational signal.

The complexity readout gives you a number you can track as you iterate on a design. Did that layout change reduce clutter? By how much? In which region of the page? You can explore visually in the desktop app, gate deployments with the CLI, or let an AI agent measure and suggest improvements through MCP. All three run the same σ=2.5 pipeline.

Update (v2.2): Congestion measurement now feeds back into the rendering pipeline. The congestion heatmap sets a shader uniform (u_congestion_pooling) that increases the V1 stage’s crowding distortion in high-clutter regions, so dense text gets stronger peripheral pooling than clean layouts at the same eccentricity. Congestion now both reports clutter and changes how the peripheral simulation responds to it. Shipped as the default in all research modes with zero measurable frame-time cost. See the v2.2 release notes.


References: Rosenholtz, Li, Mansfield & Jin (2005), “Feature Congestion: A Measure of Display Clutter”, Proc. CHI 2005. Rosenholtz, Li & Nakano (2007), “Measuring Visual Clutter”, Journal of Vision. Original MATLAB implementation on MIT DSpace. Scrutinizer implements a simplified version validated at Spearman ρ=0.93 (rank-order correlation) against visual-clutter, a third-party Python port of that implementation. · Pernice, K. (2017), “F-Shaped Pattern of Reading on the Web: Misunderstood, But Still Relevant (Even on Mobile)”, Nielsen Norman Group.

Links: v1.8 release notes · GitHub · Congestion journey (implementation log) · scrutinizer-audit CLI · MCP server · Model Context Protocol