Compare commits

...

230 Commits

Author SHA1 Message Date
Matt Morley
ccbd46be1a Release processed Focus mat to not leak, cache, and fix cvmat refcounting (#2356) 2026-02-08 21:17:43 +00:00
Watermilan412
994dfe77fa Fix Arducam OV9782 Exposure Changing After Reboot (#2355) 2026-02-07 18:24:54 +00:00
Devon Doyle
5a87e4c738 Add calibration info tooltip (#2350) 2026-02-03 05:41:24 +00:00
Alan Everett
284e818e74 Fix dimmable LED off-state, topic names, PWM flicker, brightness update outside kDefault, indefinite blinking, and blinking reliability (#2337) 2026-02-02 21:11:34 -08:00
Gold856
f4b30da6b3 Dynamically import echarts and three.js (#2349) 2026-02-02 08:06:48 -08:00
Sam Freund
798b01c3a6 Copy old configs before testing (#2348)
## Description

Presently, any tests using our old configs happen in place. This is
problematic, as it changes the files themselves. This means that anyone
running the tests will cause unintentional modifications, which stand a
chance of being committed and merged into main. We want these old
database files to remain untouched, thus we copy them to a temp
directory prior to running our tests.

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_, including events
that led to this PR
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with all settings going back to the previous seasons's last release
(seasons end after champs ends)
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2026-02-02 03:30:37 +00:00
Sam Freund
23392f8d46 Add missing early-return to legacy ML model load (#2347)
## Description

Previously, when attempting to load a model that did not follow the old
naming pattern that was in the models directory, we caused PV to crash
due to a missing early return (reproducable on this branch with the
extra bonus ML model and missing label file):

```
    [2026-02-01 17:35:15] [Config - NeuralNetworkModelManager] [ERROR] Model properties are null. This could mean the config for model /home/matth/photonvision/test-resources/old_configs/2025.3.1-old-nnmm/models/iCauseProblems.rknn was unable to be found in the database. Trying legacy...
    [2026-02-01 17:35:15] [Config - NeuralNetworkModelManager] [ERROR] Failed to translate legacy model filename to properties: /home/matth/photonvision/test-resources/old_configs/2025.3.1-old-nnmm/models/iCauseProblems.rknn: /home/matth/photonvision/test-resources/old_configs/2025.3.1-old-nnmm/models/iCauseProblems-labels.txt

SQLConfigTest > testLoadNewNNMM() FAILED
    java.lang.NullPointerException: Cannot invoke "org.photonvision.common.configuration.NeuralNetworkModelsSettings$ModelProperties.toString()" because "properties" is null
        at org.photonvision.common.configuration.NeuralNetworkModelManager.loadModel(NeuralNetworkModelManager.java:342)
        at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)
```

This PR fixes it by adding the missing early-return, and adding unit
tests to make sure we handle this.

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_, including events
that led to this PR
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with all settings going back to the previous seasons's last release
(seasons end after champs ends)
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [x] If this PR addresses a bug, a regression test for it is added

---------

Co-authored-by: Matt M <matthew.morley.ca@gmail.com>
2026-02-02 02:09:13 +00:00
Wave Robotics - 2826
09e6d45e77 Allow configuring maximum target count (#2338) 2026-02-01 16:19:13 -08:00
Craig Schardt
77457219c7 Remove unused commands from the custom hardware configuration documentation (#2343)
## Description

#2255 introduced a new, cross-platform method for monitoring hardware
and removed the custom shell commands that had been used previously.
This PR updates the documentation to reflect the removal of those
commands from hardwareConfig.json.

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_, including events
that led to this PR
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with all settings going back to the previous seasons's last release
(seasons end after champs ends)
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added

---------

Co-authored-by: Gold856 <117957790+Gold856@users.noreply.github.com>
2026-01-29 23:40:09 -06:00
Bruce Kahn
da88867c60 Doc: Add a warning about versions 2.0.2 and later of the Raspberry Pi Imager not working properly (#2340)
## Description

FRC Team 6413 ran into problems trying to write the 2026.1.1 image using
the Raspberry Pi Imager tool. After some investigation we discovered a
bug was introduced in v2.0.2 of the imager tool which prevents it from
writing the PhotonVision images to any SD card on any Windows 10 or
Windows 11 computer.

Attempting to write a custom image (.img and .img.xz) results in the
following error dialog appearing as soon as the write action is
confirmed:

<img width="684" height="483" alt="photonvisionBurnFailure202"
src="https://github.com/user-attachments/assets/5c802ef0-75f9-4056-ac1f-760dcd3605bf"
/>

Versions 2.0.0 and earlier will successfully write the image files to
the selected SD card.

## Meta

Merge checklist:
- [X] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [X] The description documents the _what_ and _why_, including events
that led to this PR
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with all settings going back to the previous seasons's last release
(seasons end after champs ends)
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2026-01-28 19:45:30 -05:00
Matt Morley
a39844328d Add 2025.3.1 backwards-compat to ML models (#2331) 2026-01-28 12:54:35 -08:00
John Fogarty
1b5f4fa802 Update RubikPi Collab Notebook with working Device (#2339)
The RubikPi collab notebook included a device for creating a quantized
model that was deprecated by the Qualcomm team. I've included a separate
cell with a command to check available devices and updated the command
to a known working state.
2026-01-28 07:11:25 +00:00
Cooper Bouchard
7cc22e52ea Add FRC 2026 game piece detection models (#2332)
Added a model for detecting Rebuilt's FUEL game piece for versions of
the Orange Pi 5 and the Rubik Pi 3, which lowers the bar for less
experienced FRC teams to use object detection on their robots. Also
updated the documentation of object detection to reference the new
models.

The original model weights were trained by FRC team 2826 and were
published on this [Chief Delphi
thread](https://www.chiefdelphi.com/t/introducing-wave-robotics-yolov11-model-for-rebuilt/512701).

The dataset used for RKNN quantization is the [Fuel V3
dataset](https://universe.roboflow.com/frcroboraiders/frc-2026-fuel-sbrdk/dataset/3)
provided by
[frcroboraiders](https://universe.roboflow.com/frcroboraiders) on
Roboflow Universe.
2026-01-27 23:47:54 -06:00
Chris Gerth
49629afe9b not 2024 anymore (#2328)
Update apriltag field language in the docs to be year agnostic
2026-01-27 04:42:22 +00:00
Gold856
ae74b171aa Check POST request status before displaying success (#2336) 2026-01-26 20:11:25 -08:00
Gold856
cfd5773e7c Update README (#2333) 2026-01-26 06:25:59 +00:00
Gold856
c348f0e3ba Update PR template (#2334) 2026-01-26 05:56:17 +00:00
Matt Morley
4139566514 Add OV9281 AE startup quirk (#1814)
## Description

In https://github.com/PhotonVision/photonvision/issues/1771, we
discovered that the OV9281 on Linux seems to sometimes ignore our
exposure requests on first boot if we're in manual mode. Cycling the
camera's auto exposure with pauses in-between seems to fix it, which is
what this PR does.

Tested by [Team 2881 on
Discord](https://discord.com/channels/725836368059826228/725846784131203222/1465098110765105456);
thanks for helping us test this fix out!

## Meta

Closes https://github.com/PhotonVision/photonvision/issues/1771

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2024.3.1
- [ ] If this PR addresses a bug, a regression test for it is added

---------

Co-authored-by: Gold856 <117957790+Gold856@users.noreply.github.com>
2026-01-26 00:11:14 +00:00
Sam Freund
b7a0fad54c Set max target limit to 50 (#2320)
## Description

<!-- What changed? Why? (the code + comments should speak for itself on
the "how") -->

<!-- Fun screenshots or a cool video or something are super helpful as
well. If this touches platform-specific behavior, this is where test
evidence should be collected. -->

<!-- Any issues this pull request closes or pull requests this
supersedes should be linked with `Closes #issuenumber`. -->

closes #2318
closes #2319 

For the 2027 game, teams might want to detect more than 10 results.
Therefore, we're increasing the limit.

We also ran into an issue with our sim, where a user can create too many
objects and cause an overflow. We implement that same limit of 50
targets here.

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2026-01-21 16:44:49 -08:00
Gold856
e73420d62a Clarify when estimate methods return empty (#2323)
## Description

Clarifies when estimate methods return an empty optional. This also
fixes some parity issues with Constrained SolvePnP not checking the
optional returned by the heading buffer.

Resolves #2322.

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2026-01-21 23:13:47 +00:00
Jay Ticku
12f74423d9 Added clarifying details to wiring part of docs for wiring regulator to coprocessor (#2293)
## Description
Added specific details to the wiring section of the photonvision docs
for two wiring methods for connecting a power regulator to a
coprocessor. This aims to help prevent any possible misunderstandings of
how to wire a regulator to a coprocessor.

## Meta

Merge checklist:
- [ ] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [ ] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added

Co-authored-by: Matt Morley <matthew.morley.ca@gmail.com>
2026-01-20 05:36:38 +00:00
Sam Freund
6c9a142622 Propagate changes after modifying OD model (#2299)
## Description

<!-- What changed? Why? (the code + comments should speak for itself on
the "how") -->

<!-- Fun screenshots or a cool video or something are super helpful as
well. If this touches platform-specific behavior, this is where test
evidence should be collected. -->

<!-- Any issues this pull request closes or pull requests this
supersedes should be linked with `Closes #issuenumber`. -->

This PR adjusts how model modification is handled. Any updates to
metadata, or deletion is handled in the frontend after sending data to
the backend. Any creation of models occurs in the backend, and we push
an update to the frontend.

This PR also fixes a typo in PhotonUtils which meant we hit the wrong
endpoint when an IP was passed into the status check.

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2026-01-19 11:58:23 -08:00
William H Wang
149c214897 Chore: Updated documentation for Fx/Fy units. (#2314)
https://mrcal.secretsauce.net/lensmodels.html

Updated the labeling for camera calibration results with MRCAL.
2026-01-19 08:46:59 +00:00
Gold856
a952bab4c9 Remove strict WPILib version requirement (#2307) 2026-01-18 18:56:07 +00:00
Space646
bc208bca85 Add comment documenting typo copied from COCO docs (#2253)
Signed-off-by: Jade Turner <spacey-sooty@proton.me>
Co-authored-by: Jade Turner <spacey-sooty@proton.me>
Co-authored-by: Matt Morley <matthew.morley.ca@gmail.com>
2026-01-19 01:50:56 +08:00
Matt Morley
dbd6eea4e9 Cache requested calibration format outside of state store (#2310)
## Description

On main, this pop-up lists the wrong resolution. The index in the state
is getting updated out from underneath us. The easiest solution I have
is to just cache this number.

<img width="739" height="1051" alt="image"
src="https://github.com/user-attachments/assets/a4383ea0-aa1b-4446-87f1-ece8c5ea9ad5"
/>

<img width="873" height="1116" alt="image"
src="https://github.com/user-attachments/assets/7da4cd60-f8a6-4ac6-8bc6-9f0d5c165fee"
/>

Not sure of a great way to test this, since it requires the backend
taking calibration snapshots

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2026-01-18 16:04:20 +00:00
David Vo
afb73b3918 refactor: separate build, test, and deploy in Python workflow (#2308)
_Test what you build, deploy what you test._

This refactors the Python CI workflow to wait for _all_ tests to pass
before publishing photonlibpy to PyPI.

- build-python-examples reuses the built wheel, removes redundant builds
- Simplify run.sh to not rebuild wheel since it's already installed
2026-01-17 15:46:19 +00:00
David Vo
9011e285d2 Stop shipping unit tests in photonlibpy wheel (#2309) 2026-01-17 14:27:29 +00:00
Matt Morley
8a141904a6 WPILib 2026.2.1 (#2306)
## Description

Find-and-replace 2026.1.1 -> 2026.2.1. This gets us 2026 field layouts
[among other
things](https://github.com/wpilibsuite/allwpilib/releases/tag/v2026.2.1)

<!-- What changed? Why? (the code + comments should speak for itself on
the "how") -->

<!-- Fun screenshots or a cool video or something are super helpful as
well. If this touches platform-specific behavior, this is where test
evidence should be collected. -->

<!-- Any issues this pull request closes or pull requests this
supersedes should be linked with `Closes #issuenumber`. -->

## Meta

Merge checklist:
- [ ] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [ ] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2026-01-17 00:55:30 +00:00
Sam Freund
121433fd90 Loosely pin numpy to 2.3 (#2303) 2026-01-15 16:41:39 -06:00
Sam Freund
22567dea74 Smoketest after uploading images (#2294) 2026-01-14 18:39:21 -08:00
Rikhil Chilka
ba4eb621c3 Rknn numpy fix (#2298) 2026-01-14 15:22:35 -06:00
David Vo
43608c5113 fix: simplify version regex and fix rc tag handling (#2296) 2026-01-13 14:17:36 +00:00
Sam Freund
021053d43e Fix parentheses in workflow (#2292)
Mismatched parenthesis meant releases were never created
2026-01-12 17:59:28 -06:00
Sam Freund
3b57125d96 Update to 2026 (#2288) 2026-01-12 14:00:11 -08:00
Jordan McMichael
e088050902 Fix rendering of tags 30, 31, and 32 in simulated camera streams (#2291) 2026-01-12 18:53:03 +00:00
Vasista Vovveti
224ce46f14 Make the latency/fps setting per camera instead of global (#2260)
There's a new `low latency mode` switch on the input tab.

This replaces use_new_cscore_frametime and makes it per pipeline.

<img width="684" height="535" alt="image"
src="https://github.com/user-attachments/assets/a7ba8bc0-69b6-44f3-83e3-9b88d8219dfa"
/>

The default behavior is still to block for new frames (ie, preserve old
behavior)

---------

Co-authored-by: Matt Morley <matthew.morley.ca@gmail.com>
2026-01-12 05:21:50 +00:00
DeltaDizzy
12a8b88b4a 2026 Test Mode Images (#2285)
Adds test image from wpilib 2026 field test images

Co-authored-by: Matt <matthew.morley.ca@gmail.com>
2026-01-11 13:48:10 -06:00
Gold856
e4749a3ea9 Fix javadoc warnings (#2266)
Persuant to #1093, I added as many docstrings as I could, at least for
things I knew about. Some of the classes I just suppressed the Javadoc
warnings in because they aren't particularly useful to document. This
gets us down to less than 100 Javadoc warnings in total. Docs for core
classes on the C++ side were also added for parity.
2026-01-11 19:25:49 +00:00
Gold856
a5dc9ec0d6 Open up pose estimator strategy methods (#2252) 2026-01-11 18:58:53 +00:00
Devon Doyle
8e9fe38477 Device metrics and control reorganization (#2144) 2026-01-11 10:25:49 -08:00
Matt Morley
5aefb2957d Bump IMAGE_VERSION to v2026.1.1 (#2286) 2026-01-11 09:30:32 -08:00
Craig Schardt
1bedadde97 Fix OSHI spamming failures to console on Windows (#2282)
## Description

Monitoring CPU Temperature on Windows is challenging because most
vendors don't publish this data to WMI. As a work-around, OSHI tries to
use
[LibreHardwareMonitor](https://github.com/LibreHardwareMonitor/LibreHardwareMonitor)
via
[jLibreHardwareMonitor](https://github.com/pandalxb/jLibreHardwareMonitor).
If the temperature isn't found in WMI and jLibreHardwareMonitor isn't
present, OSHI issues warnings every time `getCpuTemperature()` is
called. This clogs the console with useless information when running on
Windows and makes testing difficult.

We could include jLibreHardwareMonitor as a dependency for our Windows
jar, but LibreHardwareMonitor installs Winring0.sys, which is a kernel
level driver with an unfixed severe vulnerability. Windows defender
flags Winring0 as a vulnuratble driver and blocks it from installing.
Rather than messing with it, this PR prevents Windows systems from
calling the `getCpuTemperature()` method in OSHI.

Fixes #2280

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_

---------

Co-authored-by: Gold856 <117957790+Gold856@users.noreply.github.com>
2026-01-08 00:22:17 -05:00
Sam Freund
de8905ee10 Bump actions for Node 24 (#2274)
## Description

This bumps our actions to versions that use Node 24, instead of 20. Node
20 [enters EOL in April 2026](https://endoflife.date/nodejs).

This PR also includes various cleanups that should speed up CI, and make
it less complicated. This includes removing the architecture field from
setup-java, as it detects the native architecture.

We also upload our Gradle dependencies for charting in GitHub, this
helps us keep track of what we're using, and if we need to upgrade.

Finally, we bump the version of our image, to fix issues with the Rubik
Pi fan among other reasons.

continuation of #2194

supercedes #2276

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2026-01-07 17:03:36 -05:00
Charlotte Wilson
6ca7354542 Build Python examples during CI run (#2273)
Adds `photonlib-python-examples` to CI build run to make sure that
Python builds cleanly against that PR/commit.
2026-01-07 16:14:39 -05:00
Sam Freund
3f9e2a9fa8 Force reload after restart and switch URL after IP change (#2278)
## Description

Forces a reload after restarting PhotonVision, restarting the
coprocessor, performing an offline update, or nuking the install. We
wait until we are reconnected to the coprocessor to reload, this is
accomplished by the addition of a status API endpoint.

This is being implemented due to issues experienced when the webpage is
not updated (particularly during offline updates).

---

Using the same statusCheck, we also wait until a new IP is available,
then change to it, after changing our static IP.

---

closes #2169
closes #903

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2026-01-07 02:53:05 -05:00
Mark McBride
e993cca067 Updating wiring doc to include RUBIK Pi instructions (#2281) 2026-01-07 06:58:25 +00:00
Alan Everett
a780c9dc41 Add macOS mrcal support (#2264) 2026-01-06 07:11:33 -08:00
Matt Morley
a2b06bd3dd Use WPILib Artifactory mirror instead of Maven Central (#2277)
## Description

By hitting WPIlib's Artifactory Maven Central mirror instead of Maven
Central, we should reduce the number of 403 flakes we see during
workflow runs. This also removes jogamp from the repository list, as
jogamp dependencies were removed in #1926.

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added

---------

Co-authored-by: samfreund <samf.236@proton.me>
2026-01-04 22:23:42 -05:00
Matt Morley
db0667f1dc Use forked WPILib tool plugin (#2120)
## Description

Update to use our fork of the WPILib tool plugin. WPILib has indicated
that it won't be maintained past 2026, so we're forking it under our
org. We also fixed a bug that results in native libraries for multiple
platforms being included in a jar when only the native libraries for the
platform being built should be included.

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added

---------

Co-authored-by: Gold856 <117957790+Gold856@users.noreply.github.com>
Co-authored-by: samfreund <samf.236@proton.me>
2026-01-04 17:22:58 -05:00
Gold856
eb2b681f24 [ci] Restore mypy checks (#2128)
## Description

Fixed in https://github.com/robotpy/mostrobotpy/pull/191. Closes #1968.

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2026-01-03 05:03:45 +00:00
Sam Freund
8db1341b79 Download artifacts for testing examples (#2270)
Instead of building photon-lib for our examples, wait until it's built
elsewhere then use that to run the examples. This means we don't have to
build photon-lib twice.
2026-01-02 21:09:30 -06:00
Sam Freund
940c3430b8 Fix opengraph embed (#2265)
The opengraph embed requires the image to be accessible at the linked
location. As you can see by clicking on this link,
https://photonvision.org/images/PhotonVision-Icon-BG_2.png, it is not
accessible. This is because we need to configure a public directory when
we publish with vite, as the subfolders will not inherently be
published.

This PR adds a public directory and moves all our images there. I also
moved the video to a video directory rather than it being in images.
2026-01-03 02:42:59 +00:00
Sam Freund
70fed3535e Bump various versions in actions (#2194)
Bumps various versions in actions, as well as ensuring consistency
across workflows wrt versions used. One of the key reasons is ensuring
that we're up to date and consistent (previously having used 22.04 and
24.04), also since 3.11 is EOL.
2026-01-02 19:49:34 -06:00
Craig Schardt
5409573f0d Rewrite system monitoring to use OSHI (#2255)
## Description

[OSHI](https://github.com/oshi/oshi) is a free (MIT license) JNA-based
library for accessing hardware and system performance information. This
PR includes a re-write of the metrics monitoring code to be based on
OSHI. The original intent was to gain access to data about network
traffic for addition to the Settings tab. An additional benefit is that
collecting the data is now around two orders of magnitude (or more)
faster!

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [x] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [x] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added

---------

Co-authored-by: samfreund <samf.236@proton.me>
2026-01-02 17:55:12 -05:00
Sam Freund
6b9599d68a Remove etcher from recommended flashers (#2271)
Etcher isn't being maintained very well, so we don't want teams to use
it.
2026-01-02 04:00:39 +00:00
Gold856
00ca5e06ba Remove unused scripting system (#2269) 2026-01-01 16:35:14 -08:00
Graham
d30ae6cc27 Refactor website (#2243)
## Description

Switches to Vue for easier maintenance (pre-built in CI). Changes UI to
look a little nicer
<img width="1920" height="1080" alt="image"
src="https://github.com/user-attachments/assets/c5933326-c391-4d5c-8b3c-d3eeaa11a2f9"
/>
<img width="1920" height="1080" alt="image"
src="https://github.com/user-attachments/assets/d933c8d0-e5a0-40c0-bc80-0c4c8a4cc4f0"
/>
<img width="1920" height="1080" alt="image"
src="https://github.com/user-attachments/assets/3d8a652b-bd49-4147-a269-140e79fd4164"
/>
<img width="1021" height="1352" alt="image"
src="https://github.com/user-attachments/assets/545a9c02-541c-4a34-b81f-587f21bc682f"
/>
<img width="957" height="1196" alt="image"
src="https://github.com/user-attachments/assets/0dfd8080-0ffe-48c5-8fe0-11177f95dfc9"
/>


## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added

---------

Co-authored-by: Gold856 <117957790+Gold856@users.noreply.github.com>
2025-12-30 01:44:08 -05:00
Sam Freund
80d3efe00e Add NT controlled framerate limiter (#2257)
Adds a method to lower the speed of a pipeline over NT, primarily to
reduce power consumption.
2025-12-29 23:01:10 -06:00
Matt Morley
fddff5dbca Show board outliers in calibration info card (#1267) 2025-12-26 21:20:36 -05:00
Vishnu Bharath
235e601cbc Fix typo in selecting-hardware documentation (#2256)
Corrects a spelling error in the hardware selection documentation
(`dependant` → `dependent`). No functional changes.
2025-12-25 23:08:03 +00:00
Sam Freund
3fbda538f4 qdl instructions for flashing rubik pi 3 (#2254) 2025-12-20 15:27:09 -06:00
Craig Schardt
9d587d5746 Log fewer errors (#2246)
## Description

We currently log some things with ERROR status and include stack traces
for events that are typical behavior. This pollutes the logs and makes
it harder to track down real errors.

This PR changes the way that some events are logged:
* missing configs in the database are logged as [INFO] without the
exception stack trace.
* skip parsing NPU usage when the command is blank so that it doesn't
throw a NumberFormatException.
* log warn instead of error for unsupported NN backends (added by
@samfreund)
* skip warn when we don't add a model, only debug when we add it (added
by @samfreund)

Before:
```
Oct 22 20:56:26 photonvision java[831]: [2024-10-22 20:56:26] [Config - SqlConfigProvider] [ERROR] Could not deserialize apriltag layout! Loading defaults: Provided empty string for class edu.wpi.first.apriltag.AprilTagFieldLayout
Oct 22 20:56:26 photonvision java[831]: [2024-10-22 20:56:26] [Config - SqlConfigProvider] [ERROR] org.eclipse.jetty.io.EofException: Provided empty string for class edu.wpi.first.apriltag.AprilTagFieldLayout
Oct 22 20:56:26 photonvision java[831]:         at org.photonvision.common.util.file.JacksonUtils.deserialize(JacksonUtils.java:136)
Oct 22 20:56:26 photonvision java[831]:         at org.photonvision.common.configuration.SqlConfigProvider.load(SqlConfigProvider.java:298)
Oct 22 20:56:26 photonvision java[831]:         at org.photonvision.common.configuration.ConfigManager.load(ConfigManager.java:198)
Oct 22 20:56:26 photonvision java[831]:         at org.photonvision.Main.main(Main.java:290)
```

After:
```
Dec 15 22:29:09 photonvision java[662]: [2025-12-15 22:29:09] [Config - SqlConfigProvider] [INFO] Missing AprilTag Field Layout in database. Loading defaults
```

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added

---------

Co-authored-by: samfreund <samf.236@proton.me>
Co-authored-by: Gold856 <117957790+Gold856@users.noreply.github.com>
2025-12-20 02:35:18 +00:00
Craig Schardt
7a88487131 Wait for NetworkManager on first call to getAllInterfaces (#2240)
## Description

On the RubikPi3, PhotonVision starts before NetworkManager has fully
initialized. This causes it to fail to identify the network interfaces
available on the board, which leads to problems with configuring and
controling networking from the UI. The failure can be detected by the
call to `nmcli` returning an [exit status of
8](https://networkmanager.dev/docs/api/latest/nmcli.html#exit_status),
which means "NetworkManager is not running."

This PR retries the call to nmcli every 0.5 seconds until the exit
status does not equal 8, or a maximum of 10 attempts have been made. The
retry only occurs the first time `getAllInterfaces()` is called.
Subsequent calls to this method will only make one attempt to avoid
locking up the program if networking isn't responding as expected.

In my testing on the RubikPi3, the code has to retry for less than 2
seconds in order to get a valid response from NetworkManager.

The need for this is greatly reduced by
https://github.com/PhotonVision/photon-image-modifier/pull/114, but this
code adds an additional layer of robustness against slow network
startup.

Closes #2212

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added

---------

Co-authored-by: samfreund <samf.236@proton.me>
2025-12-19 19:55:48 -06:00
Gold856
0e33aef8f9 Check if returned NetworkInterface is null before attempting to get MAC address (#2245)
## Description

On boot, the interface might not be fully configured yet, and the
returned NetworkInterface will be null. This would cause an NPE to be
thrown when attempting to get the MAC address from the interface. This
checks if the interface is null and silently returns an empty string if
it is, which is okay if the networking is being managed because if that
interface is broken, there's bigger problems. Fixes #2244.

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2025-12-19 02:40:32 +00:00
Charlotte
6a349aa8bb Discourage use of NT APIs (#2182)
## Description

As per the linked issue, raw NT APIs are old and mostly obsolete, this
PR adds user-facing notifications of that.

Closes #1828

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [x] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
2025-12-19 01:47:10 +00:00
Sam Freund
50f2285937 Integrate smoketests into image build (#2248)
Runs the smoketest on each image after we build it in CI. This helps
ensure that we check every image that we build has the requisite JNI
libraries, and that they can be loaded. This is needed cause we've been
breaking that, and our current smoketests don't actually test all our
libraries since some are gated behind platform checks.
2025-12-17 10:16:21 -06:00
Sam Freund
90acd19361 Bump libcamera and mrcal versions (#2251)
The libcamera and mrcal jars weren't being included/put in the correct
place. This pr bumps the version of the dependencies to rectify that
issue.
2025-12-17 01:50:39 +00:00
Tyler Veness
5362e0cc6c [ci] Upgrade to wpiformat 2025.79 (#2250)
wpiformat now skips files with extensions known to be binary files.
2025-12-16 18:22:15 -05:00
Jade
ff69ddc247 Fix default settings for deploy (#2242) 2025-12-14 08:55:38 -08:00
Sam Freund
07affd4fe3 RobotPy 2026 beta (#2237)
## Description

This updates RobotPy and our Python examples to the 2026 beta.

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2025-12-12 07:52:17 +00:00
Gold856
a585a1d339 Fix incorrect hostname on non-managed devices (#2203)
I have all sorts of weird adapters from npcap and Hyper-V that don't
have MAC addresses, so I'm simplifying the logic down so that it always
tries to find _any_ adapter with a MAC address, but attempts to see if
it can find what adapter is in use right now and use the MAC address
from that. This also unpublishes old MAC address topics, which wasn't
done before.
2025-12-12 00:40:34 -06:00
Sam Freund
9490c2f2cd Bump wpilib to 2026 beta (#2192)
## Description

<!-- What changed? Why? (the code + comments should speak for itself on
the "how") -->

<!-- Fun screenshots or a cool video or something are super helpful as
well. If this touches platform-specific behavior, this is where test
evidence should be collected. -->

<!-- Any issues this pull request closes or pull requests this
supersedes should be linked with `Closes #issuenumber`. -->

Bump to wpilib 2026 beta. This does not bump our pythonlib, as robotpy
hasn't come out yet.

## Meta

Merge checklist:
- [ ] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [ ] The description documents the _what_ and _why_
- [ ] This PR has been
[linted](https://docs.photonvision.org/en/latest/docs/contributing/linting.html).
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added

---------

Co-authored-by: Gold856 <117957790+Gold856@users.noreply.github.com>
2025-12-11 23:53:54 -06:00
Alan Everett
467f22bfdc Use diozero for GPIO (#2171)
The old GPIO abstraction was replaced with
[`diozero`](https://www.diozero.com), which supports most hardware
running Linux due to its use of GPIO character devices provided by the
Linux kernel. `diozero` also supports [alternate
providers](https://www.diozero.com/concepts/providers.html#providers) if
for some reason the character device API is insufficient. Certain
capabilities outside of the character device API is also implemented for
common hardware.

Custom GPIO commands are implemented via a custom `diozero` provider.
The configuration for custom GPIO will need manually updated according
to the Hardware Config documentation page.

The status LED class was also reworked to support additional statuses or
LED indication types, although none have been added yet.

This was tested on a RPi 5 with LL3 illumination LEDs and an RGB status
led attached. All capabilities worked as expected. All 8 status LED
colors were tested and functional via modifying the code. Basic
functionality of custom GPIO was tested with dummy commands.
2025-12-11 22:28:18 -06:00
Gold856
c2433e0332 Fix Jackson being unable to deserialize neural network config (#2232)
## Description

#2224 removed the custom deserializers for `Path`, but we still need one
to be able to deserialize the `Path` key in
`NeuralNetworkPropertyManager`. Additionally, Jackson seems to
auto-convert the `Path` key to a `String` using `toString` instead of
its own serializers, so a custom key serializer is also needed to
consistently use the same format for paths. This also removes unused
serde methods in `JacksonUtils` to minimize potential future churn, and
tacks an `@JsonIgnore` on `getModels` to prevent Jackson from
serializing a `ModelProperty` array into the database.

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [x] If this PR addresses a bug, a regression test for it is added
2025-12-09 18:09:49 -05:00
Sam Freund
1bb05a0e3e Remove PhotonJNICommon in favor of CombinedRuntimeLoader (#2223)
## Description

PhotonJNICommon is just our implementation of combined runtime loader,
which we don't really need. This removes it and just uses
CombinedRuntimeLoader directly. This also fixes the issues introduced in
#2219, which lead to some of our JNIs not loading.

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [x] If this PR addresses a bug, a regression test for it is added

---------

Co-authored-by: Matt M <matthew.morley.ca@gmail.com>
2025-12-09 08:39:41 +00:00
Sam Freund
0c4c310c66 Fix pipeline type when creating a new pipeline being off by one (#2229)
## Description

<!-- What changed? Why? (the code + comments should speak for itself on
the "how") -->

<!-- Fun screenshots or a cool video or something are super helpful as
well. If this touches platform-specific behavior, this is where test
evidence should be collected. -->

<!-- Any issues this pull request closes or pull requests this
supersedes should be linked with `Closes #issuenumber`. -->

This cherry-picks the bug fix from #2225, but not the tests as we're
having issues with those. #2225 will remain open for the tests.

https://github.com/PhotonVision/photonvision/pull/2204 fixed the
off-by-one error on the frontend, but again, because enums are
serialized with ordinal(), DataSocketHandler needed to be updated to
account for the indices shifting by one.

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] ~If this PR addresses a bug, a regression test for it is added~
Deferred

Co-authored-by: Gold856 <117957790+Gold856@users.noreply.github.com>
2025-12-08 20:49:18 -08:00
Matt Morley
98f4a8642f Add WireShark dissector (#2140)
## Description

<img width="1918" height="1030" alt="image"
src="https://github.com/user-attachments/assets/5af1a5ee-012d-461d-9162-2d4de6ad0c62"
/>

A wireshark dissector can be handy for quickly visualizing time sync
messages. See the docs for how to use this!

Full disclosure -- this dissector was generated by Claude 4.5, and I
spot-checked all the numbers for correctness. This seems like idiomatic
Lua to me, but I don't know Lua at all. I don't see a nice QOL thing
nobody else will use as being a tech debt concern.

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2025-12-08 05:14:24 +00:00
Sam Freund
88d0f16e80 Bump image version to v2026.0.6 (#2228) 2025-12-07 22:14:18 -05:00
Gold856
6285f1ee24 Consistently serialize neural network data (#2224) 2025-12-07 17:45:14 -06:00
Craig Schardt
192e2a115c Use photon-image-runner to build images (#2210)
## Description

This PR switches image building from `pguyot/arm-runner-action` to
`PhotonVision/photon-image-runner`. The new runner uses native arm
runners on GitHub and is much faster than the emulation used by the
pguyot version.

Images from
a95914ca36
tested and working on:

- [x] RaspberryPi4b
- [x] OrangePi5
- [x] OrangePi5pro
- [x] RubikPi3

Closes #2191

<!-- Any issues this pull request closes or pull requests this
supersedes should be linked with `Closes #issuenumber`. -->

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2024.3.1
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2025-12-07 22:46:23 +00:00
Gold856
1c802f5eca Restore original dashboard view behavior (#2227) 2025-12-07 15:59:07 -06:00
Gold856
d44d9fbbeb Prevent slider from going past bounds (#2222)
## Description

#1900 updated how the value was handled in pv-slider, and
unintentionally removed bounds protection. This restores bounds
protection.

Unfortunately, there is an edge case that might be rather difficult to
solve. If the slider is already at the min/max, you can enter a number
through the text field, and while the value won't actually update, the
text field keeps the entered value, likely because the model value
didn't change, and therefore, a rerender isn't triggered. However, this
is an edge case that I doubt many people will actually encounter, so we
should still ship this.

Fixes #2221.

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [x] If this PR addresses a bug, a regression test for it is added
2025-12-07 09:49:14 -08:00
Gold856
9e45e8b80a Remove redundant photon-targeting JNI library load (#2220)
## Description

#2137 made it so `TestUtils.loadLibraries()` loads both WPILib and
photon-targeting, which is used in Main.java to load libraries, but it
left the separate photon-targeting load in Main. This removes the extra
library load, since it's covered by `TestUtils.loadLibraries()`.

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2025-12-07 09:48:59 -08:00
Gold856
9d7222a19e Optimize UI CPU usage (#2168) 2025-12-06 22:35:49 -08:00
Matt Morley
674f6e2361 [NFC] Kill stupid while loop copy in PhotonJNICommon (#2219)
## Description

We can avoid copying files by chunks just using `Files.copy`. This
should be NFC, just makes the code cleaner

<!-- What changed? Why? (the code + comments should speak for itself on
the "how") -->

<!-- Fun screenshots or a cool video or something are super helpful as
well. If this touches platform-specific behavior, this is where test
evidence should be collected. -->

<!-- Any issues this pull request closes or pull requests this
supersedes should be linked with `Closes #issuenumber`. -->

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [ ] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2025-12-06 16:51:14 -05:00
Gold856
5e830fae57 Update mrcal-java (#2214)
## Description

Updating to take advantage of the now independent mrcal-java (no longer
need to install SuiteSparse and friends!)

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added

---------

Co-authored-by: Matt Morley <matthew.morley.ca@gmail.com>
2025-12-06 18:10:47 +00:00
Vasista Vovveti
16751e9ecd revert demo domain url (#2218) 2025-12-05 21:30:50 -06:00
Sam Freund
b7054f2a6f Update PR linting instructions (#2213)
## Description

Removes the checkbox for linting because it's easily invalidated by
later pushes. Also prints a link to the linting docs on CI failure.

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] This PR has been
[linted](https://docs.photonvision.org/en/latest/docs/contributing/linting.html).
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2025-12-05 16:50:05 -05:00
Gold856
3ab8bc0e5d Fix import for GlobalSettingsCard (#2217)
Co-authored-by: samfreund <samf.236@proton.me>
2025-12-05 19:36:45 +00:00
Sam Freund
ca0923e27f Rename NetworkingCard.vue to GlobalSettingsCard.vue (#2215)
## Description

In the UI, the title for this card is `Global Settings`. This more
closely follows the UI.

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] This PR has been
[linted](https://docs.photonvision.org/en/latest/docs/contributing/linting.html).
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2025-12-05 04:35:13 -05:00
Devon Doyle
369d10eb91 Fix button theming for custom themes (#2216) 2025-12-05 03:02:46 -06:00
Sam Freund
017b074eae Add playwright E2E tests (#2174) 2025-12-04 22:25:48 -06:00
Sam Freund
f821657d2b Use updated image metadata (#2209) 2025-11-29 18:33:21 -06:00
Jade
8c7ca1697e Fix OV9281 resolution options when libcamera (#2100)
Resolves https://github.com/PhotonVision/photonvision/issues/2087

Values are taken from the
[datasheet](https://www.ovt.com/wp-content/uploads/2022/01/OV9281-OV9282-PB-v1.3-WEB.pdf)
and have been tested on the LL4

Signed-off-by: Jade Turner <spacey-sooty@proton.me>
2025-11-27 01:00:46 +08:00
Gold856
a8d825919e Fix PipelineType index mismatch (#2204)
## Description

#2180 added an additional value to PipelineType.java. Enums are
serialized with `ordinal()`, and the frontend wasn't updated to account
for this, causing an off-by-one error where the UI thought the pipeline
was actually the next pipeline over. This resyncs the enum on the
frontend to the backend and adjusts the mapping from PipelineType to
WebsocketPipelineType to make everything match.

Fixes #2201.

closes #2202 

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [x] This PR has been
[linted](https://docs.photonvision.org/en/latest/docs/contributing/linting.html).
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [x] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2025-11-24 02:12:22 -05:00
Michael Jansen
63593b873a Catch other boards in isRK3588 check (#2199) 2025-11-22 23:50:51 +00:00
Tyler Veness
aa64bfe82e [ci] Upgrade to wpiformat 2025.75 (#2198) 2025-11-22 07:39:30 -08:00
Sam Freund
d27b3d0775 Modal template for deletion confirmation (#2190)
## Description

<!-- What changed? Why? (the code + comments should speak for itself on
the "how") -->

<!-- Fun screenshots or a cool video or something are super helpful as
well. If this touches platform-specific behavior, this is where test
evidence should be collected. -->

<!-- Any issues this pull request closes or pull requests this
supersedes should be linked with `Closes #issuenumber`. -->

This adds a template modal that can be used for confirming that the user
wants to delete something. The main goal is to reduce complication and
duplicated code, and standardize the way we handle deletion.

closes #2175

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [x] This PR has been
[linted](https://docs.photonvision.org/en/latest/docs/contributing/linting.html).
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added

---------

Co-authored-by: Devolian <devondoyle@outlook.com>
2025-11-18 03:41:20 -05:00
Tyler Veness
77e5545eef [ci] Upgrade to wpiformat 2025.69 (#2193) 2025-11-17 06:48:36 +00:00
ElectricTurtle32
618072c3dd Add Camera Focus Mode (#2180)
## Description
 
Camera focus tool pipeline using a Laplacian and finding the variance.
Similar to Limelight.


closes #1597 

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [x] This PR has been
[linted](https://docs.photonvision.org/en/latest/docs/contributing/linting.html).
- [x] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [x] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2025-11-17 00:15:42 +00:00
Dan Katzuv
7d2c69dbdb Fix "ArUco" and "ChAruCo" spellings (#2184)
## Description
Looks like this is the convention from a quick Google search.
## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] This PR has been
[linted](https://docs.photonvision.org/en/latest/docs/contributing/linting.html).
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2025-11-13 16:07:33 -05:00
Tyler Veness
a2b19c080e Upgrade to wpiformat 2025.48 (#2186) 2025-11-11 05:28:28 +00:00
Alan Everett
def3b9faa8 Only show images during tests when requested via project property (#2177)
## Description

Revision of #2164. Instead of only running headless tests during a
build, now only the images are disabled. To reenable showing images, the
`enableTestUi` project property needs to be passed to gradle.

```bash
./gradlew test -PenableTestUi
```

## Meta

Merge checklist:
- [X] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [X] The description documents the _what_ and _why_
- [X] This PR has been
[linted](https://docs.photonvision.org/en/latest/docs/contributing/linting.html).
- [x] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2025-11-06 19:38:07 +00:00
Charlotte
25c355ebc2 Add warning about arducam-pivariety incompatibility (#2178) 2025-11-06 11:04:02 -06:00
Sam Freund
dad7f0a82d Add support for removing calib coefficients (#2150)
## Description

Adds the ability to remove old calibrations. This might be helpful if
you have a bad calibration.

closes #1262

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added

---------

Co-authored-by: Gold856 <117957790+Gold856@users.noreply.github.com>
2025-11-02 21:17:22 +00:00
Sam Freund
6f2603f0cb Fix bug with import nickname (#2176)
## Description

`.` is a special regex character, which means we weren't actually
matching the `.` in the string, but rather the character before it. This
resulted in the last letter in a model nickname getting cut off on
import. This fix resolves that issue.

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [x] This PR has been
[linted](https://docs.photonvision.org/en/latest/docs/contributing/linting.html).
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2025-11-02 20:14:57 +00:00
Jade
f499e4fb50 [NFC] Invert AprilTag family default (#2124)
## Description

Switch default tag family used in AprilTag pipeline. We already
functionally changed the default in
https://github.com/PhotonVision/photonvision/pull/1333 but since 2024
FIRST appears to have been using 36h11 so switching here seems to make
sense as well.

## Meta

Merge checklist:
- [ ] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [x] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added

---------

Signed-off-by: Jade Turner <spacey-sooty@proton.me>
2025-11-02 13:32:39 -05:00
Rikhil Chilka
e5c8859c57 Add notice to notebook about running on linux (#2163) 2025-11-02 18:29:08 +00:00
Sam Freund
2cde701cff Add documentation for linting (#2166)
## Description

This adds documentation for how to lint PhotonVision, as it is not
available elsewhere and people have historically needed to figure it out
for themselves. It also adds an alias for linting PV.

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2025-11-02 03:38:51 +00:00
Sam Freund
d649a9cb9e Use progress bar for file uploads (#2148)
## Description
<img width="3840" height="2160" alt="image"
src="https://github.com/user-attachments/assets/c0289923-a6c8-48b9-84c1-ce92c7acbc9d"
/>
<img width="3840" height="2160" alt="image"
src="https://github.com/user-attachments/assets/3b58c7d0-e12e-45d6-b328-c3061949349a"
/>


closes #871

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [ ] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2025-11-02 01:56:12 +00:00
Sam Freund
695742bfcf Add axios post util (#2153)
## Description

This replaces boilerplate that checks whether we've successfully sent
the request, whether there was an error, etc.

closes #2151

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2025-11-01 17:48:28 -04:00
Alan Everett
5df9137256 Gradle build task improvements (#2164)
## Description

This fixes a few problems with the Gradle `build` task and subtasks.
1. Spotless was being run on `node_modules`, resulting in errors for
out-of-source files. This is now disabled.
2. All tests were running from the `build` task, resulting in unexpected
windows popping up. Now only headless tests are run.
3. Headless tests were updated to run from the same root directory as
the other tests.

## Meta

Merge checklist:
- [X] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [X] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2025-11-01 17:42:30 -04:00
Sam Freund
36b437323f Add cross-compilation toolchain to docs (#2172) 2025-10-31 21:23:33 -05:00
Sam Freund
5d39ef5b62 Fix client linting in CI (#2167) 2025-10-30 18:52:14 -05:00
Gold856
2bb59f8437 Add Merch link to header on desktop (#2165) 2025-10-30 15:49:01 -05:00
Charlotte
cd502a22c7 Add message discouraging chessboard usage (#2160) 2025-10-30 16:49:57 +00:00
Sam Freund
f16ffe3cd2 Add progress indicator for single model OD upload (#2154) 2025-10-30 01:11:25 -05:00
Chris Gerth
45236b872a Add Merch link to header (#2161)
## Description

added a merch store link


## Meta

Merge checklist:
- [x ] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [ ] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2025-10-29 21:32:02 -05:00
Sam Freund
6b20dc3c1b Fix rubik_jni bounding box bug (#2162) 2025-10-28 21:48:44 -07:00
Sam Freund
9f6d8caf48 Fix calibration resolution default bug (#2156) 2025-10-23 23:42:04 -05:00
Rikhil Chilka
3cbac8117e Merge rknn conversion scripts into notebook (#2157) 2025-10-23 23:41:49 -05:00
Rikhil Chilka
8e88a9a780 Update notebook links in docs to point to docs version (#2155)
Co-authored-by: Sam Freund <samf.236@proton.me>
2025-10-23 16:35:40 -05:00
Rikhil Chilka
7cb3b7a37b Add downgrade fix for ONNX error during RKNN conversion (#2136) 2025-10-23 17:17:31 +00:00
Alan
054ed8b6a1 Add camera mismatch banner to dashboard (#1921)
## Description

Detects if a camera mismatch is present in any camera and displays a
banner in the dashboard for better visibility to the user. All detection
occurs in the backend, and is sent to the frontend via use of a mismatch
boolean included in each vision module.

<img width="1235" alt="image"
src="https://github.com/user-attachments/assets/19219a56-c366-4c56-8c4b-cb5a36fe4a04"
/>

Closes #1920

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [x] If this PR touches configuration, this is backwards compatible
with settings back to v2024.3.1
- [x] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added

---------

Co-authored-by: Sam Freund <techguy763@gmail.com>
Co-authored-by: samfreund <samf.236@proton.me>
Co-authored-by: Matt Morley <matthew.morley.ca@gmail.com>
2025-10-21 20:53:22 -04:00
Riley Brewer
d44480ddad Fix typo: s/Specifc/Specific (#2143)
## Description

Simple typo fix:
s/Specifc/Specific

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [x] If this PR changes behavior or adds a feature, user documentation
is updated
- [x] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [x] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [x] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [x] If this PR addresses a bug, a regression test for it is added
2025-10-22 00:18:01 +00:00
Sam Freund
c71921c41e Add documentation for forcing OD UI (#2018) 2025-10-21 19:09:42 -05:00
Michael Jansen
ee4501f1d6 Add Luma P1 support (#2135)
## Description

Adds support for building images for the Luma P1. This bumps the image
modifier pin to v2025.0.4. This pulls in:

* Allow users to install any release via install.sh by @crschardt in
https://github.com/PhotonVision/photon-image-modifier/pull/49
* Exit install script if run on systemcore by @crschardt in
https://github.com/PhotonVision/photon-image-modifier/pull/58
* Fix --list-versions in install.sh by @crschardt in
https://github.com/PhotonVision/photon-image-modifier/pull/59
* Remove large folders of firmware that (probably) isn't needed by
@crschardt in
https://github.com/PhotonVision/photon-image-modifier/pull/41
* Cancel in progress runs by @spacey-sooty in
https://github.com/PhotonVision/photon-image-modifier/pull/65
* Add limelight 4 support by @spacey-sooty in
https://github.com/PhotonVision/photon-image-modifier/pull/52

**Full Changelog**:
https://github.com/PhotonVision/photon-image-modifier/compare/v2025.0.3...v2025.0.4


## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2025-10-21 10:20:42 -07:00
Gold856
d9b86a718e Revert "Make HardwareConfig a record" (#2142) 2025-10-21 10:56:17 -05:00
Sam Freund
1ac185c247 Fix versioning helper (#2141)
## Description

When we tagged `v2026.0.0-alpha-1`, we broke the versioning-helper
logic. It doesn't expect `alpha` in the version string. This PR adds
matching for lowercase alphanumeric to the versioning helper, which
resolves that issue.

Also turns out `getProviders()` is now broken as a gradle method. Sad.

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2025-10-21 01:14:57 -04:00
Sam Freund
7170c29efe Version rubik pi image (#2129) 2025-10-21 04:43:33 +00:00
Gold856
4f549ba579 Use the tool plugin to include photon-targeting into photon-core (#2137)
## Description

This allows photon-targeting to be loaded using the same mechanism as
the rest of the WPILib libraries, fixing issues with libraries not being
able to find and load their dependent libraries.

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [x] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2025-10-20 07:45:54 -07:00
ElectricTurtle32
b531fe6b81 Made steam overlay buttons primary color (#2139) 2025-10-19 00:59:16 +00:00
Jade
373ed2ff05 Fix most gradle deprecation warnings (#2093) 2025-10-15 22:22:55 -04:00
Jade
115bc09f2e Update LimeLight installation documentation (#2133) 2025-10-15 17:51:43 +00:00
Gold856
7497566f56 [ci] Fix releaser (#2127) 2025-10-14 00:55:14 -05:00
Henry Martin
85f155c77b chore: Bump Javalin (#2126) 2025-10-13 20:58:14 -05:00
Sam Freund
797936865f Add support for building rubik image (#2110) 2025-10-13 17:56:23 -05:00
Sam Freund
831df409f7 Disable 3d mode for OD (#2121)
## Description

There isn't anything that 3D mode adds for OD, and the results are
typically messed up. Thus, we disable 3D mode when we're using an OD
pipeline.

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2025-10-13 21:56:16 +00:00
Drew Williams
b89ab49d34 Add Constrained PNP Pose Strategies to C++ photonlib (#1908)
This adds the two missing pose strategies from the java version of
photonlib (Constrained PNP and the Trig solve), to C++ photonlib

---------

Co-authored-by: Matthew Morley <matthew.morley.ca@gmail.com>
2025-10-12 12:26:12 -07:00
Sam Freund
099c88e0b7 Remove gh-action-releaser (#2119)
## Description

See https://github.com/photonvision/photon-image-modifier/pull/77

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2025-10-11 09:48:20 -07:00
Sam Freund
1ee2ecb608 Make NT client name the same as hostname (#2107) 2025-10-10 22:07:27 -07:00
Gold856
82d6b6b845 Make exported journalctl logs less verbose and always display everything (#2101)
## Description

There was a recent occurance of journalctl logs saying something like
`[66B blob data]`. We don't log anything binary, so journalctl might be
hiding some lines, thinking they're actually binary data when they're
actually plain text. Use `-a` to always log everything. If it's binary
data anyways, we'll want to know what it is anyways. Use `--output cat`
because we output our own timestamps and we don't want journalctl's own
timestamping. Filter the output through `sed` to remove ANSI color codes
to make reading logs easier.

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [x] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2025-10-11 00:25:54 -04:00
Sam Freund
8ec041493a Conglomerate release artifacts (#2115) 2025-10-10 23:10:40 -05:00
Gold856
da608a5070 Fix file uploads not overwriting existing files (#2116)
## Description

#2023 changed how file uploads were handled to use `Files.copy`, but
incorrectly didn't specify the `REPLACE_EXISTING` copy option, causing
file uploads to fail if the file already existed.

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2025.3.2
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2025-10-10 04:44:43 +00:00
Alan Everett
17c23b0390 Fix documentation tab set syncing (#2109) 2025-10-09 16:27:58 -05:00
Gold856
e486ff5a50 Modify build-image to use global variable for image version (#2113) 2025-10-09 17:22:23 +00:00
Gold856
e84e3e7c7c Refactor LibCameraJNILoader to use PhotonJNICommon (#2048) 2025-09-16 08:04:25 -07:00
Jade
a1d06a9920 [ci] Set versions for image builds to 24.04 (#2092)
This should fix the [LL4 CI](https://github.com/PhotonVision/photonvision/actions/runs/17751849602/job/50450131089).
From [here](https://github.com/pguyot/arm-runner-action?tab=readme-ov-file#cpu)
we can see that 24.04 is required for the cortex-a76 used by the pi5,
> cortex-a76 equivalent to max:cortex-a76. Note that this requires a
newer version of qemu, for example with runner ubuntu 24.

Signed-off-by: Jade Turner <spacey-sooty@proton.me>
2025-09-16 12:56:41 +08:00
Gold856
04e9bffeb7 Properly declare inputs and output for buildClient (#2086)
Co-authored-by: samfreund <samf.236@proton.me>
2025-09-16 04:35:33 +00:00
Jade
4b01b66ab7 Add limelight 4 support (#1807)
Resolves #1804

---------

Signed-off-by: Jade Turner <spacey-sooty@proton.me>
2025-09-16 09:48:02 +08:00
Craig Schardt
ee56bf7597 Update libcameraDriverVersion to v2025.0.4 (#1769)
Signed-off-by: Jade Turner <spacey-sooty@proton.me>
Co-authored-by: Jade <spacey-sooty@proton.me>
2025-09-15 03:05:53 -04:00
Jade
058ca19262 Publish FPS with camera (#2083) 2025-09-12 09:10:37 -05:00
Devon Doyle
b43d0dde20 Add custom theming (#2081)
Adds support for user-created custom themes. Custom theme interface is
tucked into the global settings in a non-invasive manner to avoid major
design changes. Builds on the theme structure established by the dark
theme update.

<img width="1486" height="953" alt="image"
src="https://github.com/user-attachments/assets/716bcfc7-af74-41dc-b14a-cfc2f2d2caa9"
/>

<img width="1486" height="956" alt="image"
src="https://github.com/user-attachments/assets/a00f9620-0b1d-4f67-b010-e94dda5dc212"
/>



Here's a few examples of what teams could do, using a few color schemes
from local teams. Imagine the possibilities!

<img width="1485" height="951" alt="image"
src="https://github.com/user-attachments/assets/c3da37b8-f6be-4152-81e0-533297f517fc"
/>

<img width="1483" height="951" alt="image"
src="https://github.com/user-attachments/assets/0d453f7a-cf6f-4c27-97db-603b54c1f73e"
/>

<img width="1485" height="952" alt="image"
src="https://github.com/user-attachments/assets/bf8c7770-e60d-4875-9580-ed7e54e089f4"
/>

<img width="1484" height="952" alt="image"
src="https://github.com/user-attachments/assets/326d89e6-dd6e-4e05-a9fa-c9fc6f880847"
/>

<img width="1482" height="951" alt="image"
src="https://github.com/user-attachments/assets/eb5a2a5d-c103-482c-a62a-5ccd5ba21cc5"
/>

<img width="1482" height="950" alt="image"
src="https://github.com/user-attachments/assets/4831ca56-f322-4345-97af-8963ae8539b1"
/>



Looking for high contrast? Just moments away:
<img width="1484" height="949" alt="image"
src="https://github.com/user-attachments/assets/7ffc65c6-7000-4566-b4f0-c8247f75fb3d"
/>
2025-09-07 00:33:37 -04:00
Kevin Cooney
3300b90823 Set the initial capacity of ArrayLists in getAllUnreadResults() (#2079)
If there are more than 10 queued results,
PhotonCamera.getAllUnreadResults() would resize two arrays at least once
each.

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2024.3.1
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2025-09-01 23:36:02 -04:00
Sam Freund
f58416fe16 Update compatible version to 2025 (#2068)
## Description

The version of settings that needs to be compatible is last years, in
this case 2025.

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2024.3.1
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2025-09-01 23:34:58 -04:00
Graham
62eb66a493 Set canonical root URL for documentation (#2078) 2025-08-29 02:19:50 +00:00
Gold856
fcbc392d83 Use deploy-utils instead of an external ssh plugin (#2077) 2025-08-22 06:10:13 +00:00
Kevin Cooney
7d927aca3b Fix 'Resource leak: <variable> is never closed' warnings (#2023)
Fix numerous places where using AutoCloseable objects without closing
them.

Changes:
- Upgrade JUnit from 5.10.0 to 5.11.4 (so `@AutoClose` can be used)
- Use `Files.copy()` to copy files
- Use try-with-resources when calling `Files.list()` or `Files.walk()`
- Use try-with-resources or `@AutoClose` to close `PhotonCamera` and
  `PhotonCameraSim` objects created by tests
- Update `SQLConfigTest` to use `@TempDir`

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2024.3.1
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2025-08-18 23:37:00 -04:00
Sam Freund
bd2c5062f9 Bump website and docs dependencies (#2075) 2025-08-19 01:30:47 +00:00
Sam Freund
354f4e945e Remove codecov from workflow (#2070)
## Description

It's unused and fails when run, so I'm removing it.

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2024.3.1
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2025-08-17 01:54:01 +00:00
Rikhil Chilka
2eb224a55f Preload OD models before import to check quantization (#2056)
Co-authored-by: samfreund <samf.236@proton.me>
Co-authored-by: Sam Freund <techguy763@gmail.com>
2025-08-16 00:59:22 -05:00
Sam Freund
2ab7a2e389 Bump Gradle to 8.14.3 (#2064) 2025-08-15 22:07:12 -04:00
Sam Freund
27a1cfcb12 Bump wpiformat to 2025.34 (#2066) 2025-08-15 22:05:54 -04:00
Sam Freund
c7f5edc262 Exclude license from being loaded as a model (#2063) 2025-08-15 15:17:21 -05:00
Gold856
6fe96316a4 Fix calibrationUtils.py (#2055) 2025-08-13 06:16:49 -07:00
Gold856
b32d9c6ee3 Only update UI when there's been a change in conflict detection (#2054)
## Description

After #1991, the program state was always resent in an attempt to
simplify logic, but this had the side effect of causing the settings UI
to reset periodically when the hostname check was performed. This
restores the original logic in #1791 to check for differences in the
conflict state, and to only send the program state if it's changed.

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2024.3.1
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2025-08-11 18:21:19 -04:00
Gold856
7766d99ca6 Only make vendor-json-repo PR if the repo is ours (#2053)
## Description

This prevents jobs from failing on forks of the repo.
Closes #1975.

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2024.3.1
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2025-08-10 19:26:50 -04:00
Gold856
81a5a48ac4 Use new vendor JSON repo action (#2049) 2025-08-10 13:59:20 -07:00
Craig Schardt
29e183660f Remove log spam from periodic network and IP address queries (#2051) 2025-08-10 07:22:21 -07:00
cuttestkittensrule
8676649ebc Add PNP_DISTANCE_TRIG_SOLVE strategy to C++ (#2021) 2025-08-10 06:55:04 -07:00
Matt Morley
35dcc3ce5a Verify that nmcli installed (#1929)
## Description

Previously, NetworkManager would happily go asking for networkmanager to
do things even if it wasn't installed. This should never be the case,
but we should bail out early if it is regardless IMO. This prevents logs
and the UI from looking suspiciously "working", if you ignore the exit
code. Up for debate if we actually need this feature.

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2024.3.1
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added

---------

Co-authored-by: Craig Schardt <crschardt@fastem.com>
2025-08-09 22:40:55 -04:00
Sam Freund
9277960018 Add support for object detection on Rubik Pi 3 (#2005) 2025-08-09 10:02:55 -05:00
Gold856
e23df8c9a4 Make dashboard theme transition smooth and fix link color (#2046) 2025-08-08 13:06:26 -07:00
Gold856
22490b8c38 Add an error when the connection for the end calibration request fails (#1840) 2025-08-08 13:06:08 -07:00
Sam Freund
3ac509b40d Expose and document NMS slider (#2028) 2025-08-08 13:05:54 -07:00
Sam Freund
0ea108e17f Run metrics with debug false to reduce log spam (#2027) 2025-08-08 08:14:20 -05:00
Sam Freund
da715244cb Add COCO trained model for RKNN (#2035)
## Description

See #2026 for the previous iteration of this PR.

This adds the RKNN model trained on the COCO dataset as one of the
models shipped with PV. This model is fairly general, and has been
trained to identify a number of objects, including people, animals,
cars, and more. This model is meant for teams to test object detection,
particularly for teams who might not have access to the game elements
that our other models are trained on.

It additionally acknowledges Ultralytics for the model, and includes the
AGPL copyleft license.

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [x] If this PR changes behavior or adds a feature, user documentation
is updated
- [x] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [x] If this PR touches configuration, this is backwards compatible
with settings back to v2024.3.1
- [x] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [x] If this PR addresses a bug, a regression test for it is added
2025-08-08 05:55:14 +00:00
Kevin Cooney
ab854e91e5 [photon-lib] Invalidate pose cache when setting referencePose (#2040) 2025-08-08 00:14:44 -05:00
Rikhil Chilka
a930852bee Add yes pipe for rubik conversion cmd (#2043) 2025-08-07 19:11:43 -05:00
Sam Freund
65c214ac2d Add notebook for Rubik Model conversion (#2006) 2025-08-07 11:11:35 -05:00
Rikhil Chilka
bf8073ab26 Update RKNN Conversion notebook permalinks (#2042) 2025-08-06 22:09:58 +00:00
Rikhil Chilka
2bf166bc3f Update notebook links (#2037) 2025-08-06 16:33:03 -05:00
Gold856
2c98d10a92 Fix labeler labelling everything as backend (#2041) 2025-08-06 16:31:36 -05:00
Gold856
923f9564dc Fix buildAndCopyUI and update build instructions (#2036) 2025-08-05 17:19:37 -05:00
Gold856
ad64bfeaa9 Switch to pnpm (and update some dependencies) (#2032) 2025-08-04 13:59:45 -07:00
Gold856
ffd4d1f80e Add PR labeler (#2031)
## Description

It's nice to be able to filter PRs by what components they've modified.
The 5 labels that have been selected are `frontend`, `backend`,
`documentation`, `photonlib`, and `website`, since those are the primary
components in the monorepo.

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2024.3.1
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2025-08-04 12:18:52 -07:00
Gold856
1310640e10 Revert "Add COCO model for RKNN (#2026)" (#2033)
This reverts commit 753123844b.

## Description

The COCO dataset contains images that use the NC and/or ND variants of
the CC license, and distributing a model based on that dataset is most
likely a violation of licenses. Additionally, the model is licensed under AGPL,
which might be a concern for PhotonVision, and at a minimum, there's no
license file bundled with the model right now.

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [x] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2024.3.1
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2025-08-04 13:51:24 -04:00
Sam Freund
753123844b Add COCO model for RKNN (#2026)
## Description

This adds the RKNN model trained on the COCO dataset as one of the
models shipped with PV. This model is fairly general, and has been
trained to identify a number of objects, including people, animals,
cars, and more. This model is meant for teams to test object detection,
particularly for teams who might not have access to the game elements
that our other models are trained on.

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [x] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2024.3.1
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2025-08-04 05:20:51 +00:00
Rikhil Chilka
ba1c0db7e1 RKNN conversion tool (#2024) 2025-08-04 05:15:54 +00:00
Devon Doyle
fce54d12c1 Dark mode and minor interface tweaks (#2016)
Co-authored-by: Sam Freund <samf.236@proton.me>
2025-08-04 05:15:33 +00:00
Gold856
3e19cd45cc Disable linkcheck for www.gnu.org (#2030) 2025-08-03 22:54:08 -05:00
Rikhil Chilka
6b49e92d00 Add model benchmark data (#2025) 2025-08-03 23:37:16 +00:00
Kevin Cooney
29e24bbac2 [photon-lib] Python support for PNP_DISTANCE_TRIG_SOLVE (#2015)
This adds support for PNP_DISTANCE_TRIG_SOLVE in the the python
PhotonPoseEstimator, mirroring the implementation in the Java
PhotonPoseEstimator.

Changes:
- Add PoseStrategy.PNP_DISTANCE_TRIG_SOLVE
- Add addHeadingData() and resetHeadingData() to PhotonPoseEstimator
- Fix PhotonCameraSim.process() to set ntReceiveTimestampMicros in the
result
- Minor readability improvements to PhotonPipelineResult
- Minor test improvements to PhotonPoseEstimatorTest
- Add .vscode/settings.json (to make running python tests in VSCode
easier)

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2024.3.1
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added

---------

Co-authored-by: Sam948-byte <samf.236@proton.me>
2025-08-01 11:04:01 -07:00
Kevin Cooney
cefaa313df Add an overload of resetHeadingData() which takes in a Rotation3d (#2013) 2025-07-27 19:28:16 -05:00
Sam Freund
4b5bc6ae84 Update logic for metric publisher topic name (#2011) 2025-07-23 23:23:10 -05:00
Sam Freund
758fbb9110 Update metrics publisher hostname when hostname is changed (#2008) 2025-07-22 23:56:32 +00:00
Sam Freund
02e6b6d3e2 Move metrics subtable to root PV table (#2007) 2025-07-22 18:28:48 -05:00
Sam Freund
af689b61d5 Add Gradle wrapper validation (#2004)
Check Gradle wrapper with gradle/actions/wrapper-validation to avoid supply chain attacks
2025-07-19 18:59:26 -04:00
Gold856
8215cafbae Fix camera calibration card contents completely vanishing during calibration (#1998)
## Description

Per
https://github.com/PhotonVision/photonvision/pull/1972#issuecomment-3066574742,
camera calibration got broken because I accidently hid the entire
contents of the camera calibration card in #1972. Now, v-show is only
applied to the calibration table so that only the calibration table is
hidden during calibration.

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2024.3.1
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2025-07-13 21:56:19 -04:00
Gold856
ed58f69275 Dynamically import dependencies that are not critical to viewing the UI (#2001) 2025-07-13 14:28:45 -07:00
Gold856
a62d5e0eee Make testHeadless output the same stuff as test from photon-lib (#2000) 2025-07-13 06:24:09 -07:00
Gold856
2e97c95be1 Restore original video mode index order (#1999) 2025-07-13 06:23:35 -07:00
Gold856
6610b21b6e Refactor MAC address detection (#1991)
Co-authored-by: Sam Freund <techguy763@gmail.com>
2025-07-13 04:59:16 +00:00
Jade
ef5e6463cb Clarify and fix OpenCV/WPILib version checking error (#1963)
## Description

Fixed the error in the OpenCV/WPILib version checking crash and
clarified it, since it's not PhotonVision that needs updating, but
rather WPILib.

Reported on chief
https://www.chiefdelphi.com/t/opencv-is-version-4-6-0-and-needs-to-be-4-10-0/501751/7

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2024.3.1
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added

---------

Signed-off-by: Jade Turner <spacey-sooty@proton.me>
Co-authored-by: Gold856 <117957790+Gold856@users.noreply.github.com>
2025-07-12 05:44:55 -04:00
Sam Freund
7f6edcd567 feat: add metrics publisher for NT (#1791)
Publishes metrics to NT using a protobuf under
`photonvision/coprocessors/metrics` using the device host name as the
key.

Refactors metrics to use numbers where possible, instead of strings.

Removes GPU mem display from metrics card when it can't be determined.

Updates UI metrics periodically.

Resolves #1988

Closes #830

---------

Co-authored-by: Matt <matthew.morley.ca@gmail.com>
Co-authored-by: Gold856 <117957790+Gold856@users.noreply.github.com>
2025-07-12 09:40:58 +00:00
Gold856
d7e536dda9 Clean up spacing and other things in various parts of the UI (#1972)
## Description

After the Vue 3 upgrade, the spacing for various UI elements was left
inconsistent in many places. Dialogs were hit especially hard and had
some very inconsistent spacing. Additionally, the 24 pixels of padding
around all cards was noted as a waste of space and unnecessary, so it
has been shrunk down to 20 pixels to make the UI a tiny bit more compact
and to make it visually closer to some parts of the UI that have 16
pixels of padding (the camera views are the most notable example).
Padding between input elements has also been reduced to 20 pixels (this
required some hackery to get consistent sizes on input elements, since
switches and sliders have different heights.)

Some other minor UI tweaks were made, such as removing the divider
between dialog contents and dialog buttons because it visually looks
better, shrinking the banner padding so it doesn't displace as much
content, making the banner background one uniform color instead of a
highlight around the icon, fixing the targets tab so that the columns
stop shifting around when the values change, preserving newlines in the
log view, cleaning up the object detection UI, and making the import
dialogs have consistently inset input elements.

Old dashboard:

![image](https://github.com/user-attachments/assets/409c7ddd-4b7d-4535-9f3f-3970d9dd85f8)

New dashboard:

![image](https://github.com/user-attachments/assets/587ac540-1d6d-40e5-9c6b-00697bab6cbc)

Old Camera tab:

![image](https://github.com/user-attachments/assets/2f1d50a1-131f-4fb7-8617-e1cb4dc5504c)

New Camera tab:

![image](https://github.com/user-attachments/assets/6d5581b7-faff-400a-8e34-e3abf00e0af6)

Old Calibration Info:

![image](https://github.com/user-attachments/assets/81133cc1-c861-4746-9b1e-8320312037de)

New Calibration Info:

![image](https://github.com/user-attachments/assets/0de5935c-84a7-4606-bbc1-8e6d227b7b60)

Old Log Viewer:

![image](https://github.com/user-attachments/assets/f2c32a10-3353-4781-93d7-8e0ffa8ca7fe)

New Log Viewer:

![image](https://github.com/user-attachments/assets/0aeee866-c182-4e80-9025-56bf383d714f)

Old Pipeline Creation Dialog:

![image](https://github.com/user-attachments/assets/a0eb368d-d9af-4cb3-8d9c-fcd12a5caf36)

New Pipeline Creation Dialog:

![image](https://github.com/user-attachments/assets/f05f34a3-f42e-4e8f-9ccd-171a48980b8f)

Old Factory Reset:

![image](https://github.com/user-attachments/assets/9c16a7f7-a454-4ee4-8574-98abf9b94e2d)

New Factory Reset:

![image](https://github.com/user-attachments/assets/fb67888c-c4f1-4e8e-9d02-6943e7a918eb)

Old Pipeline Change:

![image](https://github.com/user-attachments/assets/3acb215a-6639-4d50-a4e6-18b50c3ec1bd)

New Pipeline Change:

![image](https://github.com/user-attachments/assets/a2b18582-cdbd-407c-9690-f11aecf78c76)

Old Import Dialog:

![image](https://github.com/user-attachments/assets/ff43b0bd-3f99-44e5-97fa-c250cd331790)

New Import Dialog:

![image](https://github.com/user-attachments/assets/7ec46023-d47a-45d7-80b8-6881b812300e)

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2024.3.1
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2025-07-12 00:02:23 -04:00
Gold856
dbbb00f955 Reenable and fix flaky tests (#1837) 2025-07-09 21:16:44 -07:00
Gold856
78f57600cc Set a more sane timeout for WS connections and log WS errors (#1992) 2025-07-08 21:40:21 -07:00
Sam Freund
d341ebbadf Initial hardware support for Rubik pi (#1989) 2025-07-06 19:39:29 +00:00
Sam Freund
d88ea4a75d De-conflict camera names and hostnames by use of a banner (#1982) 2025-07-04 21:43:17 +00:00
Sam Freund
46ac1baa69 Delete photon-server/photonvision_config_from_2024.3.1 (#1985)
Seems to be unused, since the settings import test we use pulls from test-resources.
2025-07-01 01:34:53 -04:00
Sam Freund
f802e8c10c Update server index wording (#1984) 2025-07-01 00:23:07 -04:00
Jade
647c238987 Fix usage reporting. (#1964)
## Description

Fixes the amount of cameras and pose estimators reported by usage reporting.

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2024.3.1
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added

---------

Signed-off-by: Jade Turner <spacey-sooty@proton.me>
2025-06-30 23:08:23 -04:00
Sam Freund
4a648b302a Migrate NNM Settings to SQLITE (#1894)
Co-authored-by: Gold856 <117957790+Gold856@users.noreply.github.com>
Co-authored-by: Chris Gerth <gerth2@users.noreply.github.com>
2025-06-30 22:02:44 -05:00
Gold856
cc7923eeb4 Fix camera setup modal not closing and navigation not working (#1979) 2025-06-28 23:18:54 -05:00
Sam Freund
a9c26202a0 Fix logic for no cameras detected modal (#1978)
fixes #1977 

Previously, the logic was checking for the camera object to be the same
as the placeholder camera object. Logic has been changed to check only
the name of the camera object.
2025-06-28 16:09:35 -05:00
Matthew Liang
783d9d73be Update list of coprocessors on quick-install.md (#1976) 2025-06-25 21:07:35 -05:00
Jade
efc997dfbd [ci] Run on push or PR (#1974)
## Description

CI will now run whenever a branch is pushed or a PR is opened. Fixes
2027 behaviour and allows running CI on forks

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2024.3.1
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added

Signed-off-by: Jade Turner <spacey-sooty@proton.me>
2025-06-23 23:50:13 -04:00
Gold856
3df58485c2 Disable linkcheck for gnu.org 2025-06-22 03:26:31 -07:00
Sam Freund
25851525ce Fix Broken Docs (#1971)
Some dummy updated the
[Caddyfile](https://github.com/PhotonVision/ansible-playbooks/blob/main/files/caddy/Caddyfile)
on the server but never updated the playbook repo or the CI on main to
reflect that. 🤷 Anyways, we're keeping the new Caddyfile because
it makes pydocs work, and it more accurately reflects our CI process.
2025-06-18 17:26:20 -04:00
Gold856
4b740a5485 Disable Alerts test in PhotonCameraTest (#1969)
## Description

Disable the Alerts test in PhotonCameraTest because it's consistently
failing on Linux.

## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2024.3.1
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added
2025-06-18 03:26:52 +00:00
Matthew Morley
413cf8c4af Fuse off mypy 2025-06-16 15:37:15 -07:00
Matthew Morley
d2193037f9 Nix merch because we can't have nice things 2025-06-16 15:37:03 -07:00
Sam Freund
52125067ac Remove unused template from docs (#1960) 2025-05-08 20:53:48 -07:00
Sam Freund
db591f720c Remove manual links from README (#1959) 2025-05-08 20:51:35 -07:00
Sam Freund
c81d4addb9 [docs] upgrade dependencies (#1958)
Upgrade dependencies for docs page
2025-05-08 20:47:47 -07:00
Sam Freund
aa0760e97a Add basic linux troubleshooting tips (#1885) 2025-05-07 16:09:11 -05:00
Graham
74322affde Clean up client dependencies (#1954)
Co-authored-by: samfreund <techguy763@gmail.com>
Co-authored-by: Gold856 <117957790+Gold856@users.noreply.github.com>
2025-05-07 11:06:07 -05:00
Graham
bec8092660 Vue 3 Upgrade (#1900)
## Description

Upgrades to Vue 3 and necessary associated dependencies. Also fixes some
issues with the layout and adds validation for object detection models.

Closes #885, closes #1943, closes #1449.
## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2024.3.1
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added

---------

Co-authored-by: Matt M <matthew.morley.ca@gmail.com>
Co-authored-by: Gold856 <117957790+Gold856@users.noreply.github.com>
Co-authored-by: samfreund <techguy763@gmail.com>
2025-05-06 18:21:41 -04:00
508 changed files with 25084 additions and 16822 deletions

View File

@@ -68,19 +68,26 @@ ForEachMacros:
- BOOST_FOREACH
IncludeBlocks: Regroup
IncludeCategories:
- Regex: '^<ext/.*\.h>'
Priority: 2
SortPriority: 0
- Regex: '^<.*\.h>'
Priority: 1
SortPriority: 0
- Regex: '^<.*'
Priority: 2
SortPriority: 0
- Regex: '.*'
Priority: 3
SortPriority: 0
IncludeIsMainRegex: '([-_](test|unittest))?$'
# C standard library headers
#
# https://en.cppreference.com/w/cpp/header:
# * C compatibility headers
# * Special C compatibility headers
# * Empty C headers
# * Meaningless C headers
# * Unsupported C headers
- Regex: '^<(assert\.h|ctype\.h|errno\.h|fenv\.h|float\.h|inttypes\.h|limits\.h|locale\.h|math\.h|setjmp\.h|signal\.h|stdarg\.h|stddef\.h|stdint\.h|stdio\.h|stdlib\.h|string\.h|time\.h|uchar\.h|wchar\.h|wctype\.h|stdatomic\.h|ccomplex|complex\.h|ctgmath|tgmath\.h|ciso646|cstdalign|cstdbool|iso646\.h|stdalign\.h|stdbool\.h|stdatomic\.h|stdnoreturn\.h|threads\.h)>'
Priority: 1
# C++ standard library headers (lowercase and underscores with no .h suffix)
- Regex: '^<[a-z_]+>'
Priority: 2
# Other library headers (angle brackets)
- Regex: '^<.*'
Priority: 3
# Project headers (double quotes)
- Regex: '^".*'
Priority: 4
IncludeIsMainRegex: '(Test|_test)?$'
IncludeIsMainSourceRegex: ''
IndentCaseLabels: true
IndentGotoLabels: true
@@ -136,7 +143,7 @@ RawStringFormats:
CanonicalDelimiter: ''
BasedOnStyle: google
ReflowComments: true
SortIncludes: false
SortIncludes: true
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false

15
.github/labeler.yml vendored Normal file
View File

@@ -0,0 +1,15 @@
"backend":
- changed-files:
- any-glob-to-any-file: [photon-core/**, photon-server/**]
"documentation":
- changed-files:
- any-glob-to-any-file: [docs/**, photon-docs/**]
"frontend":
- changed-files:
- any-glob-to-any-file: photon-client/**
"photonlib":
- changed-files:
- any-glob-to-any-file: photon-lib*/**
"website":
- changed-files:
- any-glob-to-any-file: website/**

View File

@@ -1,18 +1,18 @@
## Description
<!-- What changed? Why? (the code + comments should speak for itself on the "how") -->
What changed? Why? (the code + comments should speak for itself on the "how")
<!-- Fun screenshots or a cool video or something are super helpful as well. If this touches platform-specific behavior, this is where test evidence should be collected. -->
Include fun testing screenshots or a cool video, to collect test evidence in a place where we can later reference it. Including proof this change was tested makes reviewing easier, helps us make sure we tested all our edge cases, and helps provide context for the future.
<!-- Any issues this pull request closes or pull requests this supersedes should be linked with `Closes #issuenumber`. -->
Any issues this pull request closes or pull requests this supersedes should be linked with `Closes #issuenumber`.
## Meta
Merge checklist:
- [ ] Pull Request title is [short, imperative summary](https://cbea.ms/git-commit/) of proposed changes
- [ ] The description documents the _what_ and _why_
- [ ] The description documents the _what_ and _why_, including events that led to this PR
- [ ] If this PR changes behavior or adds a feature, user documentation is updated
- [ ] If this PR touches photon-serde, all messages have been regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible with settings back to v2024.3.1
- [ ] If this PR touches configuration, this is backwards compatible with all settings going back to the previous seasons's last release (seasons end after champs ends)
- [ ] If this PR touches pipeline settings or anything related to data exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added

View File

@@ -3,38 +3,24 @@ name: Build
on:
# Run on pushes to main and pushed tags, and on pull requests against main, but ignore the docs folder
push:
branches: [ main ]
tags:
- 'v*'
pull_request:
branches: [ main ]
merge_group:
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
cancel-in-progress: true
env:
IMAGE_VERSION: v2026.1.2
jobs:
build-client:
name: "PhotonClient Build"
defaults:
run:
working-directory: photon-client
runs-on: ubuntu-22.04
validation:
name: "Validation"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 18
- name: Install Dependencies
run: npm ci
- name: Build Production Client
run: npm run build
- uses: actions/upload-artifact@v4
with:
name: built-client
path: photon-client/dist/
- uses: actions/checkout@v6
- uses: gradle/actions/wrapper-validation@v5
build-examples:
strategy:
@@ -42,77 +28,148 @@ jobs:
matrix:
include:
- os: windows-2022
architecture: x64
artifact-name: Win64
- os: macos-14
architecture: aarch64
- os: ubuntu-22.04
artifact-name: macOS
- os: ubuntu-24.04
artifact-name: Linux
name: "Photonlib - Build Examples - ${{ matrix.os }}"
runs-on: ${{ matrix.os }}
needs: [build-photonlib-host, build-photonlib-docker]
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Fetch tags
run: git fetch --tags --force
- name: Install Java 17
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
java-version: 17
distribution: temurin
- name: Install RoboRIO Toolchain
run: ./gradlew installRoboRioToolchain
# Need to publish to maven local first, so that C++ sim can pick it up
- name: Publish photonlib to maven local
run: ./gradlew photon-targeting:publishtomavenlocal photon-lib:publishtomavenlocal -x check
- name: Delete duplicate toolchains
run: |
find ~/.gradle/cache/ -name *roborio-academic* -exec rm -rf {} +
du -h . | sort -h
if: matrix.os == 'ubuntu-24.04'
# Download prebuilt photonlib artifacts
- uses: actions/download-artifact@v7
with:
name: maven-${{ matrix.artifact-name }}
- uses: actions/download-artifact@v7
with:
name: maven-Athena
- name: Move to maven local
run: |
mkdir -p ~/.m2/repository/
mv maven/org ~/.m2/repository/
- name: Copy vendordeps
shell: bash
run: |
for vendordep_folder in photonlib-*-examples/*/; do
# Remove trailing slash for cross-platform compatibility
vendordep_folder="${vendordep_folder%/}"
# Filter for projects only
if [ -e "$vendordep_folder/build.gradle" ]; then
mkdir -p "$vendordep_folder/vendordeps/"
cp vendordeps/photonlib-json-1.0.json "$vendordep_folder/vendordeps/"
fi
done
- name: Build Java examples
working-directory: photonlib-java-examples
run: ./gradlew build
run: |
./gradlew build
./gradlew clean
- name: Build C++ examples
working-directory: photonlib-cpp-examples
run: ./gradlew build
build-gradle:
name: "Gradle Build"
runs-on: ubuntu-22.04
run: |
./gradlew build
./gradlew clean
playwright-tests:
name: "Playwright E2E tests"
runs-on: ubuntu-24.04
needs: [validation]
steps:
# Checkout code.
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Fetch tags
run: git fetch --tags --force
- name: Install Java 17
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
java-version: 17
distribution: temurin
- name: Install mrcal deps
run: sudo apt-get update && sudo apt-get install -y libcholmod3 liblapack3 libsuitesparseconfig5
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 10
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 22
- name: Setup tests
working-directory: photon-client
run: |
pnpm install
pnpm test-setup
- name: Prebuild Gradle
run: ./gradlew photon-targeting:build photon-core:build photon-server:build -x check
- name: Run Playwright tests
working-directory: photon-client
run: pnpm test
- uses: actions/upload-artifact@v6
if: ${{ !cancelled() }}
with:
name: playwright-report
path: photon-client/playwright-report/
retention-days: 30
build-gradle:
name: "Gradle Build"
runs-on: ubuntu-24.04
needs: [validation]
steps:
# Checkout code.
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Fetch tags
run: git fetch --tags --force
- name: Install Java 17
uses: actions/setup-java@v5
with:
java-version: 17
distribution: temurin
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 10
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 22
- name: Gradle Build
run: ./gradlew photon-targeting:build photon-core:build photon-server:build -x check
- name: Gradle Tests
run: ./gradlew testHeadless -i --stacktrace
- name: Gradle Coverage
run: ./gradlew jacocoTestReport
- name: Publish Coverage Report
uses: codecov/codecov-action@v4
with:
file: ./photon-server/build/reports/jacoco/test/jacocoTestReport.xml
- name: Publish Core Coverage Report
uses: codecov/codecov-action@v4
with:
file: ./photon-core/build/reports/jacoco/test/jacocoTestReport.xml
- name: Gradle Tests and Coverage
run: ./gradlew test jacocoTestReport --stacktrace
build-offline-docs:
name: "Build Offline Docs"
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: '3.11'
python-version: 3.14
- name: Install graphviz
run: |
sudo apt-get update
@@ -127,21 +184,22 @@ jobs:
working-directory: docs
run: |
make html
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v6
with:
name: built-docs
path: docs/build/html
build-photonlib-vendorjson:
name: "Build Vendor JSON"
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
needs: [validation]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Install Java 17
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
java-version: 17
distribution: temurin
@@ -156,41 +214,39 @@ jobs:
mv photon-lib/build/generated/vendordeps/photonlib.json photon-lib/build/generated/vendordeps/photonlib-$(git describe --tags --match=v*).json
# Upload it here so it shows up in releases
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v6
with:
name: photonlib-vendor-json
path: photon-lib/build/generated/vendordeps/photonlib-*.json
build-photonlib-host:
env:
MACOSX_DEPLOYMENT_TARGET: 13
MACOSX_DEPLOYMENT_TARGET: 14
strategy:
fail-fast: false
matrix:
include:
- os: windows-2022
artifact-name: Win64
architecture: x64
- os: macos-14
- os: macos-26
artifact-name: macOS
architecture: aarch64
- os: ubuntu-22.04
- os: ubuntu-24.04
artifact-name: Linux
name: "Photonlib - Build Host - ${{ matrix.artifact-name }}"
runs-on: ${{ matrix.os }}
needs: [validation]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Install Java 17
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
java-version: 17
distribution: temurin
architecture: ${{ matrix.architecture }}
- run: git fetch --tags --force
- run: ./gradlew photon-targeting:build photon-lib:build -i
- run: ./gradlew photon-targeting:build photon-lib:build
name: Build with Gradle
- run: ./gradlew photon-lib:publish photon-targeting:publish
name: Publish
@@ -199,7 +255,7 @@ jobs:
if: github.event_name == 'push' && github.repository_owner == 'photonvision'
# Copy artifacts to build/outputs/maven
- run: ./gradlew photon-lib:publish photon-targeting:publish -PcopyOfflineArtifacts
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v6
with:
name: maven-${{ matrix.artifact-name }}
path: build/outputs
@@ -219,11 +275,12 @@ jobs:
artifact-name: Aarch64
build-options: "-Ponlylinuxarm64"
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
container: ${{ matrix.container }}
name: "Photonlib - Build Docker - ${{ matrix.artifact-name }}"
needs: [validation]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Config Git
@@ -231,7 +288,7 @@ jobs:
git config --global --add safe.directory /__w/photonvision/photonvision
- name: Build PhotonLib
# We don't need to run tests, since we specify only non-native platforms
run: ./gradlew photon-targeting:build photon-lib:build ${{ matrix.build-options }} -i -x test
run: ./gradlew photon-targeting:build photon-lib:build ${{ matrix.build-options }} -x test
- name: Publish
run: ./gradlew photon-lib:publish photon-targeting:publish ${{ matrix.build-options }}
env:
@@ -239,7 +296,7 @@ jobs:
if: github.event_name == 'push' && github.repository_owner == 'photonvision'
# Copy artifacts to build/outputs/maven
- run: ./gradlew photon-lib:publish photon-targeting:publish -PcopyOfflineArtifacts ${{ matrix.build-options }}
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v6
with:
name: maven-${{ matrix.artifact-name }}
path: build/outputs
@@ -247,14 +304,14 @@ jobs:
combine:
name: Combine
needs: [build-photonlib-docker, build-photonlib-host]
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
fetch-depth: 0
- run: git fetch --tags --force
# download all maven-* artifacts to outputs/
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v7
with:
merge-multiple: true
path: output
@@ -264,68 +321,51 @@ jobs:
name: ZIP stuff up
working-directory: output
- run: ls output
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v6
with:
name: photonlib-offline
path: output/*.zip
build-package:
needs: [build-client, build-gradle, build-offline-docs]
build-package-linux:
needs: [build-gradle, build-offline-docs]
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
artifact-name: Win64
architecture: x64
arch-override: winx64
- os: macos-latest
artifact-name: macOS
architecture: x64
arch-override: macx64
- os: macos-latest
artifact-name: macOSArm
architecture: x64
arch-override: macarm64
- os: ubuntu-22.04
- os: ubuntu-24.04
artifact-name: Linux
architecture: x64
arch-override: linuxx64
- os: ubuntu-22.04
- os: ubuntu-24.04
artifact-name: LinuxArm64
architecture: x64
arch-override: linuxarm64
runs-on: ${{ matrix.os }}
name: "Build fat JAR - ${{ matrix.artifact-name }}"
steps:
- uses: actions/checkout@v4
steps: &build-package-steps
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Install Java 17
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
java-version: 17
distribution: temurin
architecture: ${{ matrix.architecture }}
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 10
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 22
cache: pnpm
cache-dependency-path: photon-client/pnpm-lock.yaml
- name: Install Arm64 Toolchain
run: ./gradlew installArm64Toolchain
if: ${{ (matrix.artifact-name) == 'LinuxArm64' }}
- run: |
rm -rf photon-server/src/main/resources/web/*
mkdir -p photon-server/src/main/resources/web/docs
if: ${{ (matrix.os) != 'windows-latest' }}
- run: |
del photon-server\src\main\resources\web\*.*
mkdir photon-server\src\main\resources\web\docs
if: ${{ (matrix.os) == 'windows-latest' }}
- uses: actions/download-artifact@v4
with:
name: built-client
path: photon-server/src/main/resources/web/
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v7
with:
name: built-docs
path: photon-server/src/main/resources/web/docs
@@ -333,228 +373,275 @@ jobs:
if: ${{ (matrix.arch-override != 'none') }}
- run: ./gradlew photon-server:shadowJar
if: ${{ (matrix.arch-override == 'none') }}
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v6
with:
name: jar-${{ matrix.artifact-name }}
path: photon-server/build/libs
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v6
with:
name: photon-targeting_jar-${{ matrix.artifact-name }}
path: photon-targeting/build/libs
run-smoketest-native:
needs: [build-package]
build-package-macos:
needs: [build-gradle, build-offline-docs]
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-22.04
- os: macos-latest
artifact-name: macOSArm
arch-override: macarm64
- os: macos-latest
artifact-name: macOS
arch-override: macx64
runs-on: ${{ matrix.os }}
name: "Build fat JAR - ${{ matrix.artifact-name }}"
steps: *build-package-steps
build-package-windows:
needs: [build-gradle, build-offline-docs]
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
artifact-name: Win64
arch-override: winx64
runs-on: ${{ matrix.os }}
name: "Build fat JAR - ${{ matrix.artifact-name }}"
steps: *build-package-steps
run-smoketest-native:
needs: [build-package-linux, build-package-macos, build-package-windows]
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-24.04
artifact-name: jar-Linux
extraOpts: -Djdk.lang.Process.launchMechanism=vfork
- os: windows-latest
artifact-name: jar-Win64
extraOpts: ""
- os: macos-latest
artifact-name: jar-macOS
architecture: x64
runs-on: ${{ matrix.os }}
steps:
- name: Install Java 17
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
java-version: 17
distribution: temurin
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v7
with:
name: ${{ matrix.artifact-name }}
# On linux, install mrcal packages
- run: |
sudo apt-get update
sudo apt-get install --yes libcholmod3 liblapack3 libsuitesparseconfig5
if: ${{ (matrix.os) == 'ubuntu-22.04' }}
# and actually run the jar
- run: java -jar ${{ matrix.extraOpts }} *.jar --smoketest
if: ${{ (matrix.os) != 'windows-latest' }}
- run: ls *.jar | %{ Write-Host "Running $($_.Name)"; Start-Process "java" -ArgumentList "-jar `"$($_.FullName)`" --smoketest" -NoNewWindow -Wait; break }
if: ${{ (matrix.os) == 'windows-latest' }}
run-smoketest-chroot:
needs: [build-package]
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-22.04
artifact-name: LinuxArm64
image_suffix: RaspberryPi
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/v2025.0.3/photonvision_raspi.img.xz
cpu: cortex-a7
image_additional_mb: 0
extraOpts: -Djdk.lang.Process.launchMechanism=vfork
runs-on: ${{ matrix.os }}
name: smoketest-${{ matrix.image_suffix }}
steps:
- uses: actions/download-artifact@v4
with:
name: jar-${{ matrix.artifact-name }}
- uses: pguyot/arm-runner-action@v2
name: Run photon smoketest
id: generate_image
with:
base_image: ${{ matrix.image_url }}
image_additional_mb: ${{ matrix.image_additional_mb }}
optimize_image: yes
cpu: ${{ matrix.cpu }}
# We do _not_ wanna copy photon into the image. Bind mount instead
bind_mount_repository: true
# our image better have java installed already
commands: |
java -jar ${{ matrix.extraOpts }} *.jar --smoketest
build-image:
needs: [build-package]
if: ${{ github.event_name != 'pull_request' }}
needs: [build-package-linux]
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-22.04
- os: ubuntu-24.04-arm
artifact-name: LinuxArm64
image_suffix: RaspberryPi
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/v2025.0.3/photonvision_raspi.img.xz
cpu: cortex-a7
image_additional_mb: 0
- os: ubuntu-22.04
plat_override: LINUX_RASPBIAN64
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_raspi.img.xz
minimum_free_mb: 100
- os: ubuntu-24.04-arm
artifact-name: LinuxArm64
image_suffix: limelight2
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/v2025.0.3/photonvision_limelight.img.xz
cpu: cortex-a7
image_additional_mb: 0
- os: ubuntu-22.04
plat_override: LINUX_RASPBIAN64
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_limelight.img.xz
minimum_free_mb: 100
- os: ubuntu-24.04-arm
artifact-name: LinuxArm64
image_suffix: limelight3
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/v2025.0.3/photonvision_limelight3.img.xz
cpu: cortex-a7
image_additional_mb: 0
- os: ubuntu-22.04
plat_override: LINUX_RASPBIAN64
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_limelight3.img.xz
minimum_free_mb: 100
- os: ubuntu-24.04-arm
artifact-name: LinuxArm64
image_suffix: limelight3G
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/v2025.0.3/photonvision_limelight3g.img.xz
cpu: cortex-a7
image_additional_mb: 0
- os: ubuntu-22.04
plat_override: LINUX_RASPBIAN64
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_limelight3g.img.xz
minimum_free_mb: 100
- os: ubuntu-24.04-arm
artifact-name: LinuxArm64
image_suffix: limelight4
plat_override: LINUX_RASPBIAN64
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_limelight4.img.xz
minimum_free_mb: 100
- os: ubuntu-24.04-arm
artifact-name: LinuxArm64
image_suffix: luma_p1
plat_override: LINUX_RASPBIAN64
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_luma_p1.img.xz
minimum_free_mb: 100
- os: ubuntu-24.04-arm
artifact-name: LinuxArm64
image_suffix: orangepi5
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/v2025.0.3/photonvision_opi5.img.xz
cpu: cortex-a8
image_additional_mb: 1024
- os: ubuntu-22.04
plat_override: LINUX_RK3588_64
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_opi5.img.xz
minimum_free_mb: 1024
- os: ubuntu-24.04-arm
artifact-name: LinuxArm64
image_suffix: orangepi5b
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/v2025.0.3/photonvision_opi5b.img.xz
cpu: cortex-a8
image_additional_mb: 1024
- os: ubuntu-22.04
plat_override: LINUX_RK3588_64
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_opi5b.img.xz
minimum_free_mb: 1024
- os: ubuntu-24.04-arm
artifact-name: LinuxArm64
image_suffix: orangepi5plus
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/v2025.0.3/photonvision_opi5plus.img.xz
cpu: cortex-a8
image_additional_mb: 1024
- os: ubuntu-22.04
plat_override: LINUX_RK3588_64
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_opi5plus.img.xz
minimum_free_mb: 1024
- os: ubuntu-24.04-arm
artifact-name: LinuxArm64
image_suffix: orangepi5pro
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/v2025.0.3/photonvision_opi5pro.img.xz
cpu: cortex-a8
image_additional_mb: 1024
- os: ubuntu-22.04
plat_override: LINUX_RK3588_64
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_opi5pro.img.xz
minimum_free_mb: 1024
- os: ubuntu-24.04-arm
artifact-name: LinuxArm64
image_suffix: orangepi5max
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/v2025.0.3/photonvision_opi5max.img.xz
cpu: cortex-a8
image_additional_mb: 1024
- os: ubuntu-22.04
plat_override: LINUX_RK3588_64
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_opi5max.img.xz
minimum_free_mb: 1024
- os: ubuntu-24.04-arm
artifact-name: LinuxArm64
image_suffix: rock5c
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/v2025.0.3/photonvision_rock5c.img.xz
cpu: cortex-a8
image_additional_mb: 1024
plat_override: LINUX_RK3588_64
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_rock5c.img.xz
minimum_free_mb: 1024
- os: ubuntu-24.04-arm
artifact-name: LinuxArm64
image_suffix: rubikpi3
plat_override: LINUX_QCS6490
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_rubikpi3.tar.xz
minimum_free_mb: 1024
root_location: 'offset=569376768'
shrink_image: 'no'
runs-on: ${{ matrix.os }}
name: "Build image - ${{ matrix.image_url }}"
name: "Build image - ${{ matrix.image_suffix }}"
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v7
with:
name: jar-${{ matrix.artifact-name }}
- uses: pguyot/arm-runner-action@HEAD
- uses: photonvision/photon-image-runner@HEAD
name: Generate image
id: generate_image
with:
base_image: ${{ matrix.image_url }}
image_additional_mb: ${{ matrix.image_additional_mb }}
optimize_image: yes
cpu: ${{ matrix.cpu }}
# We do _not_ wanna copy photon into the image. Bind mount instead
bind_mount_repository: true
commands: |
chmod +x scripts/armrunner.sh
./scripts/armrunner.sh
image_url: ${{ matrix.image_url }}
minimum_free_mb: ${{ matrix.minimum_free_mb }}
root_location: ${{ matrix.root_location || 'partition=2' }}
shrink_image: ${{ matrix.shrink_image || 'yes' }}
commands: ./scripts/armrunner.sh
- name: Compress image
# Compress the standard images
if: ${{ ! startsWith(matrix.image_suffix, 'rubik') }}
run: |
set -ex
new_jar=$(realpath $(find . -name photonvision\*-linuxarm64.jar))
new_image_name=$(basename "${new_jar/.jar/_${{ matrix.image_suffix }}.img}")
mv ${{ steps.generate_image.outputs.image }} $new_image_name
sudo xz -T 0 -v $new_image_name
- uses: actions/upload-artifact@v4
sudo mv ${{ steps.generate_image.outputs.image }} $new_image_name
sudo xz -T 0 -kv $new_image_name
echo "smoketest_image_loc=${new_image_name}" >> $GITHUB_ENV
- name: Tar built image (Rubik)
# Build the RubikPi3-specific tar file
if: ${{ startsWith(matrix.image_suffix, 'rubik') }}
run: |
set -ex
new_jar=$(realpath $(find . -name photonvision\*-linuxarm64.jar))
tardir=$(basename "${new_jar/.jar/_${{ matrix.image_suffix }}.img}")
imagedir=$(dirname ${{ steps.generate_image.outputs.image }})
sudo mkdir --parents ${tardir}
sudo cp ${imagedir}/* ${tardir}/
sudo tar -I 'xz -T0' -cf ${tardir}.tar.xz ${tardir} --checkpoint=10000 --checkpoint-action=echo='%T'
# Point smoketest to the old image
echo "smoketest_image_loc=${{ steps.generate_image.outputs.image }}" >> $GITHUB_ENV
- uses: actions/upload-artifact@v6
name: Upload image
with:
name: image-${{ matrix.image_suffix }}
path: photonvision*.xz
# This is done after uploading the image to avoid contaminating the image with logs, caches, etc.
- uses: photonvision/photon-image-runner@HEAD
name: Smoketest Image
with:
image_url: file://${{ env.smoketest_image_loc }}
minimum_free_mb: ${{ matrix.minimum_free_mb }}
root_location: ${{ matrix.root_location || 'partition=2' }}
shrink_image: ${{ matrix.shrink_image || 'yes' }}
commands: java -jar *.jar --smoketest --platform=${{ matrix.plat_override }}
matrix-checker:
# This job always runs last to set the overall result based on the matrix jobs. If any matrix job failed, this job will fail.
# This makes it so that we don't need to add each matrix job individually to CI checks.
runs-on: ubuntu-latest
needs: [build-image]
if: always()
steps:
- run: ${{!contains(needs.*.result, 'failure')}}
release:
needs: [build-package, build-image, combine]
runs-on: ubuntu-22.04
# Require smoketest-native so that if those fail, we don't release broken artifacts
needs: [build-photonlib-vendorjson, build-image, combine, build-package-linux, build-package-macos, build-package-windows, run-smoketest-native]
if: (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) && github.repository == 'PhotonVision/photonvision'
runs-on: ubuntu-24.04
steps:
# Download all fat JARs
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v7
with:
merge-multiple: true
pattern: jar-*
# Download offline photonlib
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v7
with:
merge-multiple: true
pattern: photonlib-offline
# Download vendor json
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v7
with:
merge-multiple: true
pattern: photonlib-vendor-json
# Download all images
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v7
with:
merge-multiple: true
pattern: image-*
- run: find
# Push to dev release
- uses: pyTooling/Actions/releaser@r0
- uses: pyTooling/Actions/releaser@r6
with:
token: ${{ secrets.GITHUB_TOKEN }}
tag: 'Dev'
rm: true
snapshots: false
files: |
**/*.xz
**/*linux*.jar
@@ -562,38 +649,12 @@ jobs:
**/photonlib*.json
**/photonlib*.zip
if: github.event_name == 'push'
# Upload all jars and xz archives
# Split into two uploads to work around max size limits in action-gh-releases
# https://github.com/softprops/action-gh-release/issues/353
- uses: softprops/action-gh-release@v2.0.9
with:
files: |
**/@(*orangepi5*|*rock5*).xz
if: startsWith(github.ref, 'refs/tags/v')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: softprops/action-gh-release@v2.0.9
with:
files: |
**/!(*orangepi5*|*rock5*).xz
**/*.jar
**/photonlib*.json
**/photonlib*.zip
if: startsWith(github.ref, 'refs/tags/v')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
dispatch:
name: dispatch
needs: [build-photonlib-vendorjson, release]
runs-on: ubuntu-22.04
steps:
- uses: peter-evans/repository-dispatch@v3
if: |
github.repository == 'PhotonVision/photonvision' &&
startsWith(github.ref, 'refs/tags/v')
- name: Create Vendor JSON Repo PR
uses: wpilibsuite/vendor-json-repo/.github/actions/add_vendordep@HEAD
with:
repo: PhotonVision/vendor-json-repo
token: ${{ secrets.VENDOR_JSON_REPO_PUSH_TOKEN }}
repository: PhotonVision/vendor-json-repo
event-type: tag
client-payload: '{"run_id": "${{ github.run_id }}", "package_version": "${{ github.ref_name }}"}'
vendordep_file: ${{ github.workspace }}/photonlib-${{ github.ref_name }}.json
pr_title: Update photonlib to ${{ github.ref_name }}
pr_branch: photonlib-${{ github.ref_name }}
if: github.repository == 'PhotonVision/photonvision' && startsWith(github.ref, 'refs/tags/v')

View File

@@ -0,0 +1,22 @@
name: Dependency Submission
on:
push:
branches: [ 'main' ]
permissions:
contents: write
jobs:
dependency-submission:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v6
- name: Setup Java
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: 17
- name: Generate and submit dependency graph
uses: gradle/actions/dependency-submission@v5

14
.github/workflows/labeler.yml vendored Normal file
View File

@@ -0,0 +1,14 @@
name: "Pull Request Labeler"
on:
- pull_request_target
jobs:
labeler:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v6
with:
sync-labels: true

View File

@@ -3,87 +3,110 @@ name: Lint and Format
on:
# Run on pushes to main and pushed tags, and on pull requests against main, but ignore the docs folder
push:
branches: [ main ]
tags:
- 'v*'
pull_request:
branches: [ main ]
merge_group:
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
cancel-in-progress: true
jobs:
validation:
name: "Validation"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: gradle/actions/wrapper-validation@v5
wpiformat:
name: "wpiformat"
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Fetch all history and metadata
run: |
git fetch --prune --unshallow
git checkout -b pr
git branch -f main origin/main
- name: Set up Python 3.8
uses: actions/setup-python@v4
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: 3.11
python-version: 3.14
- name: Install wpiformat
run: pip3 install wpiformat==2025.33
run: pip3 install wpiformat==2025.79
- name: Run
run: wpiformat
- name: Check output
run: git --no-pager diff --exit-code HEAD
run: |
set +e
git --no-pager diff --exit-code HEAD
exit_code=$?
if test "$exit_code" -ne "0"; then
echo "::error ::Linting failed. See https://docs.photonvision.org/en/latest/docs/contributing/linting.html"
exit $exit_code
fi
- name: Generate diff
run: git diff HEAD > wpiformat-fixes.patch
if: ${{ failure() }}
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v6
with:
name: wpiformat fixes
path: wpiformat-fixes.patch
if: ${{ failure() }}
javaformat:
name: "Java Formatting"
runs-on: ubuntu-22.04
needs: [validation]
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: actions/setup-java@v4
- uses: actions/setup-java@v5
with:
java-version: 17
distribution: temurin
- run: ./gradlew spotlessCheck
- run: |
set +e
./gradlew spotlessCheck
exit_code=$?
if test "$exit_code" -ne "0"; then
echo "::error ::Linting failed. See https://docs.photonvision.org/en/latest/docs/contributing/linting.html"
exit $exit_code
fi
name: Run spotless
client-lint-format:
name: "PhotonClient Lint and Formatting"
defaults:
run:
working-directory: photon-client
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
- uses: actions/checkout@v6
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
node-version: 18
version: 10
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 22
cache: pnpm
cache-dependency-path: photon-client/pnpm-lock.yaml
- name: Install Dependencies
run: npm ci
run: pnpm i --frozen-lockfile
- name: Check Linting
run: npm run lint-ci
- name: Check Formatting
run: npm run format-ci
server-index:
name: "Check server index.html not changed"
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Fetch all history and metadata
run: |
git fetch --prune --unshallow
git checkout -b pr
git branch -f main origin/main
- name: Check index.html not changed
run: git --no-pager diff --exit-code origin/main photon-server/src/main/resources/web/index.html
set +e
pnpm run lint-ci
exit_code=$?
if test "$exit_code" -ne "0"; then
echo "::error ::Linting failed. See https://docs.photonvision.org/en/latest/docs/contributing/linting.html"
exit $exit_code
fi
- name: Check Formatting
run: |
set +e
pnpm run format-ci
exit_code=$?
if test "$exit_code" -ne "0"; then
echo "::error ::Linting failed. See https://docs.photonvision.org/en/latest/docs/contributing/linting.html"
exit $exit_code
fi

View File

@@ -3,12 +3,7 @@ name: Photon API Documentation
on:
# Run on pushes to main and pushed tags, and on pull requests against main, but ignore the docs folder
push:
branches: [ main ]
tags:
- 'v*'
pull_request:
branches: [ main ]
merge_group:
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
@@ -21,39 +16,52 @@ permissions:
id-token: write
jobs:
validation:
name: "Validation"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: gradle/actions/wrapper-validation@v5
build_demo:
name: Build PhotonClient Demo
defaults:
run:
working-directory: photon-client
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
- uses: actions/checkout@v6
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
node-version: 18
version: 10
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 22
cache: pnpm
cache-dependency-path: photon-client/pnpm-lock.yaml
- name: Install Dependencies
run: npm ci
run: pnpm i --frozen-lockfile
- name: Build Production Client
run: npm run build-demo
- uses: actions/upload-artifact@v4
run: pnpm run build-demo
- uses: actions/upload-artifact@v6
with:
name: built-demo
path: photon-client/dist/
run_api_docs:
name: Build API Docs
runs-on: "ubuntu-22.04"
run_java_cpp_docs:
name: Build Java and C++ API Docs
needs: [validation]
runs-on: "ubuntu-24.04"
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Fetch tags
run: git fetch --tags --force
- name: Install Java 17
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
java-version: 17
distribution: temurin
@@ -61,20 +69,20 @@ jobs:
run: |
chmod +x gradlew
./gradlew photon-docs:generateJavaDocs photon-docs:doxygen
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v6
with:
name: built-docs
name: docs-java-cpp
path: photon-docs/build/docs
publish_api_docs:
name: Publish API Docs
needs: [run_api_docs]
runs-on: ubuntu-22.04
needs: [run_java_cpp_docs]
runs-on: ubuntu-24.04
steps:
# Download docs artifact
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v7
with:
name: built-docs
pattern: docs-*
- run: find .
- name: Publish Docs To Development
if: github.ref == 'refs/heads/main'
@@ -83,7 +91,7 @@ jobs:
HOST: ${{ secrets.WEBMASTER_SSH_HOST }}
USER: ${{ secrets.WEBMASTER_SSH_USERNAME }}
KEY: ${{secrets.WEBMASTER_SSH_KEY}}
TARGET: /var/www/html/photonvision-docs/development
TARGET: /var/www/html/photonvision-docs/development/
- name: Publish Docs To Release
if: startsWith(github.ref, 'refs/tags/v')
uses: up9cloud/action-rsync@v1.4
@@ -96,9 +104,9 @@ jobs:
publish_demo:
name: Publish PhotonClient Demo
needs: [build_demo]
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
steps:
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v7
with:
name: built-demo
- run: find .

View File

@@ -2,10 +2,7 @@ name: PhotonVision ReadTheDocs Checks
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
merge_group:
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
@@ -17,14 +14,14 @@ env:
jobs:
build:
name: Build and Check Docs
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: actions/setup-python@v4
- uses: actions/setup-python@v6
with:
python-version: '3.11'
python-version: 3.14
- name: Install and upgrade pip
run: python -m pip install --upgrade pip

View File

@@ -5,68 +5,141 @@ permissions:
on:
push:
branches: [ main ]
tags:
- 'v*'
pull_request:
branches: [ main ]
merge_group:
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
cancel-in-progress: true
jobs:
buildAndDeploy:
runs-on: ubuntu-22.04
build-py:
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: 3.11
python-version: 3.14
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install setuptools wheel pytest mypy
pip install setuptools wheel
- name: Build wheel
working-directory: ./photon-lib/py
run: |
python setup.py sdist bdist_wheel
- name: Run Unit Tests
working-directory: ./photon-lib/py
run: |
pip install --no-cache-dir dist/*.whl
pytest
- name: Run mypy type checking
uses: liskin/gh-problem-matcher-wrap@v3
with:
linters: mypy
run: |
mypy --show-column-numbers --config-file photon-lib/py/pyproject.toml photon-lib
run: python setup.py sdist bdist_wheel
- name: Upload artifacts
uses: actions/upload-artifact@master
uses: actions/upload-artifact@v6
with:
name: dist
path: ./photon-lib/py/dist/
- name: Publish package distributions to TestPyPI
# Only upload on tags
if: startsWith(github.ref, 'refs/tags/v')
test-py:
needs: build-py
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: 3.14
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pytest mypy
- name: Download artifacts
uses: actions/download-artifact@v6
with:
name: dist
path: dist/
- name: Install package
shell: bash
run: pip install --no-cache-dir dist/*.whl
- name: Run Unit Tests
shell: bash
run: pytest --import-mode=importlib photon-lib/py/test/
- name: Run mypy type checking
run: mypy --show-column-numbers --config-file photon-lib/py/pyproject.toml photon-lib
build-python-examples:
needs: build-py
strategy:
matrix:
os: [ubuntu-24.04, windows-2022, macos-14]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: 3.14
- name: Install dependencies
run: |
python -m pip install --upgrade pip
- name: Download artifacts
uses: actions/download-artifact@v6
with:
name: dist
path: ./photon-lib/py/dist/
- name: Install PhotonLibPy package
working-directory: ./photon-lib/py
shell: bash
run: |
pip install --no-cache-dir dist/*.whl
- name: Build Python examples
working-directory: photonlib-python-examples
shell: bash
run: |
for folder in */;
do
echo $folder
./run.sh $folder
done
deploy:
needs: [test-py, build-python-examples]
runs-on: ubuntu-24.04
# Only upload on tags
if: startsWith(github.ref, 'refs/tags/v')
steps:
- name: Download artifacts
uses: actions/download-artifact@v6
with:
name: dist
path: dist/
- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages_dir: ./photon-lib/py/dist/
packages-dir: ./dist/
permissions:
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing

View File

@@ -2,27 +2,29 @@ name: Website
on:
push:
# For now, run on all commits to main
branches: [ main ]
tags:
- 'v*'
pull_request:
branches: [ main ]
merge_group:
jobs:
rsync:
name: Build and Sync Files
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 10
- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: 22
cache: pnpm
cache-dependency-path: website/pnpm-lock.yaml
- name: Install packages
run: npm ci
run: pnpm i --frozen-lockfile
working-directory: website
- name: Build project
run: npm run build
run: pnpm run build
working-directory: website
- uses: up9cloud/action-rsync@v1.4
if: github.ref == 'refs/heads/main'
@@ -37,12 +39,20 @@ jobs:
name: Check Formatting
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 10
- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: 22
cache: pnpm
cache-dependency-path: website/pnpm-lock.yaml
- name: Install Packages
run: npm ci
run: pnpm i --frozen-lockfile
working-directory: website
- name: Run Formatting Check
run: npx prettier -c .
run: pnpm prettier -c .
working-directory: website

12
.gitignore vendored
View File

@@ -5,7 +5,8 @@ __pycache__/
/.vs
backend/settings/
.vscode/
.vscode/*
!.vscode/settings.json
# Docs
_build
# Compiled class file
@@ -145,4 +146,13 @@ networktables.json
photon-server/src/main/resources/web/*
node_modules
dist
dist-ssr
components.d.ts
photon-server/src/main/resources/web/index.html
# Playwright
photon-client/test-results/
photon-client/playwright-report/
photon-client/blob-report/
photon-client/playwright/.cache/
photon-client/playwright/.auth/

1
.python-version Normal file
View File

@@ -0,0 +1 @@
3.14

View File

@@ -6,9 +6,9 @@ sphinx:
fail_on_warning: true
build:
os: ubuntu-22.04
os: ubuntu-24.04
tools:
python: "3.11"
python: "3.12"
apt_packages:
- graphviz
jobs:

View File

@@ -1,40 +0,0 @@
cppHeaderFileInclude {
\.h$
\.hpp$
\.inc$
\.inl$
}
cppSrcFileInclude {
\.cpp$
}
modifiableFileExclude {
\.jpg$
\.jpeg$
\.png$
\.gif$
\.so$
\.dll$
\.webp$
\.ico$
\.rknn$
\.mp4$
\.ttf$
\.woff2$
gradlew
photon-lib/py/photonlibpy/generated/
photon-targeting/src/main/native/cpp/photon/constrained_solvepnp/generate/
photon-targeting/src/generated/
}
includeProject {
^photonLib/
}
includeOtherLibs {
^frc/
^networktables/
^units/
^wpi/
}

6
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,6 @@
{
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
"python.testing.cwd": "photon-lib/py",
"java.configuration.updateBuildConfiguration": "automatic"
}

9
.wpiformat Normal file
View File

@@ -0,0 +1,9 @@
cppHeaderFileInclude {
\.h$
}
modifiableFileExclude {
photon-lib/py/photonlibpy/generated/
photon-targeting/src/generated/
photon-targeting/src/main/native/cpp/photon/constrained_solvepnp/generate/
}

View File

@@ -8,22 +8,22 @@ The latest release of platform-specific jars and images is found [here](https://
If you are interested in contributing code or documentation to the project, please [read our getting started page for contributors](https://docs.photonvision.org/en/latest/docs/contributing/index.html) and **[join the Discord](https://discord.gg/wYxTwym) to introduce yourself!** We hope to provide a welcoming community to anyone who is interested in helping.
## Documentation
- Our main documentation page: [docs.photonvision.org](https://docs.photonvision.org)
- Photon UI demo: [demo.photonvision.org](https://demo.photonvision.org)
- Javadocs: [javadocs.photonvision.org](https://javadocs.photonvision.org)
- C++ Doxygen: [cppdocs.photonvision.org](https://cppdocs.photonvision.org)
## Authors
<a href="https://github.com/PhotonVision/photonvision/graphs/contributors">
<img src="https://contrib.rocks/image?repo=PhotonVision/photonvision" />
</a>
## Documentation
- Our main documentation page: [docs.photonvision.org](https://docs.photonvision.org)
- Photon UI demo: [http://photonvision.global/](http://photonvision.global/) (or [manual link](https://photonvision.github.io/photonvision/built-client/))
- Javadocs: [javadocs.photonvision.org](https://javadocs.photonvision.org) (or [manual link](https://photonvision.github.io/photonvision/built-docs/javadoc/))
- C++ Doxygen [cppdocs.photonvision.org](https://cppdocs.photonvision.org) (or [manual link](https://photonvision.github.io/photonvision/built-docs/doxygen/html/))
## Building
Gradle is used for all C++ and Java code, and NPM is used for the web UI. Instructions to compile PhotonVision yourself can be found [in our docs](https://docs.photonvision.org/en/latest/docs/contributing/building-photon.html#compiling-instructions).
Gradle is used for all C++ and Java code, and pnpm is used for the web UI. Instructions to compile PhotonVision yourself can be found [in our docs](https://docs.photonvision.org/en/latest/docs/contributing/building-photon.html#compiling-instructions).
You can run one of the many built in examples straight from the command line, too! They contain a fully featured robot project, and some include simulation support. The projects can be found inside the [`photonlib-java-examples`](photonlib-java-examples) and [`photonlib-cpp-examples`](photonlib-cpp-examples) subdirectories, respectively. Instructions for running these examples directly from the repo are found [in the docs](https://docs.photonvision.org/en/latest/docs/contributing/building-photon.html#running-examples).
@@ -32,7 +32,6 @@ You can run one of the many built in examples straight from the command line, to
Note that these are case sensitive!
* `-PArchOverride=foobar`: builds for a target system other than your current architecture. [Valid overrides](https://github.com/wpilibsuite/wpilib-tool-plugin/blob/main/src/main/java/edu/wpi/first/tools/NativePlatforms.java) are:
* winx32
* winx64
* winarm64
* macx64
@@ -41,42 +40,39 @@ Note that these are case sensitive!
* linuxarm64
* linuxathena
- `-PtgtIP`: Specifies where `./gradlew deploy` should try to copy the fat JAR to
- `-PtgtUser`: Specifies custom username for `./gradlew deploy` to SSH into
- `-PtgtPw`: Specifies custom password for `./gradlew deploy` to SSH into
- `-Pprofile`: enables JVM profiling
- `-PwithSanitizers`: On Linux, enables `-fsanitize=address,undefined,leak`
If you're cross-compiling, you'll need the wpilib toolchain installed. This can be done via Gradle: for example `./gradlew installArm64Toolchain` or `./gradlew installRoboRioToolchain`
If you're cross-compiling, you'll need the WPILib toolchain installed. This must be done via Gradle: for example `./gradlew installArm64Toolchain` or `./gradlew installRoboRioToolchain`
## Out-of-Source Dependencies
PhotonVision uses the following additional out-of-source repositories for building code.
- Base system images for Raspberry Pi & Orange Pi: https://github.com/PhotonVision/photon-image-modifier
- Base system images for supported coprocessors: https://github.com/PhotonVision/photon-image-modifier
- C++ driver for Raspberry Pi CSI cameras: https://github.com/PhotonVision/photon-libcamera-gl-driver
- JNI code for [mrcal](https://mrcal.secretsauce.net/): https://github.com/PhotonVision/mrcal-java
- Custom build of OpenCV with GStreamer/Protobuf/other custom flags: https://github.com/PhotonVision/thirdparty-opencv
- JNI code for aruco-nano: https://github.com/PhotonVision/aruconano-jni
## Additional packages
For now, using mrcal requires installing these additional packages on Linux systems:
```
sudo apt install libcholmod3 liblapack3 libsuitesparseconfig5
```
- JNI code for RKNN: https://github.com/PhotonVision/rknn_jni
- JNI code for Rubik Pi NPU: https://github.com/PhotonVision/rubik_jni
## Acknowledgments
PhotonVision was forked from [Chameleon Vision](https://github.com/Chameleon-Vision/chameleon-vision/). Thank you to everyone who worked on the original project.
* [WPILib](https://github.com/wpilibsuite) - Specifically [cscore](https://github.com/wpilibsuite/allwpilib/tree/main/cscore), [CameraServer](https://github.com/wpilibsuite/allwpilib/tree/main/cameraserver), [NTCore](https://github.com/wpilibsuite/allwpilib/tree/main/ntcore), and [OpenCV](https://github.com/wpilibsuite/thirdparty-opencv).
* [Apache Commons](https://commons.apache.org/) - Specifically [Commons Math](https://commons.apache.org/proper/commons-math/), and [Commons Lang](https://commons.apache.org/proper/commons-lang/)
* [WPILib](https://github.com/wpilibsuite) - Specifically [allwpilib](https://github.com/wpilibsuite/allwpilib) and [their build of OpenCV](https://github.com/wpilibsuite/thirdparty-opencv).
* [Apache Commons](https://commons.apache.org/) - Specifically [Commons IO](https://commons.apache.org/proper/commons-io/), and [Commons CLI](https://commons.apache.org/proper/commons-cli/)
* [diozero](https://www.diozero.com/)
* [EJML](https://github.com/lessthanoptimal/ejml)
* [Javalin](https://javalin.io/)
* [JSON](https://json.org)
* [FasterXML](https://github.com/FasterXML) - Specifically [jackson](https://github.com/FasterXML/jackson)
* [MessagePack for Java](https://github.com/msgpack/msgpack-java)
* [OSHI](https://github.com/oshi/oshi)
* [QuickBuffers](https://github.com/HebiRobotics/QuickBuffers)
* [SQLite JDBC](https://github.com/xerial/sqlite-jdbc)
* [ZT ZIP](https://github.com/zeroturnaround/zt-zip)
## License

View File

@@ -2,25 +2,24 @@ import edu.wpi.first.toolchain.*
plugins {
id "cpp"
id "com.diffplug.spotless" version "6.24.0"
id "com.diffplug.spotless" version "8.1.0"
id "edu.wpi.first.wpilib.repositories.WPILibRepositoriesPlugin" version "2020.2"
id "edu.wpi.first.GradleRIO" version "2025.3.2"
id 'edu.wpi.first.WpilibTools' version '1.3.0'
id "edu.wpi.first.GradleRIO" version "2026.2.1"
id 'org.photonvision.tools.WpilibTools' version '2.3.3-photon'
id 'com.google.protobuf' version '0.9.3' apply false
id 'edu.wpi.first.GradleJni' version '1.1.0'
id "org.ysb33r.doxygen" version "1.0.4" apply false
id "org.ysb33r.doxygen" version "2.0.0" apply false
id 'com.gradleup.shadow' version '8.3.4' apply false
id "com.github.node-gradle.node" version "7.0.1" apply false
id "org.hidetake.ssh" version "2.11.2" apply false
}
allprojects {
repositories {
maven { url = "https://frcmaven.wpi.edu/artifactory/ex-mvn/" }
mavenCentral()
mavenLocal()
maven { url = "https://maven.photonvision.org/releases" }
maven { url = "https://maven.photonvision.org/snapshots" }
maven { url = "https://jogamp.org/deployment/maven/" }
}
wpilibRepositories.addAllReleaseRepositories(it)
wpilibRepositories.addAllDevelopmentRepositories(it)
@@ -33,28 +32,22 @@ ext.allOutputsFolder = file("$project.buildDir/outputs")
apply from: "versioningHelper.gradle"
ext {
wpilibVersion = "2025.3.2"
wpilibVersion = "2026.2.1"
wpimathVersion = wpilibVersion
openCVYear = "2025"
openCVversion = "4.10.0-3"
javalinVersion = "5.6.2"
libcameraDriverVersion = "v2025.0.3"
rknnVersion = "dev-v2025.0.0-1-g33b6263"
frcYear = "2025"
mrcalVersion = "v2025.0.0";
javalinVersion = "6.7.0"
libcameraDriverVersion = "v2026.0.0"
rknnVersion = "v2026.0.1"
rubikVersion = "v2026.0.1"
frcYear = "2026"
mrcalVersion = "v2026.0.0";
pubVersion = versionString
isDev = pubVersion.startsWith("dev")
// A list, for legacy reasons, with only the current platform contained
wpilibNativeName = wpilibTools.platformMapper.currentPlatform.platformName;
def nativeName = wpilibNativeName
if (wpilibNativeName == "linuxx64") nativeName = "linuxx86-64";
if (wpilibNativeName == "winx64") nativeName = "windowsx86-64";
if (wpilibNativeName == "macx64") nativeName = "osxx86-64";
if (wpilibNativeName == "macarm64") nativeName = "osxarm64";
jniPlatform = nativeName
jniPlatform = wpilibTools.platformMapper.wpilibClassifier;
println("Building for platform " + jniPlatform + " wpilib: " + wpilibNativeName)
println("Using Wpilib: " + wpilibVersion)
@@ -92,7 +85,7 @@ spotless {
format 'misc', {
target fileTree('.') {
include '**/*.md', '**/.gitignore'
exclude '**/build/**', '**/build-*/**'
exclude '**/build/**', '**/build-*/**', '**/node_modules/**'
}
trimTrailingWhitespace()
indentWithSpaces(2)
@@ -101,7 +94,7 @@ spotless {
}
wrapper {
gradleVersion '8.11'
gradleVersion = '8.14.3'
}
ext.getCurrentArch = {
@@ -119,5 +112,6 @@ subprojects {
options.addStringOption("charset", "utf-8")
options.addStringOption("docencoding", "utf-8")
options.addStringOption("encoding", "utf-8")
options.addBooleanOption("Xdoclint/package:-org.photonvision.proto,-org.photonvision.struct,-org.photonvision.targeting.proto,-org.photonvision.jni", true)
}
}

View File

@@ -1,10 +1,8 @@
import argparse
import base64
import json
import os
from dataclasses import dataclass
import cv2
import mrcal
import numpy as np
from wpimath.geometry import Quaternion as _Quat
@@ -12,8 +10,8 @@ from wpimath.geometry import Quaternion as _Quat
@dataclass
class Size:
width: int
height: int
width: float
height: float
@dataclass
@@ -24,14 +22,6 @@ class JsonMatOfDoubles:
data: list[float]
@dataclass
class JsonMat:
rows: int
cols: int
type: int
data: str # Base64-encoded PNG data
@dataclass
class Point2:
x: float
@@ -84,8 +74,7 @@ class Observation:
# If we should use this observation when re-calculating camera calibration
includeObservationInCalibration: bool
snapshotName: str
# The actual image the snapshot is from
snapshotData: JsonMat
snapshotDataLocation: str
@dataclass
@@ -97,6 +86,7 @@ class CameraCalibration:
calobjectWarp: list[float]
calobjectSize: Size
calobjectSpacing: float
lensmodel: str
def __convert_cal_to_mrcal_cameramodel(
@@ -127,6 +117,13 @@ def __convert_cal_to_mrcal_cameramodel(
]
return np.concatenate((r, t))
imagersize = (int(cal.resolution.width), int(cal.resolution.height))
def fill_missing_corners(observations: list[list[float]], width: int, height: int):
num_corners = width * height
observations += [[0, 0, -1] for x in range(num_corners - len(observations))]
return observations
imagersize = (cal.resolution.width, cal.resolution.height)
# Always weight=1 for Photon data
@@ -135,8 +132,12 @@ def __convert_cal_to_mrcal_cameramodel(
[
# note that we expect row-major observations here. I think this holds
np.array(
list(map(lambda it: [it.x, it.y, WEIGHT], o.locationInImageSpace))
).reshape((cal.calobjectSize.width, cal.calobjectSize.height, 3))
fill_missing_corners(
list(map(lambda it: [it.x, it.y, WEIGHT], o.locationInImageSpace)),
int(cal.calobjectSize.width),
int(cal.calobjectSize.height),
)
).reshape((int(cal.calobjectSize.width), int(cal.calobjectSize.height), 3))
for o in cal.observations
]
)
@@ -206,14 +207,6 @@ def convert_photon_to_mrcal(photon_cal_json_path: str, output_folder: str):
if not os.path.exists(output_folder):
os.makedirs(output_folder)
# Decode each image and save it as a png
for obs in camera_cal_data.observations:
image = obs.snapshotData.data
decoded_data = base64.b64decode(image)
np_data = np.frombuffer(decoded_data, np.uint8)
img = cv2.imdecode(np_data, cv2.IMREAD_UNCHANGED)
cv2.imwrite(f"{output_folder}/{obs.snapshotName}", img)
# And create a VNL file for use with mrcal
with open(f"{output_folder}/corners.vnl", "w+") as vnl_file:
vnl_file.write("# filename x y level\n")

165
devTools/photon.lua Normal file
View File

@@ -0,0 +1,165 @@
-- PhotonVision Time Synchronization Protocol Dissector
-- Protocol runs on UDP port 5810
-- Reference: https://docs.photonvision.org/en/v2026.0.0-alpha-1/docs/contributing/design-descriptions/time-sync.html
photon_timesync_proto = Proto("photon_timesync", "PhotonVision Time Sync Protocol")
-- Protocol fields
local pf_version = ProtoField.uint8("photon_timesync.version", "Version", base.DEC)
local pf_message_id = ProtoField.uint8("photon_timesync.message_id", "Message ID", base.DEC, {
[0] = "Ping",
[1] = "Pong"
})
local pf_client_time = ProtoField.uint64("photon_timesync.client_time", "Client Time (μs)", base.DEC)
local pf_server_time = ProtoField.uint64("photon_timesync.server_time", "Server Time (μs)", base.DEC)
local pf_response_in = ProtoField.framenum("photon_timesync.response_in", "Response In Frame", base.NONE,
frametype.RESPONSE)
local pf_response_to = ProtoField.framenum("photon_timesync.response_to", "Response To Frame", base.NONE,
frametype.REQUEST)
local pf_response_time = ProtoField.relative_time("photon_timesync.response_time", "Response Time")
-- Register fields
photon_timesync_proto.fields = {
pf_version,
pf_message_id,
pf_client_time,
pf_server_time,
pf_response_in,
pf_response_to,
pf_response_time
}
-- Table to track ping/pong relationships
-- Key: client_time as string, Value: frame number of ping
local ping_table = {}
-- Table to store pong responses for pings
-- Key: ping frame number, Value: pong frame number
local pong_table = {}
-- Dissector function
function photon_timesync_proto.dissector(buffer, pinfo, tree)
-- Check if buffer has minimum length (TspPing = 10 bytes)
local length = buffer:len()
if length < 10 then
return 0
end
-- Set protocol column
pinfo.cols.protocol = photon_timesync_proto.name
-- Create protocol tree
local subtree = tree:add(photon_timesync_proto, buffer(), "PhotonVision Time Sync Protocol Data")
-- Parse version (1 byte)
local version = buffer(0, 1):uint()
subtree:add(pf_version, buffer(0, 1))
-- Parse message_id (1 byte)
local msg_id = buffer(1, 1):uint()
subtree:add(pf_message_id, buffer(1, 1))
-- Parse client_time (8 bytes, little-endian uint64)
local client_time = buffer(2, 8):le_uint64()
subtree:add_le(pf_client_time, buffer(2, 8))
-- Convert client_time to string for use as key
local client_time_key = tostring(client_time)
local frame_num = pinfo.number
-- Track relationships between ping and pong
if not pinfo.visited then
-- First pass: build the relationship tables
if msg_id == 1 then
-- This is a Ping - store it
ping_table[client_time_key] = frame_num
elseif msg_id == 2 then
-- This is a Pong - find matching Ping
local ping_frame = ping_table[client_time_key]
if ping_frame then
pong_table[ping_frame] = frame_num
end
end
end
-- Update info column and parse based on message type
if msg_id == 1 then
-- TspPing: version(1) + message_id(1) + client_time(8) = 10 bytes
pinfo.cols.info = string.format("Time Sync Ping (client_time: %s μs)", tostring(client_time))
-- Check if we have a response for this ping
local pong_frame = pong_table[frame_num]
if pong_frame then
local response_item = subtree:add(pf_response_in, pong_frame)
response_item:set_generated()
end
elseif msg_id == 2 then
-- TspPong: TspPing + server_time(8) = 18 bytes
pinfo.cols.info = "Time Sync Pong"
if length >= 18 then
local server_time = buffer(10, 8):le_uint64()
subtree:add_le(pf_server_time, buffer(10, 8))
pinfo.cols.info = string.format("Time Sync Pong (client: %s, server: %s μs)",
tostring(client_time), tostring(server_time))
-- Find the matching ping frame
local ping_frame = ping_table[client_time_key]
if ping_frame then
local request_item = subtree:add(pf_response_to, ping_frame)
request_item:set_generated()
-- Calculate response time if we can get the ping packet
local ping_time = pinfo.abs_ts - pinfo.rel_ts
-- Note: This is an approximation. For accurate timing, we'd need to
-- store the timestamp of the ping packet
end
end
else
pinfo.cols.info = string.format("Time Sync Unknown (ID: %d)", msg_id)
end
return length
end
-- Register dissector on UDP port 5810
local udp_port = DissectorTable.get("udp.port")
udp_port:add(5810, photon_timesync_proto)
-- Heuristic dissector function
local function heuristic_checker(buffer, pinfo, tree)
local length = buffer:len()
-- Check minimum length (TspPing = 10 bytes)
if length < 10 then
return false
end
local version = buffer(0, 1):uint()
local msg_id = buffer(1, 1):uint()
-- Check if this looks like our protocol
-- Version should be reasonable (0-10), message_id should be 1 or 2
if version <= 10 and (msg_id == 1 or msg_id == 2) then
-- Validate packet structure
if msg_id == 1 and length == 10 then
-- TspPing is exactly 10 bytes
photon_timesync_proto.dissector(buffer, pinfo, tree)
return true
elseif msg_id == 2 and length == 18 then
-- TspPong is exactly 18 bytes
photon_timesync_proto.dissector(buffer, pinfo, tree)
return true
end
end
return false
end
-- Register heuristic dissector
photon_timesync_proto:register_heuristic("udp", heuristic_checker)
-- Initialize function to reset tables on new capture
function photon_timesync_proto.init()
ping_table = {}
pong_table = {}
end

View File

@@ -1,17 +0,0 @@
modifiableFileExclude {
\.jpg$
\.jpeg$
\.png$
\.gif$
\.so$
\.pdf$
\.mp4$
\.dll$
\.webp$
\.ico$
\.rknn$
\.svg$
\.woff2$
gradlew
}

View File

@@ -1,16 +1,16 @@
alabaster==1.0.0
alabaster==0.7.16
anyio==4.9.0
babel==2.17.0
beautifulsoup4==4.13.3
certifi==2025.1.31
charset-normalizer==3.4.1
beautifulsoup4==4.13.4
certifi==2025.4.26
charset-normalizer==3.4.2
click==8.1.8
colorama==0.4.6
doc8==1.1.2
docopt==0.6.2
docutils==0.21.2
docutils==0.20.1
furo==2024.8.6
h11==0.14.0
h11==0.16.0
idna==3.10
imagesize==1.4.1
Jinja2==3.1.6
@@ -19,20 +19,19 @@ MarkupSafe==3.0.2
mdit-py-plugins==0.4.2
mdurl==0.1.2
myst-parser==4.0.1
packaging==24.2
packaging==25.0
pbr==6.1.1
pipreqs==0.4.13
Pygments==2.19.1
PyYAML==6.0.2
requests==2.32.3
restructuredtext_lint==1.4.0
requests==2.32.4
restructuredtext-lint==1.4.0
roman-numerals-py==3.1.0
setuptools==77.0.3
setuptools==80.3.1
six==1.17.0
sniffio==1.3.1
snowballstemmer==2.2.0
soupsieve==2.6
Sphinx==8.2.3
snowballstemmer==3.0.0.1
soupsieve==2.7
Sphinx==8.1.3
sphinx-autobuild==2024.10.3
sphinx-basic-ng==1.0.0b2
sphinx-notfound-page==1.1.0
@@ -47,13 +46,13 @@ sphinxcontrib-jquery==4.1
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==2.0.0
sphinxcontrib-serializinghtml==2.0.0
sphinxext-opengraph==0.9.1
sphinxext-opengraph==0.10.0
sphinxext-remoteliteralinclude==0.5.0
starlette==0.46.1
starlette==0.47.2
stevedore==5.4.1
typing_extensions==4.12.2
urllib3==2.3.0
uvicorn==0.34.0
watchfiles==1.0.4
typing_extensions==4.13.2
urllib3==2.5.0
uvicorn==0.34.2
watchfiles==1.1.1
websockets==15.0.1
yarg==0.1.10
yarg==0.1.9

View File

@@ -1,74 +0,0 @@
{# Import the theme's layout. #}
{% extends '!layout.html' %}
{%- block extrahead %}
<script>
if (localStorage.getItem("colorTheme") === "dark") {
document.documentElement.setAttribute('data-theme', 'dark');
} else if (localStorage.getItem("colorTheme") === "light") {
document.documentElement.setAttribute('data-theme', 'light');
} else {
var userPrefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
if (userPrefersDark) {
document.documentElement.setAttribute('data-theme', 'dark');
} else {
document.documentElement.setAttribute('data-theme', 'light');
}
}
</script>
{# Call the parent block #}
{{ super() }}
{% endblock %}
{%- block extrafooter %}
{# Add custom things to the head HTML tag #}
<div class="dark-mode-toggle-container">
<strong class="light-label md-icon">&#xE430</strong>
<div class="dark-mode-toggle">
<input type="checkbox" id="switch" name="theme"/><label class="toggle" for="switch">Toggle</label>
</div>
<strong class="dark-label md-icon">&#xE42D</strong>
</div>
<script>
var checkbox = document.querySelector('input[name=theme]');
var element = document.documentElement.getAttribute('data-theme');
if (element == 'dark') {
// Auto check the checkbox if the set theme is "dark".
checkbox.checked = true;
}
checkbox.addEventListener('change', function() {
if (this.checked) {
document.documentElement.setAttribute('data-theme', 'dark');
localStorage.setItem("colorTheme", "dark");
} else {
document.documentElement.setAttribute('data-theme', 'light');
localStorage.setItem("colorTheme", "light");
}
});
window.matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', event => {
if (event.matches) {
document.documentElement.setAttribute('data-theme', 'dark');
localStorage.setItem("colorTheme", "dark");
checkbox.checked = true;
} else {
document.documentElement.setAttribute('data-theme', 'light');
localStorage.setItem("colorTheme", "light");
checkbox.checked = false;
}
});
</script>
{# Call the parent block #}
{{ super() }}
{%- endblock %}

View File

@@ -21,6 +21,29 @@ project = "PhotonVision"
copyright = "2024, PhotonVision"
author = "Banks Troutman, Matt Morley"
# -- Git configuration -----------------------------------------------------
import subprocess
try:
# Use closest tag
git_tag_ref = (
subprocess.check_output(
[
"git",
"describe",
"--tags",
],
stderr=subprocess.DEVNULL,
)
.strip()
.decode()
)
except subprocess.CalledProcessError:
# Couldn't find closest tag, fallback to main
git_tag_ref = "main"
myst_substitutions = {"git_tag_ref": git_tag_ref}
# -- General configuration ---------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
@@ -30,7 +53,6 @@ extensions = [
"sphinx_rtd_theme",
"sphinx.ext.autosectionlabel",
"sphinx.ext.todo",
"sphinx_tabs.tabs",
"notfound.extension",
"sphinxext.remoteliteralinclude",
"sphinxext.opengraph",
@@ -47,9 +69,6 @@ ogp_site_url = "https://docs.photonvision.org/en/latest/"
ogp_site_name = "PhotonVision Documentation"
ogp_image = "https://raw.githubusercontent.com/PhotonVision/photonvision-docs/main/source/assets/RectLogo.png"
# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
@@ -70,6 +89,10 @@ html_title = "PhotonVision Docs"
html_theme = "furo"
html_favicon = "assets/RoundLogo.png"
# Specify canonical root
# This tells search engines that this domain is preferred
html_baseurl = "https://docs.photonvision.org/en/latest/"
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
@@ -147,11 +170,15 @@ sphinx_tabs_valid_builders = ["epub", "linkcheck"]
# Excluded links for linkcheck
# These should be periodically checked by hand to ensure that they are still functional
linkcheck_ignore = [R"https://www.raspberrypi.com/software/", R"http://10\..+"]
linkcheck_ignore = [
R"https://www.raspberrypi.com/software/",
R"http://10\..+",
R"https://www.gnu.org/",
]
token = os.environ.get("GITHUB_TOKEN", None)
if token:
linkcheck_auth = [(R"https://github.com/.+", token)]
# MyST configuration (https://myst-parser.readthedocs.io/en/latest/configuration.html)
myst_enable_extensions = ["colon_fence"]
myst_enable_extensions = ["colon_fence", "substitution"]

View File

@@ -3,13 +3,15 @@
## About
:::{warning}
PhotonVision interfaces with PhotonLib, our vendor dependency, using NetworkTables. If you are running PhotonVision on a robot (ie. with a RoboRIO), you should **turn the NetworkTables server switch (in the settings tab) off** in order to get PhotonLib to work. Also ensure that you set your team number. The NetworkTables server should only be enabled if you know what you're doing!
PhotonVision interfaces with PhotonLib, our vendor dependency, using NetworkTables. If you are running PhotonVision on a robot (ie. with a RoboRIO), you should **turn the NetworkTables server switch (in the settings tab) off** in order to get PhotonLib to work. Also ensure that you set your team number. **The NetworkTables server should only be enabled if you know what you're doing!**
:::
## API
:::{warning}
NetworkTables is not a supported setup/viable option when using PhotonVision as we only send one target at a time (this is problematic when using AprilTags, which will return data from multiple tags at once). We recommend using PhotonLib.
NetworkTables is not a supported setup/viable option when using PhotonVision as we only send one target at a time (this is problematic when using AprilTags, which will return data from multiple tags at once).
**We strongly recommend using PhotonLib instead, as the NetworkTables API will most likely be removed in 2027.**
:::
The tables below contain the the name of the key for each entry that PhotonVision sends over the network and a short description of the key. The entries should be extracted from a subtable with your camera's nickname (visible in the PhotonVision UI) under the main `photonvision` table.

View File

@@ -3,7 +3,6 @@
"supportURL" : "https://limelightvision.io",
"ledPins" : [ 13, 18 ],
"ledsCanDim" : true,
"ledPWMRange" : [ 0, 100 ],
"ledPWMFrequency" : 30000,
"ledPWMFrequency" : 1000,
"vendorFOV" : 75.76079874010732
}

View File

@@ -12,7 +12,7 @@ VERY Limited macOS support is available.
## Installing Java
PhotonVision requires a JDK installed and on the system path. JDK 17 is needed (different versions will not work). You may already have this if you have installed WPILib 2025+. If not, [download and install it from here](https://adoptium.net/temurin/releases?version=17).
PhotonVision requires a JDK installed and on the system path. JDK 17 is needed (different versions will not work). You may already have this if you have installed WPILib 2026+. If not, [download and install it from here](https://adoptium.net/temurin/releases?version=17).
:::{warning}
Using a JDK other than JDK17 will cause issues when running PhotonVision and is not supported.

View File

@@ -12,15 +12,7 @@ Bonjour provides more stable networking when using Windows PCs. Install [Bonjour
## Installing Java
PhotonVision requires a JDK installed and on the system path. **JDK 17 is needed. Windows Users must use the JDK that ships with WPILib.** [Download and install it from here.](https://github.com/wpilibsuite/allwpilib/releases/tag/v2025.3.2) Either ensure the only Java on your PATH is the WPILIB Java or specify it to gradle with `-Dorg.gradle.java.home=C:\Users\Public\wpilib\2025\jdk`:
```
> ./gradlew run "-Dorg.gradle.java.home=C:\Users\Public\wpilib\2025\jdk"
```
:::{warning}
Using a JDK other than WPILIB's JDK17 will cause issues when running PhotonVision and is not supported.
:::
PhotonVision requires a JDK installed and on the system path. **JDK 17 is needed.** You may already have it if you installed WPILib, but ensure that running `java -version` shows JDK 17. You will likely have to add WPILib's JDK to JAVA_HOME and the JDK's `bin` directory to PATH. If you do not have a JDK 17 install, [download and install it from here.](https://adoptium.net/temurin/releases?version=17)
## Downloading the Latest Stable Release of PhotonVision

View File

@@ -2,7 +2,7 @@
## Tracking AprilTags
Before you get started tracking AprilTags, ensure that you have followed the previous sections on installation, wiring and networking. Next, open the Web UI, go to the top right card, and switch to the "AprilTag" or "Aruco" type. You should see a screen similar to the one below.
Before you get started tracking AprilTags, ensure that you have followed the previous sections on installation, wiring and networking. Next, open the Web UI, go to the top right card, and switch to the "AprilTag" or "ArUco" type. You should see a screen similar to the one below.
```{image} images/apriltag.png
:align: center
@@ -12,7 +12,7 @@ You are now able to detect and track AprilTags in 2D (yaw, pitch, roll, etc.). I
## Tuning AprilTags
AprilTag pipelines come with reasonable defaults to get you up and running with tracking. However, in order to optimize your performance and accuracy, you must tune your AprilTag pipeline using the settings below. Note that the settings below are different between the AprilTag and Aruco detectors but the concepts are the same.
AprilTag pipelines come with reasonable defaults to get you up and running with tracking. However, in order to optimize your performance and accuracy, you must tune your AprilTag pipeline using the settings below. Note that the settings below are different between the AprilTag and ArUco detectors but the concepts are the same.
```{image} images/apriltag-tune.png
:align: center
@@ -23,7 +23,7 @@ AprilTag pipelines come with reasonable defaults to get you up and running with
Target families are defined by two numbers (before and after the h). The first number is the number of bits the tag is able to encode (which means more tags are available in the respective family) and the second is the hamming distance. Hamming distance describes the ability for error correction while identifying tag ids. A high hamming distance generally means that it will be easier for a tag to be identified even if there are errors. However, as hamming distance increases, the number of available tags decreases.
The 2025 FRC game will be using 36h11 tags, which can be found [here](https://github.com/AprilRobotics/apriltag-imgs/tree/main/tag36h11).
The 2026 FRC game will be using 36h11 tags, which can be found [here](https://github.com/AprilRobotics/apriltag-imgs/tree/2bc821edb4eb7b408d13c6a590d326d8a9ec98f3/tag36h11).
### Decimate

View File

@@ -10,5 +10,5 @@ AprilTags are a common type of visual fiducial marker. Visual fiducial markers a
A more technical explanation can be found in the [WPILib documentation](https://docs.wpilib.org/en/latest/docs/software/vision-processing/apriltag/apriltag-intro.html).
:::{note}
You can get FIRST's [official PDF of the targets used in 2025 here](https://firstfrc.blob.core.windows.net/frc2025/FieldAssets/Apriltag_Images_and_User_Guide.pdf).
You can get FIRST's [official PDF of the targets used in 2026 here](https://firstfrc.blob.core.windows.net/frc2026/FieldAssets/2026-apriltag-images-user-guide.pdf).
:::

View File

@@ -8,8 +8,8 @@ Note that both of these pipeline types detect AprilTag markers and are just two
## AprilTag
The AprilTag pipeline type is based on the [AprilTag](https://april.eecs.umich.edu/software/apriltag.html) library from the University of Michigan and we recommend it for most use cases. It is (to our understanding) most accurate pipeline type, but is also ~2x slower than AruCo. This was the pipeline type used by teams in the 2023 season and is well tested.
The AprilTag pipeline type is based on the [AprilTag](https://april.eecs.umich.edu/software/apriltag.html) library from the University of Michigan and we recommend it for most use cases. It is (to our understanding) most accurate pipeline type, but is also ~2x slower than ArUco. This was the pipeline type used by teams in the 2023 season and is well tested.
## AruCo
## ArUco
The AruCo pipeline is based on the [AruCo](https://docs.opencv.org/4.8.0/d9/d6a/group__aruco.html) library implementation from OpenCV. It is ~2x higher fps and ~2x lower latency than the AprilTag pipeline type, but is less accurate. We recommend this pipeline type for teams that need to run at a higher framerate or have a lower powered device. This pipeline type was new for the 2024 season.
The ArUco pipeline is based on the [ArUco](https://docs.opencv.org/4.8.0/d9/d6a/group__aruco.html) library implementation from OpenCV. It is ~2x higher fps and ~2x lower latency than the AprilTag pipeline type, but is less accurate. We recommend this pipeline type for teams that need to run at a higher framerate or have a lower powered device. This pipeline type was new for the 2024 season.

View File

@@ -7,7 +7,7 @@ MultiTag requires an accurate field layout JSON to be uploaded! Differences betw
:::
:::{warning}
For the 2025 Reefscape Season, there are two different field layouts. The first is the [welded field layout](https://github.com/wpilibsuite/allwpilib/blob/main/apriltag/src/main/native/resources/edu/wpi/first/apriltag/2025-reefscape-welded.json), which photonvision ships with. The second is the [Andymark field layout](https://github.com/wpilibsuite/allwpilib/blob/main/apriltag/src/main/native/resources/edu/wpi/first/apriltag/2025-reefscape-andymark.json). It is very important to ensure that you use the correct field layout, both in the [PhotonPoseEstimator](https://docs.photonvision.org/en/latest/docs/programming/photonlib/robot-pose-estimator.html#apriltags-and-photonposeestimator) and on the [coprocessor](https://docs.photonvision.org/en/latest/docs/apriltag-pipelines/multitag.html#updating-the-field-layout).
For the 2026 Rebuilt Season, there are two different field layouts. The first is the [welded field layout](https://github.com/wpilibsuite/allwpilib/blob/main/apriltag/src/main/native/resources/edu/wpi/first/apriltag/2026-rebuilt-welded.json), which photonvision ships with. The second is the [Andymark field layout](https://github.com/wpilibsuite/allwpilib/blob/main/apriltag/src/main/native/resources/edu/wpi/first/apriltag/2026-rebuilt-andymark.json). It is very important to ensure that you use the correct field layout, both in the [PhotonPoseEstimator](https://docs.photonvision.org/en/latest/docs/programming/photonlib/robot-pose-estimator.html#apriltags-and-photonposeestimator) and on the [coprocessor](https://docs.photonvision.org/en/latest/docs/apriltag-pipelines/multitag.html#updating-the-field-layout).
:::
## Enabling MultiTag
@@ -28,7 +28,7 @@ This multi-target pose estimate can be accessed using PhotonLib. We suggest usin
```{eval-rst}
.. tab-set-code::
.. code-block:: Java
.. code-block:: java
var results = camera.getAllUnreadResults();
for (var result : results) {
@@ -39,7 +39,7 @@ This multi-target pose estimate can be accessed using PhotonLib. We suggest usin
}
.. code-block:: C++
.. code-block:: c++
auto results = camera.GetAllUnreadResults();
for (auto &result : results)
@@ -51,7 +51,7 @@ This multi-target pose estimate can be accessed using PhotonLib. We suggest usin
}
.. code-block:: Python
.. code-block:: python
results = camera.getAllUnreadResults()
for result in results:
@@ -66,7 +66,7 @@ The returned field to camera transform is a transform from the fixed field origi
## Updating the Field Layout
PhotonVision ships by default with the [2025 welded field layout JSON](https://github.com/wpilibsuite/allwpilib/blob/main/apriltag/src/main/native/resources/edu/wpi/first/apriltag/2025-reefscape-welded.json). The layout can be inspected by navigating to the settings tab and scrolling down to the "AprilTag Field Layout" card, as shown below.
PhotonVision ships by default with the [2026 welded field layout JSON](https://github.com/wpilibsuite/allwpilib/blob/main/apriltag/src/main/native/resources/edu/wpi/first/apriltag/2026-rebuilt-welded.json). The layout can be inspected by navigating to the settings tab and scrolling down to the "AprilTag Field Layout" card, as shown below.
```{image} images/field-layout.png
:alt: The currently saved field layout in the Photon UI

View File

@@ -0,0 +1,8 @@
# Performance Benchmarks
```{toctree}
:maxdepth: 0
:titlesonly: true
rknn-model-benchmarks
```

View File

@@ -0,0 +1,125 @@
# RKNN Benchmarks
## Description
This benchmark compares the performance of four object detection models: YOLOv5, YOLOv5u, YOLOv8, and YOLOv11 on the [COCO 2017 Validation Set](http://images.cocodataset.org/zips/val2017.zip). The main purpose is to assess and compare the inference speed and detection accuracy of these models when deployed on the Orange Pi devices using the RKNN framework and int8 quantization.
## Methodology
- **Dataset**: [COCO 2017 Validation Set](http://images.cocodataset.org/zips/val2017.zip) (5,000 images)
- **Platform**: Orange Pi 5 with RK3588
- **Quantization**: int8 using 20 randomly selected images from the validation set
- **Framework**: RKNN Toolkit 2
## Operator-Level Benchmark Results
The following tables break down the average CPU time, NPU time, and total execution time (in microseconds) for each operator used by the models. Each value represents the mean ± standard deviation across 5,000 inferences.
### YOLOv5
| OpType | CPU Time (μs) | NPU Time (μs) | Total Time (μs) | Time Ratio (%) | Number of Times Called |
|-----------------|---------------------|----------------------|-----------------------|---------------------|-----------------------|
| ConvExSwish | 0.00 ± 0.00 | 10968.81 ± 1126.00 | 10968.81 ± 1126.00 | 73.06 ± 0.94 | 57 |
| ConvSigmoid | 0.00 ± 0.00 | 1243.49 ± 67.66 | 1243.49 ± 67.66 | 8.33 ± 0.57 | 3 |
| Concat | 0.00 ± 0.00 | 1080.68 ± 259.40 | 1080.68 ± 259.40 | 7.09 ± 0.87 | 13 |
| Conv | 0.00 ± 0.00 | 732.15 ± 29.42 | 732.15 ± 29.42 | 4.92 ± 0.42 | 1 |
| Add | 0.00 ± 0.00 | 473.71 ± 131.48 | 473.71 ± 131.48 | 3.10 ± 0.50 | 7 |
| MaxPool | 0.00 ± 0.00 | 272.40 ± 110.52 | 272.40 ± 110.52 | 1.76 ± 0.51 | 6 |
| Resize | 0.00 ± 0.00 | 147.61 ± 38.89 | 147.61 ± 38.89 | 0.97 ± 0.15 | 2 |
| OutputOperator | 106.60 ± 15.00 | 0.00 ± 0.00 | 106.60 ± 15.00 | 0.72 ± 0.13 | 3 |
| InputOperator | 8.64 ± 1.79 | 0.00 ± 0.00 | 8.64 ± 1.79 | 0.06 ± 0.02 | 1 |
| **Total** | **115.24 ± 16.16** | **14918.85 ± 1735.45**| **15034.09 ± 1734.28**| | **93** |
### YOLOv5u
| OpType | CPU Time (μs) | NPU Time (μs) | Total Time (μs) | Time Ratio (%) | Number of Times Called |
|-----------------|---------------------|----------------------|-----------------------|---------------------|-----------------------|
| ConvExSwish | 0.00 ± 0.00 | 16828.24 ± 1332.73 | 16828.24 ± 1332.73 | 83.04 ± 1.61 | 69 |
| Concat | 0.00 ± 0.00 | 1265.94 ± 250.24 | 1265.94 ± 250.24 | 6.17 ± 0.69 | 13 |
| ConvSigmoid | 0.00 ± 0.00 | 613.88 ± 62.97 | 613.88 ± 62.97 | 3.03 ± 0.15 | 3 |
| Add | 0.00 ± 0.00 | 553.75 ± 131.17 | 553.75 ± 131.17 | 2.69 ± 0.44 | 7 |
| Conv | 0.00 ± 0.00 | 298.61 ± 72.72 | 298.61 ± 72.72 | 1.45 ± 0.25 | 3 |
| ConvClip | 0.00 ± 0.00 | 256.02 ± 64.48 | 256.02 ± 64.48 | 1.24 ± 0.23 | 3 |
| MaxPool | 0.00 ± 0.00 | 178.68 ± 58.72 | 178.68 ± 58.72 | 0.86 ± 0.23 | 3 |
| Resize | 0.00 ± 0.00 | 170.87 ± 40.14 | 170.87 ± 40.14 | 0.83 ± 0.13 | 2 |
| OutputOperator | 126.89 ± 16.53 | 0.00 ± 0.00 | 126.89 ± 16.53 | 0.63 ± 0.10 | 9 |
| InputOperator | 8.69 ± 1.45 | 0.00 ± 0.00 | 8.69 ± 1.45 | 0.04 ± 0.01 | 1 |
| **Total** | **135.57 ± 17.51** | **20165.99 ± 1963.70**| **20301.56 ± 1965.88**| | **113** |
### YOLOv8
| OpType | CPU Time (μs) | NPU Time (μs) | Total Time (μs) | Time Ratio (%) | Number of Times Called |
|-----------------|---------------------|----------------------|-----------------------|---------------------|-----------------------|
| ConvExSwish | 0.00 ± 0.00 | 13017.04 ± 1165.76 | 13017.04 ± 1165.76 | 75.66 ± 1.96 | 57 |
| Concat | 0.00 ± 0.00 | 1489.94 ± 257.22 | 1489.94 ± 257.22 | 8.58 ± 0.53 | 13 |
| Split | 0.00 ± 0.00 | 681.47 ± 166.62 | 681.47 ± 166.62 | 3.89 ± 0.53 | 8 |
| ConvSigmoid | 0.00 ± 0.00 | 596.08 ± 75.01 | 596.08 ± 75.01 | 3.45 ± 0.18 | 3 |
| Add | 0.00 ± 0.00 | 443.60 ± 118.05 | 443.60 ± 118.05 | 2.53 ± 0.41 | 6 |
| Conv | 0.00 ± 0.00 | 269.61 ± 78.65 | 269.61 ± 78.65 | 1.54 ± 0.30 | 3 |
| Resize | 0.00 ± 0.00 | 236.79 ± 37.74 | 236.79 ± 37.74 | 1.37 ± 0.08 | 2 |
| ConvClip | 0.00 ± 0.00 | 231.82 ± 68.44 | 231.82 ± 68.44 | 1.32 ± 0.27 | 3 |
| MaxPool | 0.00 ± 0.00 | 156.85 ± 56.94 | 156.85 ± 56.94 | 0.89 ± 0.23 | 3 |
| OutputOperator | 124.86 ± 20.74 | 0.00 ± 0.00 | 124.86 ± 20.74 | 0.73 ± 0.15 | 9 |
| InputOperator | 8.47 ± 1.66 | 0.00 ± 0.00 | 8.47 ± 1.66 | 0.05 ± 0.01 | 1 |
| **Total** | **133.33 ± 21.95** | **17123.19 ± 1985.72**| **17256.52 ± 1986.77** | | **108** |
---
### YOLOv11
| OpType | CPU Time (μs) | NPU Time (μs) | Total Time (μs) | Time Ratio (%) | Number of Times Called |
|-----------------|---------------------|----------------------|-----------------------|---------------------|-----------------------|
| ConvExSwish | 0.00 ± 0.00 | 16034.00 ± 1331.95 | 16034.00 ± 1331.95 | 69.90 ± 1.55 | 77 |
| Concat | 0.00 ± 0.00 | 1888.89 ± 293.99 | 1888.89 ± 293.99 | 8.17 ± 0.51 | 17 |
| exSDPAttention | 0.00 ± 0.00 | 1210.88 ± 17.73 | 1210.88 ± 17.73 | 5.32 ± 0.52 | 1 |
| Split | 0.00 ± 0.00 | 908.30 ± 183.92 | 908.30 ± 183.92 | 3.91 ± 0.45 | 10 |
| Add | 0.00 ± 0.00 | 871.64 ± 212.79 | 871.64 ± 212.79 | 3.73 ± 0.60 | 12 |
| ConvSigmoid | 0.00 ± 0.00 | 617.61 ± 59.61 | 617.61 ± 59.61 | 2.69 ± 0.16 | 3 |
| Conv | 0.00 ± 0.00 | 419.72 ± 89.88 | 419.72 ± 89.88 | 1.80 ± 0.24 | 5 |
| Resize | 0.00 ± 0.00 | 272.09 ± 49.91 | 272.09 ± 49.91 | 1.18 ± 0.12 | 2 |
| ConvClip | 0.00 ± 0.00 | 260.08 ± 59.12 | 260.08 ± 59.12 | 1.12 ± 0.18 | 3 |
| MaxPool | 0.00 ± 0.00 | 181.93 ± 53.32 | 181.93 ± 53.32 | 0.78 ± 0.18 | 3 |
| OutputOperator | 131.48 ± 22.93 | 0.00 ± 0.00 | 131.48 ± 22.93 | 0.58 ± 0.12 | 9 |
| ConvAdd | 0.00 ± 0.00 | 126.79 ± 35.28 | 126.79 ± 35.28 | 0.54 ± 0.11 | 2 |
| Reshape | 0.00 ± 0.00 | 56.61 ± 18.03 | 56.61 ± 18.03 | 0.24 ± 0.06 | 3 |
| InputOperator | 8.66 ± 1.59 | 0.00 ± 0.00 | 8.66 ± 1.59 | 0.04 ± 0.01 | 1 |
| **Total** | **140.14 ± 24.26** | **22848.54 ± 2351.95**| **22988.68 ± 2355.97**| | **148** |
## Model Summary and Accuracy Metrics
The table below summarizes the mean average precision (mAP) and total inference time for each model. These metrics provide a high-level view of how each model performs in terms of both detection accuracy and runtime efficiency.
### Mean Average Precision (mAP) by Model
| Metric | YOLOv5 | YOLOv5u | YOLOv8 | YOLOv11 |
|--------|------------|------------|------------|------------|
| **mAP** | 0.2243 | 0.2745 | 0.3051 | 0.3251 |
| **mAP50** | 0.3538 | 0.3834 | 0.4145 | 0.4406 |
| **mAP75** | 0.2432 | 0.2997 | 0.3349 | 0.3568 |
| **mAP85** | 0.3054 | 0.3472 | 0.3867 | 0.4068 |
| **mAP95** | 0.3708 | 0.4822 | 0.5483 | 0.5858 |
### Model Execution Time and Call Frequency
| Model | Total Time (μs) | Number of Processing Calls |
|---------|------------------------|----------------------------|
| **YOLOv5** | 15034.09 ± 1734.28 | 93 |
| **YOLOv5u** | 20301.56 ± 1965.88 | 113 |
| **YOLOv8** | 17256.52 ± 1986.77 | 108 |
| **YOLOv11** | 22988.68 ± 2355.97 | 148 |
## Conclusion
The benchmark reveals a clear performance trade-off between inference time and detection accuracy:
- **YOLOv5** is the fastest model with the lowest total inference time, making it well-suited for situations where speed is more important than high detection precision.
- **YOLOv11** achieves the highest accuracy (mAP) across all IoU thresholds but comes with the longest inference time, which may limit its use in real-time applications.
- **YOLOv8** offers a strong balance between speed and accuracy, making it a practical choice when both factors matter.
- **YOLOv5u** improves accuracy compared to YOLOv5 but falls behind YOLOv8 in both speed and detection quality.
When choosing a model for edge devices like the Orange Pi 5, its important to weigh how much latency your system can tolerate versus how much accuracy you need. A faster model may give quicker results, while a more accurate one may offer better detection reliability, but at the cost of speed.

View File

@@ -4,7 +4,7 @@
In order to detect AprilTags and use 3D mode, your camera must be calibrated at the desired resolution! Inaccurate calibration will lead to poor performance.
:::
To calibrate a camera, images of a Charuco board (or chessboard) are taken. By comparing where the grid corners should be in object space (for example, a corner once every inch in an 8x6 grid) with where they appear in the camera image, we can find a least-squares estimate for intrinsic camera properties like focal lengths, center point, and distortion coefficients. For more on camera calibration, please review the [OpenCV documentation](https://docs.opencv.org/4.x/dc/dbb/tutorial_py_calibration.html).
To calibrate a camera, images of a ChArUco board (or chessboard) are taken. By comparing where the grid corners should be in object space (for example, a corner once every inch in an 8x6 grid) with where they appear in the camera image, we can find a least-squares estimate for intrinsic camera properties like focal lengths, center point, and distortion coefficients. For more on camera calibration, please review the [OpenCV documentation](https://docs.opencv.org/4.x/dc/dbb/tutorial_py_calibration.html).
:::{warning}
While any resolution can be calibrated, higher resolutions may be too performance-intensive for some coprocessors to handle. Therefore, we recommend experimenting to see what works best for your coprocessor.
@@ -16,6 +16,10 @@ The calibration data collected during calibration is specific to each physical c
## Calibration Tips
:::{warning}
The usage of chessboards can result in bad calibration results if multiple similar images are taken. We strongly recommend that teams use ChArUco boards instead!
:::
Accurate camera calibration is required in order to get accurate pose measurements when using AprilTags and 3D mode. The tips below should help ensure success:
01. Ensure the images you take have the target in different positions and angles, with as big of a difference between angles as possible. It is important to make sure the target overlay still lines up with the board while doing this. Tilt no more than 45 degrees.
@@ -34,11 +38,11 @@ Following the ideas above should help in getting an accurate calibration.
### 1. Navigate to the calibration section in the UI.
The Cameras tab of the UI houses PhotonVision's camera calibration tooling. It assists users with calibrating their cameras, as well as allows them to view previously calibrated resolutions. We support both charuco and chessboard calibrations.
The Cameras tab of the UI houses PhotonVision's camera calibration tooling. It assists users with calibrating their cameras, as well as allows them to view previously calibrated resolutions. We support both ChArUco and chessboard calibrations.
### 2. Print out the calibration target.
In the Camera Calibration tab, we'll print out the calibration target using the "Download" button. This should be printed on 8.5x11 printer paper. This page shows using an 8x8 charuco board (or chessboard depending on the selected calibration type).
In the Camera Calibration tab, we'll print out the calibration target using the "Download" button. This should be printed on 8.5x11 printer paper. This page shows using an 8x8 ChArUco board (or chessboard depending on the selected calibration type).
:::{warning}
Ensure that there is no scaling applied during printing (it should be at 100%) and that the PDF is printed as is on regular printer paper. Check the square size with calipers or an accurate measuring device after printing to ensure squares are sized properly, and enter the true size of the square in the UI text box. For optimal results, various resources are available online to calibrate your specific printer if needed.
@@ -46,13 +50,13 @@ Ensure that there is no scaling applied during printing (it should be at 100%) a
### 3. Select calibration resolution and fill in appropriate target data.
We'll next select a resolution to calibrate and populate our pattern spacing, marker size, and board size. The provided chessboard and charuco board are an 8x8 grid of 1 inch square. The provided charuco board uses the 4x4 dictionary with a marker size of 0.75 inches (this board does not need the old OpenCV pattern selector selected). Printers are not perfect, and you need to measure your calibration target and enter the correct marker size (size of the aruco marker) and pattern spacing (aka size of the black square) using calipers or similar. Finally, once our entered data is correct, we'll click "start calibration."
We'll next select a resolution to calibrate and populate our pattern spacing, marker size, and board size. The provided chessboard and ChArUco board are an 8x8 grid of 1 inch square. The provided ChArUco board uses the 4x4 dictionary with a marker size of 0.75 inches (this board does not need the old OpenCV pattern selector selected). Printers are not perfect, and you need to measure your calibration target and enter the correct marker size (size of the ArUco marker) and pattern spacing (aka size of the black square) using calipers or similar. Finally, once our entered data is correct, we'll click "start calibration."
:::{warning} Old OpenCV Pattern selector. This should be used in the case that the calibration image is generated from a version of OpenCV before version 4.6.0. This would include targets created by calib.io. If this selector is not set correctly the calibration will be completely invalid. For more info view [this GitHub issue](https://github.com/opencv/opencv_contrib/issues/3291).
:::
:::{note}
If you have a [calib.io](https://calib.io/) CharuCo Target you will have to enter the paramaters of your target. For example if your target says "9x12 | Checker Size: 30 mm | Marker Size: 22 mm | Dictionary: AruCo DICT 5x5", you would have to set the board type to Dict_5x5_1000, the pattern spacing to 1.1811 in (30 mm converted to inches), the marker size 0.866142 in (22 mm converted to inches), the board width to 12 and the board height to 9. If you chose the wrong tag family the board wont be detected during calibration. If you swap the width and height your calibration will have a very high error.
If you have a [calib.io](https://calib.io/) ChArUco Target you will have to enter the paramaters of your target. For example if your target says "9x12 | Checker Size: 30 mm | Marker Size: 22 mm | Dictionary: ArUco DICT 5x5", you would have to set the board type to Dict_5x5_1000, the pattern spacing to 1.1811 in (30 mm converted to inches), the marker size 0.866142 in (22 mm converted to inches), the board width to 12 and the board height to 9. If you chose the wrong tag family the board wont be detected during calibration. If you swap the width and height your calibration will have a very high error.
:::
### 4. Take at calibration images from various angles.
@@ -79,7 +83,7 @@ Details about a particular calibration can be viewed by clicking on that resolut
More info on what these parameters mean can be found in [OpenCV's docs](https://docs.opencv.org/4.8.0/d4/d94/tutorial_camera_calibration.html)
:::
- Fx/Fy: Estimated camera focal length, in mm
- Fx/Fy: Estimated camera focal length, in pixels
- Fx/Cy: Estimated camera optical center, in pixels. This should be at about the center of the image
- Distortion: OpenCV camera model distortion coefficients
- FOV: calculated using estimated focal length and image size. Useful for gut-checking calibration results

View File

@@ -1,5 +1,9 @@
# Arducam Cameras
:::{warning}
Arducam Pivariety cameras are **incompatible** with PhotonVision as they require a custom camera library not compatible with PhotonVision.
:::
Arducam cameras are supported for setups with multiple devices. This is possible because Arducam provides software that allows you to assign truly different device names to each camera. This feature is particularly useful in complex setups where multiple cameras are used simultaneously.
## Setting Up Arducam Cameras

View File

@@ -1,4 +1,4 @@
# Camera-Specifc Configuration
# Camera-Specific Configuration
```{toctree}
:maxdepth: 2

View File

@@ -8,21 +8,19 @@ This section contains the build instructions from the source code available at [
**Java Development Kit:**
This project requires Java Development Kit (JDK) 17 to be compiled. This is the same Java version that comes with WPILib for 2025+. **Windows Users must use the JDK that ships with WPILib.** For other platforms, you can follow the instructions to install JDK 17 for your platform [here](https://bell-sw.com/pages/downloads/#jdk-17-lts).
This project requires Java Development Kit (JDK) 17 to be compiled. This is the same Java version that comes with WPILib for 2026+. **Windows Users must use the JDK that ships with WPILib.** For other platforms, you can follow the instructions to install JDK 17 for your platform [here](https://bell-sw.com/pages/downloads/#jdk-17-lts).
**Node JS:**
The UI is written in Node JS. To compile the UI, Node 18.20.4 to Node 20.0.0 is required. To install Node JS follow the instructions for your platform [on the official Node JS website](https://nodejs.org/en/download/). However, modify this line
The UI is written in Node JS. To compile the UI, Node 22 or later is required. To install Node JS, follow the instructions for your platform [on the official Node JS website](https://nodejs.org/en/download/).
```bash
nvm install 20
```
**pnpm:**
so that it instead reads
[pnpm](https://pnpm.io/) is the package manager used to download dependencies for the UI. To install pnpm, follow [the instructions on the official pnpm website](https://pnpm.io/installation).
```javascript
nvm install 18.20.4
```
**Cross-Compilation Toolchains (Optional):**
If you plan to deploy PhotonVision to a coprocessor like a Raspberry Pi, you will need to install the appropriate cross-compilation toolchain for your platform. For `linuxarm64` devices, this can be accomplished by running `./gradlew installArm64Toolchain` in the root folder of the project.
## Compiling Instructions
@@ -46,27 +44,7 @@ or alternatively download the source code from GitHub and extract the zip:
In the photon-client directory:
```bash
npm install
```
### Build and Copy UI to Java Source
In the root directory:
```{eval-rst}
.. tab-set::
.. tab-item:: Linux
``./gradlew buildAndCopyUI``
.. tab-item:: macOS
``./gradlew buildAndCopyUI``
.. tab-item:: Windows (cmd)
``gradlew buildAndCopyUI``
pnpm install
```
### Using hot reload on the UI
@@ -74,7 +52,7 @@ In the root directory:
In the photon-client directory:
```bash
npm run dev
pnpm run dev
```
This allows you to make UI changes quickly without having to spend time rebuilding the jar. Hot reload is enabled, so changes that you make and save are reflected in the UI immediately. Running this command will give you the URL for accessing the UI, which is on a different port than normal. You must use the printed URL to use hot reload.
@@ -87,14 +65,17 @@ To compile and run the project, issue the following command in the root director
.. tab-set::
.. tab-item:: Linux
:sync: linux
``./gradlew run``
.. tab-item:: macOS
:sync: macos
``./gradlew run``
.. tab-item:: Windows (cmd)
:sync: windows
``gradlew run``
```
@@ -105,21 +86,24 @@ Running the following command under the root directory will build the jar under
.. tab-set::
.. tab-item:: Linux
:sync: linux
``./gradlew shadowJar``
.. tab-item:: macOS
:sync: macos
``./gradlew shadowJar``
.. tab-item:: Windows (cmd)
:sync: windows
``gradlew shadowJar``
```
### Build and Run PhotonVision on a Raspberry Pi Coprocessor
As a convenience, the build has a built-in `deploy` command which builds, deploys, and starts the current source code on a coprocessor.
As a convenience, the build has a built-in `deploy` command which builds, deploys, and starts the current source code on a coprocessor. It uses [deploy-utils](https://github.com/wpilibsuite/deploy-utils/blob/main/README.md), so it works very similarly to deploys on robot projects.
An architecture override is required to specify the deploy target's architecture.
@@ -127,18 +111,21 @@ An architecture override is required to specify the deploy target's architecture
.. tab-set::
.. tab-item:: Linux
:sync: linux
``./gradlew clean``
``./gradlew deploy -PArchOverride=linuxarm64``
.. tab-item:: macOS
:sync: macos
``./gradlew clean``
``./gradlew deploy -PArchOverride=linuxarm64``
.. tab-item:: Windows (cmd)
:sync: windows
``gradlew clean``
@@ -157,14 +144,17 @@ The photonlib source can be published to your local maven repository after build
.. tab-set::
.. tab-item:: Linux
:sync: linux
``./gradlew publishToMavenLocal``
.. tab-item:: macOS
:sync: macos
``./gradlew publishToMavenLocal``
.. tab-item:: Windows (cmd)
:sync: windows
``gradlew publishToMavenLocal``
```
@@ -187,6 +177,29 @@ With the VSCode [Extension Pack for Java](https://marketplace.visualstudio.com/i
To correctly run PhotonVision tests this way, you must [delegate the tests to Gradle](https://code.visualstudio.com/docs/java/java-build#_delegate-tests-to-gradle). Debugging tests like this will [**not** currently](https://github.com/microsoft/build-server-for-gradle/issues/119) collect outputs.
### Running Tests With UI
By default, tests are run with UI disabled so they are not obtrusive during a build. All tests should be useful when the UI is disabled. However, if a particular test would benefit from having UI access (i.e. for debugging info), the UI can be enabled by passing the `enableTestUi` project property to Gradle. This will run all tests by default, but the Gradle `--tests` option can be used to [filter for specific tests](https://docs.gradle.org/current/userguide/java_testing.html#test_filtering).
```{eval-rst}
.. tab-set::
.. tab-item:: Linux
:sync: linux
``./gradlew test -PenableTestUi``
.. tab-item:: macOS
:sync: macos
``./gradlew test -PenableTestUi``
.. tab-item:: Windows (cmd)
:sync: windows
``gradlew test -PenableTestUi``
```
### Debugging PhotonVision Running Locally
Unit tests can instead be debugged through the ``test`` Gradle task for a specific subproject in VSCode, found in the Gradle tab:
@@ -207,7 +220,7 @@ Similarly, a local instance of PhotonVision can be debugged in the same way usin
Set up a VSCode configuration in {code}`launch.json`
```
```json
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
@@ -289,3 +302,9 @@ Using the [GitHub CLI](https://cli.github.com/), we can download artifacts from
MacOS builds are not published to releases as MacOS is not an officially
supported platform. However, MacOS builds are still available from the MacOS
build action, which can be found [here](https://github.com/PhotonVision/photonvision/actions/workflows/build.yml).
#### Forcing Object Detection in the UI
In order to force the Object Detection interface to be visible, it's necessary to hardcode the platform that `Platform.java` returns. This can be done by changing the function that detects the RK3588S/QCS6490 platform to always return true, and changing the `getCurrentPlatform()` function to always return the RK3588S/QCS6490 architecture.
Alternatively, it's possible to modify the frontend code by changing all instances of `useSettingsStore().general.supportedBackends.length > 0` to `true`, which will force the card to render.
Make sure to revert these changes before submitting a Pull Request.

Binary file not shown.

After

Width:  |  Height:  |  Size: 512 KiB

View File

@@ -109,3 +109,13 @@ Clients may publish statistics to NetworkTables. If they do, they shall publish
| rtt2_us | Integer | The time in us from last complete (ping transmission to pong reception) |
PhotonVision has chosen to publish to the sub-table `/photonvision/.timesync/{DEVICE_HOSTNAME}`. Future implementations of this protocol may decide to implement this as a structured data type.
## Wireshark Dissector
![](images/wireshark.jpg)
A [WireShark dissector](https://raw.githubusercontent.com/PhotonVision/photonvision/refs/heads/main/devTools/photon.lua) created for Wireshark ~=4.6 can be used to inspect Time Syncronization messages. Copy the dissector to your Wireshark plugin directory (for me, that's `C:\Users\Me\AppData\Roaming\Wireshark\plugins`), and open the capture. Because TSP uses UDP Unicast, data must be collected on the coprocessor or robot processor using a command similar to:
```
sudo tcpdump -i any port 5810 -w tsp_capture.pcap
```

View File

@@ -3,6 +3,7 @@
```{toctree}
building-photon
building-docs
linting
developer-docs/index
design-descriptions/index
```

View File

@@ -0,0 +1,43 @@
# Linting the PhotonVision Codebase
## Versions
:::{note}
If you work on other projects that use different versions of the same linters as PhotonVision, you may find it beneficial to use a [venv](https://docs.python.org/3/library/venv.html) instead of installing the linters globally. This will allow you to have different versions of the same linter installed for different projects.
:::
The correct versions for each linter can be found under the linting workflow located [here](https://github.com/PhotonVision/photonvision/tree/main/.github/workflows). For *doc8*, the version can be found in `docs/requirements.txt`. If you've linted, and are still unable to pass CI, please check the versions of your linters.
## Frontend
### Linting the frontend
In order to lint the frontend, run `pnpm -C photon-client lint && pnpm -C photon-client format`. This should be done from the base level of the repo.
## Backend
### wpiformat installation
To lint the backend, PhotonVision uses *wpiformat* and *spotless*. Spotless is included with gradle, which means installation is not needed. To install wpiformat, run `pipx install wpiformat`. To install a specific version, run `pipx install wpiformat==<version>`.
### Linting the backend
To lint, run `./gradlew spotlessApply` and `wpiformat`.
## Documentation
### doc8 installation
To install *doc8*, the python tool we use to lint our documentation, run `pipx install doc8`. To install a specific version, run `pipx install doc8==<version>`.
### Linting the documentation
To lint the documentation, run `doc8 docs` from the root level of the docs.
## Alias
The following [alias](https://www.computerworld.com/article/1373210/how-to-use-aliases-in-linux-shell-commands.html) can be added to your shell config, which will allow you to lint the entirety of the PhotonVision project by running `pvLint`. The alias will work on Linux, macOS, Git Bash on Windows, and WSL.
```sh
alias pvLint='wpiformat -v && ./gradlew spotlessApply && pnpm -C photon-client lint && pnpm -C photon-client format && doc8 docs'
```

View File

@@ -14,8 +14,10 @@ To do this, we'll use the _pitch_ of the target in the camera image and trigonom
```{eval-rst}
.. tab-set::
:sync-group: code
.. tab-item:: Java
:sync: java
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-java-examples/aimandrange/src/main/java/frc/robot/Robot.java
:language: java
@@ -24,6 +26,7 @@ To do this, we'll use the _pitch_ of the target in the camera image and trigonom
:lineno-start: 84
.. tab-item:: C++ (Header)
:sync: c++
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-cpp-examples/aimandrange/src/main/include/Robot.h
:language: c++
@@ -32,6 +35,7 @@ To do this, we'll use the _pitch_ of the target in the camera image and trigonom
:lineno-start: 25
.. tab-item:: C++ (Source)
:sync: c++
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-cpp-examples/aimandrange/src/main/cpp/Robot.cpp
:language: c++
@@ -40,6 +44,7 @@ To do this, we'll use the _pitch_ of the target in the camera image and trigonom
:lineno-start: 58
.. tab-item:: Python
:sync: python
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-python-examples/aimandrange/robot.py
:language: python

View File

@@ -6,8 +6,8 @@ The following example is from the PhotonLib example repository ([Java](https://g
- A Robot
- A camera mounted rigidly to the robot's frame, centered and pointed forward.
- A coprocessor running PhotonVision with an AprilTag or Aruco 2D Pipeline.
- [A printout of AprilTag 7](https://firstfrc.blob.core.windows.net/frc2025/FieldAssets/Apriltag_Images_and_User_Guide.pdf), mounted on a rigid and flat surface.
- A coprocessor running PhotonVision with an AprilTag or ArUco 2D Pipeline.
- [A printout of AprilTag 7](https://firstfrc.blob.core.windows.net/frc2026/FieldAssets/2026-apriltag-images-user-guide.pdf), mounted on a rigid and flat surface.
## Code
@@ -19,8 +19,10 @@ In this example, while the operator holds a button down, the robot will turn tow
```{eval-rst}
.. tab-set::
:sync-group: code
.. tab-item:: Java
:sync: java
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-java-examples/aimattarget/src/main/java/frc/robot/Robot.java
:language: java
@@ -29,6 +31,7 @@ In this example, while the operator holds a button down, the robot will turn tow
:lineno-start: 77
.. tab-item:: C++ (Header)
:sync: c++
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-cpp-examples/aimattarget/src/main/include/Robot.h
:language: c++
@@ -37,6 +40,7 @@ In this example, while the operator holds a button down, the robot will turn tow
:lineno-start: 25
.. tab-item:: C++ (Source)
:sync: c++
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-cpp-examples/aimattarget/src/main/cpp/Robot.cpp
:language: c++
@@ -45,6 +49,7 @@ In this example, while the operator holds a button down, the robot will turn tow
:lineno-start: 56
.. tab-item:: Python
:sync: python
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-python-examples/aimattarget/robot.py
:language: python

View File

@@ -18,35 +18,27 @@ The {code}`Drivetrain` class includes functionality to fuse multiple sensor read
Please reference the [WPILib documentation](https://docs.wpilib.org/en/stable/docs/software/advanced-controls/state-space/state-space-pose_state-estimators.html) on using the {code}`SwerveDrivePoseEstimator` class.
We use the 2024 game's AprilTag Locations:
We use the current game's AprilTag Locations:
```{eval-rst}
.. tab-set::
.. tab-set-code::
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-java-examples/poseest/src/main/java/frc/robot/Vision.java
:language: java
:lines: 68-68
:linenos:
:lineno-start: 68
.. tab-item:: Java
:sync: java
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-cpp-examples/poseest/src/main/include/Constants.h
:language: c++
:lines: 42-43
:linenos:
:lineno-start: 42
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-java-examples/poseest/src/main/java/frc/robot/Vision.java
:language: java
:lines: 68-68
:linenos:
:lineno-start: 68
.. tab-item:: C++
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-cpp-examples/poseest/src/main/include/Constants.h
:language: c++
:lines: 42-43
:linenos:
:lineno-start: 42
.. tab-item:: Python
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-python-examples/poseest/robot.py
:language: python
:lines: 46-46
:linenos:
:lineno-start: 46
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-python-examples/poseest/robot.py
:language: python
:lines: 46-46
:linenos:
:lineno-start: 46
```
@@ -56,63 +48,47 @@ To incorporate PhotonVision, we need to create a {code}`PhotonCamera`:
```{eval-rst}
.. tab-set::
.. tab-set-code::
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-java-examples/poseest/src/main/java/frc/robot/Vision.java
:language: java
:lines: 57-57
:linenos:
:lineno-start: 57
.. tab-item:: Java
:sync: java
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-cpp-examples/poseest/src/main/include/Vision.h
:language: c++
:lines: 145-145
:linenos:
:lineno-start: 145
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-java-examples/poseest/src/main/java/frc/robot/Vision.java
:language: java
:lines: 57-57
:linenos:
:lineno-start: 57
.. tab-item:: C++
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-cpp-examples/poseest/src/main/include/Vision.h
:language: c++
:lines: 145-145
:linenos:
:lineno-start: 145
.. tab-item:: Python
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-python-examples/poseest/robot.py
:language: python
:lines: 44-44
:linenos:
:lineno-start: 44
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-python-examples/poseest/robot.py
:language: python
:lines: 44-44
:linenos:
:lineno-start: 44
```
During periodic execution, we read back camera results. If we see AprilTags in the image, we calculate the camera-measured pose of the robot and pass it to the {code}`Drivetrain`.
```{eval-rst}
.. tab-set::
.. tab-set-code::
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-java-examples/poseest/src/main/java/frc/robot/Robot.java
:language: java
:lines: 64-74
:linenos:
:lineno-start: 64
.. tab-item:: Java
:sync: java
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-cpp-examples/poseest/src/main/cpp/Robot.cpp
:language: c++
:lines: 38-46
:linenos:
:lineno-start: 38
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-java-examples/poseest/src/main/java/frc/robot/Robot.java
:language: java
:lines: 64-74
:linenos:
:lineno-start: 64
.. tab-item:: C++
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-cpp-examples/poseest/src/main/cpp/Robot.cpp
:language: c++
:lines: 38-46
:linenos:
:lineno-start: 38
.. tab-item:: Python
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-python-examples/poseest/robot.py
:language: python
:lines: 54-56
:linenos:
:lineno-start: 54
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-python-examples/poseest/robot.py
:language: python
:lines: 54-56
:linenos:
:lineno-start: 54
```
@@ -121,56 +97,45 @@ During periodic execution, we read back camera results. If we see AprilTags in t
First, we create a new {code}`VisionSystemSim` to represent our camera and coprocessor running PhotonVision, and moving around our simulated field.
```{eval-rst}
.. tab-set::
.. tab-set-code::
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-java-examples/poseest/src/main/java/frc/robot/Vision.java
:language: java
:lines: 65-69
:linenos:
:lineno-start: 65
.. tab-item:: Java
:sync: java
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-cpp-examples/poseest/src/main/include/Vision.h
:language: c++
:lines: 49-52
:linenos:
:lineno-start: 49
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-java-examples/poseest/src/main/java/frc/robot/Vision.java
:language: java
:lines: 65-69
:linenos:
:lineno-start: 65
.. code-block:: python
.. tab-item:: C++
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-cpp-examples/poseest/src/main/include/Vision.h
:language: c++
:lines: 49-52
:linenos:
:lineno-start: 49
.. tab-item:: Python
# Coming Soon!
# Coming Soon!
```
Then, we add configure the simulated vision system to match the camera system being simulated.
```{eval-rst}
.. tab-set::
.. tab-set-code::
.. tab-item:: Java
:sync: java
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-java-examples/poseest/src/main/java/frc/robot/Vision.java
:language: java
:lines: 69-82
:linenos:
:lineno-start: 69
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-java-examples/poseest/src/main/java/frc/robot/Vision.java
:language: java
:lines: 69-82
:linenos:
:lineno-start: 69
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-cpp-examples/poseest/src/main/include/Vision.h
:language: c++
:lines: 53-65
:linenos:
:lineno-start: 53
.. tab-item:: C++
.. code-block:: python
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-cpp-examples/poseest/src/main/include/Vision.h
:language: c++
:lines: 53-65
:linenos:
:lineno-start: 53
.. tab-item:: Python
# Coming Soon!
# Coming Soon!
```
@@ -179,28 +144,23 @@ Then, we add configure the simulated vision system to match the camera system be
During simulation, we periodically update the simulated vision system.
```{eval-rst}
.. tab-set::
.. tab-set-code::
.. tab-item:: Java
:sync: java
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-java-examples/poseest/src/main/java/frc/robot/Robot.java
:language: java
:lines: 114-132
:linenos:
:lineno-start: 114
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-java-examples/poseest/src/main/java/frc/robot/Robot.java
:language: java
:lines: 114-132
:linenos:
:lineno-start: 114
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-cpp-examples/poseest/src/main/cpp/Robot.cpp
:language: c++
:lines: 95-109
:linenos:
:lineno-start: 95
.. tab-item:: C++
.. code-block:: python
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/abe95dfaa055bbe3609f72cfcaaba0f96ee7978c/photonlib-cpp-examples/poseest/src/main/cpp/Robot.cpp
:language: c++
:lines: 95-109
:linenos:
:lineno-start: 95
.. tab-item:: Python
# Coming Soon!
# Coming Soon!
```
The rest is done behind the scenes.

View File

@@ -8,7 +8,7 @@ By default, PhotonVision attempts to make minimal assumptions of the hardware it
## LED Support
For Raspberry-Pi based hardware, PhotonVision can use [PiGPIO](https://abyz.me.uk/rpi/pigpio/) to control IO pins. The mapping of which pins control which LED's is part of the hardware config. The pins are active-high: set high when LED's are commanded on, and set low when commanded off.
When running on Linux, PhotonVision can use [diozero](https://www.diozero.com) to control IO pins. The mapping of which pins control which LED's is part of the hardware config. The illumination LED pins are active-high: set high when LED's are commanded on, and set low when commanded off.
```{eval-rst}
.. tab-set-code::
@@ -16,14 +16,11 @@ For Raspberry-Pi based hardware, PhotonVision can use [PiGPIO](https://abyz.me.u
{
"ledPins" : [ 13 ],
"ledSetCommand" : "",
"ledsCanDim" : true,
"ledPWMRange" : [ 0, 100 ],
"ledPWMSetRange" : "",
"ledBrightnessRange" : [ 0, 100 ],
"ledPWMFrequency" : 0,
"ledDimCommand" : "",
"ledBlinkCommand" : "",
"statusRGBPins" : [ ],
"statusRGBActiveHigh" : false,
}
```
@@ -31,29 +28,66 @@ For Raspberry-Pi based hardware, PhotonVision can use [PiGPIO](https://abyz.me.u
No hardware boards with status RGB LED pins or non-dimming LED's have been tested yet. Please reach out to the development team if these features are desired, they can assist with configuration and testing.
:::
### GPIO Pinout
::::{tab-set}
:::{tab-item} Raspberry Pi
The following diagram shows the GPIO pin numbering of the 40-pin header on Raspberry Pi hardware, courtesy of [pinout.xyz](https://pinout.xyz). Compute modules use the pin numbering from their respective datasheet.
```{image} https://raw.githubusercontent.com/pinout-xyz/Pinout.xyz/master/resources/raspberry-pi-pinout.png
:alt: Raspberry Pi GPIO Pinout
```
:::
::::
### Custom GPIO
If your hardware does not support diozero's default provider, custom commands can be provided to interact with the GPIO lines. The examples below show what parameters are provided to each command, which can be used in any order or multiple times as needed.
```{eval-rst}
.. tab-set-code::
.. code-block:: json
{
"getGPIOCommand" : "getGPIO {p}",
"setGPIOCommand" : "setGPIO {p} {s}",
"setPWMCommand" : "setPWM {p} {v}",
"setPWMFrequencyCommand" : "setPWMFrequency {p} {f}",
"releaseGPIOCommand" : "releseGPIO {p}",
}
```
The following template strings are used to input parameters to the commands:
| Template | Parameter | Values |
| -------- | ---------- | ---------- |
| `{p}` | pin number | integers |
| `{s}` | state | true/false |
| `{v}` | value | 0.0-1.0 |
| `{f}` | frequency | integers |
If you were using custom LED commands from 2025 or earlier and still need custom GPIO commands, they can likely be copied over. `ledSetCommand` can be reused as `setGPIOCommand`. `ledDimCommand` can be reused with edits as `setPWMCommand`, replacing any occurrences of `{v}` with `$(awk 'BEGIN{ print int({v}*100) }')` if your command requires integer percentages.
## Hardware Interaction Commands
For Non-Raspberry-Pi hardware, users must provide valid hardware-specific commands for some parts of the UI interaction (including performance metrics, and executing system restarts).
For non-Linux hardware, users must provide the hardware-specific command for executing system restarts.
Leaving a command blank will disable the associated functionality.
Leaving this command blank will disable the restart functionality.
```{eval-rst}
.. tab-set-code::
.. code-block:: json
{
"cpuTempCommand" : "",
"cpuMemoryCommand" : "",
"cpuUtilCommand" : "",
"gpuMemoryCommand" : "",
"gpuTempCommand" : "",
"ramUtilCommand" : "",
"restartHardwareCommand" : "",
}
```
:::{note}
These settings have no effect if PhotonVision detects it is running on a Raspberry Pi. See [the MetricsBase class](https://github.com/PhotonVision/photonvision/blob/dbd631da61b7c86b70fa6574c2565ad57d80a91a/photon-core/src/main/java/org/photonvision/common/hardware/metrics/MetricsBase.java) for the commands utilized.
This setting has no effect if PhotonVision detects it is running on Linux. On Linux, the restart is accomplished by executing `reboot now` in a shell.
:::
## Known Camera FOV
@@ -101,20 +135,16 @@ Here is a complete example `hardwareConfig.json`:
"deviceLogoPath" : "",
"supportURL" : "https://www.youtube.com/watch?v=b-CvLWbfZhU",
"ledPins" : [2, 13],
"ledSetCommand" : "",
"ledsCanDim" : true,
"ledPWMRange" : [ 0, 100 ],
"ledPWMSetRange" : "",
"ledBrightnessRange" : [ 0, 100 ],
"ledPWMFrequency" : 0,
"ledDimCommand" : "",
"ledBlinkCommand" : "",
"statusRGBPins" : [ ],
"cpuTempCommand" : "",
"cpuMemoryCommand" : "",
"cpuUtilCommand" : "",
"gpuMemoryCommand" : "",
"gpuTempCommand" : "",
"ramUtilCommand" : "",
"statusRGBActiveHigh" : false,
"getGPIOCommand" : "getGPIO {p}",
"setGPIOCommand" : "setGPIO {p} {s}",
"setPWMCommand" : "setPWM {p} {v}",
"setPWMFrequencyCommand" : "setPWMFrequency {p} {f}",
"releaseGPIOCommand" : "releaseGPIO {p}",
"restartHardwareCommand" : "",
"vendorFOV" : 72.5
}

View File

@@ -88,7 +88,7 @@ Cameras capable of capturing a good image with very short exposures will also he
### Using Multiple Cameras
Keeping the target(s) in view of the robot often requires more than one camera. PhotonVision has no hardcoded limit on the number of cameras supported. The limit is usually dependant on CPU (can all frames be processed fast enough?) and USB bandwidth (Can all cameras send their images without overwhelming the bus?).
Keeping the target(s) in view of the robot often requires more than one camera. PhotonVision has no hardcoded limit on the number of cameras supported. The limit is usually dependent on CPU (can all frames be processed fast enough?) and USB bandwidth (Can all cameras send their images without overwhelming the bus?).
Note that cameras are not synchronized together. Frames are captured and processed asynchronously. Robot Code must fuse estimates together. For more information, see {ref}`the programming reference. <docs/programming/index:programming reference>`.

View File

@@ -43,7 +43,7 @@ A simple way to use a pose estimate is to activate robot functions automatically
```{eval-rst}
.. tab-set-code::
.. code-block:: Java
.. code-block:: java
Pose3d robotPose;
boolean launcherSpinCmd;

View File

@@ -2,9 +2,9 @@
## How does it work?
PhotonVision supports object detection using neural network accelerator hardware built into Orange Pi 5/5+ coprocessors. Please note that the Orange Pi 5/5+ are the only coprocessors that are currently supported. The Neural Processing Unit, or NPU, is [used by PhotonVision](https://github.com/PhotonVision/rknn_jni/tree/main) to massively accelerate certain math operations like those needed for running ML-based object detection.
PhotonVision supports object detection using neural network accelerator hardware, commonly known as an NPU. The two coprocessors currently supported are the {ref}`Orange Pi 5 <docs/objectDetection/opi:Orange Pi 5 (and variants) Object Detection>` and the {ref}`Rubik Pi 3 <docs/objectDetection/rubik:Rubik Pi 3 Object Detection>`.
For the 2025 season, PhotonVision ships with a pretrained ALGAE model. A model to detect coral is not currently stable, and interested teams should ask in the Photonvision discord.
PhotonVision currently ships with a model trained on the [COCO dataset](https://cocodataset.org/) by [Ultralytics](https://github.com/ultralytics/ultralytics) (this model is licensed under [AGPLv3](https://www.gnu.org/licenses/agpl-3.0.en.html)). This model is meant to be used for testing and other miscellaneous purposes. It is not meant to be used in competition. For the 2026 season, PhotonVision ships with a model to detect FUEL, this is also licensed under AGPL.
## Tracking Objects
@@ -18,7 +18,7 @@ This model output means that while its fairly easy to say that "this rectangle p
## Tuning and Filtering
Compared to other pipelines, object detection exposes very few tuning handles. The Confidence slider changes the minimum confidence that the model needs to have in a given detection to consider it valid, as a number between 0 and 1 (with 0 meaning completely uncertain and 1 meaning maximally certain).
Compared to other pipelines, object detection exposes very few tuning handles. The Confidence slider changes the minimum confidence that the model needs to have in a given detection to consider it valid, as a number between 0 and 1 (with 0 meaning completely uncertain and 1 meaning maximally certain). The Non-Maximum Suppresion (NMS) Threshold slider is used to filter out overlapping detections. Higher values mean more detections are allowed through, but may result in false positives. It's generally recommended that teams leave this set at the default, unless they find they're unable to get usable results with solely the Confidence slider.
```{raw} html
<video width="85%" controls>
@@ -33,31 +33,19 @@ The same area, aspect ratio, and target orientation/sort parameters from {ref}`r
Photonvision will letterbox your camera frame to 640x640. This means that if you select a resolution that is larger than 640 it will be scaled down to fit inside a 640x640 frame with black bars if needed. Smaller frames will be scaled up with black bars if needed.
## Training Custom Models
It is recommended that you select a resolution that results in the smaller dimension being just greater than, or equal to, 640. Anything above this will not see any increased performance.
:::{warning}
Power users only. This requires some setup, such as obtaining your own dataset and installing various tools. It's additionally advised to have a general knowledge of ML before attempting to train your own model. Additionally, this is not officially supported by Photonvision, and any problems that may arise are not attributable to Photonvision.
:::
## Custom Models
Before beginning, it is necessary to install the [rknn-toolkit2](https://github.com/airockchip/rknn-toolkit2). Then, install the relevant [Ultralytics repository](https://github.com/airockchip?tab=repositories&q=yolo&type=&language=&sort=) from this list. After training your model, export it to `rknn`. This will give you an `onnx` file, formatted for conversion. Copy this file to the relevant folder in [rknn_model_zoo](https://github.com/airockchip/rknn_model_zoo), and use the conversion script located there to convert it. If necessary, modify the script to provide the path to your training database for quantization.
For information regarding converting custom models and supported models for each platform, refer to the page detailing information about your specific coprocessor.
## Uploading Custom Models
- {ref}`Orange Pi 5 <docs/objectDetection/opi:Orange Pi 5 (and variants) Object Detection>`
- {ref}`Rubik Pi 3 <docs/objectDetection/rubik:Rubik Pi 3 Object Detection>`
:::{warning}
PhotonVision currently ONLY supports 640x640 Ultralytics YOLOv5, YOLOv8, and YOLOv11 models trained and converted to `.rknn` format for RK3588 CPUs! Other models require different post-processing code and will NOT work. The model conversion process is also highly particular. Proceed with care.
:::
### Training Custom Models
:::{warning}
Non-quantized models are not supported! If you have the option, make sure quantization is enabled when exporting to .rknn format. This will represent the weights and activations of the model as 8-bit integers, instead of 32-bit floats which PhotonVision doesn't support. Quantized models are also much faster.
:::
PhotonVision does not offer any support for training custom models, only conversion. For information on which models are supported for a given coprocessor, use the links above.
In the settings, under `Device Control`, there's an option to upload a new object detection model. Naming convention
should be `name-verticalResolution-horizontalResolution-yolovXXX`. The
`name` should only include alphanumeric characters, periods, and underscores. Additionally, the labels
file ought to have the same name as the RKNN file, with `-labels` appended to the end. For
example, if the RKNN file is named `Algae_1.03.2025-640-640-yolov5s.rknn`, the labels file should be
named `Algae_1.03.2025-640-640-yolov5s-labels.txt`.
### Managing Custom Models
:::{note}
Currently there is no way to delete custom models in the GUI, though this is a planned feature. To do this, you have to SSH into the coprocessor and delete the files manually from `/opt/photonvision/photonvision_config/models`.
:::
Custom models can now be managed from the Object Detection tab in settings. You can upload a custom model by clicking the "Upload Model" button, selecting your model file, and filling out the property fields. Models can also be exported, both individually and in bulk. Models exported in bulk can be imported using the `import bulk` button. Models exported individually must be re-imported as an individual model, and all the relevant metadata is stored in the filename of the model.

View File

@@ -1,8 +1,8 @@
# Object Detection
```{toctree}
:maxdepth: 0
:titlesonly: true
about-object-detection
opi
rubik
```

View File

@@ -0,0 +1,19 @@
# Orange Pi 5 (and variants) Object Detection
## How it works
PhotonVision runs object detection on the Orange Pi 5 by use of the RKNN model architecture, and [this JNI code](https://github.com/PhotonVision/rknn_jni).
## Supported models
PhotonVision currently ONLY supports 640x640 Ultralytics YOLOv5, YOLOv8, and YOLOv11 models trained and converted to `.rknn` format for RK3588 SOCs! Other models require different post-processing code and will NOT work.
## Converting Custom Models
:::{warning}
Only quantized models are supported, so take care when exporting to select the option for quantization.
:::
PhotonVision now ships with a {{ '[Python Notebook](https://github.com/PhotonVision/photonvision/blob/{}/scripts/rknn_conversion.ipynb)'.format(git_tag_ref) }} that you can use in [Google Colab](https://colab.research.google.com) or in a local **Linux** environment (since `rknn-toolkit2` only supports Linux). In Google Colab, you can simply paste the PhotonVision GitHub URL into the "GitHub" tab and select the `rknn_conversion.ipynb` notebook without needing to manually download anything.
Please ensure that the model you are attempting to convert is among the {ref}`supported models <docs/objectDetection/opi:Supported Models>` and using the PyTorch format.

View File

@@ -0,0 +1,25 @@
# Rubik Pi 3 Object Detection
## How it works
PhotonVision runs object detection on the Rubik Pi 3 by use of [TensorflowLite](https://github.com/tensorflow/tensorflow), and [this JNI code](https://github.com/PhotonVision/rubik_jni).
## Supported models
PhotonVision currently ONLY supports 640x640 Ultralytics YOLOv8 and YOLOv11 models trained and converted to `.tflite` format for QCS6490 SOCs! Other models require different post-processing code and will NOT work.
## Converting Custom Models
:::{warning}
Only quantized models are supported, so take care when exporting to select the option for quantization.
:::
PhotonVision now ships with a {{ '[Python Notebook](https://github.com/PhotonVision/photonvision/blob/{}/scripts/rubik_conversion.ipynb)'.format(git_tag_ref) }} that you can use in [Google Colab](https://colab.research.google.com), [Kaggle](https://kaggle.com/code), or in a local environment. In Google Colab, you can simply paste the PhotonVision GitHub URL into the "GitHub" tab and select the `rubik_conversion.ipynb` notebook without needing to manually download anything.
Please ensure that the model you are attempting to convert is among the {ref}`supported models <docs/objectDetection/rubik:Supported Models>` and using the PyTorch format.
## Benchmarking
Before you can perform benchmarking, it's necessary to install `tensorflow-lite-qcom-apps` with apt.
By SSHing into your Rubik Pi and running this command, replacing `PATH/TO/MODEL` with the path to your model, `benchmark_model --graph=src/test/resources/yolov8nCoco.tflite --external_delegate_path=/usr/lib/libQnnTFLiteDelegate.so --external_delegate_options=backend_type:htp --external_delegate_options=htp_use_conv_hmx:1 --external_delegate_options=htp_performance_mode:2` you can determine how long it takes for inference to be performed with your model.

View File

@@ -10,7 +10,7 @@ A vision pipeline represents a series of steps that are used to acquire an image
## Types of Pipelines
### AprilTag / AruCo
### AprilTag / ArUco
This pipeline type is based on detecting AprilTag fiducial markers. More information about AprilTags can be found in the [WPILib documentation](https://docs.wpilib.org/en/stable/docs/software/vision-processing/apriltag/apriltag-intro.html). This pipeline provides easy to use 3D pose information which allows localization.
@@ -56,7 +56,7 @@ Each pipeline has a set of tabs that are used to configure the pipeline. All pip
Pipielines also have additional tabs that are specific to the pipeline type. Listed below are the tabs for each pipeline type.
### AprilTag / AruCo Pipelines
### AprilTag / ArUco Pipelines
- AprilTag: This tab includes AprilTag specific tuning parameters, such as decimate, blur, threads, pose iterations, and more.

View File

@@ -4,17 +4,17 @@ You can control the vision LEDs of supported hardware via PhotonLib using the `s
```{eval-rst}
.. tab-set-code::
.. code-block:: Java
.. code-block:: java
// Blink the LEDs.
camera.setLED(VisionLEDMode.kBlink);
.. code-block:: C++
.. code-block:: c++
// Blink the LEDs.
camera.SetLED(photonlib::VisionLEDMode::kBlink);
.. code-block:: Python
.. code-block:: python
# Coming Soon!
```

View File

@@ -9,17 +9,17 @@ You can use the `setDriverMode()`/`SetDriverMode()` (Java and C++ respectively)
```{eval-rst}
.. tab-set-code::
.. code-block:: Java
.. code-block:: java
// Set driver mode to on.
camera.setDriverMode(true);
.. code-block:: C++
.. code-block:: c++
// Set driver mode to on.
camera.SetDriverMode(true);
.. code-block:: Python
.. code-block:: python
# Coming Soon!
```
@@ -31,17 +31,17 @@ You can use the `setPipelineIndex()`/`SetPipelineIndex()` (Java and C++ respecti
```{eval-rst}
.. tab-set-code::
.. code-block:: Java
.. code-block:: java
// Change pipeline to 2
camera.setPipelineIndex(2);
.. code-block:: C++
.. code-block:: c++
// Change pipeline to 2
camera.SetPipelineIndex(2);
.. code-block:: Python
.. code-block:: python
# Coming Soon!
```
@@ -52,17 +52,17 @@ You can also get the pipeline latency from a pipeline result using the `getLaten
```{eval-rst}
.. tab-set-code::
.. code-block:: Java
.. code-block:: java
// Get the pipeline latency.
double latencySeconds = result.getLatencyMillis() / 1000.0;
.. code-block:: C++
.. code-block:: c++
// Get the pipeline latency.
units::second_t latency = result.GetLatency();
.. code-block:: Python
.. code-block:: python
# Coming Soon!
```

View File

@@ -0,0 +1,37 @@
# FPS Limiter
:::{warning}
When using the FPS limiter, it's important to disable it before a match begins.
:::
The FPS limiter can be used to lower the frames processed per second for a given camera. This is intended to be used for power-saving, particularly in the case of high FPS cameras with powerful coprocessors. The value passed to the function will indicate the frames per second that should be processed. A value of -1 should be passed to indicate that the FPS limiter should not restrict processing; this is the default behavior.
```{eval-rst}
.. tab-set-code::
.. code-block:: java
int limit = camera.getFPSLimit();
camera.setFPSLimit(10);
// This removes any previously set FPS limit.
camera.setFPSLimit(-1);
.. code-block:: c++
int limit = camera.GetFPSLimit();
camera.SetFPSLimit(10);
// This removes any previously set FPS limit.
camera.SetFPSLimit(-1);
.. code-block:: python
limit = camera.getFPSLimit()
camera.setFPSLimit(10)
# This removes any previously set FPS limit.
camera.setFPSLimit(-1)
```

View File

@@ -20,7 +20,7 @@ The `PhotonCamera` class has two constructors: one that takes a `NetworkTable` a
:language: c++
:lines: 42-43
.. code-block:: Python
.. code-block:: python
# Change this to match the name of your camera as shown in the web ui
self.camera = PhotonCamera("your_camera_name_here")
@@ -51,7 +51,7 @@ Use the `getLatestResult()`/`GetLatestResult()` (Java and C++ respectively) to o
:language: c++
:lines: 35-36
.. code-block:: Python
.. code-block:: python
# Query the latest result from PhotonVision
result = self.camera.getLatestResult()
@@ -69,17 +69,17 @@ Each pipeline result has a `hasTargets()`/`HasTargets()` (Java and C++ respectiv
```{eval-rst}
.. tab-set-code::
.. code-block:: Java
.. code-block:: java
// Check if the latest result has any targets.
boolean hasTargets = result.hasTargets();
.. code-block:: C++
.. code-block:: c++
// Check if the latest result has any targets.
bool hasTargets = result.HasTargets();
.. code-block:: Python
.. code-block:: python
# Check if the latest result has any targets.
hasTargets = result.hasTargets()
@@ -99,17 +99,17 @@ You can get a list of tracked targets using the `getTargets()`/`GetTargets()` (J
```{eval-rst}
.. tab-set-code::
.. code-block:: Java
.. code-block:: java
// Get a list of currently tracked targets.
List<PhotonTrackedTarget> targets = result.getTargets();
.. code-block:: C++
.. code-block:: c++
// Get a list of currently tracked targets.
wpi::ArrayRef<photonlib::PhotonTrackedTarget> targets = result.GetTargets();
.. code-block:: Python
.. code-block:: python
# Get a list of currently tracked targets.
targets = result.getTargets()
@@ -121,18 +121,18 @@ You can get the {ref}`best target <docs/reflectiveAndShape/contour-filtering:Con
```{eval-rst}
.. tab-set-code::
.. code-block:: Java
.. code-block:: java
// Get the current best target.
PhotonTrackedTarget target = result.getBestTarget();
.. code-block:: C++
.. code-block:: c++
// Get the current best target.
photonlib::PhotonTrackedTarget target = result.GetBestTarget();
.. code-block:: Python
.. code-block:: python
# Coming Soon!
@@ -149,7 +149,7 @@ You can get the {ref}`best target <docs/reflectiveAndShape/contour-filtering:Con
```{eval-rst}
.. tab-set-code::
.. code-block:: Java
.. code-block:: java
// Get information from target.
double yaw = target.getYaw();
@@ -159,7 +159,7 @@ You can get the {ref}`best target <docs/reflectiveAndShape/contour-filtering:Con
Transform2d pose = target.getCameraToTarget();
List<TargetCorner> corners = target.getCorners();
.. code-block:: C++
.. code-block:: c++
// Get information from target.
double yaw = target.GetYaw();
@@ -169,7 +169,7 @@ You can get the {ref}`best target <docs/reflectiveAndShape/contour-filtering:Con
frc::Transform2d pose = target.GetCameraToTarget();
wpi::SmallVector<std::pair<double, double>, 4> corners = target.GetCorners();
.. code-block:: Python
.. code-block:: python
# Get information from target.
yaw = target.getYaw()
@@ -193,7 +193,7 @@ All of the data above (**except skew**) is available when using AprilTags.
```{eval-rst}
.. tab-set-code::
.. code-block:: Java
.. code-block:: java
// Get information from target.
int targetID = target.getFiducialId();
@@ -201,7 +201,7 @@ All of the data above (**except skew**) is available when using AprilTags.
Transform3d bestCameraToTarget = target.getBestCameraToTarget();
Transform3d alternateCameraToTarget = target.getAlternateCameraToTarget();
.. code-block:: C++
.. code-block:: c++
// Get information from target.
int targetID = target.GetFiducialId();
@@ -209,7 +209,7 @@ All of the data above (**except skew**) is available when using AprilTags.
frc::Transform3d bestCameraToTarget = target.getBestCameraToTarget();
frc::Transform3d alternateCameraToTarget = target.getAlternateCameraToTarget();
.. code-block:: Python
.. code-block:: python
# Get information from target.
targetID = target.getFiducialId()
@@ -227,7 +227,7 @@ Images are stored within the PhotonVision configuration directory. Running the "
```{eval-rst}
.. tab-set-code::
.. code-block:: Java
.. code-block:: java
// Capture pre-process camera stream image
camera.takeInputSnapshot();
@@ -235,7 +235,7 @@ Images are stored within the PhotonVision configuration directory. Running the "
// Capture post-process camera stream image
camera.takeOutputSnapshot();
.. code-block:: C++
.. code-block:: c++
// Capture pre-process camera stream image
camera.TakeInputSnapshot();
@@ -243,7 +243,7 @@ Images are stored within the PhotonVision configuration directory. Running the "
// Capture post-process camera stream image
camera.TakeOutputSnapshot();
.. code-block:: Python
.. code-block:: python
# Capture pre-process camera stream image
camera.takeInputSnapshot()

View File

@@ -9,4 +9,5 @@ using-target-data
robot-pose-estimator
driver-mode-pipeline-index
controlling-led
fps-limiter
```

View File

@@ -48,91 +48,88 @@ Another necessary argument for creating a `PhotonPoseEstimator` is the `Transfor
## Creating a `PhotonPoseEstimator`
The PhotonPoseEstimator has a constructor that takes an `AprilTagFieldLayout` (see above), `PoseStrategy`, `PhotonCamera`, and `Transform3d`. `PoseStrategy` has nine possible values:
- MULTI_TAG_PNP_ON_COPROCESSOR
- Calculates a new robot position estimate by combining all visible tag corners. Recommended for all teams as it will be the most accurate.
- Must configure the AprilTagFieldLayout properly in the UI, please see {ref}`here <docs/apriltag-pipelines/multitag:multitag localization>` for more information.
- LOWEST_AMBIGUITY
- Choose the Pose with the lowest ambiguity.
- CLOSEST_TO_CAMERA_HEIGHT
- Choose the Pose which is closest to the camera height.
- CLOSEST_TO_REFERENCE_POSE
- Choose the Pose which is closest to the pose from setReferencePose().
- CLOSEST_TO_LAST_POSE
- Choose the Pose which is closest to the last pose calculated.
- AVERAGE_BEST_TARGETS
- Choose the Pose which is the average of all the poses from each tag.
- MULTI_TAG_PNP_ON_RIO
- A slower, older version of MULTI_TAG_PNP_ON_COPROCESSOR, not recommended for use.
- PNP_DISTANCE_TRIG_SOLVE
- Use distance data from best visible tag to compute a Pose. This runs on the RoboRIO in order
to access the robot's yaw heading, and MUST have addHeadingData called every frame so heading
data is up-to-date. Based on a reference implementation by [FRC Team 6328 Mechanical Advantage](https://www.chiefdelphi.com/t/frc-6328-mechanical-advantage-2025-build-thread/477314/98).
- CONSTRAINED_SOLVEPNP
- Solve a constrained version of the Perspective-n-Point problem with the robot's drivebase
flat on the floor. This computation takes place on the RoboRIO, and should not take more than 2ms.
This also requires addHeadingData to be called every frame so heading data is up to date.
If Multi-Tag PNP is enabled on the coprocessor, it will be used to provide an initial seed to
the optimization algorithm -- otherwise, the multi-tag fallback strategy will be used as the
seed.
The PhotonPoseEstimator has a constructor that takes an `AprilTagFieldLayout` (see above) and `Transform3d`.
```{eval-rst}
.. tab-set-code::
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/refs/heads/main/photonlib-java-examples/poseest/src/main/java/frc/robot/Vision.java
:language: java
:lines: 65-66
:lines: 63
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/refs/heads/main/photonlib-cpp-examples/poseest/src/main/include/Vision.h
:language: c++
:lines: 150-153
:lines: 149-150
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/refs/heads/main/photonlib-python-examples/poseest/robot.py
:language: python
:lines: 45-50
:lines: 45-48
```
:::{note}
Python still takes a `PhotonCamera` in the constructor, so you must create the camera as shown in the next section and then return and use it to create the `PhotonPoseEstimator`.
:::
## Using a `PhotonPoseEstimator`
The final prerequisite to using your `PhotonPoseEstimator` is creating a `PhotonCamera`. To do this, you must set the name of your camera in Photon Client. From there you can define the camera in code.
To use your `PhotonPoseEstimator`, you must create a `PhotonCamera` and feed the results into your `PhotonPoseEstimator`. To do this, you must first set the name of your camera in Photon Client. From there you can define the camera in code.
```{eval-rst}
.. tab-set-code::
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/refs/heads/main/photonlib-java-examples/poseest/src/main/java/frc/robot/Vision.java
:language: java
:lines: 63
:lines: 62
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/refs/heads/main/photonlib-cpp-examples/aimattarget/src/main/include/Robot.h
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/refs/heads/main/photonlib-cpp-examples/poseest/src/main/include/Vision.h
:language: c++
:lines: 55
:lines: 151
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/refs/heads/main/photonlib-python-examples/poseest/robot.py
:language: python
:lines: 44
```
Calling `update()` on your `PhotonPoseEstimator` will return an `EstimatedRobotPose`, which includes a `Pose3d` of the latest estimated pose (using the selected strategy) along with a `double` of the timestamp when the robot pose was estimated.
When taking in a result from a `PhotonCamera`, PhotonPoseEstimator offers nine possible "strategies" for calculating a pose from a pipeline result in the form of methods that you can call, following the pattern `estimate<strategy name>Pose`:
- Coprocessor MultiTag (`estimateCoprocMultiTagPose`)
- Calculates a new robot position estimate by combining all visible tag corners. Recommended for all teams as it will be the most accurate.
- Must configure the AprilTagFieldLayout properly in the UI, please see {ref}`here <docs/apriltag-pipelines/multitag:multitag localization>` for more information.
- Lowest Ambiguity (`estimateLowestAmbiguityPose`)
- Choose the Pose with the lowest ambiguity.
- Closest to Camera Height (`estimateClosestToCameraHeightPose`)
- Choose the Pose which is closest to the camera height.
- Closest to Reference Pose (`estimateClosestToReferencePose`)
- Choose the Pose which is closest to the pose that is passed into the function.
- Average Best Targets (`estimateAverageBestTargetsPose`)
- Choose the Pose which is the average of all the poses from each tag.
- roboRio MultiTag (`estimateRioMultiTagPose`)
- A slower, older version of Coprocessor MultiTag, not recommended for use.
- PnP Distance Trig Solve (`estimatePnpDistanceTrigSolvePose`)
- Use distance data from best visible tag to compute a Pose. This runs on the RoboRIO in order
to access the robot's yaw heading, and MUST have addHeadingData called every frame so heading
data is up-to-date. Based on a reference implementation by [FRC Team 6328 Mechanical Advantage](https://www.chiefdelphi.com/t/frc-6328-mechanical-advantage-2025-build-thread/477314/98).
- Constrained SolvePnP (`estimateConstrainedSolvepnpPose`)
- Solve a constrained version of the Perspective-n-Point problem with the robot's drivebase
flat on the floor. This computation takes place on the RoboRIO, and should not take more than 2ms.
This also requires addHeadingData to be called every frame so heading data is up to date.
Calling one of the `estimate<strategy>Pose()` methods on your `PhotonPoseEstimator` will return an `Optional<EstimatedRobotPose>`, which will be empty if there are no detected tags, not enough detected tags (for multi-tag strategies), missing data (typically heading data), or if the internal solvers failed (this is a rare scenario). `EstimatedRobotPose` includes a `Pose3d` of the latest estimated pose (using the selected strategy) along with a `double` of the timestamp when the robot pose was estimated. The recommended way to use the estimatePose methods is to
1. do estimation with one of MultiTag methods, check if the result is empty, then
2. fallback to single tag estimation using a method like `estimateLowestAmbiguityPose`.
```{eval-rst}
.. tab-set-code::
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/refs/heads/main/photonlib-java-examples/poseest/src/main/java/frc/robot/Vision.java
:language: java
:lines: 93-116
:lines: 91-94
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/refs/heads/main/photonlib-cpp-examples/poseest/src/main/include/Vision.h
:language: c++
:lines: 80-100
:lines: 79-82
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/refs/heads/main/photonlib-python-examples/poseest/robot.py
:language: python
:lines: 53
:lines: 52-54
```
You should be updating your [drivetrain pose estimator](https://docs.wpilib.org/en/latest/docs/software/advanced-controls/state-space/state-space-pose-estimators.html) with the result from the `PhotonPoseEstimator` every loop using `addVisionMeasurement()`.
For Constrained SolvePnP, it's recommended to do the previously mentioned steps, and then feed the pose (if it exists) into `estimateConstrainedSolvepnpPose`, and if the Constrained SolvePnP result is empty, simply feed the seed pose into your drivetrain pose estimator.
Once you have the `Optional<EstimatedRobotPose>`, you can check to see if there's an actual pose inside, and act accordingly. You should be updating your [drivetrain pose estimator](https://docs.wpilib.org/en/latest/docs/software/advanced-controls/state-space/state-space-pose-estimators.html) with the result from the `PhotonPoseEstimator` every loop using `addVisionMeasurement()`. For Java and C++, the examples pass a method from the drivetrain to a `Vision` object, with the parameter being called `estConsumer`. Python calls the drivetrain directly.
```{eval-rst}
.. tab-set-code::
@@ -146,7 +143,22 @@ You should be updating your [drivetrain pose estimator](https://docs.wpilib.org/
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/refs/heads/main/photonlib-python-examples/poseest/robot.py
:language: python
:lines: 54-57
:lines: 56-58
```
```{eval-rst}
.. tab-set-code::
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/refs/heads/main/photonlib-java-examples/poseest/src/main/java/frc/robot/Vision.java
:language: java
:lines: 89-115
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/refs/heads/main/photonlib-cpp-examples/poseest/src/main/include/Vision.h
:language: c++
:lines: 77-100
.. rli:: https://raw.githubusercontent.com/PhotonVision/photonvision/refs/heads/main/photonlib-python-examples/poseest/robot.py
:language: python
:lines: 51-54
```
## Complete Examples

View File

@@ -8,17 +8,17 @@ A `PhotonUtils` class with helpful common calculations is included within `Photo
```{eval-rst}
.. tab-set-code::
.. code-block:: Java
.. code-block:: java
// Calculate robot's field relative pose
if (aprilTagFieldLayout.getTagPose(target.getFiducialId()).isPresent()) {
Pose3d robotPose = PhotonUtils.estimateFieldToRobotAprilTag(target.getBestCameraToTarget(), aprilTagFieldLayout.getTagPose(target.getFiducialId()).get(), cameraToRobot);
}
.. code-block:: C++
.. code-block:: c++
//TODO
.. code-block:: Python
.. code-block:: python
# Coming Soon!
```
@@ -29,19 +29,19 @@ You can get your robot's `Pose2D` on the field using various camera data, target
```{eval-rst}
.. tab-set-code::
.. code-block:: Java
.. code-block:: java
// Calculate robot's field relative pose
Pose2D robotPose = PhotonUtils.estimateFieldToRobot(
kCameraHeight, kTargetHeight, kCameraPitch, kTargetPitch, Rotation2d.fromDegrees(-target.getYaw()), gyro.getRotation2d(), targetPose, cameraToRobot);
.. code-block:: C++
.. code-block:: c++
// Calculate robot's field relative pose
frc::Pose2D robotPose = photonlib::EstimateFieldToRobot(
kCameraHeight, kTargetHeight, kCameraPitch, kTargetPitch, frc::Rotation2d(units::degree_t(-target.GetYaw())), frc::Rotation2d(units::degree_t(gyro.GetRotation2d)), targetPose, cameraToRobot);
.. code-block:: Python
.. code-block:: python
# Coming Soon!
@@ -54,15 +54,15 @@ If your camera is at a fixed height on your robot and the height of the target i
```{eval-rst}
.. tab-set-code::
.. code-block:: Java
.. code-block:: java
// TODO
.. code-block:: C++
.. code-block:: c++
// TODO
.. code-block:: Python
.. code-block:: python
# Coming Soon!
@@ -78,15 +78,15 @@ The C++ version of PhotonLib uses the Units library. For more information, see [
```{eval-rst}
.. tab-set-code::
.. code-block:: Java
.. code-block:: java
double distanceToTarget = PhotonUtils.getDistanceToPose(robotPose, targetPose);
.. code-block:: C++
.. code-block:: c++
//TODO
.. code-block:: Python
.. code-block:: python
# Coming Soon!
```
@@ -97,19 +97,19 @@ You can get a [translation](https://docs.wpilib.org/en/latest/docs/software/adva
```{eval-rst}
.. tab-set-code::
.. code-block:: Java
.. code-block:: java
// Calculate a translation from the camera to the target.
Translation2d translation = PhotonUtils.estimateCameraToTargetTranslation(
distanceMeters, Rotation2d.fromDegrees(-target.getYaw()));
.. code-block:: C++
.. code-block:: c++
// Calculate a translation from the camera to the target.
frc::Translation2d translation = photonlib::PhotonUtils::EstimateCameraToTargetTranslation(
distance, frc::Rotation2d(units::degree_t(-target.GetYaw())));
.. code-block:: Python
.. code-block:: python
# Coming Soon!
@@ -125,14 +125,14 @@ We are negating the yaw from the camera from CV (computer vision) conventions to
```{eval-rst}
.. tab-set-code::
.. code-block:: Java
.. code-block:: java
Rotation2d targetYaw = PhotonUtils.getYawToPose(robotPose, targetPose);
.. code-block:: C++
.. code-block:: c++
//TODO
.. code-block:: Python
.. code-block:: python
# Coming Soon!
```

View File

@@ -8,9 +8,9 @@ If youre not using cameras in 3D mode, calibration is optional, but it can st
## Print the Calibration Target
- Downloaded from our [demo site](http://photonvision.global/#/cameras), or directly from your coprocessors cameras tab.
- Use the Charuco calibration board:
- Board Type: Charuco
- Downloaded from our [demo site](https://demo.photonvision.org/#/cameras), or directly from your coprocessors cameras tab.
- Use the ChArUco calibration board:
- Board Type: ChAruCo
- Tag Family: 4x4
- Pattern Spacing: 1.00in
- Marker Size: 0.75in

View File

@@ -0,0 +1,19 @@
# Camera Focusing
## Prepare Camera
:::{warning}
Refocusing your camera **will** make your calibration inaccurate, make sure to recalibrate after focusing.
:::
To ensure that your camera is focused properly, mount it to a secure surface and ensure it does not move drastically. Point your camera at a detailed surface like a calibration board, and make sure that it not too close to the camera.
## Using Focus Mode
:::{important}
When you enable Focus Mode, it will assign a *Score* to the current focus, this score depends on your environment and the lighting. This score cannot be compared to a focus score collected from other environments.
:::
- In the Cameras tab, turn on Focus Mode.
- Rotate the lens on your camera to try and get the focus score as high as possible.
- Once you cannot get a higher score, this indicates that your camera is fully focused and can be set in place using glue if desired.
```{image} images/focusModeExample.png
:scale: 50%
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 KiB

View File

@@ -9,5 +9,6 @@ wiring
networking
camera-matching
camera-calibration
camera-focusing
quick-configure
```

View File

@@ -75,15 +75,15 @@ If you would like to access your Ethernet-connected vision device from a compute
```{eval-rst}
.. tab-set-code::
.. code-block:: Java
.. code-block:: java
PortForwarder.add(5800, "photonvision.local", 5800);
.. code-block:: C++
.. code-block:: c++
wpi::PortForwarder::GetInstance().Add(5800, "photonvision.local", 5800);
.. code-block:: Python
.. code-block:: python
# Coming Soon!
```
@@ -99,3 +99,7 @@ The camera streams start at 1181 with two ports for each camera (ex. 1181 and 11
:::{warning}
If your camera stream isn't sent to the same port as it's originally found on, its stream will not be visible in the UI.
:::
## SSH Access
For advanced users, SSH access is available for coprocessors running PhotonVision. This allows you to perform advanced configurations and troubleshooting. The default credentials are: `photon:vision` for all devices using an image of `v2026.0.3` or later. The legacy credentials of `pi:raspberry` will still work, but it's recommended to switch to the new credentials as the old ones will be deprecated in a future release.

View File

@@ -1,33 +1,89 @@
# Quick Install
# Quick Installation Guide
## Install the latest image of photonvision for your coprocessor
- For the following supported coprocessors
- {ref}`Raspberry Pi 3,4,5 <docs/quick-start/quick-install:Raspberry Pi and Orange Pi Installation>`
- {ref}`Orange Pi 5, 5B, 5 Pro <docs/quick-start/quick-install:Raspberry Pi and Orange Pi Installation>`
- {ref}`Limelight 2, 2+, 3, 3G, 4 <docs/quick-start/quick-install:LimeLight Installation>`
- {ref}`Rubik Pi 3 <docs/quick-start/quick-install:Rubik Pi 3 Installation>`
- For the supported coprocessors
- RPI 3,4,5
- Orange Pi 5
- Limelight
For installing on non-supported devices {ref}`see. <docs/advanced-installation/sw_install/index:Software Installation>`
For installing on non-supported devices {ref}`see here. <docs/advanced-installation/sw_install/index:Software Installation>`
[Download the latest preconfigured image of photonvision for your coprocessor](https://github.com/PhotonVision/photonvision/releases/latest)
| Coprocessor | Image filename | Jar |
| -------------------- | ---------------------------------------------------- | ------------------------------------- |
| OrangePi 5 | photonvision-{version}-linuxarm64_orangepi5.img.xz | photonvision-{version}-linuxarm64.jar |
| Raspberry Pi 3, 4, 5 | photonvision-{version}-linuxarm64_RaspberryPi.img.xz | photonvision-{version}-linuxarm64.jar |
| Limelight 2 | photonvision-{version}-linuxarm64_limelight2.img.xz | photonvision-{version}-linuxarm64.jar |
| Limelight 3 | photonvision-{version}-linuxarm64_limelight3.img.xz | photonvision-{version}-linuxarm64.jar |
| Coprocessor | Image filename | Jar |
| -------------------- | -------------------------------------------------------- | ------------------------------------- |
| Raspberry Pi 3, 4, 5 | photonvision-{version}-linuxarm64_RaspberryPi.img.xz | photonvision-{version}-linuxarm64.jar |
| OrangePi 5 | photonvision-{version}-linuxarm64_orangepi5.img.xz | photonvision-{version}-linuxarm64.jar |
| OrangePi 5B | photonvision-{version}-linuxarm64_orangepi5b.img.xz | photonvision-{version}-linuxarm64.jar |
| OrangePi 5 Pro | photonvision-{version}-linuxarm64_orangepi5pro.img.xz | photonvision-{version}-linuxarm64.jar |
| Limelight 2 | photonvision-{version}-linuxarm64_limelight2.img.xz | photonvision-{version}-linuxarm64.jar |
| Limelight 3 | photonvision-{version}-linuxarm64_limelight3.img.xz | photonvision-{version}-linuxarm64.jar |
| Limelight 3G | photonvision-{version}-linuxarm64_limelight3G.img.xz | photonvision-{version}-linuxarm64.jar |
| Limelight 4 | photonvision-{version}-linuxarm64_limelight4.img.xz | photonvision-{version}-linuxarm64.jar |
| Rubik Pi 3 | photonvision-{version}-linuxarm64_rubikpi3.tar.xz | photonvision-{version}-linuxarm64.jar |
Unless otherwise noted in release notes or if updating from the prior years version, to update PhotonVision after the initial installation, use the offline update option in the settings page with the downloaded jar file from the latest release.
## Raspberry Pi and Orange Pi Installation
Use the [Raspberry Pi Imager](https://www.raspberrypi.com/software/) to flash the image onto the coprocessors microSD card. Select the downloaded `.img.xz` file, select your microSD card, and flash.
:::{warning}
Balena Etcher can also be used, but historically has had issues such as bootlooping (the system will repeatedly boot and restart) when imaging your device. Use at your own risk.
Avoid using Raspberry Pi Imager version 2.0.2 or later. Those versions fail to write the image to an SD card. Versions 2.0.0 and earlier write images successfully. [GitHub issue 1489](https://github.com/raspberrypi/rpi-imager/issues/1489) was created for this problem.
:::
Limelights have a different installation processes. Simply connect the limelight to your computer using the proper usb cable. Select the compute module. If it doesnt show up after 30s try using another USB port, initialization may take a while. If prompted, install the recommended missing drivers. Select the image, and flash.
:::{warning}
Balena Etcher has been recommended in the past, but should no longer be used due to instability and lack of ongoing support from developers.
:::
Unless otherwise noted in release notes or if updating from the prior years version, to update PhotonVision after the initial installation, use the offline update option in the settings page with the downloaded jar file from the latest release.
## Limelight Installation
In order to flash your Limelight you should follow the instructions on the Limelight documentation for the relevant version. Make sure to replace the Limelight OS image with the relevant PhotonVision image.
| Limelight Version | Limelight Documentation | PhotonVision Image | |
| ----------------- | ------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | --- |
| 2 | [Updating Limelight 2 OS](https://docs.limelightvision.io/docs/docs-limelight/getting-started/limelight-2#4-updating-limelightos) | photonvision-{version}-linuxarm64_limelight2.img.xz | |
| 3 | [Updating Limelight 3 OS](https://docs.limelightvision.io/docs/docs-limelight/getting-started/limelight-3#4-updating-limelightos) | photonvision-{version}-linuxarm64_limelight3.img.xz | |
| 3G | [Updating Limelight 3G OS](https://docs.limelightvision.io/docs/docs-limelight/getting-started/limelight-3g#4-updating-limelightos) | photonvision-{version}-linuxarm64_limelight3g.img.xz | |
| 4 | [Updating Limelight 4 OS](https://docs.limelightvision.io/docs/docs-limelight/getting-started/limelight-4#4-updating-limelightos) | photonvision-{version}-linuxarm64_limelight4.img.xz | |
:::{note}
Limelight 2, 2+, and 3 will need a [custom hardware config file](https://github.com/PhotonVision/photonvision/tree/main/docs/source/docs/advanced-installation/sw_install/files) for lighting to work. Currently only limelight 2 and 2+ files are available.
Limelight models will need a [custom hardware config file](https://github.com/PhotonVision/photonvision/tree/main/docs/source/docs/advanced-installation/sw_install/files) for LEDs or other hardware features to work.
:::
## Rubik Pi 3 Installation
:::{warning}
The Qualcomm Launcher caches files. If you flash multiple times, you may need to clear the cache by navigating to your temp directory, and deleting the `qualcomm-launcher` folder.
:::
To flash the Rubik Pi 3 coprocessor, it's necessary to use the [Qualcomm Launcher](https://softwarecenter.qualcomm.com/catalog/item/Qualcomm_Launcher). Upload a custom image by selecting the *Custom* option in the launcher. Choose the downloaded PhotonVision `.tar.xz` file and follow the prompts to complete the installation. It is recommended to skip the *Configure Login* process, as PhotonVision will handle the necessary settings.
### Alternative Flashing Method (advanced users only)
Follow the specific steps listed below from the [Rubik Pi 3 Docs](https://www.thundercomm.com/rubik-pi-3/en/docs/rubik-pi-3-user-manual/1.0.0-u/Troubleshooting/11.1.flash-over-android/).
[Step 1](https://www.thundercomm.com/rubik-pi-3/en/docs/rubik-pi-3-user-manual/1.0.0-u/Troubleshooting/11.1.flash-over-android/#1%EF%B8%8F%E2%83%A3-setup-qdl-tool) should be completed once per computer. [Step 2](https://www.thundercomm.com/rubik-pi-3/en/docs/rubik-pi-3-user-manual/1.0.0-u/Troubleshooting/11.1.flash-over-android/#2%EF%B8%8F%E2%83%A3-ufs-provisioning) and [Step 3](https://www.thundercomm.com/rubik-pi-3/en/docs/rubik-pi-3-user-manual/1.0.0-u/Troubleshooting/11.1.flash-over-android/#3%EF%B8%8F%E2%83%A3-flash-renesas-firmware) should be completed once per Rubik Pi 3.
After completing these steps, unzip your downloaded PhotonVision image to a folder. Navigate to that folder in your terminal or command prompt. After putting your Rubik Pi 3 into EDL mode, run the command below to flash PhotonVision. There is no need to complete any further steps from the Rubik Pi 3 documentation after running this command.
::::{tab-set}
:::{tab-item} Ubuntu host
```shell
qdl --storage ufs prog_firehose_ddr.elf rawprogram*.xml patch*.xml
```
:::
:::{tab-item} Windows host
```shell
QDL.exe prog_firehose_ddr.elf rawprogram0.xml rawprogram1.xml rawprogram2.xml rawprogram3.xml rawprogram4.xml rawprogram5.xml rawprogram6.xml patch1.xml patch2.xml patch3.xml patch4.xml patch5.xml patch6.xml
```
:::
:::{tab-item} macOS host
```shell
qdl prog_firehose_ddr.elf rawprogram*.xml patch*.xml
```
:::
::::

View File

@@ -2,15 +2,21 @@
## Coprocessor with regulator
1. **IT IS STRONGLY RECOMMENDED** to use one of the recommended power regulators to prevent vision from cutting out from voltage drops while operating the robot. We recommend wiring the regulator directly to the power header pins or using a locking USB C cable. In any case we recommend hot gluing the connector.
1. **IT IS STRONGLY RECOMMENDED** to use one of the recommended power regulators to prevent vision from cutting out from voltage drops while operating the robot. We recommend wiring the regulator directly to the power header pins using either of the two methods listed below or using a locking USB C cable.
* Method 1: Soldering to GPIO Header Pins
* Using 20 AWG or preferably 18 AWG wires, solder two wires from the regulator to the power header pins on the coprocessor and cover with heat-shrink tubing.
* Method 2: Using a Wire-to-Board Connector
* Using a wire-to-board connector with 20 AWG or preferably 18 AWG wires, connect two wires from the regulator to the power header pins on the coprocessor. To prevent the connector from becoming unseated, we recommend applying hot glue to the connector.
2. Run an ethernet cable from your Pi to your network switch / radio.
2. Run an ethernet cable from your coprocessor to your network switch / radio.
This diagram shows how to use the recommended regulator to power a coprocessor.
## Raspberry Pi and Orange Pi
This diagram shows how to use the recommended regulator to power a Raspberry Pi or Orange Pi.
::::{tab-set}
:::{tab-item} Orange Pi Zinc V USB C
:::{tab-item} Orange Pi 5 Zinc V USB C
```{image} images/OrangePiZincUSBC.png
:alt: Wiring the opi5 to the pdp using the Redux Robotics Zinc V and usb c
@@ -78,6 +84,12 @@ This diagram shows how to use the recommended regulator to power a coprocessor.
Pigtails can be purchased from many sources we recommend [(USB C)](https://ctr-electronics.com/products/usb-type-c-wire-breakout?_pos=19&_sid=bf06b6a6b&_ss=r) [(Micro USB)](https://ctr-electronics.com/products/usb-micro-power-wire-breakout?pr_prod_strat=e5_desc&pr_rec_id=10bf36ce7&pr_rec_pid=7863771070637&pr_ref_pid=7863771103405&pr_seq=uniform)
## RUBIK Pi
The RUBIK Pi has very different power requirements than the Orange Pi (or standard Raspberry Pi). In particular it requires 12V inputs, and has
a higher maximum power draw than those coprocessors. [First Rubik](https://first-rubik.github.io/docs/power/) has recommendations for both
on-robot and off-robot scenarios.
## Limelight
Follow the wiring instructions located in the [Limelight Documentation](https://docs.limelightvision.io/) for your Limelight model.

View File

@@ -54,7 +54,7 @@ A `VisionSystemSim` represents the simulated world for one or more cameras, and
```{eval-rst}
.. tab-set-code::
.. code-block:: Java
.. code-block:: java
// A vision system sim labelled as "main" in NetworkTables
VisionSystemSim visionSim = new VisionSystemSim("main");
@@ -67,7 +67,7 @@ Vision targets require a `TargetModel`, which describes the shape of the target.
```{eval-rst}
.. tab-set-code::
.. code-block:: Java
.. code-block:: java
// A 0.5 x 0.25 meter rectangular target
TargetModel targetModel = new TargetModel(0.5, 0.25);
@@ -78,7 +78,7 @@ These `TargetModel` are paired with a target pose to create a `VisionTargetSim`.
```{eval-rst}
.. tab-set-code::
.. code-block:: Java
.. code-block:: java
// The pose of where the target is on the field.
// Its rotation determines where "forward" or the target x-axis points.
@@ -100,7 +100,7 @@ For convenience, an `AprilTagFieldLayout` can also be added to automatically cre
```{eval-rst}
.. tab-set-code::
.. code-block:: Java
.. code-block:: java
// The layout of AprilTags which we want to add to the vision system
AprilTagFieldLayout tagLayout = AprilTagFieldLayout.loadFromResource(AprilTagFields.kDefaultField.m_resourceFile);
@@ -121,7 +121,7 @@ Before adding a simulated camera, we need to define its properties. This is done
```{eval-rst}
.. tab-set-code::
.. code-block:: Java
.. code-block:: java
// The simulated camera properties
SimCameraProperties cameraProp = new SimCameraProperties();
@@ -132,7 +132,7 @@ By default, this will create a 960 x 720 resolution camera with a 90 degree diag
```{eval-rst}
.. tab-set-code::
.. code-block:: Java
.. code-block:: java
// A 640 x 480 camera with a 100 degree diagonal FOV.
cameraProp.setCalibration(640, 480, Rotation2d.fromDegrees(100));
@@ -150,7 +150,7 @@ These properties are used in a `PhotonCameraSim`, which handles generating captu
```{eval-rst}
.. tab-set-code::
.. code-block:: Java
.. code-block:: java
// The PhotonCamera used in the real robot code.
PhotonCamera camera = new PhotonCamera("cameraName");
@@ -164,7 +164,7 @@ The `PhotonCameraSim` can now be added to the `VisionSystemSim`. We have to defi
```{eval-rst}
.. tab-set-code::
.. code-block:: Java
.. code-block:: java
// Our camera is mounted 0.1 meters forward and 0.5 meters up from the robot pose,
// (Robot pose is considered the center of rotation at the floor level, or Z = 0)
@@ -186,7 +186,7 @@ If the camera is mounted on a mobile mechanism (like a turret) this transform ca
```{eval-rst}
.. tab-set-code::
.. code-block:: Java
.. code-block:: java
// The turret the camera is mounted on is rotated 5 degrees
Rotation3d turretRotation = new Rotation3d(0, 0, Math.toRadians(5));
@@ -203,7 +203,7 @@ To update the `VisionSystemSim`, we simply have to pass in the simulated robot p
```{eval-rst}
.. tab-set-code::
.. code-block:: Java
.. code-block:: java
// Update with the simulated drivetrain pose. This should be called every loop in simulation.
visionSim.update(robotPoseMeters);
@@ -218,7 +218,7 @@ Each `VisionSystemSim` has its own built-in `Field2d` for displaying object pose
```{eval-rst}
.. tab-set-code::
.. code-block:: Java
.. code-block:: java
// Get the built-in Field2d used by this VisionSystemSim
visionSim.getDebugField();
@@ -233,7 +233,7 @@ A `PhotonCameraSim` can also draw and publish generated camera frames to a MJPEG
```{eval-rst}
.. tab-set-code::
.. code-block:: Java
.. code-block:: java
// Enable the raw and processed streams. These are enabled by default.
cameraSim.enableRawStream(true);

View File

@@ -7,4 +7,5 @@ common-errors
logging
camera-troubleshooting
networking-troubleshooting
unix-commands
```

View File

@@ -11,8 +11,8 @@ A few issues make up the majority of support requests. Run through this checklis
- Even if there's a switch between your laptop and coprocessor, you'll still want a radio or router in the loop somehow.
- The FRC radio is the _only_ router we will officially support due to the innumerable variations between routers.
- (Raspberry Pi, Orange Pi & Limelight only) have you flashed the correct image, and is it [up to date](https://github.com/PhotonVision/photonvision/releases/latest)?
- Is your robot code using a **2025** version of WPILib, and is your coprocessor using the most up to date **2025** release?
- 2022, 2023, 2024, and 2025 versions of either cannot be mix-and-matched!
- Is your robot code using a **2026** version of WPILib, and is your coprocessor using the most up to date **2026** release?
- 2022, 2023, 2024, 2025, and 2026 versions of either cannot be mix-and-matched!
- Your PhotonVision version can be checked on the settings tab.
- Is your team number correctly set on the settings tab?
@@ -30,7 +30,7 @@ Please check that:
1\. You don't have the NetworkTables Server on (toggleable in the settings tab). Turn this off when doing work on a robot.
2\. You have your team number set properly in the settings tab.
3\. Your camera name in the `PhotonCamera` constructor matches the name in the UI.
4\. You are using the 2025 version of WPILib and RoboRIO image.
4\. You are using the 2026 version of WPILib and RoboRIO image.
5\. Your robot is on.
If all of the above are met and you still have issues, feel free to {ref}`contact us <index:contact us>` and provide the following information:

View File

@@ -0,0 +1,134 @@
# Useful Unix Commands
## Networking
### SSH
[SSH (Secure Shell)](https://www.mankier.com/1/ssh) is used to securely connect from a local to a remote system (ex. from a laptop to a coprocessor). Unlike other commands on this page, ssh is not Unix specific and can be done on Windows and MacOS from their respective terminals.
:::{note}
You may see a warning similar to `The authenticity of host 'xxx' can't be established...` or `WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!`, in most cases this can be safely ignored if you have confirmed that you are connecting to the correct host over a secure connection, and the fingerprint will change when your operating system is reinstalled or PhotonVision's coprocessor image is re-flashed. This can also occur if you have multiple coprocessors with the same hostname on your network. You can read more about it [here](https://superuser.com/questions/421997/what-is-a-ssh-key-fingerprint-and-how-is-it-generated)
:::
Example:
```
ssh pi@hostname
```
For PhotonVision, the username will be `pi` and the password will be `raspberry`.
### ip
Run [ip address](https://www.mankier.com/8/ip) with your coprocessor connected to a monitor in order to see its IP address and other network configuration information.
Your output might look something like this:
```
2: end1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether de:9a:8f:7d:31:aa brd ff:ff:ff:ff:ff:ff
inet 10.88.47.12/24 brd 10.88.47.255 scope global dynamic noprefixroute end1
valid_lft 27367sec preferred_lft 27367sec
```
In this example, the numbers following `inet` (10.88.47.12) are your IP address.
### ping
[ping](https://www.mankier.com/8/ping) is a command-line utility used to test the reachability of a host on an IP network. It also measures the round-trip time for messages sent from the originating host to a destination computer. It can be used to determine if a network interface is available, which can be helpful when debugging.
## File Transfer
All files under `/opt/photonvision` are owned by the root user. This means that if you want to modify them, the commands to do so must be ran as sudo.
### SCP
[SCP (Secure Copy)](https://www.mankier.com/1/scp) is used to securely transfer files between local and remote systems.
Example:
```
scp [file] pi@hostname:/path/to/destination
```
### SFTP
[SFTP (SSH File Transfer Protocol)](https://www.mankier.com/1/sftp#) is another option for transferring files between local and remote systems.
### Filezilla
[Filezilla](https://filezilla-project.org/) is a GUI alternative to SCP and SFTP. It is available for Windows, MacOS, and Linux.
## Miscellaneous
### v4l2-ctl
[v4l2-ctl](https://www.mankier.com/1/v4l2-ctl) is a command-line tool for controlling video devices.
List available video devices (used to verify the device recognized a connected camera):
```
v4l2-ctl --list-devices
```
List supported formats and resolutions for a specific video device:
```
v4l2-ctl --list-formats-ext --device /path/to/video_device
```
List all video device's controls and their values:
```
v4l2-ctl --list-ctrls --device path/to/video_device
```
:::{note}
This command is especially useful in helping to debug when certain camera controls, like exposure, aren't behaving as expected. If you see an error in the logs similar to `WARNING 30: failed to set property [property name] (UsbCameraImpl.cpp:646)`, that means that PhotonVision is trying to use a control that doesn't exist or has a different name on your hardware. If you encounter this issue, please [file an issue](https://github.com/PhotonVision/photonvision/issues) with the necessary logs and output of the `v4l2-ctl --list-ctrls` command.
:::
### systemctl
[systemctl](https://www.mankier.com/1/systemctl) is a command that controls the `systemd` system and service manager.
Start PhotonVision:
```
systemctl start photonvision
```
Stop PhotonVision:
```
systemctl stop photonvision
```
Restart PhotonVision:
```
systemctl restart photonvision
```
Check the status of PhotonVision:
```
systemctl status photonvision
```
### journalctl
[journalctl](https://www.mankier.com/1/journalctl) is a command that queries the systemd journal, which is a logging system used by many Linux distributions.
View the PhotonVision logs:
```
journalctl --output cat -u photonvision
```
View the PhotonVision logs in real-time:
```
journalctl --output cat -u photonvision -f
```
`--output cat` is used to prevent journalctl from printing its own timestamps, because we log our own timestamps.

View File

@@ -127,6 +127,7 @@ docs/troubleshooting/index
docs/additional-resources/best-practices
docs/additional-resources/config
docs/additional-resources/nt-api
docs/benchmarks/index
docs/contributing/index
```

Binary file not shown.

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=permwrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

9
gradlew vendored
View File

@@ -86,8 +86,7 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -115,7 +114,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
@@ -206,7 +205,7 @@ fi
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
@@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.

4
gradlew.bat vendored
View File

@@ -70,11 +70,11 @@ goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell

View File

@@ -1,21 +0,0 @@
{
"root": true,
"extends": [
"plugin:vue/vue3-recommended",
"eslint:recommended",
"@vue/eslint-config-typescript",
"@vue/eslint-config-prettier/skip-formatting"
],
"rules": {
"quotes": ["error", "double"],
"comma-dangle": ["error", "never"],
"comma-spacing": ["error", { "before": false, "after": true }],
"semi": ["error", "always"],
"eol-last": "error",
"object-curly-spacing": ["error", "always"],
"quote-props": ["error", "as-needed"],
"no-case-declarations": "off",
"vue/require-default-prop": "off",
"vue/v-on-event-hyphenation": "off"
}
}

View File

@@ -1,8 +0,0 @@
node_modules
.DS_Store
dist
dist-ssr
# Editor directories and files
.idea
components.d.ts

View File

@@ -1 +1,2 @@
src/assets/fonts/PromptRegular.ts
pnpm-lock.yaml

View File

@@ -0,0 +1,38 @@
import pluginVue from "eslint-plugin-vue";
import { defineConfigWithVueTs, vueTsConfigs } from "@vue/eslint-config-typescript";
import skipFormattingConfig from "@vue/eslint-config-prettier/skip-formatting";
export default defineConfigWithVueTs(
pluginVue.configs["flat/recommended"],
vueTsConfigs.recommended,
skipFormattingConfig,
{
ignores: ["**/dist/**", "playwright-report"]
},
{
//extends: ["js/recommended"],
rules: {
quotes: ["error", "double"],
"comma-dangle": ["error", "never"],
"comma-spacing": [
"error",
{
before: false,
after: true
}
],
semi: ["error", "always"],
"eol-last": "error",
"object-curly-spacing": ["error", "always"],
"quote-props": ["error", "as-needed"],
"no-case-declarations": "off",
"vue/require-default-prop": "off",
"vue/v-on-event-hyphenation": "off",
"@typescript-eslint/no-explicit-any": "off",
"vue/valid-v-slot": ["error", { allowModifiers: true }]
}
}
);

File diff suppressed because it is too large Load Diff

View File

@@ -5,48 +5,47 @@
"type": "module",
"scripts": {
"dev": "vite",
"build": "run-p build-only",
"preview": "vite preview --port 4173",
"build-only": "vite build",
"build": "vite build",
"build-demo": "vite build --mode demo",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
"format": "prettier --write src/",
"lint-ci": "eslint . --max-warnings 0 --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
"format-ci": "prettier --check src/"
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix",
"format": "prettier --write src/ tests/",
"lint-ci": "eslint . --max-warnings 0 --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts",
"format-ci": "prettier --check src/",
"test": "playwright test",
"test-ui": "playwright test --ui",
"test-setup": "playwright install --with-deps"
},
"dependencies": {
"@fontsource/prompt": "^5.0.9",
"@fontsource/prompt": "^5.2.6",
"@mdi/font": "^7.4.47",
"@msgpack/msgpack": "^3.0.0-beta2",
"axios": "^1.6.3",
"jspdf": "^2.5.1",
"lodash": "^4.17.21",
"pinia": "^2.1.4",
"three": "^0.160.0",
"vue": "^2.7.14",
"vue-router": "^3.6.5",
"vue-virtual-scroll-list": "^2.3.5",
"vue2-helpers": "^2.1.1",
"vuetify": "^2.7.1"
"@msgpack/msgpack": "^3.1.2",
"axios": "^1.11.0",
"echarts": "^6.0.0",
"jspdf": "^3.0.1",
"pinia": "^3.0.2",
"three": "^0.178.0",
"vue": "^3.5.13",
"vue-router": "^4.5.1",
"vue3-virtual-scroll-list": "^0.2.1",
"vuetify": "^3.8.3"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.3.2",
"@types/node": "^18.19.45",
"@types/three": "^0.160.0",
"@vitejs/plugin-vue2": "^2.3.1",
"@vue/eslint-config-prettier": "^9.0.0",
"@vue/eslint-config-typescript": "^12.0.0",
"@vue/tsconfig": "^0.5.1",
"deepmerge": "^4.3.1",
"eslint": "^8.56.0",
"eslint-plugin-vue": "^9.19.2",
"npm-run-all": "^4.1.5",
"prettier": "3.2.2",
"sass": "~1.32",
"sass-loader": "^13.3.2",
"terser": "^5.14.2",
"typescript": "^5.3.3",
"unplugin-vue-components": "^0.26.0",
"vite": "^5.4.2"
"@eslint/js": "^9.31.0",
"@playwright/test": "^1.56.1",
"@types/node": "^22.15.14",
"@types/three": "^0.178.0",
"@vitejs/plugin-vue": "^6.0.0",
"@vue/eslint-config-prettier": "^10.2.0",
"@vue/eslint-config-typescript": "^14.5.0",
"@vue/tsconfig": "^0.7.0",
"eslint": "^9.31.0",
"eslint-plugin-vue": "^10.3.0",
"prettier": "^3.6.2",
"sass": "^1.89.2",
"typescript": "^5.8.3",
"vite": "^7.0.5",
"vite-plugin-vuetify": "^2.1.1"
}
}

View File

@@ -0,0 +1,83 @@
import { defineConfig, devices } from "@playwright/test";
import path from "path";
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// import dotenv from 'dotenv';
// import path from 'path';
// dotenv.config({ path: path.resolve(__dirname, '.env') });
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
globalSetup: "./tests/global-setup",
testDir: "./tests",
/* Run tests in files in parallel */
fullyParallel: false,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: 1,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: "html",
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('')`. */
baseURL: "http://localhost:5800",
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: "on-first-retry"
},
/* Configure projects for major browsers */
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] }
},
{
name: "firefox",
use: { ...devices["Desktop Firefox"] }
},
{
name: "webkit",
use: { ...devices["Desktop Safari"] }
},
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },
/* Test against branded browsers. */
{
name: "Microsoft Edge",
use: { ...devices["Desktop Edge"], channel: "msedge" }
}
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],
/* Run your local dev server before starting the tests */
webServer: {
command: process.platform == "win32" ? "" : "./" + "gradlew run",
url: "http://localhost:5800",
timeout: 300 * 1000,
reuseExistingServer: !process.env.CI,
cwd: path.normalize("../")
}
});

2914
photon-client/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -3,10 +3,12 @@ import { useStateStore } from "@/stores/StateStore";
import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore";
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
import { AutoReconnectingWebsocket } from "@/lib/AutoReconnectingWebsocket";
import { inject } from "vue";
import { inject, onBeforeMount } from "vue";
import PhotonSidebar from "@/components/app/photon-sidebar.vue";
import PhotonLogView from "@/components/app/photon-log-view.vue";
import PhotonErrorSnackbar from "@/components/app/photon-error-snackbar.vue";
import { useTheme } from "vuetify";
import { restoreThemeConfig } from "@/lib/ThemeManager";
const is_demo = import.meta.env.MODE === "demo";
if (!is_demo) {
@@ -50,6 +52,11 @@ if (!is_demo) {
);
useStateStore().$patch({ websocket: websocket });
}
const theme = useTheme();
onBeforeMount(() => {
restoreThemeConfig(theme);
});
</script>
<template>
@@ -58,9 +65,9 @@ if (!is_demo) {
<v-main>
<v-container class="main-container" fluid fill-height>
<v-layout>
<v-flex>
<v-container class="align-start pa-0 ma-0" fluid>
<router-view />
</v-flex>
</v-container>
</v-layout>
</v-container>
</v-main>
@@ -71,9 +78,11 @@ if (!is_demo) {
</template>
<style lang="scss">
@import "vuetify/src/styles/settings/_variables";
@use "@/assets/styles/settings";
@use "@/assets/styles/variables";
@use "sass:map";
@media #{map-get($display-breakpoints, 'md-and-down')} {
@media #{map.get(settings.$display-breakpoints, 'md-and-down')} {
html {
font-size: 14px !important;
}
@@ -85,24 +94,27 @@ if (!is_demo) {
}
::-webkit-scrollbar-track {
background: #232c37;
background: rgb(var(--v-theme-background));
}
::-webkit-scrollbar-thumb {
background-color: #ffd843;
background-color: rgb(var(--v-theme-accent));
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background-color: #e4c33c;
background-color: rgb(var(--v-theme-primary));
}
.main-container {
background-color: #232c37;
padding: 0 !important;
}
#title {
color: #ffd843;
.v-overlay__scrim {
background-color: #111111;
}
div.v-layout {
overflow: unset !important;
}
</style>

View File

@@ -1,4 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" width="200" height="200" style="shape-rendering: auto; display: block; background: rgb(0, 100, 146);" xmlns:xlink="http://www.w3.org/1999/xlink"><g><g transform="translate(80,50)">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" width="200" height="200" style="shape-rendering: auto; display: block; background: rgba(0, 100, 146, 0);" xmlns:xlink="http://www.w3.org/1999/xlink"><g><g transform="translate(80,50)">
<g transform="rotate(0)">
<circle fill-opacity="1" fill="#ffd943" r="6" cy="0" cx="0">
<animateTransform repeatCount="indefinite" dur="0.9345794392523364s" keyTimes="0;1" values="1.5 1.5;1 1" begin="-0.8177570093457943s" type="scale" attributeName="transform"></animateTransform>

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 508 507" version="1.1" xmlns="http://www.w3.org/2000/svg"
xml:space="preserve"
style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(1,0,0,1,-1279,0)">
<g id="PhotonVision-Icon-BG" transform="matrix(0.264062,0,0,0.469444,1279.5,0)">
<rect x="0" y="0" width="1920" height="1080" style="fill:none;"/>
<clipPath id="_clip1">
<rect x="0" y="0" width="1920" height="1080"/>
</clipPath>
<g clip-path="url(#_clip1)">
<g transform="matrix(4.27015,0,0,2.40196,-20444.8,-3235.56)">
<circle cx="5012.55" cy="1571.77" r="224.918" style="fill:rgb(0,100,146,0);"/>
</g>
<g transform="matrix(4.95901,0,0,2.78944,-13955,-10313.5)">
<path d="M3055.09,3977.51C3050.3,3984.25 3045,3990.56 3039.21,3996.35C2987.91,4047.65 2917.1,4038.77 2881.16,3976.54C2845.23,3914.3 2857.71,3822.13 2909.01,3770.83C2960.31,3719.53 3031.13,3728.41 3067.06,3790.64C3069.85,3795.48 3072.35,3800.49 3074.56,3805.67L3039.78,3811.64C3012.82,3769.64 2962.9,3764.58 2926.45,3801.04C2888.89,3838.59 2879.76,3906.07 2906.07,3951.63C2932.37,3997.19 2984.22,4003.69 3021.77,3966.14L3021.89,3966.01L3055.09,3977.51ZM3085.02,3841.47C3090.86,3875.56 3086.6,3912.35 3073.22,3944.57L3043.91,3934.42C3056.74,3907.59 3060.53,3875.54 3054.13,3846.78L3085.02,3841.47Z" style="fill:white;"/>
</g>
<g transform="matrix(4.95901,0,0,2.78944,-13955,-3827.86)">
<path d="M2906.78,1571.77L3111.02,1642.48L3116.61,1626.34L3147.2,1664.74L3099.42,1675.99L3105,1659.86L2910.03,1592.35C2908.25,1585.69 2907.18,1578.77 2906.78,1571.77ZM2917.45,1517.07L3114.77,1483.17L3111.88,1466.34L3157.2,1485.21L3120.78,1518.13L3117.88,1501.3L2910.22,1536.97C2911.99,1530.09 2914.41,1523.4 2917.45,1517.07Z" style="fill:rgb(255,216,67);"/>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -0,0 +1,4 @@
@forward "vuetify/settings" with (
$button-colored-disabled: false,
$button-disabled-opacity: 0.4
);

View File

@@ -1,29 +1,74 @@
@import "@fontsource/prompt";
@use "@fontsource/prompt";
$default-font: "Prompt", sans-serif !default;
$body-font-family: $default-font;
$heading-font-family: $default-font;
$body-background: #282c34;
$body-background: rgb(var(--v-theme-background));
body {
background: $body-background;
}
.v-application {
html {
font-family: $default-font !important;
}
.v-row-group__header {
background: #005281 !important;
}
.theme--dark.v-data-table
> .v-data-table__wrapper
.v-table
> .v-table__wrapper
> table
> tbody
> tr:hover:not(.v-data-table__expanded__content):not(.v-data-table__empty-wrapper) {
background: #005281 !important;
background: rgba(0, 0, 0, 0.2);
}
.v-card__title {
.v-card-title,
.v-dialog > .v-overlay__content > .v-card > .v-card-title,
.v-dialog > .v-overlay__content > form > .v-card > .v-card-title {
padding: 20px;
word-break: break-word !important;
}
.v-card-text,
.v-dialog > .v-overlay__content > .v-card > .v-card-text,
.v-dialog > .v-overlay__content > form > .v-card > .v-card-text {
font-size: 1rem;
padding: 20px;
}
.v-card-subtitle,
.v-dialog > .v-overlay__content > .v-card > .v-card-subtitle,
.v-dialog > .v-overlay__content > form > .v-card > .v-card-subtitle {
font-size: 1rem;
padding: 20px;
}
.v-field__input {
padding: 0px !important;
}
.pb-10px {
padding-bottom: 10px !important;
}
.pt-10px {
padding-top: 10px !important;
}
.pl-10px {
padding-left: 10px !important;
}
.pr-10px {
padding-right: 10px !important;
}
.pa-10px {
padding: 10px !important;
}
.rounded-12 {
border-radius: 12px;
}

View File

@@ -1,21 +1,38 @@
<script setup lang="ts">
import type { PhotonTarget } from "@/types/PhotonTrackingTypes";
// @ts-expect-error Intellisense says these conflict with the dynamic imports below
import type { Mesh, Object3D, PerspectiveCamera, Scene, WebGLRenderer } from "three";
// @ts-expect-error Intellisense says these conflict with the dynamic imports below
import type { TrackballControls } from "three/examples/jsm/controls/TrackballControls";
import { onBeforeUnmount, onMounted, watchEffect } from "vue";
import {
const {
ArrowHelper,
BoxGeometry,
CameraHelper,
Color,
ConeGeometry,
Group,
Mesh,
MeshNormalMaterial,
type Object3D,
PerspectiveCamera,
Quaternion,
Scene,
Vector3,
Scene,
WebGLRenderer
} from "three";
import { TrackballControls } from "three/examples/jsm/controls/TrackballControls";
} = await import("three");
const { TrackballControls } = await import("three/examples/jsm/controls/TrackballControls");
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
import { createPerspectiveCamera } from "@/lib/ThreeUtils";
import { useTheme } from "vuetify";
const theme = useTheme();
const calibrationCoeffs = useCameraSettingsStore().getCalibrationCoeffs(
useCameraSettingsStore().currentCameraSettings.validVideoFormats[
useCameraSettingsStore().currentPipelineSettings.cameraVideoModeIndex
].resolution
);
const props = defineProps<{
targets: PhotonTarget[];
@@ -27,17 +44,20 @@ let renderer: WebGLRenderer | undefined;
let controls: TrackballControls | undefined;
let previousTargets: Object3D[] = [];
const drawTargets = (targets: PhotonTarget[]) => {
const drawTargets = async (targets: PhotonTarget[]) => {
// Check here, since if we check in watchEffect this never gets called
if (scene === undefined || camera === undefined || renderer === undefined || controls === undefined) {
if (!scene || !camera || !renderer || !controls) {
return;
}
if (theme.global.name.value === "LightTheme") scene.background = new Color(0xa9a9a9);
else scene.background = new Color(0x000000);
scene.remove(...previousTargets);
previousTargets = [];
targets.forEach((target) => {
if (target.pose === undefined) return;
if (!target.pose) return;
const geometry = new BoxGeometry(0.3 / 5, 0.2, 0.2);
const material = new MeshNormalMaterial();
@@ -67,6 +87,22 @@ const drawTargets = (targets: PhotonTarget[]) => {
previousTargets.push(arrow);
});
if (calibrationCoeffs) {
// And show camera frustum
const calibCamera = await createPerspectiveCamera(
calibrationCoeffs.resolution,
calibrationCoeffs.cameraIntrinsics,
10
);
const helper = new CameraHelper(calibCamera);
const helperGroup = new Group();
helperGroup.add(helper);
// Flip to +Z forward
helperGroup.rotateX(-Math.PI / 2.0);
helperGroup.rotateY(-Math.PI / 2.0);
previousTargets.push(helperGroup);
}
if (previousTargets.length > 0) {
scene.add(...previousTargets);
}
@@ -75,7 +111,7 @@ const onWindowResize = () => {
const container = document.getElementById("container");
const canvas = document.getElementById("view");
if (container === null || canvas === null || camera === undefined || renderer === undefined) {
if (!container || !canvas || !camera || !renderer) {
return;
}
@@ -86,7 +122,7 @@ const onWindowResize = () => {
renderer.setSize(canvas.clientWidth, canvas.clientHeight);
};
const resetCamFirstPerson = () => {
if (scene === undefined || camera === undefined || controls === undefined) {
if (!scene || !camera || !controls) {
return;
}
@@ -100,7 +136,7 @@ const resetCamFirstPerson = () => {
}
};
const resetCamThirdPerson = () => {
if (scene === undefined || camera === undefined || controls === undefined) {
if (!scene || !camera || !controls) {
return;
}
@@ -114,15 +150,16 @@ const resetCamThirdPerson = () => {
}
};
onMounted(() => {
onMounted(async () => {
scene = new Scene();
camera = new PerspectiveCamera(75, 800 / 800, 0.1, 1000);
const canvas = document.getElementById("view");
if (canvas === null) return;
if (!canvas) return;
renderer = new WebGLRenderer({ canvas: canvas });
scene.background = new Color(0xa9a9a9);
if (theme.global.name.value === "LightTheme") scene.background = new Color(0xa9a9a9);
else scene.background = new Color(0x000000);
onWindowResize();
window.addEventListener("resize", onWindowResize);
@@ -166,7 +203,7 @@ onMounted(() => {
controls.update();
const animate = () => {
if (scene === undefined || camera === undefined || renderer === undefined || controls === undefined) {
if (!scene || !camera || !renderer || !controls) {
return;
}
@@ -189,18 +226,31 @@ watchEffect(() => {
<template>
<div id="container" style="width: 100%">
<v-row>
<v-col align-self="stretch" style="display: flex; justify-content: center">
<canvas id="view" />
<div class="d-flex flex-wrap pt-0 pb-2">
<v-col cols="12" md="6" class="pl-0">
<v-card-title class="pa-0"> Target Visualization </v-card-title>
</v-col>
</v-row>
<v-row style="margin-bottom: 24px">
<v-col style="display: flex; justify-content: center">
<v-btn color="secondary" @click="resetCamFirstPerson"> First Person </v-btn>
<v-col cols="6" md="3" class="d-flex align-center pt-0 pt-md-3 pl-6 pl-md-3">
<v-btn
style="width: 100%"
color="buttonActive"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
@click="resetCamFirstPerson"
>
First Person
</v-btn>
</v-col>
<v-col style="display: flex; justify-content: center">
<v-btn color="secondary" @click="resetCamThirdPerson"> Third Person </v-btn>
<v-col cols="6" md="3" class="d-flex align-center pt-0 pt-md-3 pr-0">
<v-btn
style="width: 100%"
color="buttonActive"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
@click="resetCamThirdPerson"
>
Third Person
</v-btn>
</v-col>
</v-row>
</div>
<canvas id="view" class="w-100" />
</div>
</template>

View File

@@ -0,0 +1,371 @@
<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref, watch, watchEffect, type Ref } from "vue";
const {
AmbientLight,
AxesHelper,
BoxGeometry,
CameraHelper,
Color,
ConeGeometry,
Group,
Mesh,
MeshNormalMaterial,
MeshPhongMaterial,
PerspectiveCamera,
Scene,
SphereGeometry,
WebGLRenderer
} = await import("three");
const { TrackballControls } = await import("three/examples/jsm/controls/TrackballControls");
import type { BoardObservation, CameraCalibrationResult } from "@/types/SettingTypes";
import axios from "axios";
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
import { useTheme } from "vuetify";
import { createPerspectiveCamera } from "@/lib/ThreeUtils";
const theme = useTheme();
const props = defineProps<{
cameraUniqueName: string;
resolution: { width: number; height: number };
title: string;
}>();
let scene: Scene | undefined;
let camera: PerspectiveCamera | undefined;
let renderer: WebGLRenderer | undefined;
let controls: TrackballControls | undefined;
const createChessboard = (obs: BoardObservation, cal: CameraCalibrationResult): Group => {
const group = new Group();
if (obs.locationInImageSpace.length === 0) return group;
// Add corner spheres
obs.locationInObjectSpace.forEach((corner, idx) => {
if (corner.x < 0 || corner.y < 0) return;
const isOutlier = !obs.cornersUsed[idx];
const color = isOutlier ? 0xff3333 : 0x33ff33;
const sphereGeom = new SphereGeometry(cal.calobjectSpacing / 8, 8, 8);
const sphereMat = new MeshPhongMaterial({
color: color,
opacity: 1,
transparent: !isOutlier
});
const sphere = new Mesh(sphereGeom, sphereMat);
sphere.position.set(corner.x, corner.y, corner.z);
group.add(sphere);
});
return group;
};
let previousTargets: Object3D[] = [];
let baseAspect: number | undefined;
const drawCalibration = async (cal: CameraCalibrationResult | null) => {
// Check here, since if we check in watchEffect this never gets called
if (!cal || !scene || !camera || !renderer || !controls) {
return;
}
scene.remove(...previousTargets);
previousTargets = [];
// Draw all chessboards with transparency
cal.observations.forEach((obs) => {
const pose = obs.optimisedCameraToObject;
// Create chessboard
const board = createChessboard(obs, cal);
board.userData.isCalibrationObject = true;
// Apply transform from camera to chessboard
const pos = pose.translation;
board.position.set(pos.x, pos.y, pos.z);
if (pose.rotation.quaternion) {
const q = pose.rotation.quaternion;
board.quaternion.set(q.X, q.Y, q.Z, q.W);
}
previousTargets.push(board);
});
// And show camera frustum
const calibCamera = await createPerspectiveCamera(props.resolution, cal.cameraIntrinsics);
const helper = new CameraHelper(calibCamera);
// Flip to +Z forward
const helperGroup = new Group();
helperGroup.add(helper);
helperGroup.rotateY(Math.PI);
previousTargets.push(helperGroup);
if (previousTargets.length > 0) {
scene.add(...previousTargets);
}
};
const calibrationData: Ref<CameraCalibrationResult | null> = ref(null);
const isLoading: Ref<boolean> = ref(true);
const error: Ref<string | null> = ref(null);
const fetchCalibrationData = async () => {
isLoading.value = true;
error.value = null;
try {
const response = await axios.get("/settings/camera/getCalibration", {
params: {
cameraUniqueName: props.cameraUniqueName,
width: props.resolution.width,
height: props.resolution.height
}
});
calibrationData.value = response.data;
} catch (err) {
console.error("Failed to fetch calibration data:", err);
error.value = "Failed to load calibration data";
} finally {
isLoading.value = false;
}
};
const onWindowResize = () => {
const container = document.getElementById("container");
const canvas = document.getElementById("view");
if (!container || !canvas || !camera || !renderer) {
return;
}
// Compute a concrete width from the container and derive height from a
// stable base aspect ratio (calculated on mount) to avoid feedback loops
// where updating canvas size changes container size while resizing
const width = Math.max(1, Math.floor(container.clientWidth));
let height: number;
if (baseAspect && baseAspect > 0) {
height = Math.max(1, Math.floor(width / baseAspect));
} else {
height = Math.max(1, Math.floor(container.clientHeight));
}
// Use updateStyle=false so Three.js does not write to canvas style,
// which can affect layout and re-trigger resize events
renderer.setSize(width, height, false);
camera.aspect = width / height;
camera.updateProjectionMatrix();
};
const resetCamFirstPerson = () => {
if (!scene || !camera || !controls) {
return;
}
controls.reset();
camera.position.set(0, 0, 0.05);
camera.up.set(0, -1, 0);
controls.target.set(0.0, 0.0, 1.0);
controls.update();
if (previousTargets.length > 0) {
scene.add(...previousTargets);
}
};
const resetCamThirdPerson = () => {
if (!scene || !camera || !controls) {
return;
}
controls.reset();
camera.position.set(-0.3, -0.2, -0.3);
camera.up.set(0, -1, 0);
controls.target.set(0.0, 0.0, 0.4);
controls.update();
if (previousTargets.length > 0) {
scene.add(...previousTargets);
}
};
let animationFrameId: number | null = null;
onMounted(async () => {
// Grab data first off
fetchCalibrationData();
scene = new Scene();
camera = new PerspectiveCamera(75, 800 / 800, 0.1, 1000);
const canvas = document.getElementById("view");
if (!canvas) return;
renderer = new WebGLRenderer({ canvas: canvas });
// Add lights
const ambientLight = new AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
if (theme.global.name.value === "LightTheme") scene.background = new Color(0xa9a9a9);
else scene.background = new Color(0x000000);
// Initialize a stable aspect ratio so subsequent resize events derive
// height from width, avoiding layout feedback during continuous resizing
try {
const initWidth = Math.max(1, Math.floor(document.getElementById("container")?.clientWidth || 1));
const initHeight = Math.max(1, Math.floor(document.getElementById("container")?.clientHeight || 1));
baseAspect = initWidth / Math.max(1, initHeight);
} catch {
baseAspect = undefined;
}
onWindowResize();
window.addEventListener("resize", onWindowResize);
const referenceFrameCues: Object3D[] = [];
// Draw the reference frame
referenceFrameCues.push(new AxesHelper(0.3));
// Draw the Camera Body
const camSize = 0.04;
const camBodyGeometry = new BoxGeometry(camSize, camSize, camSize);
const camLensGeometry = new ConeGeometry(camSize * 0.4, camSize * 0.8, 30);
const camMaterial = new MeshNormalMaterial();
const camBody = new Mesh(camBodyGeometry, camMaterial);
const camLens = new Mesh(camLensGeometry, camMaterial);
camBody.position.set(0, 0, 0);
camLens.rotateX(-Math.PI / 2);
camLens.position.set(0, 0, camSize * 0.8);
referenceFrameCues.push(camBody);
referenceFrameCues.push(camLens);
controls = new TrackballControls(camera, renderer.domElement);
controls.rotateSpeed = 1.0;
controls.zoomSpeed = 1.2;
controls.panSpeed = 0.8;
controls.noZoom = false;
controls.noPan = false;
controls.staticMoving = true;
controls.dynamicDampingFactor = 0.3;
scene.add(...referenceFrameCues);
resetCamThirdPerson();
controls.update();
const animate = () => {
if (!scene || !camera || !renderer || !controls) {
return;
}
animationFrameId = requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
};
animate();
});
const cleanup = () => {
window.removeEventListener("resize", onWindowResize);
if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
}
if (controls) {
controls.dispose();
}
if (renderer) {
renderer.dispose();
renderer.forceContextLoss();
}
if (scene) {
scene.traverse((object) => {
if (object instanceof Mesh) {
object.geometry?.dispose();
if (object.material) {
if (Array.isArray(object.material)) {
object.material.forEach((material) => material.dispose());
} else {
object.material.dispose();
}
}
}
});
}
scene = undefined;
camera = undefined;
renderer = undefined;
controls = undefined;
previousTargets = [];
};
onBeforeUnmount(cleanup);
// If hot-reloading, cleanup on hot reload
if (import.meta.hot) {
import.meta.hot.dispose(() => {
cleanup();
});
}
watchEffect(() => {
drawCalibration(calibrationData.value);
});
watch(
() => [
props.cameraUniqueName,
props.resolution.width,
props.resolution.height,
useCameraSettingsStore().getCalibrationCoeffs(props.resolution)
],
() => {
console.log("Camera or resolution changed, refetching calibration");
fetchCalibrationData();
}
);
</script>
<template>
<div style="width: 100%; height: 100%" class="d-flex flex-column">
<div class="d-flex flex-wrap pt-0 pb-2">
<v-col cols="12" md="6" class="pl-0">
<v-card-title class="pa-0">
{{ props.title }}
</v-card-title>
</v-col>
<v-col cols="6" md="3" class="d-flex align-center pt-0 pt-md-3 pl-6 pl-md-3">
<v-btn
style="width: 100%"
color="buttonActive"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
@click="resetCamFirstPerson"
>
First Person
</v-btn>
</v-col>
<v-col cols="6" md="3" class="d-flex align-center pt-0 pt-md-3 pr-0">
<v-btn
style="width: 100%"
color="buttonActive"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
@click="resetCamThirdPerson"
>
Third Person
</v-btn>
</v-col>
</div>
<div id="container" style="flex: 1 1 auto">
<canvas id="view" class="w-100 h-100" />
</div>
</div>
</template>

View File

@@ -2,10 +2,10 @@
import { computed, inject, ref, onBeforeUnmount } from "vue";
import { useStateStore } from "@/stores/StateStore";
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
import loadingImage from "@/assets/images/loading-transparent.svg";
import type { StyleValue } from "vue/types/jsx";
import type { StyleValue } from "vue";
import PvIcon from "@/components/common/pv-icon.vue";
import type { UiCameraConfiguration } from "@/types/SettingTypes";
import PvLoading from "@/components/common/pv-loading.vue";
const props = defineProps<{
streamType: "Raw" | "Processed";
@@ -92,7 +92,7 @@ onBeforeUnmount(() => {
<template>
<div class="stream-container" :style="containerStyle">
<img :src="loadingImage" class="stream-loading" />
<pv-loading class="stream-loading" />
<img
:id="id"
ref="mjpgStream"
@@ -105,18 +105,21 @@ onBeforeUnmount(() => {
/>
<div class="stream-overlay" :style="overlayStyle">
<pv-icon
color="primary"
icon-name="mdi-camera-image"
tooltip="Capture and save a frame of this stream"
class="ma-1 mr-2"
@click="handleCaptureClick"
/>
<pv-icon
color="primary"
icon-name="mdi-fullscreen"
tooltip="Open this stream in fullscreen"
class="ma-1 mr-2"
@click="handleFullscreenRequest"
/>
<pv-icon
color="primary"
icon-name="mdi-open-in-new"
tooltip="Open this stream in a new window"
class="ma-1 mr-2"

View File

@@ -5,12 +5,23 @@ import { useStateStore } from "@/stores/StateStore";
<template>
<v-snackbar
v-model="useStateStore().snackbarData.show"
top
location="top"
variant="elevated"
:color="useStateStore().snackbarData.color"
:timeout="useStateStore().snackbarData.timeout"
>
<p style="padding: 0; margin: 0; text-align: center">
{{ useStateStore().snackbarData.message }}
</p>
<v-progress-linear
v-if="useStateStore().snackbarData.progressBar != -1"
v-model="useStateStore().snackbarData.progressBar"
height="15"
:color="useStateStore().snackbarData.progressBarColor"
>
<template #default="{ value }">
<strong> {{ Math.ceil(value) }}% </strong>
</template>
</v-progress-linear>
</v-snackbar>
</template>

View File

@@ -7,13 +7,13 @@ const props = defineProps<{ source: LogMessage }>();
const logColorClass = computed<string>(() => {
switch (props.source.level) {
case LogLevel.ERROR:
return "red--text";
return "text-red";
case LogLevel.WARN:
return "yellow--text";
return "text-yellow";
case LogLevel.INFO:
return "light-blue--text";
return "text-light-blue";
case LogLevel.DEBUG:
return "white--text";
return "text-white";
}
return "";
});
@@ -22,3 +22,8 @@ const logColorClass = computed<string>(() => {
<template>
<div :class="logColorClass">[{{ source.timestamp.toTimeString().split(" ")[0] }}] {{ source.message }}</div>
</template>
<style scoped>
div {
white-space: pre-wrap;
}
</style>

View File

@@ -3,7 +3,7 @@ import { computed, inject, ref, watch } from "vue";
import { LogLevel, type LogMessage } from "@/types/SettingTypes";
import { useStateStore } from "@/stores/StateStore";
import LogEntry from "@/components/app/photon-log-entry.vue";
import VirtualList from "vue-virtual-scroll-list";
import VirtualList from "vue3-virtual-scroll-list";
const backendHost = inject<string>("backendHost");
@@ -74,15 +74,15 @@ document.addEventListener("keydown", (e) => {
<template>
<v-dialog v-model="useStateStore().showLogModal" width="1500" dark>
<v-card dark class="dialog-container pa-6" color="primary" flat>
<v-card class="dialog-container pa-5" color="surface" flat>
<!-- Logs header -->
<v-row class="no-gutters pb-3">
<v-row class="pb-3">
<v-col cols="4">
<v-card-title class="pa-0">Program Logs</v-card-title>
</v-col>
<v-col class="align-self-center pl-3" style="text-align: right">
<v-btn text color="white" @click="handleLogExport">
<v-icon left class="menu-icon"> mdi-download </v-icon>
<v-btn variant="text" color="white" @click="handleLogExport">
<v-icon start class="menu-icon" size="large"> mdi-download </v-icon>
<span class="menu-label">Download</span>
<!-- Special hidden link that gets 'clicked' when the user exports journalctl logs -->
@@ -94,12 +94,12 @@ document.addEventListener("keydown", (e) => {
target="_blank"
/>
</v-btn>
<v-btn text color="white" @click="handleLogClear">
<v-icon left class="menu-icon"> mdi-trash-can-outline </v-icon>
<v-btn variant="text" color="white" @click="handleLogClear">
<v-icon start class="menu-icon" size="large"> mdi-trash-can-outline </v-icon>
<span class="menu-label">Clear Client Logs</span>
</v-btn>
<v-btn text color="white" @click="() => (useStateStore().showLogModal = false)">
<v-icon left class="menu-icon"> mdi-close </v-icon>
<v-btn variant="text" color="white" @click="() => (useStateStore().showLogModal = false)">
<v-icon start class="menu-icon" size="large"> mdi-close </v-icon>
<span class="menu-label">Close</span>
</v-btn>
</v-col>
@@ -109,38 +109,33 @@ document.addEventListener("keydown", (e) => {
<div class="dialog-data">
<!-- Log view options -->
<v-row class="pt-4 pt-md-0 no-gutters">
<v-col cols="12" md="5" class="align-self-center">
<v-row no-gutters class="pt-4 pt-md-0" style="display: flex; justify-content: space-between">
<v-col cols="12" md="7" style="display: flex; align-items: center" class="pr-3">
<v-text-field
v-model="searchQuery"
dark
dense
density="compact"
clearable
hide-details="auto"
prepend-icon="mdi-magnify"
color="accent"
color="primary"
label="Search"
variant="underlined"
/>
</v-col>
<v-col cols="12" md="2" style="display: flex; align-items: center">
<input v-model="timeInput" type="time" step="1" class="white--text pl-0 pl-md-8" />
<v-btn icon class="ml-3" @click="timeInput = undefined">
<v-icon>mdi-close-circle-outline</v-icon>
<input v-model="timeInput" type="time" step="1" class="text-white pl-3" />
<v-btn icon variant="flat" @click="timeInput = undefined">
<v-icon>mdi-close</v-icon>
</v-btn>
</v-col>
<v-col cols="12" md="5" class="pr-3">
<v-row class="no-gutters">
<v-col v-for="level in [0, 1, 2, 3]" :key="level">
<v-row dense align="center">
<v-col cols="6" md="8" style="text-align: right">
{{ getLogLevelFromIndex(level) }}
</v-col>
<v-col cols="6" md="4">
<v-switch v-model="selectedLogLevels[level]" dark color="#ffd843" />
</v-col>
</v-row>
</v-col>
</v-row>
<v-col v-for="level in [0, 1, 2, 3]" :key="level" class="pr-3">
<div class="pb-0 pt-0" style="display: flex; align-items: center; flex: min-content">
{{ getLogLevelFromIndex(level)
}}<v-switch
v-model="selectedLogLevels[level]"
class="pl-2"
hide-details
color="rgb(var(--v-theme-primary))"
></v-switch>
</div>
</v-col>
</v-row>
@@ -178,9 +173,9 @@ document.addEventListener("keydown", (e) => {
.log-display {
/* Dialog data size - options */
height: calc(100% - 66px);
height: calc(100% - 56px);
padding: 10px;
background-color: #232c37 !important;
background-color: rgb(var(--v-theme-logsBackground)) !important;
border-radius: 5px;
}

View File

@@ -1,10 +1,11 @@
<script setup lang="ts">
import { computed, getCurrentInstance } from "vue";
import { computed } from "vue";
import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore";
import { useStateStore } from "@/stores/StateStore";
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
import { PlaceholderCameraSettings } from "@/types/SettingTypes";
import { useRoute } from "vue2-helpers/vue-router";
import { useRoute } from "vue-router";
import { useDisplay, useTheme } from "vuetify";
import { toggleTheme } from "@/lib/ThemeManager";
const compact = computed<boolean>({
get: () => {
@@ -14,128 +15,138 @@ const compact = computed<boolean>({
useStateStore().setSidebarFolded(val);
}
});
const { mdAndUp } = useDisplay();
// Vuetify2 doesn't yet support the useDisplay API so this is required to access the prop when using the Composition API
const mdAndUp = computed<boolean>(() => getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndUp || false);
const theme = useTheme();
const needsCamerasConfigured = computed<boolean>(() => {
return (
Object.values(useCameraSettingsStore().cameras).length === 0 ||
useCameraSettingsStore().cameras["PlaceHolder Name"] === PlaceholderCameraSettings
);
});
const renderCompact = computed<boolean>(() => compact.value || !mdAndUp.value);
</script>
<template>
<v-navigation-drawer dark app permanent :mini-variant="compact || !mdAndUp" color="primary">
<v-list>
<!-- List item for the heading; note that there are some tricks in setting padding and image width make things look right -->
<v-list-item :class="compact || !mdAndUp ? 'pr-0 pl-0' : ''" style="display: flex; justify-content: center">
<v-list-item-icon class="mr-0">
<img v-if="!(compact || !mdAndUp)" class="logo" src="@/assets/images/logoLarge.svg" alt="large logo" />
<img v-else class="logo" src="@/assets/images/logoSmall.svg" alt="small logo" />
</v-list-item-icon>
<v-navigation-drawer permanent :rail="renderCompact" color="sidebar">
<v-list nav color="primary">
<v-list-item class="pr-0 pl-0" style="display: flex; justify-content: center">
<template #prepend>
<img v-if="!renderCompact" class="logo" src="@/assets/images/logoLarge.svg" alt="large logo" />
<img v-else class="logo" src="@/assets/images/logoSmallTransparent.svg" alt="small logo" />
</template>
</v-list-item>
<v-list-item link to="/dashboard">
<v-list-item-icon>
<v-icon>mdi-view-dashboard</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>Dashboard</v-list-item-title>
</v-list-item-content>
<v-list-item link to="/dashboard" prepend-icon="mdi-view-dashboard">
<v-list-item-title>Dashboard</v-list-item-title>
</v-list-item>
<v-list-item link to="/settings">
<v-list-item-icon>
<v-icon>mdi-cog</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>Settings</v-list-item-title>
</v-list-item-content>
<v-list-item link to="/settings" prepend-icon="mdi-cog">
<v-list-item-title>Settings</v-list-item-title>
</v-list-item>
<v-list-item ref="camerasTabOpener" link to="/cameras">
<v-list-item-icon>
<v-icon>mdi-camera</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>Camera</v-list-item-title>
</v-list-item-content>
<v-list-item ref="camerasTabOpener" link to="/cameras" prepend-icon="mdi-camera">
<v-list-item-title>Camera</v-list-item-title>
</v-list-item>
<v-list-item
link
to="/cameraConfigs"
:class="{ cameraicon: needsCamerasConfigured && useRoute().path !== '/cameraConfigs' }"
:class="{
cameraicon: useCameraSettingsStore().needsCameraConfiguration && useRoute().path !== '/cameraConfigs'
}"
>
<v-list-item-icon>
<v-icon :class="{ 'red--text': needsCamerasConfigured }">mdi-swap-horizontal-bold</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title :class="{ 'red--text': needsCamerasConfigured }">Camera Matching</v-list-item-title>
</v-list-item-content>
<template #prepend>
<v-icon :class="{ 'text-red': useCameraSettingsStore().needsCameraConfiguration }"
>mdi-swap-horizontal-bold</v-icon
>
</template>
<v-list-item-title :class="{ 'text-red': useCameraSettingsStore().needsCameraConfiguration }"
>Camera Matching</v-list-item-title
>
</v-list-item>
<v-list-item link to="/docs">
<v-list-item-icon>
<v-icon>mdi-bookshelf</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>Documentation</v-list-item-title>
</v-list-item-content>
<v-list-item link to="/docs" prepend-icon="mdi-bookshelf">
<v-list-item-title>Documentation</v-list-item-title>
</v-list-item>
<div style="position: absolute; bottom: 0; left: 0">
<v-list-item v-if="mdAndUp" link @click="() => (compact = !compact)">
<v-list-item-icon>
<v-icon v-if="compact || !mdAndUp"> mdi-chevron-right </v-icon>
<v-icon v-else> mdi-chevron-left </v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>Compact Mode</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-list-item>
<v-list-item-icon>
<v-icon v-if="useSettingsStore().network.runNTServer"> mdi-server </v-icon>
<v-icon v-else-if="useStateStore().ntConnectionStatus.connected"> mdi-robot </v-icon>
<v-icon v-else style="border-radius: 100%"> mdi-robot-off </v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title v-if="useSettingsStore().network.runNTServer" class="text-wrap">
NetworkTables server running for
<span class="accent--text">{{ useStateStore().ntConnectionStatus.clients || 0 }}</span> clients
</v-list-item-title>
<v-list-item-title
v-else-if="useStateStore().ntConnectionStatus.connected && useStateStore().backendConnected"
class="text-wrap"
style="flex-direction: column; display: flex"
>
NetworkTables Server Connected!
<span class="accent--text">
{{ useStateStore().ntConnectionStatus.address }}
</span>
</v-list-item-title>
<v-list-item-title v-else class="text-wrap" style="flex-direction: column; display: flex">
Not connected to NetworkTables Server!
</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-list-item>
<v-list-item-icon>
<v-icon v-if="useStateStore().backendConnected"> mdi-server-network </v-icon>
<v-icon v-else style="border-radius: 100%"> mdi-server-network-off </v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title class="text-wrap">
{{ useStateStore().backendConnected ? "Backend connected" : "Trying to connect to backend" }}
</v-list-item-title>
</v-list-item-content>
</v-list-item>
</div>
</v-list>
<template #append>
<v-list nav>
<v-list-item
v-if="mdAndUp"
link
:prepend-icon="`mdi-chevron-${compact || !mdAndUp ? 'right' : 'left'}`"
@click="() => (compact = !compact)"
>
<v-list-item-title>Compact</v-list-item-title>
</v-list-item>
<v-list-item
link
:prepend-icon="theme.global.name.value === 'LightTheme' ? 'mdi-white-balance-sunny' : 'mdi-weather-night'"
@click="() => toggleTheme(theme)"
>
<v-list-item-title>Theme</v-list-item-title>
</v-list-item>
<v-list-item>
<template #prepend>
<v-icon
:icon="
useSettingsStore().network.runNTServer
? 'mdi-server'
: useStateStore().ntConnectionStatus.connected
? 'mdi-robot'
: 'mdi-robot-off'
"
:color="
useSettingsStore().network.runNTServer || useStateStore().ntConnectionStatus.connected
? '#00ff00'
: '#ff0000'
"
/>
</template>
<v-list-item-title v-if="useSettingsStore().network.runNTServer" v-show="!renderCompact" class="text-wrap">
NetworkTables server running for
<span class="text-primary">{{ useStateStore().ntConnectionStatus.clients || 0 }}</span> clients
</v-list-item-title>
<v-list-item-title
v-else-if="useStateStore().ntConnectionStatus.connected && useStateStore().backendConnected"
v-show="!renderCompact"
class="text-wrap"
style="flex-direction: column; display: flex"
>
NetworkTables Server Connected!
<span class="text-primary"> {{ useStateStore().ntConnectionStatus.address }} </span>
</v-list-item-title>
<v-list-item-title
v-else
v-show="!renderCompact"
class="text-wrap"
style="flex-direction: column; display: flex"
>
Not connected to NetworkTables Server!
</v-list-item-title>
</v-list-item>
<v-list-item>
<template #prepend>
<v-icon
:icon="useStateStore().backendConnected ? 'mdi-server-network' : 'mdi-server-network-off'"
:color="useStateStore().backendConnected ? '#00ff00' : '#ff0000'"
/>
</template>
<v-list-item-title v-show="!renderCompact" class="text-wrap">
{{ useStateStore().backendConnected ? "Backend connected" : "Trying to connect to backend..." }}
</v-list-item-title>
</v-list-item>
</v-list>
</template>
</v-navigation-drawer>
</template>
<style scoped>
.v-navigation-drawer {
border: none;
}
.v-navigation-drawer--rail {
border: none;
}
.v-list-item-title {
font-size: 1rem !important;
line-height: 1.2rem !important;
}
.logo {
width: 100%;
height: 70px;

View File

@@ -1,9 +1,7 @@
<script setup lang="ts">
import { computed, ref } from "vue";
import { computed, ref, watchEffect } from "vue";
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
import { CalibrationBoardTypes, CalibrationTagFamilies, type VideoFormat } from "@/types/SettingTypes";
import JsPDF from "jspdf";
import { font as PromptRegular } from "@/assets/fonts/PromptRegular";
import MonoLogo from "@/assets/images/logoMono.png";
import CharucoImage from "@/assets/images/ChArUco_Marker8x8.png";
import PvSlider from "@/components/common/pv-slider.vue";
@@ -15,6 +13,13 @@ import { WebsocketPipelineType } from "@/types/WebsocketDataTypes";
import { getResolutionString, resolutionsAreEqual } from "@/lib/PhotonUtils";
import CameraCalibrationInfoCard from "@/components/cameras/CameraCalibrationInfoCard.vue";
import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore";
import { useTheme } from "vuetify";
import TooltippedLabel from "@/components/common/pv-tooltipped-label.vue";
const PromptRegular = import("@/assets/fonts/PromptRegular");
const jspdf = import("jspdf");
const theme = useTheme();
const settingsValid = ref(true);
@@ -34,7 +39,8 @@ const getUniqueVideoFormatsByResolution = (): VideoFormat[] => {
if (!skip) {
const calib = useCameraSettingsStore().getCalibrationCoeffs(format.resolution);
if (calib !== undefined) {
// For each error, square it, sum the squares, and divide by total points N
// Mean overall reprojection error
// Calculated as average of each observation's mean error
if (calib.meanErrors.length)
format.mean = calib.meanErrors.reduce((a, b) => a + b, 0) / calib.meanErrors.length;
else format.mean = NaN;
@@ -75,6 +81,18 @@ const calibrationDivisors = computed(() =>
})
);
const uniqueVideoResolutionString = ref("");
// Use a watchEffect so the value is populated/reacts when the stores become available or update.
// This avoids trying to index into an array that may be empty during page reload.
watchEffect(() => {
const currentIndex = useCameraSettingsStore().currentVideoFormat.index ?? 0;
useStateStore().calibrationData.videoFormatIndex = currentIndex;
const names = useCameraSettingsStore().currentCameraSettings.validVideoFormats.map((f) =>
getResolutionString(f.resolution)
);
uniqueVideoResolutionString.value = names[currentIndex] ?? names[0] ?? "";
});
const squareSizeIn = ref(1);
const markerSizeIn = ref(0.75);
const patternWidth = ref(8);
@@ -82,16 +100,19 @@ const patternHeight = ref(8);
const boardType = ref<CalibrationBoardTypes>(CalibrationBoardTypes.Charuco);
const useOldPattern = ref(false);
const tagFamily = ref<CalibrationTagFamilies>(CalibrationTagFamilies.Dict_4X4_1000);
const requestedVideoFormatIndex = ref(0);
// Emperical testing - with stack size limit of 1MB, we can handle at -least- 700k points
const tooManyPoints = computed(
() => useStateStore().calibrationData.imageCount * patternWidth.value * patternHeight.value > 700000
);
const downloadCalibBoard = () => {
const doc = new JsPDF({ unit: "in", format: "letter" });
const downloadCalibBoard = async () => {
const { jsPDF } = await jspdf;
const { font } = await PromptRegular;
const doc = new jsPDF({ unit: "in", format: "letter" });
doc.addFileToVFS("Prompt-Regular.tff", PromptRegular);
doc.addFileToVFS("Prompt-Regular.tff", font);
doc.addFont("Prompt-Regular.tff", "Prompt-Regular", "normal");
doc.setFont("Prompt-Regular");
doc.setFontSize(12);
@@ -101,9 +122,8 @@ const downloadCalibBoard = () => {
switch (boardType.value) {
case CalibrationBoardTypes.Chessboard:
// eslint-disable-next-line no-case-declarations
const chessboardStartX = (paperWidth - patternWidth.value * squareSizeIn.value) / 2;
// eslint-disable-next-line no-case-declarations
const chessboardStartY = (paperHeight - patternWidth.value * squareSizeIn.value) / 2;
for (let squareY = 0; squareY < patternHeight.value; squareY++) {
@@ -124,15 +144,12 @@ const downloadCalibBoard = () => {
break;
case CalibrationBoardTypes.Charuco:
// Add pregenerated charuco
// Add pregenerated ChArUco
const charucoImage = new Image();
charucoImage.src = CharucoImage;
doc.addImage(charucoImage, "PNG", 0.25, 1.5, 8, 8);
doc.text("8 x 8 | 1in & 0.75in", paperWidth - 1, 1.0, {
maxWidth: (paperWidth - 2.0) / 2,
align: "right"
});
doc.text("8 x 8 | 1in & 0.75in", paperWidth - 1, 1.0, { maxWidth: (paperWidth - 2.0) / 2, align: "right" });
break;
}
@@ -176,12 +193,15 @@ const startCalibration = () => {
useCameraSettingsStore().currentCameraSettings.currentPipelineIndex = WebsocketPipelineType.Calib3d;
// isCalibrating.value = true;
calibCanceled.value = false;
requestedVideoFormatIndex.value = useStateStore().calibrationData.videoFormatIndex;
};
const showCalibEndDialog = ref(false);
const calibCanceled = ref(false);
const calibSuccess = ref<boolean | undefined>(undefined);
const calibEndpointFail = ref(false);
const endCalibration = () => {
calibSuccess.value = undefined;
calibEndpointFail.value = false;
if (!useStateStore().calibrationData.hasEnoughImages) {
calibCanceled.value = true;
@@ -194,7 +214,13 @@ const endCalibration = () => {
.then(() => {
calibSuccess.value = true;
})
.catch(() => {
.catch((e) => {
if (e.response) {
// Server returned a status code
} else if (e.request) {
// Something went wrong. Unsure if calibration actually worked
calibEndpointFail.value = true;
}
calibSuccess.value = false;
})
.finally(() => {
@@ -203,10 +229,10 @@ const endCalibration = () => {
});
};
let drawAllSnapshots = ref(true);
const drawAllSnapshots = ref(true);
let showCalDialog = ref(false);
let selectedVideoFormat = ref<VideoFormat | undefined>(undefined);
const showCalDialog = ref(false);
const selectedVideoFormat = ref<VideoFormat | undefined>(undefined);
const setSelectedVideoFormat = (format: VideoFormat) => {
selectedVideoFormat.value = format;
showCalDialog.value = true;
@@ -215,11 +241,18 @@ const setSelectedVideoFormat = (format: VideoFormat) => {
<template>
<div>
<v-card class="mb-3" color="primary" dark>
<v-card-title class="pa-6 pb-3">Camera Calibration</v-card-title>
<v-card-text v-show="!isCalibrating">
<v-card-subtitle class="pt-3 pl-2 pb-4 white--text">Current Calibration</v-card-subtitle>
<v-simple-table fixed-header height="100%" dense>
<v-card class="mb-3 rounded-12" color="surface" dark>
<v-card-title>Camera Calibration</v-card-title>
<v-card-text v-if="!isCalibrating" class="pb-0">
<div class="pb-3">
<tooltipped-label
label="Curent Calibrations"
icon="mdi-information"
location="top"
tooltip="Click on a resolution to view detailed calibration information and import/export a calibration."
/>
</div>
<v-table fixed-header height="100%" density="compact">
<thead>
<tr>
<th>Resolution</th>
@@ -227,287 +260,338 @@ const setSelectedVideoFormat = (format: VideoFormat) => {
<th>Horizontal FOV</th>
<th>Vertical FOV</th>
<th>Diagonal FOV</th>
<th>Info</th>
</tr>
</thead>
<tbody style="cursor: pointer">
<tr v-for="(value, index) in getUniqueVideoFormatsByResolution()" :key="index">
<td>{{ getResolutionString(value.resolution) }}</td>
<td>
{{ value.mean !== undefined ? (isNaN(value.mean) ? "Unknown" : value.mean.toFixed(2) + "px") : "-" }}
</td>
<td>{{ value.horizontalFOV !== undefined ? value.horizontalFOV.toFixed(2) + "°" : "-" }}</td>
<td>{{ value.verticalFOV !== undefined ? value.verticalFOV.toFixed(2) + "°" : "-" }}</td>
<td>{{ value.diagonalFOV !== undefined ? value.diagonalFOV.toFixed(2) + "°" : "-" }}</td>
<v-tooltip bottom>
<template #activator="{ on, attrs }">
<td v-bind="attrs" v-on="on" @click="setSelectedVideoFormat(value)">
<v-icon small class="mr-2">mdi-information</v-icon>
<v-tooltip
v-for="(value, index) in getUniqueVideoFormatsByResolution()"
:key="index"
transition=""
location="bottom"
:open-delay="100"
>
<template #activator="{ props }">
<tr :key="index" v-bind="props" @click="setSelectedVideoFormat(value)">
<td>{{ getResolutionString(value.resolution) }}</td>
<td>
{{
value.mean !== undefined ? (isNaN(value.mean) ? "Unknown" : value.mean.toFixed(2) + "px") : "-"
}}
</td>
</template>
<span>Click for more info on this calibration.</span>
</v-tooltip>
</tr>
<td>{{ value.horizontalFOV !== undefined ? value.horizontalFOV.toFixed(2) + "°" : "-" }}</td>
<td>{{ value.verticalFOV !== undefined ? value.verticalFOV.toFixed(2) + "°" : "-" }}</td>
<td>{{ value.diagonalFOV !== undefined ? value.diagonalFOV.toFixed(2) + "°" : "-" }}</td>
</tr>
</template>
<span>View calibration information</span>
</v-tooltip>
</tbody>
</v-simple-table>
</v-table>
</v-card-text>
<v-card-text v-if="useCameraSettingsStore().isConnected" class="d-flex flex-column pa-6 pt-0">
<v-card-subtitle v-show="!isCalibrating" class="pl-0 pb-3 pt-3 white--text"
>Configure New Calibration</v-card-subtitle
>
<v-form ref="form" v-model="settingsValid">
<!-- TODO: the default videoFormatIndex is 0, but the list of unique video mode indexes might not include 0. getUniqueVideoResolutionStrings indexing is also different from the normal video mode indexing -->
<pv-select
v-model="useStateStore().calibrationData.videoFormatIndex"
label="Resolution"
:select-cols="8"
:disabled="isCalibrating"
tooltip="Resolution to calibrate at (you will have to calibrate every resolution you use 3D mode on)"
:items="getUniqueVideoResolutionStrings()"
/>
<pv-select
v-show="isCalibrating && boardType != CalibrationBoardTypes.Charuco"
v-model="useCameraSettingsStore().currentPipelineSettings.streamingFrameDivisor"
label="Decimation"
tooltip="Resolution to which camera frames are downscaled for detection. Calibration still uses full-res"
:items="calibrationDivisors"
:select-cols="8"
@input="(v) => useCameraSettingsStore().changeCurrentPipelineSetting({ streamingFrameDivisor: +v }, false)"
/>
<pv-select
v-model="boardType"
label="Board Type"
tooltip="Calibration board pattern to use"
:select-cols="8"
:items="['Chessboard', 'Charuco']"
:disabled="isCalibrating"
/>
<pv-select
v-show="boardType == CalibrationBoardTypes.Charuco"
v-model="tagFamily"
label="Tag Family"
tooltip="Dictionary of aruco markers on the charuco board"
:select-cols="8"
:items="['Dict_4X4_1000', 'Dict_5X5_1000', 'Dict_6X6_1000', 'Dict_7X7_1000']"
:disabled="isCalibrating"
/>
<pv-number-input
v-model="squareSizeIn"
label="Pattern Spacing (in)"
tooltip="Spacing between pattern features in inches"
:disabled="isCalibrating"
:rules="[(v) => v > 0 || 'Size must be positive']"
:label-cols="4"
/>
<pv-number-input
v-show="boardType == CalibrationBoardTypes.Charuco"
v-model="markerSizeIn"
label="Marker Size (in)"
tooltip="Size of the tag markers in inches must be smaller than pattern spacing"
:disabled="isCalibrating"
:rules="[(v) => v > 0 || 'Size must be positive']"
:label-cols="4"
/>
<pv-number-input
v-model="patternWidth"
label="Board Width (squares)"
tooltip="Width of the board in dots or chessboard squares"
:disabled="isCalibrating"
:rules="[(v) => v >= 4 || 'Width must be at least 4']"
:label-cols="4"
/>
<pv-number-input
v-model="patternHeight"
label="Board Height (squares)"
tooltip="Height of the board in dots or chessboard squares"
:disabled="isCalibrating"
:rules="[(v) => v >= 4 || 'Height must be at least 4']"
:label-cols="4"
<v-card-text class="pt-0">
<div v-if="useCameraSettingsStore().isConnected" class="d-flex flex-column">
<v-card-subtitle v-if="!isCalibrating" class="pl-0 pb-3 pt-4 opacity-100"
>Configure New Calibration</v-card-subtitle
>
<v-form ref="form" v-model="settingsValid">
<pv-select
v-model="uniqueVideoResolutionString"
label="Resolution"
:select-cols="8"
:disabled="isCalibrating"
tooltip="Resolution to calibrate at (you will have to calibrate every resolution you use 3D mode on)"
:items="getUniqueVideoResolutionStrings()"
@update:model-value="
useStateStore().calibrationData.videoFormatIndex =
getUniqueVideoResolutionStrings().find((v) => v.value === $event)?.value || 0
"
/>
<pv-select
v-model="boardType"
label="Board Type"
tooltip="Calibration board pattern to use"
:select-cols="8"
:items="['Chessboard', 'ChArUco']"
:disabled="isCalibrating"
/>
<v-alert
v-if="boardType !== CalibrationBoardTypes.Charuco"
closable
density="compact"
variant="tonal"
color="warning"
icon="mdi-alert-box"
text="The usage of chessboards can result in bad calibration results if multiple
similar images are taken. We strongly recommend that teams use ChArUco boards instead!"
/>
<pv-select
v-if="boardType !== CalibrationBoardTypes.Charuco"
v-model="useCameraSettingsStore().currentPipelineSettings.streamingFrameDivisor"
label="Decimation"
tooltip="Resolution to which camera frames are downscaled for detection. Calibration still uses full-res"
:items="calibrationDivisors"
:select-cols="8"
@update:modelValue="
(v) => useCameraSettingsStore().changeCurrentPipelineSetting({ streamingFrameDivisor: +v }, false)
"
/>
<pv-select
v-if="boardType === CalibrationBoardTypes.Charuco"
v-model="tagFamily"
label="Tag Family"
tooltip="Dictionary of ArUco markers on the ChArUco board"
:select-cols="8"
:items="['Dict_4X4_1000', 'Dict_5X5_1000', 'Dict_6X6_1000', 'Dict_7X7_1000']"
:disabled="isCalibrating"
/>
<pv-number-input
v-model="squareSizeIn"
label="Pattern Spacing (in)"
tooltip="Spacing between pattern features in inches"
:disabled="isCalibrating"
:rules="[(v) => v > 0 || 'Size must be positive']"
:label-cols="4"
/>
<pv-number-input
v-if="boardType === CalibrationBoardTypes.Charuco"
v-model="markerSizeIn"
label="Marker Size (in)"
tooltip="Size of the tag markers in inches must be smaller than pattern spacing"
:disabled="isCalibrating"
:rules="[(v) => v > 0 || 'Size must be positive']"
:label-cols="4"
/>
<pv-number-input
v-model="patternWidth"
label="Board Width (squares)"
tooltip="Width of the board in dots or chessboard squares"
:disabled="isCalibrating"
:rules="[(v) => v >= 4 || 'Width must be at least 4']"
:label-cols="4"
/>
<pv-number-input
v-model="patternHeight"
label="Board Height (squares)"
tooltip="Height of the board in dots or chessboard squares"
:disabled="isCalibrating"
:rules="[(v) => v >= 4 || 'Height must be at least 4']"
:label-cols="4"
/>
<pv-switch
v-if="boardType === CalibrationBoardTypes.Charuco"
v-model="useOldPattern"
label="Old OpenCV Pattern"
:disabled="isCalibrating"
tooltip="If enabled, Photon will use the old OpenCV pattern for calibration."
:label-cols="4"
/>
</v-form>
</div>
<div v-if="isCalibrating">
<pv-switch
v-model="drawAllSnapshots"
label="Draw Collected Corners"
:switch-cols="8"
tooltip="Draw all snapshots"
@update:modelValue="
(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ drawAllSnapshots: args }, false)
"
/>
<pv-switch
v-show="boardType == CalibrationBoardTypes.Charuco"
v-model="useOldPattern"
label="Old OpenCV Pattern"
:disabled="isCalibrating"
tooltip="If enabled, Photon will use the old OpenCV pattern for calibration."
v-model="useCameraSettingsStore().currentPipelineSettings.cameraAutoExposure"
label="Auto Exposure"
:label-cols="4"
tooltip="Enables or Disables camera automatic adjustment for current lighting conditions"
@update:modelValue="
(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraAutoExposure: args }, false)
"
/>
<v-banner
v-if="useSettingsStore().general.mrCalWorking"
rounded
color="secondary"
text-color="white"
class="mt-3"
icon="mdi-alert-circle-outline"
<pv-slider
v-model="useCameraSettingsStore().currentPipelineSettings.cameraExposureRaw"
:disabled="useCameraSettingsStore().currentCameraSettings.pipelineSettings.cameraAutoExposure"
label="Exposure"
tooltip="Directly controls how long the camera shutter remains open. Units are dependant on the underlying driver."
:min="useCameraSettingsStore().minExposureRaw"
:max="useCameraSettingsStore().maxExposureRaw"
:slider-cols="8"
:step="1"
@update:modelValue="
(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraExposureRaw: args }, false)
"
/>
<pv-slider
v-model="useCameraSettingsStore().currentPipelineSettings.cameraBrightness"
label="Brightness"
:min="0"
:max="100"
:slider-cols="8"
@update:modelValue="
(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraBrightness: args }, false)
"
/>
<pv-slider
v-if="useCameraSettingsStore().currentPipelineSettings.cameraGain >= 0"
v-model="useCameraSettingsStore().currentPipelineSettings.cameraGain"
label="Camera Gain"
tooltip="Controls camera gain, similar to brightness"
:min="0"
:max="100"
:slider-cols="8"
@update:modelValue="
(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraGain: args }, false)
"
/>
<pv-slider
v-if="useCameraSettingsStore().currentPipelineSettings.cameraRedGain !== -1"
v-model="useCameraSettingsStore().currentPipelineSettings.cameraRedGain"
label="Red AWB Gain"
:min="0"
:max="100"
:slider-cols="8"
tooltip="Controls red automatic white balance gain, which affects how the camera captures colors in different conditions"
@update:modelValue="
(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraRedGain: args }, false)
"
/>
<pv-slider
v-if="useCameraSettingsStore().currentPipelineSettings.cameraBlueGain !== -1"
v-model="useCameraSettingsStore().currentPipelineSettings.cameraBlueGain"
label="Blue AWB Gain"
:min="0"
:max="100"
:slider-cols="8"
tooltip="Controls blue automatic white balance gain, which affects how the camera captures colors in different conditions"
@update:modelValue="
(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraBlueGain: args }, false)
"
/>
</div>
<v-alert
closable
density="compact"
class="mb-5"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'tonal'"
:color="useSettingsStore().general.mrCalWorking ? 'buttonPassive' : 'error'"
:icon="useSettingsStore().general.mrCalWorking ? 'mdi-check' : 'mdi-close'"
:text="
useSettingsStore().general.mrCalWorking
? 'Mrcal was successfully loaded and will be used!'
: 'MrCal failed to load, check journalctl logs for details.'
"
/>
<div v-if="isCalibrating" class="d-flex justify-center align-center pb-5">
<v-chip
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'tonal'"
label
:color="useStateStore().calibrationData.hasEnoughImages ? 'buttonPassive' : 'light-grey'"
>
Mrcal was successfully loaded and will be used!
</v-banner>
<v-banner v-else rounded color="error" text-color="white" class="mt-3" icon="mdi-alert-circle-outline">
MrCal JNI could not be loaded! Consult journalctl logs for additional details.
</v-banner>
</v-form>
</v-card-text>
<v-card-text v-if="isCalibrating" class="pa-6 pt-0">
<pv-switch
v-model="drawAllSnapshots"
label="Draw Collected Corners"
:switch-cols="8"
tooltip="Draw all snapshots"
@input="(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ drawAllSnapshots: args }, false)"
/>
<pv-switch
v-model="useCameraSettingsStore().currentPipelineSettings.cameraAutoExposure"
label="Auto Exposure"
:label-cols="4"
tooltip="Enables or Disables camera automatic adjustment for current lighting conditions"
@input="(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraAutoExposure: args }, false)"
/>
<pv-slider
v-model="useCameraSettingsStore().currentPipelineSettings.cameraExposureRaw"
:disabled="useCameraSettingsStore().currentCameraSettings.pipelineSettings.cameraAutoExposure"
label="Exposure"
tooltip="Directly controls how long the camera shutter remains open. Units are dependant on the underlying driver."
:min="useCameraSettingsStore().minExposureRaw"
:max="useCameraSettingsStore().maxExposureRaw"
:slider-cols="7"
:step="1"
@input="(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraExposureRaw: args }, false)"
/>
<pv-slider
v-model="useCameraSettingsStore().currentPipelineSettings.cameraBrightness"
label="Brightness"
:min="0"
:max="100"
:slider-cols="7"
@input="(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraBrightness: args }, false)"
/>
<pv-slider
v-if="useCameraSettingsStore().currentPipelineSettings.cameraGain >= 0"
v-model="useCameraSettingsStore().currentPipelineSettings.cameraGain"
label="Camera Gain"
tooltip="Controls camera gain, similar to brightness"
:min="0"
:max="100"
:slider-cols="7"
@input="(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraGain: args }, false)"
/>
<pv-slider
v-if="useCameraSettingsStore().currentPipelineSettings.cameraRedGain !== -1"
v-model="useCameraSettingsStore().currentPipelineSettings.cameraRedGain"
label="Red AWB Gain"
:min="0"
:max="100"
:slider-cols="7"
tooltip="Controls red automatic white balance gain, which affects how the camera captures colors in different conditions"
@input="(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraRedGain: args }, false)"
/>
<pv-slider
v-if="useCameraSettingsStore().currentPipelineSettings.cameraBlueGain !== -1"
v-model="useCameraSettingsStore().currentPipelineSettings.cameraBlueGain"
label="Blue AWB Gain"
:min="0"
:max="100"
:slider-cols="7"
tooltip="Controls blue automatic white balance gain, which affects how the camera captures colors in different conditions"
@input="(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraBlueGain: args }, false)"
/>
<v-banner
Snapshots: {{ useStateStore().calibrationData.imageCount }} of at least
{{ useStateStore().calibrationData.minimumImageCount }}
</v-chip>
</div>
<div>
<v-btn
color="buttonPassive"
size="small"
block
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:disabled="!settingsValid"
@click="downloadCalibBoard"
>
<v-icon start class="calib-btn-icon" size="large"> mdi-download </v-icon>
<span class="calib-btn-label">Generate Board</span>
</v-btn>
</div>
<v-alert
v-if="tooManyPoints"
rounded
class="mt-3"
class="mt-5"
color="error"
text-color="white"
density="compact"
text="Too many corners. Finish calibration now!"
icon="mdi-alert-circle-outline"
>
Too many corners. Finish calibration now!
</v-banner>
</v-card-text>
<v-card-text v-if="isCalibrating" class="d-flex justify-center align-center pa-6 pt-0">
<v-chip label :color="useStateStore().calibrationData.hasEnoughImages ? 'secondary' : 'gray'">
Snapshots: {{ useStateStore().calibrationData.imageCount }} of at least
{{ useStateStore().calibrationData.minimumImageCount }}
</v-chip>
</v-card-text>
<v-card-text class="d-flex pa-6 pt-0">
<v-col cols="6" class="pa-0 pr-2">
<v-btn
small
block
color="secondary"
:disabled="!settingsValid || tooManyPoints"
@click="isCalibrating ? useCameraSettingsStore().takeCalibrationSnapshot() : startCalibration()"
>
<v-icon left class="calib-btn-icon"> {{ isCalibrating ? "mdi-camera" : "mdi-flag-outline" }} </v-icon>
<span class="calib-btn-label">{{ isCalibrating ? "Take Snapshot" : "Start Calibration" }}</span>
</v-btn>
</v-col>
<v-col cols="6" class="pa-0 pl-2">
<v-btn
small
block
:color="useStateStore().calibrationData.hasEnoughImages ? 'accent' : 'error'"
:class="useStateStore().calibrationData.hasEnoughImages ? 'black--text' : 'white---text'"
:disabled="!isCalibrating || !settingsValid"
@click="endCalibration"
>
<v-icon left class="calib-btn-icon">
{{ useStateStore().calibrationData.hasEnoughImages ? "mdi-flag-checkered" : "mdi-flag-off-outline" }}
</v-icon>
<span class="calib-btn-label">{{
useStateStore().calibrationData.hasEnoughImages ? "Finish Calibration" : "Cancel Calibration"
}}</span>
</v-btn>
</v-col>
</v-card-text>
<v-card-text class="pa-6 pt-0">
<v-btn color="accent" small block outlined :disabled="!settingsValid" @click="downloadCalibBoard">
<v-icon left class="calib-btn-icon"> mdi-download </v-icon>
<span class="calib-btn-label">Generate Board</span>
</v-btn>
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'tonal'"
/>
<div class="d-flex pt-5">
<v-col cols="6" class="pa-0 pr-2">
<v-btn
size="small"
block
color="buttonActive"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:disabled="!settingsValid || tooManyPoints"
@click="isCalibrating ? useCameraSettingsStore().takeCalibrationSnapshot() : startCalibration()"
>
<v-icon start class="calib-btn-icon" size="large">
{{ isCalibrating ? "mdi-camera" : "mdi-flag-outline" }}
</v-icon>
<span class="calib-btn-label">{{ isCalibrating ? "Take Snapshot" : "Start Calibration" }}</span>
</v-btn>
</v-col>
<v-col cols="6" class="pa-0 pl-2">
<v-btn
size="small"
block
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:color="useStateStore().calibrationData.hasEnoughImages ? 'buttonActive' : 'error'"
:disabled="!isCalibrating || !settingsValid"
@click="endCalibration"
>
<v-icon start class="calib-btn-icon" size="large">
{{ useStateStore().calibrationData.hasEnoughImages ? "mdi-flag-checkered" : "mdi-flag-off-outline" }}
</v-icon>
<span class="calib-btn-label">{{
useStateStore().calibrationData.hasEnoughImages ? "Finish Calibration" : "Cancel Calibration"
}}</span>
</v-btn>
</v-col>
</div>
</v-card-text>
</v-card>
<v-dialog v-model="showCalibEndDialog" width="500px" :persistent="true">
<v-card color="primary" dark>
<v-card-title class="pb-8"> Camera Calibration </v-card-title>
<div class="ml-3">
<v-col style="text-align: center">
<template v-if="calibCanceled">
<v-icon color="blue" size="70"> mdi-cancel </v-icon>
<v-card-text
>Camera Calibration has been Canceled, the backend is attempting to cleanly cancel the calibration
process.</v-card-text
>
</template>
<!-- No result reported yet -->
<template v-else-if="calibSuccess === undefined">
<v-progress-circular indeterminate :size="70" :width="8" color="accent" />
<v-card-text>Camera is being calibrated. This process may take several minutes...</v-card-text>
</template>
<!-- Got positive result -->
<template v-else-if="calibSuccess">
<v-icon color="green" size="70"> mdi-check-bold </v-icon>
<v-card-text>
Camera has been successfully calibrated for
{{
getUniqueVideoResolutionStrings().find(
(v) => v.value === useStateStore().calibrationData.videoFormatIndex
)?.name
}}!
</v-card-text>
</template>
<template v-else>
<v-icon color="red" size="70"> mdi-close </v-icon>
<v-card-text
>Camera calibration failed! Make sure that the photos are taken such that the rainbow grid circles align
with the corners of the chessboard, and try again. More information is available in the program
logs.</v-card-text
>
</template>
</v-col>
<v-card color="surface" dark>
<v-card-title> Camera Calibration </v-card-title>
<div style="text-align: center">
<template v-if="calibCanceled">
<v-icon color="primary" size="70"> mdi-cancel </v-icon>
<v-card-text>
Camera calibration has been canceled. The backend is attempting to cleanly cancel the calibration process.
</v-card-text>
</template>
<!-- No result reported yet -->
<template v-else-if="calibSuccess === undefined">
<v-progress-circular indeterminate :size="70" :width="8" color="primary" />
<v-card-text>Camera is being calibrated. This process may take several minutes...</v-card-text>
</template>
<!-- Got positive result -->
<template v-else-if="calibSuccess">
<v-icon color="#00ff00" size="70"> mdi-check </v-icon>
<v-card-text>
Camera has been successfully calibrated for
{{
useCameraSettingsStore().currentCameraSettings.validVideoFormats.map((f) =>
getResolutionString(f.resolution)
)[requestedVideoFormatIndex]
}}!
</v-card-text>
</template>
<template v-else-if="calibEndpointFail">
<v-icon color="gray" size="70"> mdi-help-circle-outline </v-icon>
<v-card-text
>Unable to determine if calibration was successful. Refresh this page and manually check if calibration
was successful.</v-card-text
>
</template>
<template v-else>
<v-icon color="red" size="70"> mdi-close </v-icon>
<v-card-text>
Camera calibration failed! Make sure that the photos are taken such that the rainbow grid circles align
with the corners of the chessboard, and try again. More information is available in the program logs.
</v-card-text>
</template>
</div>
<v-card-actions>
<v-card-actions class="pa-5 pt-0">
<v-spacer />
<v-btn v-if="!isCalibrating" color="white" text @click="showCalibEndDialog = false"> OK </v-btn>
<v-btn v-if="!isCalibrating" color="white" variant="text" @click="showCalibEndDialog = false"> OK </v-btn>
</v-card-actions>
</v-card>
</v-dialog>
@@ -518,18 +602,21 @@ const setSelectedVideoFormat = (format: VideoFormat) => {
</template>
<style scoped lang="scss">
.v-data-table {
th {
text-align: center !important;
padding: 0 8px !important;
}
.v-table {
text-align: center;
width: 100%;
th,
td {
background-color: #006492 !important;
font-size: 1rem !important;
}
tbody :hover td {
background-color: #005281 !important;
cursor: pointer;
}
@@ -545,7 +632,7 @@ const setSelectedVideoFormat = (format: VideoFormat) => {
}
::-webkit-scrollbar-thumb {
background-color: #ffd843;
background-color: rgb(var(--v-theme-accent));
border-radius: 10px;
}
}

View File

@@ -1,14 +1,28 @@
<script setup lang="ts">
import PhotonCalibrationVisualizer from "@/components/app/photon-calibration-visualizer.vue";
import type { CameraCalibrationResult, VideoFormat } from "@/types/SettingTypes";
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
import { useStateStore } from "@/stores/StateStore";
import { computed, inject, ref } from "vue";
import { getResolutionString, parseJsonFile } from "@/lib/PhotonUtils";
import { axiosPost, getResolutionString, parseJsonFile } from "@/lib/PhotonUtils";
import { useTheme } from "vuetify";
import PvDeleteModal from "@/components/common/pv-delete-modal.vue";
const theme = useTheme();
const props = defineProps<{
videoFormat: VideoFormat;
}>();
const confirmRemoveDialog = ref({ show: false, vf: props.videoFormat as VideoFormat });
const removeCalibration = (vf: VideoFormat) => {
axiosPost("/calibration/remove", "delete a camera calibration", {
cameraUniqueName: useCameraSettingsStore().currentCameraSettings.uniqueName,
width: vf.resolution.width,
height: vf.resolution.height
});
};
const exportCalibration = ref();
const openExportCalibrationPrompt = () => {
exportCalibration.value.click();
@@ -65,8 +79,10 @@ const importCalibration = async () => {
};
interface ObservationDetails {
mean: number;
index: number;
mean: number;
numOutliers: number;
numMissing: number;
}
const currentCalibrationCoeffs = computed<CameraCalibrationResult | undefined>(() =>
@@ -78,7 +94,9 @@ const getObservationDetails = (): ObservationDetails[] | undefined => {
return coefficients?.meanErrors.map((m, i) => ({
index: i,
mean: parseFloat(m.toFixed(2))
mean: parseFloat(m.toFixed(2)),
numOutliers: coefficients.numOutliers[i],
numMissing: coefficients.numMissing[i]
}));
};
@@ -87,196 +105,251 @@ const exportCalibrationURL = computed<string>(() =>
);
const calibrationImageURL = (index: number) =>
useCameraSettingsStore().getCalImageUrl(inject<string>("backendHost") as string, props.videoFormat.resolution, index);
const tab = ref("details");
const viewingImg = ref(0);
</script>
<template>
<v-card color="primary" dark>
<div class="d-flex flex-wrap pr-md-3">
<v-col cols="12" md="6">
<v-card-title class="pl-3 pb-0 pb-md-4"> Calibration Details </v-card-title>
<v-card color="surface" dark>
<v-card-title class="pb-2">
<div class="d-flex flex-wrap">
<v-col cols="12" md="6" class="pa-0">
<v-card-title class="pa-0"> Calibration Details </v-card-title>
</v-col>
<v-col cols="6" md="3" class="d-flex align-center pt-0 pb-0 pl-0">
<v-btn
color="buttonPassive"
style="width: 100%"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
@click="openUploadPhotonCalibJsonPrompt"
>
<v-icon start size="large"> mdi-import</v-icon>
<span>Import</span>
</v-btn>
<input
ref="importCalibrationFromPhotonJson"
type="file"
accept=".json"
style="display: none"
@change="importCalibration"
/>
</v-col>
<v-col cols="6" md="3" class="d-flex align-center pt-0 pb-0 pr-0">
<v-btn
color="buttonPassive"
:disabled="!currentCalibrationCoeffs"
style="width: 100%"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
@click="openExportCalibrationPrompt"
>
<v-icon start size="large">mdi-export</v-icon>
<span>Export</span>
</v-btn>
<a
ref="exportCalibration"
style="color: black; text-decoration: none; display: none"
:href="exportCalibrationURL"
target="_blank"
/>
</v-col>
</div>
</v-card-title>
<v-card-text class="d-flex flex-row pt-0">
<v-col cols="4" class="pa-0">
<v-tabs v-model="tab" grow bg-color="surface" height="48" slider-color="buttonActive">
<v-tab key="details" value="details">Details</v-tab>
<v-tab key="observations" value="observations">Observations</v-tab>
</v-tabs>
<v-tabs-window v-model="tab" class="pt-3">
<v-tabs-window-item key="details" value="details">
<v-table style="width: 100%" density="compact">
<template #default>
<tbody>
<tr>
<td>Camera</td>
<td>
{{ useCameraSettingsStore().currentCameraName }}
</td>
</tr>
<tr>
<td>Resolution</td>
<td>
{{ getResolutionString(videoFormat.resolution) }}
</td>
</tr>
<tr>
<td>Fx</td>
<td>
{{
useCameraSettingsStore()
.getCalibrationCoeffs(props.videoFormat.resolution)
?.cameraIntrinsics.data[0].toFixed(2) || 0.0
}}
px
</td>
</tr>
<tr>
<td>Fy</td>
<td>
{{
useCameraSettingsStore()
.getCalibrationCoeffs(props.videoFormat.resolution)
?.cameraIntrinsics.data[4].toFixed(2) || 0.0
}}
px
</td>
</tr>
<tr>
<td>Cx</td>
<td>
{{
useCameraSettingsStore()
.getCalibrationCoeffs(props.videoFormat.resolution)
?.cameraIntrinsics.data[2].toFixed(2) || 0.0
}}
px
</td>
</tr>
<tr>
<td>Cy</td>
<td>
{{
useCameraSettingsStore()
.getCalibrationCoeffs(props.videoFormat.resolution)
?.cameraIntrinsics.data[5].toFixed(2) || 0.0
}}
px
</td>
</tr>
<tr>
<td>Distortion</td>
<td>
{{
useCameraSettingsStore()
.getCalibrationCoeffs(props.videoFormat.resolution)
?.distCoeffs.data.map((it) => parseFloat(it.toFixed(3))) || []
}}
</td>
</tr>
<tr>
<td>Mean Err</td>
<td>
{{
videoFormat.mean !== undefined
? isNaN(videoFormat.mean)
? "NaN"
: videoFormat.mean.toFixed(2) + "px"
: "-"
}}
</td>
</tr>
<tr>
<td>Horizontal FOV</td>
<td>
{{ videoFormat.horizontalFOV !== undefined ? videoFormat.horizontalFOV.toFixed(2) + "°" : "-" }}
</td>
</tr>
<tr>
<td>Vertical FOV</td>
<td>
{{ videoFormat.verticalFOV !== undefined ? videoFormat.verticalFOV.toFixed(2) + "°" : "-" }}
</td>
</tr>
<tr>
<td>Diagonal FOV</td>
<td>
{{ videoFormat.diagonalFOV !== undefined ? videoFormat.diagonalFOV.toFixed(2) + "°" : "-" }}
</td>
</tr>
<!-- Board warp, only shown for mrcal-calibrated cameras -->
<tr v-if="currentCalibrationCoeffs?.calobjectWarp?.length === 2">
<td>Board warp, X/Y</td>
<td>
{{
currentCalibrationCoeffs?.calobjectWarp?.map((it) => (it * 1000).toFixed(2) + " mm").join(" / ")
}}
</td>
</tr>
</tbody>
</template>
</v-table>
</v-tabs-window-item>
<v-tabs-window-item key="observations" value="observations">
<v-data-table
id="observations-table"
items-per-page-text="Page size:"
density="compact"
style="width: 100%"
:headers="[
{ title: 'Id', key: 'index' },
{ title: 'Mean Reprojection Error', key: 'mean' }
]"
:items="getObservationDetails()"
item-value="index"
show-expand
>
<template #item.data-table-expand="{ internalItem }">
<v-btn
class="text-none"
size="small"
variant="text"
slim
rounded
@click="viewingImg = internalItem.index"
>
<v-icon
size="large"
:color="viewingImg === internalItem.index ? 'buttonActive' : 'rgba(255, 255, 255, 0.7)'"
>mdi-eye</v-icon
>
</v-btn>
</template>
</v-data-table>
</v-tabs-window-item>
</v-tabs-window>
</v-col>
<v-col cols="6" md="3" class="d-flex align-center pt-0 pt-md-3 pl-6 pl-md-3">
<v-btn color="secondary" style="width: 100%" @click="openUploadPhotonCalibJsonPrompt">
<v-icon left> mdi-import</v-icon>
<span>Import</span>
</v-btn>
<input
ref="importCalibrationFromPhotonJson"
type="file"
accept=".json"
style="display: none"
@change="importCalibration"
/>
<v-col cols="8" class="pa-0 pl-6">
<v-card-text class="pa-0 fill-height d-flex justify-center align-center">
<div v-if="!currentCalibrationCoeffs">
<v-alert
class="pt-3 pb-3"
color="primary"
text="The selected video format has not been calibrated."
icon="mdi-alert-circle-outline"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'tonal'"
/>
</div>
<Suspense v-else-if="tab === 'details'">
<!-- Allows us to import three js when it's actually needed -->
<PhotonCalibrationVisualizer
:camera-unique-name="useCameraSettingsStore().currentCameraSettings.uniqueName"
:resolution="props.videoFormat.resolution"
title="Camera to Board Transforms"
/>
<template #fallback> Loading... </template>
</Suspense>
<div v-else style="display: flex; justify-content: center; width: 100%">
<img :src="calibrationImageURL(viewingImg)" alt="observation image" class="snapshot-preview pt-2 pb-2" />
</div>
</v-card-text>
</v-col>
<v-col cols="6" md="3" class="d-flex align-center pt-0 pt-md-3 pr-6 pr-md-3">
<v-btn
color="secondary"
:disabled="!currentCalibrationCoeffs"
style="width: 100%"
@click="openExportCalibrationPrompt"
>
<v-icon left>mdi-export</v-icon>
<span>Export</span>
</v-btn>
<a
ref="exportCalibration"
style="color: black; text-decoration: none; display: none"
:href="exportCalibrationURL"
target="_blank"
/>
</v-col>
</div>
<v-card-title class="pt-0 pb-3"
>{{ useCameraSettingsStore().currentCameraName }}@{{ getResolutionString(videoFormat.resolution) }}</v-card-title
>
<v-card-text v-if="!currentCalibrationCoeffs">
<v-banner rounded color="secondary" text-color="white" class="mt-3" icon="mdi-alert-circle-outline">
The selected video format has not been calibrated.
</v-banner>
</v-card-text>
<v-card-text>
<v-simple-table dense style="width: 100%">
<template #default>
<thead>
<tr>
<th class="text-left">Name</th>
<th class="text-left">Value</th>
</tr>
</thead>
<tbody>
<tr>
<td>Fx</td>
<td>
{{
useCameraSettingsStore()
.getCalibrationCoeffs(props.videoFormat.resolution)
?.cameraIntrinsics.data[0].toFixed(2) || 0.0
}}
mm
</td>
</tr>
<tr>
<td>Fy</td>
<td>
{{
useCameraSettingsStore()
.getCalibrationCoeffs(props.videoFormat.resolution)
?.cameraIntrinsics.data[4].toFixed(2) || 0.0
}}
mm
</td>
</tr>
<tr>
<td>Cx</td>
<td>
{{
useCameraSettingsStore()
.getCalibrationCoeffs(props.videoFormat.resolution)
?.cameraIntrinsics.data[2].toFixed(2) || 0.0
}}
px
</td>
</tr>
<tr>
<td>Cy</td>
<td>
{{
useCameraSettingsStore()
.getCalibrationCoeffs(props.videoFormat.resolution)
?.cameraIntrinsics.data[5].toFixed(2) || 0.0
}}
px
</td>
</tr>
<tr>
<td>Distortion</td>
<td>
{{
useCameraSettingsStore()
.getCalibrationCoeffs(props.videoFormat.resolution)
?.distCoeffs.data.map((it) => parseFloat(it.toFixed(3))) || []
}}
</td>
</tr>
<tr>
<td>Mean Err</td>
<td>
{{
videoFormat.mean !== undefined
? isNaN(videoFormat.mean)
? "NaN"
: videoFormat.mean.toFixed(2) + "px"
: "-"
}}
</td>
</tr>
<tr>
<td>Horizontal FOV</td>
<td>
{{ videoFormat.horizontalFOV !== undefined ? videoFormat.horizontalFOV.toFixed(2) + "°" : "-" }}
</td>
</tr>
<tr>
<td>Vertical FOV</td>
<td>{{ videoFormat.verticalFOV !== undefined ? videoFormat.verticalFOV.toFixed(2) + "°" : "-" }}</td>
</tr>
<tr>
<td>Diagonal FOV</td>
<td>{{ videoFormat.diagonalFOV !== undefined ? videoFormat.diagonalFOV.toFixed(2) + "°" : "-" }}</td>
</tr>
<!-- Board warp, only shown for mrcal-calibrated cameras -->
<tr v-if="currentCalibrationCoeffs?.calobjectWarp?.length === 2">
<td>Board warp, X/Y</td>
<td>
{{
useCameraSettingsStore()
.getCalibrationCoeffs(props.videoFormat.resolution)
?.calobjectWarp?.map((it) => (it * 1000).toFixed(2) + " mm")
.join(" / ")
}}
</td>
</tr>
</tbody>
</template>
</v-simple-table>
</v-card-text>
<v-card-title v-if="currentCalibrationCoeffs" class="pt-0">Individual Observations</v-card-title>
<v-card-text v-if="currentCalibrationCoeffs">
<v-data-table
dense
style="width: 100%"
:headers="[
{ text: 'Observation Id', value: 'index' },
{ text: 'Mean Reprojection Error', value: 'mean' },
{ text: '', value: 'data-table-expand' }
]"
:items="getObservationDetails()"
item-key="index"
show-expand
expand-icon="mdi-eye"
>
<template #expanded-item="{ headers, item }">
<td :colspan="headers.length">
<div style="display: flex; justify-content: center; width: 100%">
<img :src="calibrationImageURL(item.index)" alt="observation image" class="snapshot-preview pt-2 pb-2" />
</div>
</td>
</template>
</v-data-table>
</v-card-text>
</v-card>
<pv-delete-modal
v-model="confirmRemoveDialog.show"
:width="500"
:title="'Delete Calibration'"
:description="`Are you sure you want to delete the calibration for '${confirmRemoveDialog.vf.resolution.width}x${confirmRemoveDialog.vf.resolution.height}'? This action cannot be undone.`"
:on-confirm="() => removeCalibration(confirmRemoveDialog.vf)"
/>
</template>
<style scoped>
.v-data-table {
background-color: #006492 !important;
}
.snapshot-preview {
max-width: 55%;
}
@media only screen and (max-width: 512px) {
.snapshot-preview {
max-width: 100%;
}
max-width: 100%;
max-height: 100%;
}
</style>

Some files were not shown because too many files have changed in this diff Show More