Skip to main content

gaugefit

Subpixel caliper metrology for machine vision — measure a width, a gap, a diameter, a straightness, and know how wrong the answer can be.

PyPI Python Platform License

Put a rectangle across an edge. gaugefit resamples the strip, projects it into a 1-D profile, differentiates, and reports where the edge is to a few thousandths of a pixel — with the flags that say when not to trust it.

This is the Cognex Caliper / HALCON measure_pos family of tool: a linear or annular ROI, edge pairing policies, Line and Circle finders that absorb individual caliper failures as outliers, distortion and scale calibration, and a fixturing transform so a recipe follows the part instead of the frame.

A C++ core behind a Python API that needs only numpy. No OpenCV. A caliper measurement takes about 58 µs; a 28-caliper circle finder with robust fitting takes 0.16 ms.

pip install gaugefit
gaugefit demo            # runs on a bundled sample, nothing else needed
gaugefit serve           # the dashboard: set it up, test it, export a recipe

Two implementations, the same numbers

The package ships both a compiled core and a pure-Python reference implementation, and they are held to producing identical results — not similar ones.

what cases compared worst deviation
linear caliper (4 interp × 4 subpixel × 3 projection × 3 polarity × …) 768 edges 1.4e-13 px
annular caliper 16 7.1e-15
line / circle fitting, robust, with outliers 60 1.1e-14
Line / Circle finder end to end 6 2.3e-13

That is what makes the reference implementation useful rather than decorative: it is the specification, it is readable, and any disagreement is a bug in one of them.

import gaugefit as gauge

gauge.engine_info()        # {'engine': 'native', 'simd': 'AVX2+FMA', 'threads': 32, ...}
gauge.use_native(False)    # fall back to the reference implementation at any time

RANSAC is deterministic in both: samples come from lexicographic unranking of the combinations, not from a random number generator, so two runs — and two languages — see the same subsets. Thread count changes the speed and nothing else.


Measure something

import gaugefit as gauge

img = gauge.imread("part.png")

# one caliper: put it across the edge, 90 px long, 20 px wide
cal = gauge.Caliper(center=(320, 240), angle_deg=0, length=90, width=20)
res = cal.measure(img)
for e in res.edges:
    print(e.t, e.point, e.polarity, e.contrast)

# a width: pick the outermost opposite-polarity pair
pair = cal.measure_pair(img, policy="first_last", pair_polarity="opposite")
print(pair.width)

# a line, from 15 calipers, with the bad ones thrown out
line = gauge.LineFinder(p0=(100, 80), p1=(100, 400), n=15, length=40, width=10).run(img)
print(line.shape, line.rms, line.span)      # span is straightness

# a circle, from 24 annular calipers, curvature-corrected
circ = gauge.CircleFinder(center=(300, 300), r_nominal=61, n=24, r_span=30).run(img)
print(circ.shape.center, circ.shape.radius, circ.span)   # span is roundness

Every result carries flags (NEAR_BOUNDARY, EDGES_TOO_CLOSE, SATURATED, LOW_CONTRAST, TILTED) and a diagnostic dict — the total edge width, the lens PSF σ backed out of it, the ROI tilt. Those are what let a line stop and say why rather than returning a plausible wrong number.


Defaults chosen by measurement, not by taste

Before the engine was written, a harness rendered edges with analytically anti-aliased coverage — the ground truth is known to machine precision — and swept subpixel phase, edge angle, projection width, noise, curvature and adjacent-edge spacing. The defaults come out of that table:

  • subpixel="gauss3" (log-parabola on three samples) beats parabola3 by roughly 350× on phase-dependent bias: 0.00009 px peak-to-peak vs 0.032 px at σ = 1.5.
  • interp="bspline3" is the only resampler that matters. bicubic is indistinguishable from bilinear (0.031 vs 0.030 px); B-spline is 10× better (0.0030 px).
  • An axis-aligned edge is the worst case, not a benign one. Tilt it 5° and bias drops by an order of magnitude.
  • Curvature bias is −(σ_psf² + W²/12) / 2R — it comes from the 2-D blur and the projection width, and is independent of the caliper σ. The circle finder backs σ_psf out of the measured edge width and corrects for it.
  • Adjacent edges interfere out to 5σ, not the 3σ usually quoted.
  • Keep W·tanΔ < 3σ, where Δ is ROI misalignment: that product, not either factor, is what smears the projection.

The full tables, with the conditions attached, are in docs/bias.md — including what happens when you ignore them.


The dashboard

gaugefit serve

Set the tools up on a real image, watch the profile and the derivative under the ROI you are dragging, add measurements, and export a recipe — one self-contained JSON file holding the tools, the measurements, the fixture, the calibration and a reference image. Then, from your own application:

import gaugefit as gauge

recipe = gauge.load_recipe("part.gaugefit.json")     # or gauge.fetch_recipe() from the dashboard
out = recipe.run(img, pose=match)                # pose from geofit, or None
print(out.values, out.units, out.flags)

Nothing about the recipe is tied to the dashboard: it is a file, and run() is the whole runtime API.


Install

pip install gaugefit

Python 3.10 – 3.14 on Windows x86-64, Linux x86-64 / aarch64, macOS arm64. The only runtime dependency is numpy — the engine, the finders, the recipe runtime, all of it. Two setup-time extras are optional:

pip install "gaugefit[image]"    # JPEG/TIFF/... via Pillow (PNG and PGM need nothing)
pip install "gaugefit[calib]"    # calibration from a dot-grid target, Gage R&R F-test

Without them import gaugefit still works and measuring still works; only those two features say what they need and stop.

The wheel is tagged py3-none-<platform>: it does not link against the CPython ABI, so one file covers every supported Python and keeps working when a new one is released. The AVX2 path is selected at runtime by CPUID, so the same wheel runs on older machines.


Distortion is applied to points, not to images

There is no undistort_image(). Undistorting an image means resampling it, and resampling destroys exactly the subpixel information the caliper is there to read. So lens distortion and the pixel-to-world scale are applied to the measured coordinates, after the fact:

from gaugefit.calib import calibrate_from_dot_grid

cal = calibrate_from_dot_grid(target_img, rows=9, cols=11, pitch=2.0)
# or, when a single scale factor is enough:
cal = gauge.Calibration.from_scale(px_per_unit=41.7, unit="mm")

world = cal.to_world([e.point for e in res.edges])
print(cal.distance(world[0], world[1]), cal.unit)

gaugefit.calib.distortion.undistort_image exists only to raise NotImplementedError with that explanation.


Uncertainty

from gaugefit.quality import caliper_budget

caliper_budget(sigma=1.5, psf=0.8, angle=0, width=20, snr_db=35)
# -> per-term contributions and a combined 1σ, in pixels

A repeatability number and an accuracy number are different claims, and the budget keeps them apart: noise-driven scatter, phase-dependent estimator bias, resampling bias, curvature and projection-width terms are reported separately, so a spec argument can be settled by pointing at a row.


Documentation

Integration guide putting gaugefit inside your application (한국어)
Bias characteristics what it gets wrong, measured, with conditions (한국어)

Everything, including PDF editions: https://github.com/pashidl-lab/gaugefit


Licence

gaugefit is distributed for evaluation: 90 days from first use on each machine. See LICENSE.

gaugefit check       # licence / evaluation status, and where the key would go

For a licence key, write to pashidl.lab@gmail.com. A key is a signed statement verified offline — there is no server and nothing is transmitted anywhere. Install it with GAUGEFIT_LICENSE or by saving it to ~/.gaugefit/license.

The evaluation limit applies to the compiled core. The pure-Python reference implementation is not gated — it ships readable in the wheel, and pretending otherwise would be theatre.


See also

geofit — rotation- and scale-invariant shape matching from the same authors. geofit finds the part, gaugefit measures it, and gauge.Fixture.from_geofit(match) connects the two so a recipe follows the part around the frame.

Metadata

Release files for gaugefit 0.1.0

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

Built distributions (wheels)

Table of built distributions (wheels) for gaugefit 0.1.0
File
gaugefit-0.1.0-py3-none-win_amd64.whl Python 3 none Windows x86-64 Details
gaugefit-0.1.0-py3-none-musllinux_1_2_x86_64.whl Python 3 none Linux musl 1.2+ x86-64 Details
gaugefit-0.1.0-py3-none-musllinux_1_2_aarch64.whl Python 3 none Linux musl 1.2+ ARM64 Details
gaugefit-0.1.0-py3-none-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl Python 3 none Linux glibc 2.24+ x86-64, Linux glibc 2.28+ x86-64 Details
gaugefit-0.1.0-py3-none-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl Python 3 none Linux glibc 2.28+ ARM64, Linux glibc 2.24+ ARM64 Details
gaugefit-0.1.0-py3-none-macosx_11_0_arm64.whl Python 3 none macOS 11.0+ ARM64 Details

Total release size: 5.4 MB

Release files / gaugefit-0.1.0-py3-none-win_amd64.whl

Download URL gaugefit-0.1.0-py3-none-win_amd64.whl
Size 576.5 kB
Tags Python 3 Windows x86-64
SHA-256 checksum
How to use checksums
ce0e128c9326156c25be727914ed9d4f59bbcd892424a6f3dac4710a3f69f5a9
BLAKE2b-256 checksum
How to use checksums
f71834c1edaf8e93fed504d39e69836464733ffcce4881008e871dfba30943aa
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Sep 7, 2026.

Transparency log

Release files / gaugefit-0.1.0-py3-none-musllinux_1_2_x86_64.whl

Download URL gaugefit-0.1.0-py3-none-musllinux_1_2_x86_64.whl
Size 1.6 MB
Tags Linux musl 1.2+ x86-64 Python 3
SHA-256 checksum
How to use checksums
28d88a05db271894f7981b848d52de5a526b99b5b4ed7ed8babe752e0331a540
BLAKE2b-256 checksum
How to use checksums
605c7b581685ff4f31d788031b8de7c3218c9319318f49ace1094d33847e8dc2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Sep 7, 2026.

Transparency log

Release files / gaugefit-0.1.0-py3-none-musllinux_1_2_aarch64.whl

Download URL gaugefit-0.1.0-py3-none-musllinux_1_2_aarch64.whl
Size 1.5 MB
Tags Linux musl 1.2+ ARM64 Python 3
SHA-256 checksum
How to use checksums
d491b4021eb1c12da2d0c876ee3a875ca021bba048c232d0fc6fb0c3c18b1bff
BLAKE2b-256 checksum
How to use checksums
bc7333ef633552868f096e35acaf07b54b6f98189a5f31273f99e6bf04841b98
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Sep 7, 2026.

Transparency log

Release files / gaugefit-0.1.0-py3-none-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl

Download URL gaugefit-0.1.0-py3-none-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
Size 588.6 kB
Tags Linux glibc 2.24+ x86-64 Linux glibc 2.28+ x86-64 Python 3
SHA-256 checksum
How to use checksums
ee4fbe6e99585a85d3f2e5c5096ff9845f7088ca4a5262c9b1c06301d5806eaf
BLAKE2b-256 checksum
How to use checksums
ac985d230943006de745cb2cea81b17a9299d892680b26fb5436673a291db5fb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Sep 7, 2026.

Transparency log

Release files / gaugefit-0.1.0-py3-none-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl

Download URL gaugefit-0.1.0-py3-none-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl
Size 575.8 kB
Tags Linux glibc 2.24+ ARM64 Linux glibc 2.28+ ARM64 Python 3
SHA-256 checksum
How to use checksums
c300707a8eef1567d6f0b353b6d03d459f329b5b0cd27ca72297f43fbae4b3ec
BLAKE2b-256 checksum
How to use checksums
d596d579060dd1079254b0aa9d3161b73a7f8473d194262de68cfa8fa568fed4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Sep 7, 2026.

Transparency log

Release files / gaugefit-0.1.0-py3-none-macosx_11_0_arm64.whl

Download URL gaugefit-0.1.0-py3-none-macosx_11_0_arm64.whl
Size 567.6 kB
Tags Python 3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
278c67c48192784161b0fadde21e643cc9067f9c3035dc40a67fb0d9b2c96fc4
BLAKE2b-256 checksum
How to use checksums
f6099db1a54da7926b799bd5726819a66ae82681fc5dbf9bd235d4292d042376
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Sep 7, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

6 release files

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