gaugefit
Subpixel caliper metrology for machine vision — measure a width, a gap, a diameter, a straightness, and know how wrong the answer can be.
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) beatsparabola3by 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.bicubicis indistinguishable frombilinear(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)
| File | Reset | |||
|---|---|---|---|---|
| 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 logRelease 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 logRelease 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 logRelease 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 logRelease 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 logRelease 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