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

New here? Every colour at once builds this labeller from scratch: union-find, warps, atomics, why the white paper of a drawing is the hardest region of all, and how it compares with NVIDIA's libraries. Every figure is live, and no GPU background is needed.

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 NVIDIA A100, with the picture already on the GPU, the full 806-megapixel drawing takes 49 ms (89 ms on an RTX 3070):

colorcc against NVIDIA cuCIM and NPP on an A100, at every size from 3 to 806 megapixels

Interactive, with an RTX 3070 and the Apple M5 Pro against cc3d: Fig. 1 of the post.

3 MP render 454 MP (600 dpi) 806 MP sheet
colorcc 0.24 ms 27.2 ms 49.4 ms
NVIDIA cuCIM label() 4.65 ms (19x slower) 85.6 ms (3.1x slower) 131 ms (2.7x slower)
NVIDIA NPP LabelMarkersUF 0.65 ms (2.7x slower), wrong labels 293 ms (11x slower), wrong labels buffer-size query overflows

On the RTX 3070: cuCIM 1.7x to 5.4x slower, NPP 2.5x to 24x slower.

The same sheet takes 151 ms on an Apple M5 Pro, 17x faster than cc3d on one CPU core. cuCIM gives the same regions in the same memory; NPP needs twice the memory and splits regions at every size it ran.

Every number, at every size, with the machines, versions and method: results/README.md. The same data as live charts, with what each idea is worth: chapter 9 of the post.

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 all 40 measurements (14 on the M5 Pro, 12 on the RTX 3070, 14 on the A100), by up to 38%, and it doubles the memory. On its own, without the other switches, it was faster in only 15 of 40. 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 on an Apple M5 Pro against colorcc 0.1.1 installed from PyPI (results/m5/tests.txt). 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 and cuCIM (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.2

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.2
File Size Uploaded Provenance
colorcc-0.1.2.tar.gz 20.2 kB With provenance Details

Built distribution (wheel)

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

Total release size: 36.4 kB

Release files / colorcc-0.1.2.tar.gz

Download URL colorcc-0.1.2.tar.gz
Size 20.2 kB
Tags Source
SHA-256 checksum
How to use checksums
3e890c5a3f6849abd8adac4a9ea3203fea75989784cdb606ead48a050b3477d5
BLAKE2b-256 checksum
How to use checksums
39cad8b75f53dc4c57e028219d2b4f7fc10457c2b90a6076ef179c41ad9089b9
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 21, 2026.

Transparency log

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

Download URL colorcc-0.1.2-py3-none-any.whl
Size 16.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c7350383bb81a1296c51d64992a8451954fbb3c72eab57e551aeb967f5cb9670
BLAKE2b-256 checksum
How to use checksums
11ef2357f9c75e8a47a2c54e602bad3da721ccfc4237a08cab52b16831e470a7
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 21, 2026.

Transparency log

Security for colorcc 0.1.2

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.2.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 21, 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