Skip to main content

Color measurement instrument for vision-language model (VLM) pipelines

Project description

Coriro

License: MIT Python 3.10+

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        │  │
│  Accurate        └────────────────────────────────┘  │
│  Color Measurement                                   │
│                                                      │
└──────────────────────────────────────────────────────┘

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 accurate color measurements 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] onnxtr[cpu] (primary) + pytesseract (fallback)
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

Text detection note: Coriro uses onnxtr[cpu] by default and falls back to pytesseract when needed. The fallback requires the Tesseract system binary. For NVIDIA GPU acceleration, use onnxtr[gpu].


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 OnnxTR/Tesseract
    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 includes torch + timm, consider enabling smooth=True for improved measurement quality.

Resolution: Coriro internally downsamples to 400px max dimension (NEAREST resampling) for palette, spatial, and chroma passes. Text and accent detection run at full resolution. The max_pixels parameter applies an additional user-specified cap before processing — set it for latency-sensitive pipelines.

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, include_spatial=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 }
  ],
  "spatial": {
    "R1C1": { "hex": "#1A1A2E", "coverage": 0.85 },
    "R1C2": { "hex": "#E8453C", "coverage": 0.60 },
    "R2C1": { "hex": "#1A1A2E", "coverage": 0.92 },
    "R2C2": { "hex": "#F5F5F5", "coverage": 0.78 }
  }
}

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 hex values sampled 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 (>1.5 std deviations above achromatic-filtered 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 extracted via mode (exact pixel frequencies). Each region reports dominant color with coverage weight and up to 3 palette entries. 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 — Glyph region detection via OnnxTR (DBNet neural detector, primary) or Tesseract OCR (fallback). Background identified by exterior context ring sampling (3px outside bounding box), with palette-informed validation and pixel-ratio inversion guard. Hue-protected collapse prevents merging distinct color families. 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:

  1. 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.

  2. Smooth (optional) — CNN pixel stabilization (smooth=True). Runs before extraction to reduce compression artifacts, gradient banding, and anti-aliasing before any color analysis.

  3. Color extraction — Extracts a raw palette using vectorized mode-based counting (exact pixel frequencies via NumPy bit-packing, default) or k-means clustering (better for photos and gradients). Colors are represented in OKLCH.

  4. Consolidation — Collapses near-identical colors, merges black/white families, and filters noise. Produces a design-friendly palette at the requested size.

  5. 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.

  6. Spatial binning — Partitions the image into a fixed grid and extracts per-region palettes using mode (exact pixel frequencies). Each region reports a dominant color with coverage weight. Region IDs follow reading order: R1C1 (top-left) through R2C2 (bottom-right) for a 2×2 grid.

  7. Optional passes — Text colors (OnnxTR neural detection with Tesseract fallback, context ring background identification) 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 + spatial coverage
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
Natural language m.to_prompt() Human-readable for system prompts
XML block m.to_xml() Inline context injection (Claude, Qwen)
Native JSON m.to_json() Full schema as JSON
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.


coriro.org · Documentation · GitHub

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

coriro-2.0.0.tar.gz (50.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

coriro-2.0.0-py3-none-any.whl (58.4 kB view details)

Uploaded Python 3

File details

Details for the file coriro-2.0.0.tar.gz.

File metadata

  • Download URL: coriro-2.0.0.tar.gz
  • Upload date:
  • Size: 50.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for coriro-2.0.0.tar.gz
Algorithm Hash digest
SHA256 65b00c7d2a3bafc086b762ead0cba01a967dd98c9b6bf03f661083365b98526c
MD5 02b7d59ab20481d34416a5fc2956aee5
BLAKE2b-256 f2abc61c44d8c235a13f9b2f8f46f91c1ca64941924af0f8b20de16ba3dca9d9

See more details on using hashes here.

Provenance

The following attestation bundles were made for coriro-2.0.0.tar.gz:

Publisher: publish.yml on coriro-org/coriro

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file coriro-2.0.0-py3-none-any.whl.

File metadata

  • Download URL: coriro-2.0.0-py3-none-any.whl
  • Upload date:
  • Size: 58.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for coriro-2.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8c81ce69d0f4fc2ef68d6a2e7db6bbcca47c84919e6017ed47e7b9b0a3262bab
MD5 e413889303398cbe985c18390d342757
BLAKE2b-256 fe4328bb171e5acb05c0afddc43ade346da783f33fcfac5c67a496da958ca836

See more details on using hashes here.

Provenance

The following attestation bundles were made for coriro-2.0.0-py3-none-any.whl:

Publisher: publish.yml on coriro-org/coriro

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page