Metadata-Version: 2.4
Name: napa-vision
Version: 0.4.4
Summary: A fast, dependency-free AprilTag detector for every tag family, with sub-pixel corners and poses with covariances
Author: Zachary Wilson
License-Expression: PolyForm-Noncommercial-1.0.0
Project-URL: Homepage, https://github.com/zwatx/NAPA
Project-URL: Repository, https://github.com/zwatx/NAPA.git
Project-URL: Issues, https://github.com/zwatx/NAPA/issues
Keywords: apriltag,computer-vision,fiducial,pose-estimation,robotics
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Scientific/Engineering :: Image Processing
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: THIRD_PARTY_NOTICES
Requires-Dist: numpy>=1.24
Provides-Extra: gui
Requires-Dist: opencv-python>=4.7; extra == "gui"
Provides-Extra: headless
Requires-Dist: opencv-python-headless>=4.7; extra == "headless"
Provides-Extra: benchmark
Requires-Dist: robotpy-apriltag==2026.2.2; extra == "benchmark"
Provides-Extra: test
Requires-Dist: pytest<10,>=8; extra == "test"
Requires-Dist: pytest-cov>=5; extra == "test"
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == "dev"
Requires-Dist: pytest<10,>=8; extra == "dev"
Requires-Dist: pytest-cov>=5; extra == "dev"
Requires-Dist: ruff>=0.11; extra == "dev"
Requires-Dist: twine>=5; extra == "dev"
Dynamic: license-file

# NAPA

NAPA is a fast AprilTag detector that skips most of the pixels in each frame. Give it a greyscale frame and it returns
tag IDs and sub-pixel corners, plus poses with covariances if you give it a camera calibration. It reads every
AprilTag family, with tag36h11, the FRC family, as the default.

```bash
python -m pip install napa-vision
```

## Compared with other detectors

The benchmark is 3,000 rendered frames holding 5,521 tags at 640×480, 1280×720, and 1920×1080, each with exact ground
truth. Every detector ran single-threaded on the same CPU core, on the same frames.

| Detector | Speed vs. WPILib's defaults (median frame) | Tags found | Worst-corner error (median) | Wrong IDs |
| --- | ---: | ---: | ---: | ---: |
| **NAPA** | **25×** | **99.9%** | **0.020 px** | 1 |
| AprilTag, WPILib's defaults | 1× | 86.2% | 0.099 px | 0 |
| AprilTag, decimate 2 | 0.79× | 99.7% | 0.043 px | 3 |
| AprilTag, full resolution | 0.21× | 99.9% | 0.042 px | 3 |
| Locus | 1.3× | 97.7% | 0.044 px | 1 |
| Locus, decimate 2 | 2.4× | 98.2% | 0.044 px | 1 |
| OpenCV ArUco, subpixel | 0.55× | 99.4% | 0.289 px | 1 |
| ArUco3 | 1.2× | 97.8% | 0.986 px | 1 |
| ArUco3, tags over 64 px | 3.1× | 80.0% | 0.282 px | 0 |
| ArUco3, tags over 32 px | 2.2× | 96.1% | 0.284 px | 1 |
| ArUco Nano | 4.8× | 99.9% | 0.044 px | 0 |
| RapidTag, subpixel | 2.0× | 75.5% | 0.294 px | 19 |

NAPA was faster than every other detector on every one of the 3,000 frames. The closest was ArUco Nano, and NAPA's
smallest lead over it on any frame was 1.8×. NAPA's one wrong ID was a tag-shaped decoy with a random pattern. OpenCV
ArUco and RapidTag without subpixel refinement run at the same speed as above, with corners about a pixel off.

NAPA's lead over AprilTag grows with resolution:

| Resolution | vs. WPILib's defaults, median (slowest frame) | vs. full resolution, median (slowest frame) |
| --- | ---: | ---: |
| 640×480 | 18× (3.3×) | 93× (9.7×) |
| 1280×720 | 36× (5.1×) | 198× (19×) |
| 1920×1080 | 74× (5.1×) | 375× (20×) |

**Range.** A tag looks smaller the farther away it is, so the smallest tag a detector finds sets how far it can see. In
the [range sweep](https://github.com/zwatx/NAPA/blob/master/benchmarks/README.md#range), NAPA found every tag from 22 px
up, AprilTag at WPILib's defaults from 80 px, and AprilTag at full resolution from 14 px. On the same camera, NAPA sees
about 3.6× as far as WPILib's defaults.

**Smaller tags.** `min_tag_px=16` found every tag in the range sweep, down to 12 px, and 5,519 of the 5,521 tags above.
It takes about 3× as long as the default, which still makes it 12× faster than WPILib's defaults. ArUco Nano, ArUco3,
and Locus at decimate 2 were faster than it on 43 of the 3,000 frames.

**Corners.** NAPA's median worst-corner error is less than half of full-resolution AprilTag's and about a fifth of
WPILib's defaults, and on 95% of tags it was within 0.09 px. Heavy motion blur is the exception: NAPA's largest error
was 3.4 px, against 2.2 px for full-resolution AprilTag on the same tags.

**Stress.** A separate set of 600 harder frames has tags under 32 px, very heavy noise, and up to 80 tags per frame.
NAPA found 96% of those tags with its default settings, and all of them with `min_tag_px=16`.

On tag16h5, the 2023 FRC family, AprilTag at its default two-bit correction reported 581 wrong IDs in the [family
benchmark](https://github.com/zwatx/NAPA/blob/master/benchmarks/README.md#tag-families), and NAPA reported none.

The frames are synthetic, and the timings come from one laptop CPU (Intel i7-12700H). To check NAPA on your own camera,
[score it on your own footage](https://github.com/zwatx/NAPA/blob/master/docs/measuring.md). The [full
report](https://github.com/zwatx/NAPA/blob/master/benchmarks/results/2026-10-30_cpu_full_corpus/report.md) has every
configuration's settings, latency percentiles, and raw data, and the [benchmark
guide](https://github.com/zwatx/NAPA/blob/master/benchmarks/README.md) has results for every scene.

## How it works

NAPA finds likely tags on a sparse grid of pixels and only does full-resolution work on the tags themselves.
[How NAPA works](https://github.com/zwatx/NAPA/blob/master/docs/how-it-works.md) explains each step.

## Quick start

Requires Python 3.11 or newer.

```python
from napa_vision import NapaDetector

detector = NapaDetector()
for tag in detector.detect(image):  # uint8 greyscale, BGR, or BGRA
    print(tag.tag_id, tag.corners)
```

Code written for `pupil_apriltags` or `dt-apriltags` works after changing one import:

```python
from napa_vision.compat import Detector  # was: from pupil_apriltags import Detector
```

For poses, install OpenCV with the `headless` extra and give NAPA a calibration:

```bash
python -m pip install "napa-vision[headless]"
```

```python
from napa_vision import VisionProcessor, load_calibration_data

processor = VisionProcessor(calibration=load_calibration_data("camera.json"), tag_size_m=0.1651)
result = processor.process(frame, timestamp=capture_time)
```

## Documentation

- [Detection](https://github.com/zwatx/NAPA/blob/master/docs/detection.md): tuning, tag families, printing tags, and switching from `pupil_apriltags`
- [Poses and filters](https://github.com/zwatx/NAPA/blob/master/docs/poses.md): metric poses, coordinate frames, and WPILib pose estimators
- [Camera calibration](https://github.com/zwatx/NAPA/blob/master/docs/calibration.md)
- [Stereo rigs](https://github.com/zwatx/NAPA/blob/master/docs/stereo.md): one pose from two calibrated cameras
- [Platforms](https://github.com/zwatx/NAPA/blob/master/docs/platforms.md): C and C++, Android and Meta Quest, and building from source
- [Measuring on your own footage](https://github.com/zwatx/NAPA/blob/master/docs/measuring.md)
- [How NAPA works](https://github.com/zwatx/NAPA/blob/master/docs/how-it-works.md): the pipeline, obstructions, and limits
- [API reference](https://github.com/zwatx/NAPA/blob/master/docs/api.md): every class, method, and field

## Command-line tools

| Command | Purpose |
| --- | --- |
| `napa-tag` | Write tags to print: SVG at an exact printed size, or PNG |
| `napa-calibrate` | Measure camera intrinsics from checkerboard photographs |
| `napa-benchmark` | Score NAPA on your own labeled images, with optional recall, precision, and latency gates |

## License

Copyright © 2026 Zachary Wilson. NAPA is free for noncommercial use under the [PolyForm Noncommercial License
1.0.0](https://github.com/zwatx/NAPA/blob/master/LICENSE): personal projects, research, education, student robotics teams, and other noncommercial organizations
can use, modify, and share it. Copies, forks, and works based on NAPA must include the license and its notices
crediting Zachary Wilson. Commercial use, including selling NAPA or products built on it, needs a separate license.
Ask through [the repository](https://github.com/zwatx/NAPA).

The tag families' codewords come from [AprilTag](https://github.com/AprilRobotics/apriltag) under its BSD license. See
[THIRD_PARTY_NOTICES](https://github.com/zwatx/NAPA/blob/master/THIRD_PARTY_NOTICES).
