Skip to main content

colorcc

Connected components of every colour at once, on the GPU.

Exact region labels for RGB images in PyTorch, on NVIDIA and Apple GPUs.

PyPI Python test License: MIT

How it works, with live figures: read the post on the Fablnx engineering blog →

A drawing and colorcc's labels: every region of every colour

Why

Most connected-component labellers (OpenCV, SciPy, cuCIM's binary mode) see black and white. Real images, and construction drawings in particular, are made of many exact colours, with anti-aliased edges in between. colorcc gives every exact colour its own regions, in one pass over the image, with no loop over colours:

The same pixels: a black-and-white labeller makes all ink one region; colorcc separates each exact colour

Two pixels belong to the same region when they touch (4 or 8 neighbours) and have exactly the same RGB value. Labels run 1..n in raster order of each region's first pixel, so the output is identical, value for value, to cc3d.connected_components on the packed colours. One colour, such as white paper, can be ignored: it gets label 0 and joins nothing.

Install

Install PyTorch first, then:

pip install "colorcc[cuda12]"    # NVIDIA, PyTorch built for CUDA 12
pip install "colorcc[cuda13]"    # NVIDIA, PyTorch built for CUDA 13
pip install colorcc              # Apple silicon

Nothing is compiled at install time and no CUDA toolkit is needed: the wheel is pure Python. The kernels are compiled at the first call for the GPU you have, by NVRTC through CuPy on NVIDIA GPUs and by Metal through PyTorch on Apple GPUs (PyTorch 2.6 or later). CuPy caches the result, so only the first call in a fresh environment pays for it. Python 3.10 or later.

Quickstart

import numpy as np, torch, colorcc
from PIL import Image

rgb = np.array(Image.open("drawing.png").convert("RGB"))
img = torch.from_numpy(rgb).to("cuda")                          # or "mps"
labels, n = colorcc.label(img, conn=8, ignore=(255, 255, 255))  # labels: [H, W] int32, 1..n; paper 0

examples/label_png.py does this and saves the regions as a colour-coded PNG (pip install pillow):

python examples/label_png.py drawing.png labels.png

Benchmarks

On an RTX 3070, with the picture already on the GPU, the full 806-megapixel drawing takes 89 ms:

3 MP render 806 MP sheet
colorcc 0.50 ms 88.9 ms
NVIDIA cuCIM label() 2.73 ms (5.4x slower) 151 ms (1.7x slower)
NVIDIA NPP LabelMarkersUF wrong labels, 2x the memory buffer-size query overflows

The same sheet takes 151 ms on an Apple M5 Pro (17x faster than cc3d on one CPU core) and 49 ms on an A100. cuCIM gives the same regions in the same memory; NPP splits regions at every size it ran. NVIDIA's CV-CUDA only joins 4 neighbours, and there it is 1.6–1.9x faster than colorcc's 4-neighbour mode.

How it works

The four stages, thread by thread, with the CUDA kernel line each thread runs

Four stages, the same CUDA (colorcc/cuda/kernels.cu) and Metal (colorcc/metal/kernels.metal) code line for line, launched by one Python function (_stages in colorcc/__init__.py) on either GPU:

  1. Start (init): each pixel points at a parent. A warp vote finds runs of one colour among its 32 pixels, and each pixel points at the first pixel of its run.
  2. Connect (merge): union-find. Each pixel joins its tree to the trees of its same-colour neighbours with an atomic minimum, the row above first, then the left neighbour. Joins another thread already makes are skipped, and path halving keeps the trees shallow.
  3. Collapse (flatten): each pixel points straight at its leader.
  4. Number (rank_roots, number): each leader gets its rank in a 256-pixel chunk; after a prefix sum, each pixel takes its leader's number 1..n.

The animation is Fig. 17 of the post, where you can step through it yourself, switch each idea on and off, and see what each one is worth.

API

colorcc.label(img, conn=8, ignore=None, flags=colorcc.DEFAULT, stages=4) -> (labels, n)
Argument
img [H, W, 3] uint8 tensor on a CUDA or MPS device
conn 4 or 8 neighbours
ignore an (r, g, b) colour that gets label 0 and joins nothing, or None
flags which optimisations to switch on; see below
stages stop early, for timing: 1 init, 2 merge, 3 flatten (labels then hold root pixel indices, -1 for ignored pixels), 4 numbered labels

Returns labels, an [H, W] int32 tensor on the same device, and n, the number of components as a Python int (0 when stages is below 4). Bad input raises ValueError; an empty image returns an empty label tensor and 0.

Flags (combine with |):

Flag
RUNS init: a warp ballot finds horizontal runs of one colour, so most left joins disappear
SKIP merge: skip joins another thread already makes
PACKED init stores each colour as one int; merge reads one word instead of three bytes
HALVE merge: path halving keeps the trees shallow
DEFAULT RUNS | SKIP | HALVE
ALL all four

PACKED is off by default. Added to the default switches it was slower in 39 of 40 measurements (all 14 on the M5 Pro, all 12 on the RTX 3070, 13 of 14 on the A100, which was 1.3x faster at 600 dpi with the paper ignored), by up to 38%, and it doubles the memory. On its own, without the other switches, it was faster about as often as slower. The flags exist so each idea can be measured on its own; every combination gives the same labels.

colorcc.reference has CPU versions (cc3d, and one OpenCV call per colour) used as ground truth in the tests and as baselines in the benchmarks.

Limits

  • Images up to 2^31 pixels (labels are 32-bit pixel indices).
  • 3-channel uint8 input only.
  • Tested only on the three GPUs above.

Correctness

tests/test_colorcc.py compares every flag combination, 4 and 8 neighbours, with and without an ignored colour, against cc3d: the same labels, value for value, on ten test images (noise, checkerboards, one-pixel diagonals, a spiral, single rows and columns and others), plus a test that labels a large image many times to catch races. All 285 tests pass on an RTX 3070 against the released wheel (results/rtx3070/tests.txt), and passed on an Apple M5 Pro one commit earlier (results/m5/tests.txt; the Metal kernels are unchanged since). In every benchmark the labels were also identical to cc3d at every size.

Development

git clone https://github.com/Acero-AI/colorcc && cd colorcc
pip install -e ".[cuda12,test]"   # or cuda13; nothing extra on a Mac
pytest tests                      # CUDA if present, else the Apple GPU; with neither, only the input checks run

The benchmarks (pip install -e ".[cuda12,bench]"):

python bench/synth_sheet.py sheet.pdf
python bench/bench.py sheet.pdf --out sheet.json                  # every flag, and cc3d, 50-800 dpi
python bench/nvidia.py cache/sheet.pdf.*.npy --out nvidia.json    # vs NPP, cuCIM, CV-CUDA (whichever are installed)
python bench/e2e.py cache/sheet.pdf.*.npy --out e2e.json          # with copies to and from the GPU
python bench/tables.py                                            # the tables in results/README.md

bench.py caches each render as .npy in --cache (default cache/); an 800 dpi render is 2.4 GB. bench/profile.py times each stage and each library's kernels at one size (it compiles its kernel variants with a CUDA toolkit), and bench/check_labels.py checks a label file against its image on the CPU with SciPy.

Issues and pull requests are welcome. Releases are listed in CHANGELOG.md.

Citing

If colorcc helps your work, please cite it: GitHub's Cite this repository button (from CITATION.cff) gives BibTeX and APA.

Licence

MIT. See LICENSE.


Gokul Adethya, Fablnx. How it works, with live figures: the Fablnx engineering blog.

Metadata

Release files for colorcc 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for colorcc 0.1.0
File Size Uploaded Provenance
colorcc-0.1.0.tar.gz 19.9 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for colorcc 0.1.0
File Interpreter ABI Platform
colorcc-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 36.0 kB

Release files / colorcc-0.1.0.tar.gz

Download URL colorcc-0.1.0.tar.gz
Size 19.9 kB
Tags Source
SHA-256 checksum
How to use checksums
6c9584ad16ea5f1649537c27061e1178344fc20dc2a5ac1ea69dee20199d94ce
BLAKE2b-256 checksum
How to use checksums
fbd86d912a8b56db9fb9e58f8fb55a26167fcb0c48fa769831f7697428f77295
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

Release files / colorcc-0.1.0-py3-none-any.whl

Download URL colorcc-0.1.0-py3-none-any.whl
Size 16.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
eb697e217a4d3f3397a875c4d9605c151befae536571378357736e42828a2f42
BLAKE2b-256 checksum
How to use checksums
bfcef04c1dc4166d7355a3cc5e0419628f611305028a185dc04120b98155d931
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

Security for colorcc 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for colorcc-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page