Color measurement instrument for vision-language model (VLM) pipelines
Project description
Coriro
Color measurement runtime for vision-language and multimodal inference pipelines.
Coriro extracts color data from image pixels — dominant colors, weighted palettes, spatial distribution — and passes them alongside the image into any vision-language or multimodal model pipeline.
Core runtime is pure Python. No GPU required. Zero lock-in.
from coriro import measure
m = measure("image.png")
m.to_json() # Raw measurement JSON (schema)
m.to_xml() # XML block for context injection
m.to_prompt() # Natural language for system prompts
Why Coriro exists
It reads actual image pixels, extracts palettes in perceptually uniform OKLCH, consolidates near-identical colors, catches high-saturation outliers, and maps spatial distribution. The results are output as structured data (JSON, XML, natural language) that any pipeline can consume.
The output includes measurement criteria, coverage thresholds, collapse distances, and palette caps, so the receiving model knows exactly what was measured and what was excluded.
The most immediate reason to use it: multimodal and vision-language models lose color precision during vision encoding. Patch-based encoders downsample and average pixels, so the language model generates color values that are sometimes close, often wrong. Coriro runs pixel-level colorimetry outside the model and passes the results as plain text, the channel where language models are already precise.
But the value extends beyond compensating for encoder limitations. Even a model with perfect color perception does not produce a consolidated, weighted palette in a perceptually uniform color space with spatial distribution and measurement metadata. That is a measurement task. Coriro's output is useful as pipeline data, whether the model's vision is approximate or exact.
┌──────────────────────────────────────────────────────┐
│ VLM Pipeline │
│ │
│ ┌──────────┐ ┌────────────────────────────────┐ │
│ │ Image │───▶│ Vision Encoder │ │
│ └──────────┘ │ Sees layout, structure, UX │ │
│ │ │ Approximate color │ │
│ │ └────────────────┬───────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────┐ ┌────────────────────────────────┐ │
│ │ Coriro │───▶│ Language Model Context │ │
│ │ measure()│ │ Image + Color sidecar data │ │
│ └──────────┘ │ = Complete information │ │
│ Pixel-level └────────────────────────────────┘ │
│ colorimetry │
│ (sidecar) │
└──────────────────────────────────────────────────────┘
The sidecar principle: Coriro output runs alongside the image, not as a replacement. The model gets the image for layout, hierarchy, and semantics, plus measured color data for precise implementation. Remove Coriro from the pipeline and the model works exactly as before. No weights are modified. No configuration is changed.
Use cases
- VLM color grounding — Provide measured palettes as structured data alongside the image, compensating for vision-encoder color loss documented in published benchmarks across many models.
- Screenshot-to-code — Provide exact hex values and spatial color distribution so code generation uses measured colors instead of vision-inferred estimates.
- Product color metadata — Attach verifiable weighted palettes to product listings for perceptual color search, cross-SKU consistency checks, and return-reduction workflows.
- Design system compliance — Measure rendered screenshots against design token definitions using perceptual ΔE distance to catch cross-platform drift.
- Accessibility contrast auditing — Compute contrast from rendered pixels in perceptually uniform OKLCH, including text over gradients and background images that DOM-only scanners can miss.
- Color-aware asset search — Index images by weighted palette and spatial distribution for retrieval by color proportion, placement, and perceptual similarity.
Install
pip install coriro
Core installation requires only numpy and Pillow. Optional passes have separate dependencies:
| Feature | Install | Requires |
|---|---|---|
| Text colors | pip install coriro[text] |
pytesseract + system Tesseract OCR |
| Accent regions | pip install coriro[accents] |
scipy |
| CNN smoothing | pip install coriro[cnn] |
torch, timm |
| All features | pip install coriro[all] |
All of the above |
Quick start
Measure an image
from coriro import measure
m = measure("image.png")
# Dominant color (OKLCH)
print(m.dominant.hex) # hex from a real pixel in the cluster
print(m.dominant.L) # Lightness (0.0–1.0)
print(m.dominant.C) # Chroma (0.0–~0.32)
print(m.dominant.is_achromatic) # True when C < 0.02
# Palette (weighted, most dominant first)
for wc in m.palette:
print(f"{wc.color.hex} — {wc.weight:.0%}")
# Spatial distribution
region = m.spatial.get_region("R1C1") # Top-left
print(region.dominant.hex)
Optional passes
Three additional passes are available. Each is off by default and independently toggleable:
m = measure(
"image.png",
smooth=True, # CNN pixel stabilization (requires torch + timm)
include_text=True, # Text foreground colors via OCR (requires pytesseract)
include_accents=True, # Solid accent region detection (requires scipy)
)
CNN smoothing stabilizes color surfaces before extraction — reducing noise from compression artifacts, gradient banding, and anti-aliasing. It is off by default because it requires
torch+timm(pip install coriro[cnn]). If your environment already includestorch+timm, consider enablingsmooth=Truefor improved measurement quality.
Latency control: Coriro processes images at full resolution by default (
max_pixels=0). For latency-sensitive pipelines, setmax_pixelsto cap the pixel count before processing. Because color measurement is statistical, equivalent palettes are usually preserved at reduced resolution.
Pipeline integration
from coriro import measure
from coriro.runtime import to_tool_output
m = measure("image.png", include_text=True)
# Structured measurement — pass to your pipeline
coriro_data = to_tool_output(m, consolidated=True)
The consolidated format produces:
{
"tool": "coriro_color_measurement",
"measurement": {
"version": "1.0",
"scope": "area_dominant_surfaces",
"coverage": "complete",
"thresholds": { "min_area_pct": 1.0, "delta_e_collapse": 0.03 },
"palette_cap": 5,
"spatial_role": "diagnostic"
},
"dominant": { "hex": "#1A1A2E", "oklch": "L0.23/C0.04/H283" },
"palette": [
{ "hex": "#1A1A2E", "oklch": "L0.23/C0.04/H283", "weight": 0.62 },
{ "hex": "#E8453C", "oklch": "L0.63/C0.20/H28", "weight": 0.23 },
{ "hex": "#F5F5F5", "oklch": "L0.97/C0.00", "weight": 0.15 }
]
}
The measurement block tells the receiving system: this palette is complete above 1% area with ΔE > 0.03 between entries. Any color not listed is below those thresholds. Omission becomes signal.
Advanced integration
The simplest use is appending Coriro output as text alongside the image in a VLM prompt when building your system prompts. But the schema supports integration at any level of your architecture.
Measurements are normalized numerical values (L, C, H in perceptually uniform ranges, weights summing to 1.0) with explicit thresholds. Depending on your pipeline, you can:
- Convert palette values into auxiliary feature vectors for multimodal fusion
- Map measurements through a projection layer into your model's embedding space
- Inject color features as auxiliary tokens alongside vision embeddings
- Concatenate structured color vectors with image embeddings before downstream processing
- Use measurements as conditioning signals for adapters, LoRA modules, or FiLM-style modulation layers
- Feed data into custom embedding or feature store pipelines
Because Coriro exposes explicit perceptual thresholds (ΔE collapse distance, coverage floor), these measurements function as deterministic, reproducible conditioning signals. Not heuristic approximations that vary with prompt phrasing.
Coriro does not prescribe how the data is consumed. It provides structured, perceptually grounded color measurements. Whether you integrate them as prompt metadata, auxiliary embeddings, model conditioning, or pipeline features is implementation-specific.
For serialization formats and code examples, see the documentation.
Deployment
Coriro is a Python library, not an HTTP server. It integrates in two common ways:
- Local Python pipeline (notebooks, scripts, backend workers) — Import
measure()in the same Python process that builds model requests, tool payloads, or feature pipelines. Coriro output is available as JSON, XML, or natural language. Pass it however your architecture consumes structured data. - Hosted sidecar API (for JS/Next.js/Vercel frontends) — Run Coriro behind a small Python service that you host separately. Your frontend sends the image, receives the color measurement payload, and integrates it into your pipeline.
This package ships the measurement and serialization library only. If you need an HTTP endpoint, wrap it in any Python web framework. A minimal FastAPI example:
from fastapi import FastAPI, UploadFile
from coriro import measure
import tempfile, os
app = FastAPI()
@app.post("/measure")
async def measure_image(file: UploadFile):
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".png")
tmp.write(await file.read())
tmp.close()
m = measure(tmp.name)
os.unlink(tmp.name)
return m.to_dict()
That's the entire service — pip install coriro fastapi uvicorn python-multipart, run with uvicorn app:app, and your frontend has a color measurement endpoint.
What it measures
Core (always on)
- Dominant color — The single most prominent color, extracted from the weighted palette
- Area-ranked global palette — Colors ordered by pixel area, represented in perceptually uniform OKLCH with implementation-accurate
sample_hexvalues from real pixels - ICC profile conversion — Automatically converts Display P3, Adobe RGB, and other profiled images to sRGB before measurement via
PIL.ImageCms, ensuring colors match what color pickers show - Color consolidation — Collapses near-identical colors using perceptual ΔE thresholds in OKLab space, merges black/white families into single representatives, and limits output to a design-friendly palette size
- Chroma-aware supplementation — Two safety-net passes catch perceptually significant colors missed by area-dominant extraction:
- Chroma outliers: High-saturation pixels via z-score (>2.0 std deviations above mean chroma). Catches a yellow CTA button on a low-chroma blue page.
- Uncovered colors: Clusters pixels with ΔE > 0.15 from the nearest palette entry. Catches distinct color groups below the area threshold.
- Spatial color distribution — Fixed grid partitioning (2×2, 3×3, or 4×4) with per-region palettes. Preserves where colors appear — not just which colors exist
- Closed-world measurement metadata — States the palette's measurement criteria (coverage floor, collapse distance, palette cap). If a color isn't listed, it's below the threshold — omission is signal, not oversight
Optional passes (independently toggleable, off by default)
- Text foreground colors — OCR-assisted glyph region detection via Tesseract, extracting foreground colors with background exclusion. Uses original (unsmoothed) pixels for accuracy
- Solid accent regions — Connected-component detection for small but significant solid-color UI elements (CTAs, icons, badges) that fall below area-dominant thresholds. Filters by absolute pixel count, not percentage
- CNN-guided pixel stabilization — Shallow ConvNeXt stem (stem + stage 1 only) for reducing compression artifacts, gradient banding, and anti-aliasing. A stabilizer, not an interpreter — measurement logic remains the authority
How it works
Calling measure() runs a seven-stage pipeline:
-
Load & convert — Reads the image, converts ICC-profiled pixels (Display P3, Adobe RGB) to sRGB via
PIL.ImageCms. NumPy arrays are accepted directly but must already be sRGB. -
Smooth (optional) — CNN pixel stabilization (
smooth=True). Runs before extraction to reduce compression artifacts, gradient banding, and anti-aliasing before any color analysis. -
Color extraction — Extracts a raw palette using mode-based counting (exact pixel values, default) or k-means clustering (better for photos and gradients). Colors are represented in OKLCH.
-
Consolidation — Collapses near-identical colors, merges black/white families, and filters noise. Produces a design-friendly palette at the requested size.
-
Chroma supplementation — Two passes recover perceptually significant colors missed by area-dominant extraction (high-saturation outliers, uncovered color clusters). Both filter by novelty against the existing palette to avoid duplicates.
-
Spatial binning — Partitions the image into a fixed grid and extracts per-region palettes. Region IDs follow reading order:
R1C1(top-left) throughR2C2(bottom-right) for a 2×2 grid. -
Optional passes — Text colors (OCR) and accent regions (connected components). Each is independently toggleable and does not affect the core palette.
Serialization
Coriro separates measurement from delivery. The same ColorMeasurement can be serialized for different pipeline targets:
| Format | Function | Use case |
|---|---|---|
| Consolidated JSON | to_tool_output(m, consolidated=True) |
Recommended. Hex + OKLCH + weights in one object |
| Compact JSON | to_tool_output(m, compact=True) |
Token-constrained pipelines |
| Hex-only | to_tool_output(m, hex_only=True) |
Minimal output. Implementation tasks only |
| Full JSON | to_tool_output(m) |
Complete OKLCH data with all metadata |
| XML block | to_context_block(m, format=BlockFormat.XML) |
Inline context injection (Claude, Qwen) |
| Natural language | to_system_prompt(m) |
Human-readable for system prompts |
| Markdown | to_context_block(m, format=BlockFormat.MARKDOWN) |
Markdown-fenced JSON |
See the documentation for more details.
Contributing
Coriro is open source under the MIT license. Contributions are welcome.
License
MIT License. Copyright © 2026 Saad Irfan. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file coriro-1.0.0.tar.gz.
File metadata
- Download URL: coriro-1.0.0.tar.gz
- Upload date:
- Size: 44.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
59fb20516a78214b29daa3d02d0d9b963e99b113c7adae01973ad81fbe9556c2
|
|
| MD5 |
5c6377157220642adc02aba51309caaa
|
|
| BLAKE2b-256 |
62dede5d109d21ed83f196690eff6d039c0c9aefa849b030ff58dba9c43e1b8e
|
Provenance
The following attestation bundles were made for coriro-1.0.0.tar.gz:
Publisher:
publish.yml on coriro-org/coriro
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
coriro-1.0.0.tar.gz -
Subject digest:
59fb20516a78214b29daa3d02d0d9b963e99b113c7adae01973ad81fbe9556c2 - Sigstore transparency entry: 1004608157
- Sigstore integration time:
-
Permalink:
coriro-org/coriro@47ed9afd0e3d6326aedfa039731f7d6c981a1818 -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/coriro-org
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@47ed9afd0e3d6326aedfa039731f7d6c981a1818 -
Trigger Event:
release
-
Statement type:
File details
Details for the file coriro-1.0.0-py3-none-any.whl.
File metadata
- Download URL: coriro-1.0.0-py3-none-any.whl
- Upload date:
- Size: 53.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5a7a78eaac641048a1ff2b5d231d2736eff82bee2ae51dcbd97118709f0f0d37
|
|
| MD5 |
65e9f34cec1f4adacb6ffa5b78b66998
|
|
| BLAKE2b-256 |
3c91e747d7513fc7dfd0c31914a4dafa113bbebdebfcdbc78a08e068f2cd559d
|
Provenance
The following attestation bundles were made for coriro-1.0.0-py3-none-any.whl:
Publisher:
publish.yml on coriro-org/coriro
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
coriro-1.0.0-py3-none-any.whl -
Subject digest:
5a7a78eaac641048a1ff2b5d231d2736eff82bee2ae51dcbd97118709f0f0d37 - Sigstore transparency entry: 1004608162
- Sigstore integration time:
-
Permalink:
coriro-org/coriro@47ed9afd0e3d6326aedfa039731f7d6c981a1818 -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/coriro-org
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@47ed9afd0e3d6326aedfa039731f7d6c981a1818 -
Trigger Event:
release
-
Statement type: