mirror of
https://github.com/PhotonVision/photonvision
synced 2026-06-19 00:41:41 +00:00
Compare commits
91 Commits
v2026.0.0-
...
py-docs
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f022130bfa | ||
|
|
5457db947e | ||
|
|
8c7ca1697e | ||
|
|
a7329c48a3 | ||
|
|
ce0b00ee03 | ||
|
|
a8d825919e | ||
|
|
63593b873a | ||
|
|
aa64bfe82e | ||
|
|
d27b3d0775 | ||
|
|
77e5545eef | ||
|
|
618072c3dd | ||
|
|
7d2c69dbdb | ||
|
|
a2b19c080e | ||
|
|
def3b9faa8 | ||
|
|
25c355ebc2 | ||
|
|
dad7f0a82d | ||
|
|
6f2603f0cb | ||
|
|
f499e4fb50 | ||
|
|
e5c8859c57 | ||
|
|
2cde701cff | ||
|
|
d649a9cb9e | ||
|
|
695742bfcf | ||
|
|
5df9137256 | ||
|
|
36b437323f | ||
|
|
5d39ef5b62 | ||
|
|
2bb59f8437 | ||
|
|
cd502a22c7 | ||
|
|
f16ffe3cd2 | ||
|
|
45236b872a | ||
|
|
6b20dc3c1b | ||
|
|
99ca8228a1 | ||
|
|
46e71703ef | ||
|
|
6fbb41fb76 | ||
|
|
05fcf876cd | ||
|
|
1637be6044 | ||
|
|
6f2fd19351 | ||
|
|
892e240b18 | ||
|
|
326c77fa38 | ||
|
|
8cf48bee57 | ||
|
|
26f08a6fdf | ||
|
|
abb8ccf4e9 | ||
|
|
50adef1672 | ||
|
|
cf68403182 | ||
|
|
dc0985dfb5 | ||
|
|
8fb29ff5c4 | ||
|
|
476cd6df8b | ||
|
|
783ed82d50 | ||
|
|
416e2f7607 | ||
|
|
ebd1071553 | ||
|
|
fa8b60fe27 | ||
|
|
c2581f3e99 | ||
|
|
96b0938dc0 | ||
|
|
697e52f886 | ||
|
|
87b219d9be | ||
|
|
abcd6b8f50 | ||
|
|
b22371d7c0 | ||
|
|
3eea79f0d4 | ||
|
|
0147a44100 | ||
|
|
0bec1f239c | ||
|
|
44b46cf117 | ||
|
|
ffdda9ddfa | ||
|
|
a5bc63878d | ||
|
|
a5b1cc0ded | ||
|
|
aacbdf5010 | ||
|
|
3547d0584b | ||
|
|
40815020de | ||
|
|
e522642a48 | ||
|
|
3cdda8a84e | ||
|
|
228caf47f2 | ||
|
|
d1761d07e9 | ||
|
|
331f4f0218 | ||
|
|
eb85834180 | ||
|
|
871ca61c8d | ||
|
|
358f5747ab | ||
|
|
e334d26459 | ||
|
|
77d5388a35 | ||
|
|
88a1e789ad | ||
|
|
abc67bdd95 | ||
|
|
05309b1e25 | ||
|
|
ddacff7079 | ||
|
|
32e4f0029b | ||
|
|
34057f223d | ||
|
|
55303ccd9c | ||
|
|
3c73b68ba3 | ||
|
|
6d816b5053 | ||
|
|
7c6bab1dfa | ||
|
|
462a2aa629 | ||
|
|
47b799a0ce | ||
|
|
35c72e8446 | ||
|
|
c440ce57ce | ||
|
|
8452527589 |
@@ -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
|
||||
|
||||
1
.github/pull_request_template.md
vendored
1
.github/pull_request_template.md
vendored
@@ -11,6 +11,7 @@
|
||||
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
|
||||
|
||||
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@@ -88,10 +88,8 @@ jobs:
|
||||
run: sudo apt-get update && sudo apt-get install -y libcholmod3 liblapack3 libsuitesparseconfig5
|
||||
- name: Gradle Build
|
||||
run: ./gradlew photon-targeting:build photon-core:build photon-server:build -x check
|
||||
- name: Gradle Tests
|
||||
run: ./gradlew testHeadless --stacktrace
|
||||
- name: Gradle Coverage
|
||||
run: ./gradlew jacocoTestReport
|
||||
- name: Gradle Tests and Coverage
|
||||
run: ./gradlew test jacocoTestReport --stacktrace
|
||||
build-offline-docs:
|
||||
name: "Build Offline Docs"
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
2
.github/workflows/lint-format.yml
vendored
2
.github/workflows/lint-format.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
with:
|
||||
python-version: 3.11
|
||||
- name: Install wpiformat
|
||||
run: pip3 install wpiformat==2025.34
|
||||
run: pip3 install wpiformat==2025.75
|
||||
- name: Run
|
||||
run: wpiformat
|
||||
- name: Check output
|
||||
|
||||
38
.github/workflows/photon-api-docs.yml
vendored
38
.github/workflows/photon-api-docs.yml
vendored
@@ -46,7 +46,7 @@ jobs:
|
||||
run: pnpm run build-demo
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: built-demo
|
||||
name: demo
|
||||
path: photon-client/dist/
|
||||
|
||||
run_java_cpp_docs:
|
||||
@@ -74,9 +74,39 @@ jobs:
|
||||
name: docs-java-cpp
|
||||
path: photon-docs/build/docs
|
||||
|
||||
run_py_docs:
|
||||
name: Build Python API Docs
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r photon-lib/py/docs/requirements.txt
|
||||
|
||||
- name: Build Sphinx site
|
||||
run: |
|
||||
sphinx-apidoc -o docs/source photonlibpy
|
||||
make -C docs html
|
||||
working-directory: photon-lib/py
|
||||
|
||||
- name: Upload built site as artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: docs-python
|
||||
path: photon-lib/py/docs/build/html
|
||||
|
||||
publish_api_docs:
|
||||
name: Publish API Docs
|
||||
needs: [run_java_cpp_docs]
|
||||
needs: [ run_java_cpp_docs, run_py_docs ]
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
# Download docs artifact
|
||||
@@ -85,7 +115,7 @@ jobs:
|
||||
pattern: docs-*
|
||||
- run: find .
|
||||
- name: Publish Docs To Development
|
||||
if: github.ref == 'refs/heads/main'
|
||||
# if: github.ref == 'refs/heads/main'
|
||||
uses: up9cloud/action-rsync@v1.4
|
||||
env:
|
||||
HOST: ${{ secrets.WEBMASTER_SSH_HOST }}
|
||||
@@ -108,7 +138,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: built-demo
|
||||
name: demo
|
||||
- run: find .
|
||||
- name: Publish demo
|
||||
if: github.ref == 'refs/heads/main'
|
||||
|
||||
2
.github/workflows/python.yml
vendored
2
.github/workflows/python.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install setuptools wheel pytest mypy
|
||||
pip install setuptools wheel pytest mypy mkdocs mkdocs-gen-files
|
||||
|
||||
- name: Build wheel
|
||||
working-directory: ./photon-lib/py
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -142,9 +142,13 @@ venv
|
||||
.venv/*
|
||||
.venv
|
||||
networktables.json
|
||||
|
||||
# Web stuff
|
||||
photon-server/src/main/resources/web/*
|
||||
node_modules
|
||||
dist
|
||||
components.d.ts
|
||||
|
||||
# Py docs stuff
|
||||
photon-lib/py/docs/build
|
||||
photon-server/src/main/resources/web/index.html
|
||||
|
||||
@@ -1,41 +1,25 @@
|
||||
cppHeaderFileInclude {
|
||||
\.h$
|
||||
\.hpp$
|
||||
\.inc$
|
||||
\.inl$
|
||||
}
|
||||
|
||||
cppSrcFileInclude {
|
||||
\.cpp$
|
||||
}
|
||||
|
||||
modifiableFileExclude {
|
||||
\.jpg$
|
||||
\.jpeg$
|
||||
\.png$
|
||||
\.gif$
|
||||
\.so$
|
||||
\.dll$
|
||||
\.webp$
|
||||
\.gif$
|
||||
\.ico$
|
||||
\.rknn$
|
||||
\.tflite$
|
||||
\.jpeg$
|
||||
\.jpg$
|
||||
\.mp4$
|
||||
\.pdf$
|
||||
\.png$
|
||||
\.rknn$
|
||||
\.so$
|
||||
\.svg$
|
||||
\.tflite$
|
||||
\.ttf$
|
||||
\.webp$
|
||||
\.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/
|
||||
photon-targeting/src/main/native/cpp/photon/constrained_solvepnp/generate/
|
||||
}
|
||||
@@ -20,6 +20,7 @@ If you are interested in contributing code or documentation to the project, plea
|
||||
- Photon UI demo: [http://photonvision.global/](http://photonvision.global/)
|
||||
- Javadocs: [javadocs.photonvision.org](https://javadocs.photonvision.org)
|
||||
- C++ Doxygen [cppdocs.photonvision.org](https://cppdocs.photonvision.org)
|
||||
- Python Documentation [pydocs.photonvision.org](https://pydocs.photonvision.org)
|
||||
|
||||
## Building
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ ext {
|
||||
javalinVersion = "6.7.0"
|
||||
libcameraDriverVersion = "v2025.0.4"
|
||||
rknnVersion = "dev-v2025.0.0-5-g666c0c6"
|
||||
rubikVersion = "dev-v2025.1.0-8-g067a316"
|
||||
rubikVersion = "dev-v2025.1.0-6-g4a5e508"
|
||||
frcYear = "2025"
|
||||
mrcalVersion = "v2025.0.0";
|
||||
|
||||
@@ -92,7 +92,7 @@ spotless {
|
||||
format 'misc', {
|
||||
target fileTree('.') {
|
||||
include '**/*.md', '**/.gitignore'
|
||||
exclude '**/build/**', '**/build-*/**'
|
||||
exclude '**/build/**', '**/build-*/**', '**/node_modules/**'
|
||||
}
|
||||
trimTrailingWhitespace()
|
||||
indentWithSpaces(2)
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
|
||||
modifiableFileExclude {
|
||||
\.jpg$
|
||||
\.jpeg$
|
||||
\.png$
|
||||
\.gif$
|
||||
\.so$
|
||||
\.pdf$
|
||||
\.mp4$
|
||||
\.dll$
|
||||
\.webp$
|
||||
\.ico$
|
||||
\.rknn$
|
||||
\.tflite$
|
||||
\.svg$
|
||||
\.woff2$
|
||||
gradlew
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -18,6 +18,10 @@ This section contains the build instructions from the source code available at [
|
||||
|
||||
[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).
|
||||
|
||||
**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
|
||||
|
||||
### Getting the Source Code
|
||||
@@ -173,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:
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
```{toctree}
|
||||
building-photon
|
||||
building-docs
|
||||
linting
|
||||
developer-docs/index
|
||||
design-descriptions/index
|
||||
```
|
||||
|
||||
43
docs/source/docs/contributing/linting.md
Normal file
43
docs/source/docs/contributing/linting.md
Normal 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'
|
||||
```
|
||||
@@ -6,7 +6,7 @@ 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 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.
|
||||
|
||||
## Code
|
||||
|
||||
@@ -14,6 +14,6 @@ PhotonVision currently ONLY supports 640x640 Ultralytics YOLOv5, YOLOv8, and YOL
|
||||
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 environment. 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.
|
||||
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.
|
||||
|
||||
@@ -14,7 +14,7 @@ PhotonVision currently ONLY supports 640x640 Ultralytics YOLOv8 and YOLOv11 mode
|
||||
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) 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.
|
||||
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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@ If you’re 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
|
||||
- Use the ChArUco calibration board:
|
||||
- Board Type: ChAruCo
|
||||
- Tag Family: 4x4
|
||||
- Pattern Spacing: 1.00in
|
||||
- Marker Size: 0.75in
|
||||
|
||||
19
docs/source/docs/quick-start/camera-focusing.md
Normal file
19
docs/source/docs/quick-start/camera-focusing.md
Normal 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%
|
||||
```
|
||||
BIN
docs/source/docs/quick-start/images/focusModeExample.png
Normal file
BIN
docs/source/docs/quick-start/images/focusModeExample.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 313 KiB |
@@ -9,5 +9,6 @@ wiring
|
||||
networking
|
||||
camera-matching
|
||||
camera-calibration
|
||||
camera-focusing
|
||||
quick-configure
|
||||
```
|
||||
|
||||
@@ -138,5 +138,7 @@ docs/contributing/index
|
||||
|
||||
Java <https://javadocs.photonvision.org>
|
||||
|
||||
C++ <https://cppdocs.photonvision.org/>
|
||||
C++ <https://cppdocs.photonvision.org>
|
||||
|
||||
Python <https://pydocs.photonvision.org>
|
||||
```
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"build-demo": "vite build --mode demo",
|
||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix",
|
||||
"format": "prettier --write src/",
|
||||
"lint-ci": "eslint . --max-warnings 0 --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix",
|
||||
"lint-ci": "eslint . --max-warnings 0 --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts",
|
||||
"format-ci": "prettier --check src/"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -13,5 +13,15 @@ import { useStateStore } from "@/stores/StateStore";
|
||||
<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>
|
||||
|
||||
@@ -141,7 +141,7 @@ const downloadCalibBoard = async () => {
|
||||
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);
|
||||
@@ -297,20 +297,30 @@ const setSelectedVideoFormat = (format: VideoFormat) => {
|
||||
: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
|
||||
"
|
||||
:items="getUniqueVideoResolutionStrings()"
|
||||
/>
|
||||
<pv-select
|
||||
v-model="boardType"
|
||||
label="Board Type"
|
||||
tooltip="Calibration board pattern to use"
|
||||
:select-cols="8"
|
||||
:items="['Chessboard', 'Charuco']"
|
||||
: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"
|
||||
@@ -326,7 +336,7 @@ const setSelectedVideoFormat = (format: VideoFormat) => {
|
||||
v-if="boardType === CalibrationBoardTypes.Charuco"
|
||||
v-model="tagFamily"
|
||||
label="Tag Family"
|
||||
tooltip="Dictionary of aruco markers on the charuco board"
|
||||
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"
|
||||
|
||||
@@ -3,8 +3,9 @@ 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();
|
||||
|
||||
@@ -12,6 +13,16 @@ 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();
|
||||
@@ -93,18 +104,19 @@ const calibrationImageURL = (index: number) =>
|
||||
</script>
|
||||
<template>
|
||||
<v-card color="surface" dark>
|
||||
<div class="d-flex flex-wrap pt-2 pl-2 pr-2">
|
||||
<div class="d-flex flex-wrap pt-2 pl-2 pr-2 align-center">
|
||||
<v-col cols="12" md="6">
|
||||
<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 pt-md-3 pl-6 pl-md-3">
|
||||
<v-col cols="12" md="6" class="d-flex align-center pt-0 pt-md-3">
|
||||
<v-btn
|
||||
color="buttonPassive"
|
||||
style="width: 100%"
|
||||
class="mr-2"
|
||||
style="flex: 1"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
@click="openUploadPhotonCalibJsonPrompt"
|
||||
>
|
||||
<v-icon start size="large"> mdi-import</v-icon>
|
||||
<v-icon start size="large">mdi-import</v-icon>
|
||||
<span>Import</span>
|
||||
</v-btn>
|
||||
<input
|
||||
@@ -114,12 +126,11 @@ const calibrationImageURL = (index: number) =>
|
||||
style="display: none"
|
||||
@change="importCalibration"
|
||||
/>
|
||||
</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="buttonPassive"
|
||||
class="mr-2"
|
||||
:disabled="!currentCalibrationCoeffs"
|
||||
style="width: 100%"
|
||||
style="flex: 1"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
@click="openExportCalibrationPrompt"
|
||||
>
|
||||
@@ -132,6 +143,16 @@ const calibrationImageURL = (index: number) =>
|
||||
:href="exportCalibrationURL"
|
||||
target="_blank"
|
||||
/>
|
||||
<v-btn
|
||||
color="error"
|
||||
:disabled="!currentCalibrationCoeffs"
|
||||
style="flex: 1"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
@click="() => (confirmRemoveDialog = { show: true, vf: props.videoFormat })"
|
||||
>
|
||||
<v-icon start size="large">mdi-delete</v-icon>
|
||||
<span>Delete</span>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</div>
|
||||
<v-card-title class="pt-0 pb-0"
|
||||
@@ -289,6 +310,14 @@ const calibrationImageURL = (index: number) =>
|
||||
</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>
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import PvSelect, { type SelectItem } from "@/components/common/pv-select.vue";
|
||||
import PvInput from "@/components/common/pv-input.vue";
|
||||
import PvDeleteModal from "@/components/common/pv-delete-modal.vue";
|
||||
import PvNumberInput from "@/components/common/pv-number-input.vue";
|
||||
import PvSwitch from "@/components/common/pv-switch.vue";
|
||||
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
|
||||
import { useStateStore } from "@/stores/StateStore";
|
||||
import { computed, ref, watchEffect } from "vue";
|
||||
import { type CameraSettingsChangeRequest, ValidQuirks } from "@/types/SettingTypes";
|
||||
import axios from "axios";
|
||||
import { useTheme } from "vuetify";
|
||||
import { axiosPost } from "@/lib/PhotonUtils";
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
@@ -15,7 +16,14 @@ const tempSettingsStruct = ref<CameraSettingsChangeRequest>({
|
||||
fov: useCameraSettingsStore().currentCameraSettings.fov.value,
|
||||
quirksToChange: Object.assign({}, useCameraSettingsStore().currentCameraSettings.cameraQuirks.quirks)
|
||||
});
|
||||
|
||||
const focusMode = computed<boolean>({
|
||||
get: () => useCameraSettingsStore().isFocusMode,
|
||||
set: (v) =>
|
||||
useCameraSettingsStore().changeCurrentPipelineIndex(
|
||||
v ? -3 : useCameraSettingsStore().currentCameraSettings.lastPipelineIndex || 0,
|
||||
true
|
||||
)
|
||||
});
|
||||
const arducamSelectWrapper = computed<number>({
|
||||
get: () => {
|
||||
if (tempSettingsStruct.value.quirksToChange.ArduOV9281Controls) return 1;
|
||||
@@ -112,44 +120,10 @@ watchEffect(() => {
|
||||
});
|
||||
|
||||
const showDeleteCamera = ref(false);
|
||||
const yesDeleteMySettingsText = ref("");
|
||||
const deletingCamera = ref(false);
|
||||
const deleteThisCamera = () => {
|
||||
if (deletingCamera.value) return;
|
||||
deletingCamera.value = true;
|
||||
|
||||
const payload = { cameraUniqueName: useStateStore().currentCameraUniqueName };
|
||||
|
||||
axios
|
||||
.post("/utils/nukeOneCamera", payload)
|
||||
.then(() => {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "Successfully dispatched the delete command. Waiting for backend to start back up",
|
||||
color: "success"
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.response) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "The backend is unable to fulfil the request to delete this camera.",
|
||||
color: "error"
|
||||
});
|
||||
} else if (error.request) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "Error while trying to process the request! The backend didn't respond.",
|
||||
color: "error"
|
||||
});
|
||||
} else {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "An error occurred while trying to process the request.",
|
||||
color: "error"
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
deletingCamera.value = false;
|
||||
showDeleteCamera.value = false;
|
||||
});
|
||||
axiosPost("/utils/nukeOneCamera", "delete this camera", {
|
||||
cameraUniqueName: useStateStore().currentCameraUniqueName
|
||||
});
|
||||
};
|
||||
const wrappedCameras = computed<SelectItem[]>(() =>
|
||||
Object.keys(useCameraSettingsStore().cameras).map((cameraUniqueName) => ({
|
||||
@@ -192,6 +166,11 @@ const wrappedCameras = computed<SelectItem[]>(() =>
|
||||
]"
|
||||
:select-cols="8"
|
||||
/>
|
||||
<pv-switch
|
||||
v-model="focusMode"
|
||||
tooltip="Enable Focus Mode to start focusing the lens on your camera"
|
||||
label="Focus Mode"
|
||||
></pv-switch>
|
||||
</v-card-text>
|
||||
<v-card-text class="d-flex pt-0">
|
||||
<v-col cols="6" class="pa-0 pr-2">
|
||||
@@ -221,45 +200,13 @@ const wrappedCameras = computed<SelectItem[]>(() =>
|
||||
</v-col>
|
||||
</v-card-text>
|
||||
|
||||
<v-dialog v-model="showDeleteCamera" width="800">
|
||||
<v-card color="surface" flat>
|
||||
<v-card-title> Delete {{ useCameraSettingsStore().currentCameraSettings.nickname }}? </v-card-title>
|
||||
<v-card-text class="pt-0 pb-10px">
|
||||
Are you sure you want to delete "{{ useCameraSettingsStore().currentCameraSettings.nickname }}"? This cannot
|
||||
be undone.
|
||||
</v-card-text>
|
||||
<v-card-text class="pt-0 pb-10px">
|
||||
<pv-input
|
||||
v-model="yesDeleteMySettingsText"
|
||||
:label="'Type "' + useCameraSettingsStore().currentCameraName + '":'"
|
||||
:label-cols="6"
|
||||
:input-cols="6"
|
||||
/>
|
||||
</v-card-text>
|
||||
<v-card-actions class="pa-5 pt-0">
|
||||
<v-btn
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
color="primary"
|
||||
class="text-black"
|
||||
@click="showDeleteCamera = false"
|
||||
>
|
||||
Cancel
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="error"
|
||||
:disabled="
|
||||
yesDeleteMySettingsText.toLowerCase() !== useCameraSettingsStore().currentCameraName.toLowerCase()
|
||||
"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
:loading="deletingCamera"
|
||||
@click="deleteThisCamera"
|
||||
>
|
||||
<v-icon start class="open-icon" size="large"> mdi-trash-can-outline </v-icon>
|
||||
<span class="open-label">Delete</span>
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
<pv-delete-modal
|
||||
v-model="showDeleteCamera"
|
||||
title="Delete Camera"
|
||||
:description="`Are you sure you want to delete the camera '${useCameraSettingsStore().currentCameraSettings.nickname}'? This action cannot be undone.`"
|
||||
:expected-confirmation-text="useCameraSettingsStore().currentCameraSettings.nickname"
|
||||
:on-confirm="deleteThisCamera"
|
||||
/>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -58,6 +58,15 @@ const fpsTooLow = computed<boolean>(() => {
|
||||
<v-chip v-else label color="red" variant="text" style="font-size: 1rem; padding: 0; margin: 0">
|
||||
<span class="pr-1">Camera not connected</span>
|
||||
</v-chip>
|
||||
<v-chip
|
||||
v-if="useCameraSettingsStore().isFocusMode"
|
||||
label
|
||||
color="primary"
|
||||
variant="text"
|
||||
style="font-size: 1rem; padding: 0; margin: auto"
|
||||
>
|
||||
<span class="pr-1"> Focus: {{ Math.round(useStateStore().currentPipelineResults?.focus || 0) }} </span>
|
||||
</v-chip>
|
||||
<v-switch
|
||||
v-model="driverMode"
|
||||
:disabled="useCameraSettingsStore().isCalibrationMode || useCameraSettingsStore().pipelineNames.length === 0"
|
||||
@@ -95,7 +104,11 @@ const fpsTooLow = computed<boolean>(() => {
|
||||
color="buttonPassive"
|
||||
class="fill"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
:disabled="useCameraSettingsStore().isDriverMode || useCameraSettingsStore().isCalibrationMode"
|
||||
:disabled="
|
||||
useCameraSettingsStore().isDriverMode ||
|
||||
useCameraSettingsStore().isCalibrationMode ||
|
||||
useCameraSettingsStore().isFocusMode
|
||||
"
|
||||
>
|
||||
<v-icon start class="mode-btn-icon" size="large">mdi-import</v-icon>
|
||||
<span class="mode-btn-label">Raw</span>
|
||||
@@ -104,7 +117,11 @@ const fpsTooLow = computed<boolean>(() => {
|
||||
color="buttonPassive"
|
||||
class="fill"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
:disabled="useCameraSettingsStore().isDriverMode || useCameraSettingsStore().isCalibrationMode"
|
||||
:disabled="
|
||||
useCameraSettingsStore().isDriverMode ||
|
||||
useCameraSettingsStore().isCalibrationMode ||
|
||||
useCameraSettingsStore().isFocusMode
|
||||
"
|
||||
>
|
||||
<v-icon start class="mode-btn-icon" size="large">mdi-export</v-icon>
|
||||
<span class="mode-btn-label">Processed</span>
|
||||
|
||||
85
photon-client/src/components/common/pv-delete-modal.vue
Normal file
85
photon-client/src/components/common/pv-delete-modal.vue
Normal file
@@ -0,0 +1,85 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { useTheme } from "vuetify";
|
||||
import pvInput from "./pv-input.vue";
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const value = defineModel<boolean | undefined>({ required: true });
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
expectedConfirmationText?: string;
|
||||
onBackup?: () => void;
|
||||
onConfirm: () => void;
|
||||
title: string;
|
||||
description?: string;
|
||||
deleteText?: string;
|
||||
width?: number;
|
||||
}>(),
|
||||
{
|
||||
width: 700
|
||||
}
|
||||
);
|
||||
|
||||
const confirmationText = ref("");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-dialog v-model="value" :width="props.width" dark>
|
||||
<v-card color="surface" flat>
|
||||
<v-card-title style="display: flex; justify-content: center">
|
||||
{{ title }}
|
||||
</v-card-title>
|
||||
<v-card-text class="pt-0 pb-10px">
|
||||
<span> {{ description }} </span>
|
||||
</v-card-text>
|
||||
<v-card-text v-if="expectedConfirmationText" class="pt-0 pb-0">
|
||||
<pv-input
|
||||
v-model="confirmationText"
|
||||
:label="'Type "' + expectedConfirmationText + '":'"
|
||||
:label-cols="6"
|
||||
:input-cols="6"
|
||||
/>
|
||||
</v-card-text>
|
||||
<v-card-text class="pt-10px">
|
||||
<v-row class="align-center text-white">
|
||||
<v-col v-if="onBackup" cols="6">
|
||||
<v-btn
|
||||
color="buttonActive"
|
||||
style="float: right"
|
||||
width="100%"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
@click="onBackup"
|
||||
>
|
||||
<v-icon start class="open-icon" size="large"> mdi-export </v-icon>
|
||||
<span class="open-label">Backup Data</span>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
<v-col v-if="description" :cols="onBackup ? '6' : '12'">
|
||||
<v-btn
|
||||
color="error"
|
||||
width="100%"
|
||||
:disabled="
|
||||
expectedConfirmationText
|
||||
? confirmationText.toLowerCase() !== expectedConfirmationText.toLowerCase()
|
||||
: false
|
||||
"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
@click="
|
||||
onConfirm();
|
||||
confirmationText = '';
|
||||
value = false;
|
||||
"
|
||||
>
|
||||
<v-icon start class="open-icon" size="large"> mdi-trash-can-outline </v-icon>
|
||||
<span class="open-label">
|
||||
{{ deleteText ?? title }}
|
||||
</span>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
@@ -9,6 +9,7 @@ import PvInput from "@/components/common/pv-input.vue";
|
||||
import { PipelineType } from "@/types/PipelineTypes";
|
||||
import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore";
|
||||
import { useTheme } from "vuetify";
|
||||
import PvDeleteModal from "@/components/common/pv-delete-modal.vue";
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
@@ -92,6 +93,9 @@ const pipelineNamesWrapper = computed<SelectItem[]>(() => {
|
||||
if (useCameraSettingsStore().isDriverMode) {
|
||||
pipelineNames.push({ name: "Driver Mode", value: WebsocketPipelineType.DriverMode });
|
||||
}
|
||||
if (useCameraSettingsStore().isFocusMode) {
|
||||
pipelineNames.push({ name: "Focus Mode", value: WebsocketPipelineType.FocusCamera });
|
||||
}
|
||||
if (useCameraSettingsStore().isCalibrationMode) {
|
||||
pipelineNames.push({ name: "3D Calibration Mode", value: WebsocketPipelineType.Calib3d });
|
||||
}
|
||||
@@ -130,7 +134,7 @@ const validNewPipelineTypes = computed(() => {
|
||||
{ name: "Reflective", value: WebsocketPipelineType.Reflective },
|
||||
{ name: "Colored Shape", value: WebsocketPipelineType.ColoredShape },
|
||||
{ name: "AprilTag", value: WebsocketPipelineType.AprilTag },
|
||||
{ name: "Aruco", value: WebsocketPipelineType.Aruco }
|
||||
{ name: "ArUco", value: WebsocketPipelineType.Aruco }
|
||||
];
|
||||
if (useSettingsStore().general.supportedBackends.length > 0) {
|
||||
pipelineTypes.push({ name: "Object Detection", value: WebsocketPipelineType.ObjectDetection });
|
||||
@@ -168,7 +172,7 @@ const pipelineTypesWrapper = computed<{ name: string; value: number }[]>(() => {
|
||||
{ name: "Reflective", value: WebsocketPipelineType.Reflective },
|
||||
{ name: "Colored Shape", value: WebsocketPipelineType.ColoredShape },
|
||||
{ name: "AprilTag", value: WebsocketPipelineType.AprilTag },
|
||||
{ name: "Aruco", value: WebsocketPipelineType.Aruco }
|
||||
{ name: "ArUco", value: WebsocketPipelineType.Aruco }
|
||||
];
|
||||
if (useSettingsStore().general.supportedBackends.length > 0) {
|
||||
pipelineTypes.push({ name: "Object Detection", value: WebsocketPipelineType.ObjectDetection });
|
||||
@@ -177,6 +181,9 @@ const pipelineTypesWrapper = computed<{ name: string; value: number }[]>(() => {
|
||||
if (useCameraSettingsStore().isDriverMode) {
|
||||
pipelineTypes.push({ name: "Driver Mode", value: WebsocketPipelineType.DriverMode });
|
||||
}
|
||||
if (useCameraSettingsStore().isFocusMode) {
|
||||
pipelineTypes.push({ name: "Focus Mode", value: WebsocketPipelineType.FocusCamera });
|
||||
}
|
||||
if (useCameraSettingsStore().isCalibrationMode) {
|
||||
pipelineTypes.push({ name: "3D Calibration Mode", value: WebsocketPipelineType.Calib3d });
|
||||
}
|
||||
@@ -187,6 +194,7 @@ const pipelineType = ref<WebsocketPipelineType>(useCameraSettingsStore().current
|
||||
const currentPipelineType = computed<WebsocketPipelineType>({
|
||||
get: () => {
|
||||
if (useCameraSettingsStore().isDriverMode) return WebsocketPipelineType.DriverMode;
|
||||
if (useCameraSettingsStore().isFocusMode) return WebsocketPipelineType.FocusCamera;
|
||||
if (useCameraSettingsStore().isCalibrationMode) return WebsocketPipelineType.Calib3d;
|
||||
return pipelineType.value;
|
||||
},
|
||||
@@ -290,6 +298,7 @@ const wrappedCameras = computed<SelectItem[]>(() =>
|
||||
tooltip="Each pipeline runs on a camera output and stores a unique set of processing settings"
|
||||
:disabled="
|
||||
useCameraSettingsStore().isDriverMode ||
|
||||
useCameraSettingsStore().isFocusMode ||
|
||||
useCameraSettingsStore().isCalibrationMode ||
|
||||
!useCameraSettingsStore().hasConnected
|
||||
"
|
||||
@@ -366,6 +375,7 @@ const wrappedCameras = computed<SelectItem[]>(() =>
|
||||
tooltip="Changes the pipeline type, which changes the type of processing that will happen on input frames"
|
||||
:disabled="
|
||||
useCameraSettingsStore().isDriverMode ||
|
||||
useCameraSettingsStore().isFocusMode ||
|
||||
useCameraSettingsStore().isCalibrationMode ||
|
||||
!useCameraSettingsStore().hasConnected
|
||||
"
|
||||
@@ -413,33 +423,13 @@ const wrappedCameras = computed<SelectItem[]>(() =>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
<v-dialog v-model="showPipelineDeletionConfirmationDialog" width="500">
|
||||
<v-card color="surface">
|
||||
<v-card-title class="pb-0">Delete Pipeline</v-card-title>
|
||||
<v-card-text>
|
||||
Are you sure you want to delete
|
||||
<span style="color: white">"{{ useCameraSettingsStore().currentPipelineSettings.pipelineNickname }}"</span>?
|
||||
This cannot be undone.
|
||||
</v-card-text>
|
||||
<v-card-actions class="pa-5 pt-0">
|
||||
<v-btn
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
color="primary"
|
||||
class="text-black"
|
||||
@click="showPipelineDeletionConfirmationDialog = false"
|
||||
>
|
||||
Cancel
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="error"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
@click="confirmDeleteCurrentPipeline"
|
||||
>
|
||||
Delete
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
<pv-delete-modal
|
||||
v-model="showPipelineDeletionConfirmationDialog"
|
||||
:width="500"
|
||||
title="Delete Pipeline"
|
||||
description="Are you sure you want to delete the current pipeline? This action cannot be undone."
|
||||
:on-confirm="confirmDeleteCurrentPipeline"
|
||||
/>
|
||||
<v-dialog v-model="showPipelineTypeChangeDialog" persistent width="600">
|
||||
<v-card color="surface" dark>
|
||||
<v-card-title class="pb-0">Change Pipeline Type</v-card-title>
|
||||
|
||||
@@ -29,7 +29,7 @@ const allTabs = Object.freeze({
|
||||
thresholdTab: { tabName: "Threshold", component: ThresholdTab },
|
||||
contoursTab: { tabName: "Contours", component: ContoursTab },
|
||||
apriltagTab: { tabName: "AprilTag", component: AprilTagTab },
|
||||
arucoTab: { tabName: "Aruco", component: ArucoTab },
|
||||
arucoTab: { tabName: "ArUco", component: ArucoTab },
|
||||
objectDetectionTab: { tabName: "Object Detection", component: ObjectDetectionTab },
|
||||
outputTab: { tabName: "Output", component: OutputTab },
|
||||
targetsTab: { tabName: "Targets", component: TargetsTab },
|
||||
@@ -99,8 +99,8 @@ const tabGroups = computed<ConfigOption[][]>(() => {
|
||||
!((isAprilTag || isAruco || isObjectDetection) && tabConfig.tabName === "Threshold") && //Filter out threshold tab if we're doing AprilTags
|
||||
!((isAprilTag || isAruco || isObjectDetection) && tabConfig.tabName === "Contours") && //Filter out contours if we're doing AprilTags
|
||||
!(!isAprilTag && tabConfig.tabName === "AprilTag") && //Filter out apriltag unless we actually are doing AprilTags
|
||||
!(!isAruco && tabConfig.tabName === "Aruco") &&
|
||||
!(!isObjectDetection && tabConfig.tabName === "Object Detection") //Filter out aruco unless we actually are doing Aruco
|
||||
!(!isAruco && tabConfig.tabName === "ArUco") &&
|
||||
!(!isObjectDetection && tabConfig.tabName === "Object Detection") //Filter out ArUco unless we actually are doing ArUco
|
||||
)
|
||||
)
|
||||
.filter((it) => it.length); // Remove empty tab groups
|
||||
|
||||
@@ -21,7 +21,9 @@ const processingMode = computed<number>({
|
||||
|
||||
<template>
|
||||
<v-card
|
||||
:disabled="useCameraSettingsStore().isDriverMode || useStateStore().colorPickingMode"
|
||||
:disabled="
|
||||
useCameraSettingsStore().isDriverMode || useCameraSettingsStore().isFocusMode || useStateStore().colorPickingMode
|
||||
"
|
||||
class="mt-3 rounded-12"
|
||||
color="surface"
|
||||
style="flex-grow: 1; display: flex; flex-direction: column"
|
||||
|
||||
@@ -2,60 +2,17 @@
|
||||
import { inject, ref } from "vue";
|
||||
import { useStateStore } from "@/stores/StateStore";
|
||||
import PvSelect from "@/components/common/pv-select.vue";
|
||||
import PvInput from "@/components/common/pv-input.vue";
|
||||
import axios from "axios";
|
||||
import PvDeleteModal from "@/components/common/pv-delete-modal.vue";
|
||||
import { useTheme } from "vuetify";
|
||||
import { axiosPost } from "@/lib/PhotonUtils";
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const restartProgram = () => {
|
||||
axios
|
||||
.post("/utils/restartProgram")
|
||||
.then(() => {
|
||||
useStateStore().showSnackbarMessage({ message: "Successfully sent program restart request", color: "success" });
|
||||
})
|
||||
.catch((error) => {
|
||||
// This endpoint always return 204 regardless of outcome
|
||||
if (error.request) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "Error while trying to process the request! The backend didn't respond.",
|
||||
color: "error"
|
||||
});
|
||||
} else {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "An error occurred while trying to process the request.",
|
||||
color: "error"
|
||||
});
|
||||
}
|
||||
});
|
||||
axiosPost("/utils/restartProgram", "restart PhotonVision");
|
||||
};
|
||||
const restartDevice = () => {
|
||||
axios
|
||||
.post("/utils/restartDevice")
|
||||
.then(() => {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "Successfully dispatched the restart command. It isn't confirmed if a device restart will occur.",
|
||||
color: "success"
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.response) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "The backend is unable to fulfil the request to restart the device.",
|
||||
color: "error"
|
||||
});
|
||||
} else if (error.request) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "Error while trying to process the request! The backend didn't respond.",
|
||||
color: "error"
|
||||
});
|
||||
} else {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "An error occurred while trying to process the request.",
|
||||
color: "error"
|
||||
});
|
||||
}
|
||||
});
|
||||
axiosPost("/utils/restartDevice", "restart the device");
|
||||
};
|
||||
|
||||
const address = inject<string>("backendHost");
|
||||
@@ -77,47 +34,27 @@ const handleOfflineUpdate = () => {
|
||||
timeout: -1
|
||||
});
|
||||
|
||||
axios
|
||||
.post("/utils/offlineUpdate", formData, {
|
||||
headers: { "Content-Type": "multipart/form-data" },
|
||||
onUploadProgress: ({ progress }) => {
|
||||
const uploadPercentage = (progress || 0) * 100.0;
|
||||
if (uploadPercentage < 99.5) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "New Software Upload in Process, " + uploadPercentage.toFixed(2) + "% complete",
|
||||
color: "secondary",
|
||||
timeout: -1
|
||||
});
|
||||
} else {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "Installing uploaded software...",
|
||||
color: "secondary",
|
||||
timeout: -1
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.then((response) => {
|
||||
useStateStore().showSnackbarMessage({ message: response.data.text || response.data, color: "success" });
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.response) {
|
||||
axiosPost("/utils/offlineUpdate", "upload new software", formData, {
|
||||
headers: { "Content-Type": "multipart/form-data" },
|
||||
onUploadProgress: ({ progress }) => {
|
||||
const uploadPercentage = (progress || 0) * 100.0;
|
||||
if (uploadPercentage < 99.5) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: error.response.data.text || error.response.data
|
||||
});
|
||||
} else if (error.request) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: "Error while trying to process the request! The backend didn't respond."
|
||||
message: "New Software Upload in Progress",
|
||||
color: "secondary",
|
||||
timeout: -1,
|
||||
progressBar: uploadPercentage,
|
||||
progressBarColor: "primary"
|
||||
});
|
||||
} else {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: "An error occurred while trying to process the request."
|
||||
message: "Installing uploaded software...",
|
||||
color: "secondary",
|
||||
timeout: -1
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const exportLogFile = ref();
|
||||
@@ -166,29 +103,9 @@ const handleSettingsImport = () => {
|
||||
break;
|
||||
}
|
||||
|
||||
axios
|
||||
.post(`/settings${settingsEndpoint}`, formData, { headers: { "Content-Type": "multipart/form-data" } })
|
||||
.then((response) => {
|
||||
useStateStore().showSnackbarMessage({ message: response.data.text || response.data, color: "success" });
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.response) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: error.response.data.text || error.response.data
|
||||
});
|
||||
} else if (error.request) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: "Error while trying to process the request! The backend didn't respond."
|
||||
});
|
||||
} else {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: "An error occurred while trying to process the request."
|
||||
});
|
||||
}
|
||||
});
|
||||
axiosPost(`/settings${settingsEndpoint}`, "import settings", formData, {
|
||||
headers: { "Content-Type": "multipart/form-data" }
|
||||
});
|
||||
|
||||
showImportDialog.value = false;
|
||||
importType.value = undefined;
|
||||
@@ -196,36 +113,8 @@ const handleSettingsImport = () => {
|
||||
};
|
||||
|
||||
const showFactoryReset = ref(false);
|
||||
const expected = "Delete Everything";
|
||||
const yesDeleteMySettingsText = ref("");
|
||||
const nukePhotonConfigDirectory = () => {
|
||||
axios
|
||||
.post("/utils/nukeConfigDirectory")
|
||||
.then(() => {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "Successfully dispatched the reset command. Waiting for backend to start back up",
|
||||
color: "success"
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.response) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "The backend is unable to fulfill the request to reset the device.",
|
||||
color: "error"
|
||||
});
|
||||
} else if (error.request) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "Error while trying to process the request! The backend didn't respond.",
|
||||
color: "error"
|
||||
});
|
||||
} else {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "An error occurred while trying to process the request.",
|
||||
color: "error"
|
||||
});
|
||||
}
|
||||
});
|
||||
showFactoryReset.value = false;
|
||||
axiosPost("/utils/nukeConfigDirectory", "delete the config directory");
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -387,63 +276,15 @@ const nukePhotonConfigDirectory = () => {
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
<v-dialog v-model="showFactoryReset" width="800" dark>
|
||||
<v-card color="surface" flat>
|
||||
<v-card-title style="display: flex; justify-content: center">
|
||||
<span class="open-label">
|
||||
<v-icon end color="red" class="open-icon ma-1" size="large">mdi-alert-outline</v-icon>
|
||||
Factory Reset PhotonVision
|
||||
<v-icon end color="red" class="open-icon ma-1" size="large">mdi-alert-outline</v-icon>
|
||||
</span>
|
||||
</v-card-title>
|
||||
<v-card-text class="pt-0 pb-10px">
|
||||
<v-row class="align-center text-white">
|
||||
<v-col cols="12" md="6">
|
||||
<span> This will delete ALL OF YOUR SETTINGS and restart PhotonVision. </span>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-btn
|
||||
color="primary"
|
||||
style="float: right"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
@click="openExportSettingsPrompt"
|
||||
>
|
||||
<v-icon start class="open-icon" size="large"> mdi-export </v-icon>
|
||||
<span class="open-label">Backup Settings</span>
|
||||
<a
|
||||
ref="exportSettings"
|
||||
style="color: black; text-decoration: none; display: none"
|
||||
:href="`http://${address}/api/settings/photonvision_config.zip`"
|
||||
download="photonvision-settings.zip"
|
||||
target="_blank"
|
||||
/>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
<v-card-text class="pt-0 pb-0">
|
||||
<pv-input
|
||||
v-model="yesDeleteMySettingsText"
|
||||
:label="'Type "' + expected + '":'"
|
||||
:label-cols="6"
|
||||
:input-cols="6"
|
||||
/>
|
||||
</v-card-text>
|
||||
<v-card-text class="pt-10px">
|
||||
<v-btn
|
||||
color="error"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
:disabled="yesDeleteMySettingsText.toLowerCase() !== expected.toLowerCase()"
|
||||
@click="nukePhotonConfigDirectory"
|
||||
>
|
||||
<v-icon start class="open-icon" size="large"> mdi-trash-can-outline </v-icon>
|
||||
<span class="open-label">
|
||||
{{ $vuetify.display.mdAndUp ? "Delete everything, I have backed up what I need" : "Delete Everything" }}
|
||||
</span>
|
||||
</v-btn>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
<pv-delete-modal
|
||||
v-model="showFactoryReset"
|
||||
title="Factory Reset PhotonVision"
|
||||
description="This will delete all settings and configurations stored on this device, including network settings. This action cannot be undone."
|
||||
expected-confirmation-text="Delete Everything"
|
||||
:on-confirm="nukePhotonConfigDirectory"
|
||||
:on-backup="openExportSettingsPrompt"
|
||||
delete-text="Factory reset"
|
||||
/>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -315,23 +315,23 @@ watchEffect(() => {
|
||||
<v-col class="text-center">
|
||||
Background
|
||||
<v-color-picker
|
||||
v-model:model-value="backgroundColor"
|
||||
class="ma-auto pt-3"
|
||||
elevation="0"
|
||||
mode="hex"
|
||||
:modes="['hex']"
|
||||
v-model:model-value="backgroundColor"
|
||||
v-on:update:model-value="(hex) => setThemeColor(theme, 'background', hex)"
|
||||
@update:model-value="(hex) => setThemeColor(theme, 'background', hex)"
|
||||
></v-color-picker>
|
||||
</v-col>
|
||||
<v-col class="text-center">
|
||||
Surface
|
||||
<v-color-picker
|
||||
v-model:model-value="surfaceColor"
|
||||
class="ma-auto pt-3"
|
||||
elevation="0"
|
||||
mode="hex"
|
||||
:modes="['hex']"
|
||||
v-model:model-value="surfaceColor"
|
||||
v-on:update:model-value="(hex) => setThemeColor(theme, 'surface', hex)"
|
||||
@update:model-value="(hex) => setThemeColor(theme, 'surface', hex)"
|
||||
></v-color-picker>
|
||||
</v-col>
|
||||
</v-row>
|
||||
@@ -339,23 +339,23 @@ watchEffect(() => {
|
||||
<v-col class="text-center">
|
||||
Primary
|
||||
<v-color-picker
|
||||
v-model:model-value="primaryColor"
|
||||
class="ma-auto pt-3"
|
||||
elevation="0"
|
||||
mode="hex"
|
||||
:modes="['hex']"
|
||||
v-model:model-value="primaryColor"
|
||||
v-on:update:model-value="(hex) => setThemeColor(theme, 'primary', hex)"
|
||||
@update:model-value="(hex) => setThemeColor(theme, 'primary', hex)"
|
||||
></v-color-picker>
|
||||
</v-col>
|
||||
<v-col class="text-center">
|
||||
Secondary
|
||||
<v-color-picker
|
||||
v-model:model-value="secondaryColor"
|
||||
class="ma-auto pt-3"
|
||||
elevation="0"
|
||||
mode="hex"
|
||||
:modes="['hex']"
|
||||
v-model:model-value="secondaryColor"
|
||||
v-on:update:model-value="(hex) => setThemeColor(theme, 'secondary', hex)"
|
||||
@update:model-value="(hex) => setThemeColor(theme, 'secondary', hex)"
|
||||
></v-color-picker>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, inject } from "vue";
|
||||
import axios from "axios";
|
||||
import { useStateStore } from "@/stores/StateStore";
|
||||
import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore";
|
||||
import type { ObjectDetectionModelProperties } from "@/types/SettingTypes";
|
||||
import pvInput from "@/components/common/pv-input.vue";
|
||||
import { type ObjectDetectionModelProperties } from "@/types/SettingTypes";
|
||||
import PvDeleteModal from "@/components/common/pv-delete-modal.vue";
|
||||
import { useTheme } from "vuetify";
|
||||
import { axiosPost } from "@/lib/PhotonUtils";
|
||||
|
||||
const theme = useTheme();
|
||||
const showImportDialog = ref(false);
|
||||
@@ -43,34 +43,25 @@ const handleImport = async () => {
|
||||
timeout: -1
|
||||
});
|
||||
|
||||
axios
|
||||
.post("/objectdetection/import", formData, {
|
||||
headers: { "Content-Type": "multipart/form-data" }
|
||||
})
|
||||
.then((response) => {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: response.data.text || response.data,
|
||||
color: "success"
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.response) {
|
||||
axiosPost("/objectdetection/import", "import an object detection model", formData, {
|
||||
headers: { "Content-Type": "multipart/form-data" },
|
||||
onUploadProgress: ({ progress }) => {
|
||||
const uploadPercentage = (progress || 0) * 100.0;
|
||||
if (uploadPercentage < 99.5) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: error.response.data.text || error.response.data
|
||||
});
|
||||
} else if (error.request) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: "Error while trying to process the request! The backend didn't respond."
|
||||
message: "Object Detection Model Upload in Process, " + uploadPercentage.toFixed(2) + "% complete",
|
||||
color: "secondary",
|
||||
timeout: -1
|
||||
});
|
||||
} else {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: "An error occurred while trying to process the request."
|
||||
message: "Processing uploaded Object Detection Model...",
|
||||
color: "secondary",
|
||||
timeout: -1
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
showImportDialog.value = false;
|
||||
|
||||
@@ -82,41 +73,9 @@ const handleImport = async () => {
|
||||
};
|
||||
|
||||
const deleteModel = async (model: ObjectDetectionModelProperties) => {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "Deleting Object Detection Model...",
|
||||
color: "secondary",
|
||||
timeout: -1
|
||||
axiosPost("/objectdetection/delete", "delete an object detection model", {
|
||||
modelPath: model.modelPath
|
||||
});
|
||||
|
||||
axios
|
||||
.post("/objectdetection/delete", {
|
||||
modelPath: model.modelPath
|
||||
})
|
||||
.then((response) => {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: response.data.text || response.data,
|
||||
color: "success"
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.response) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: error.response.data.text || error.response.data
|
||||
});
|
||||
} else if (error.request) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: "Error while trying to process the request! The backend didn't respond."
|
||||
});
|
||||
} else {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: "An error occurred while trying to process the request."
|
||||
});
|
||||
}
|
||||
});
|
||||
confirmDeleteDialog.value.show = false;
|
||||
};
|
||||
|
||||
const renameModel = async (model: ObjectDetectionModelProperties, newName: string) => {
|
||||
@@ -126,35 +85,10 @@ const renameModel = async (model: ObjectDetectionModelProperties, newName: strin
|
||||
timeout: -1
|
||||
});
|
||||
|
||||
axios
|
||||
.post("/objectdetection/rename", {
|
||||
modelPath: model.modelPath.replace("file:", ""),
|
||||
newName: newName
|
||||
})
|
||||
.then((response) => {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: response.data.text || response.data,
|
||||
color: "success"
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.response) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: error.response.data.text || error.response.data
|
||||
});
|
||||
} else if (error.request) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: "Error while trying to process the request! The backend didn't respond."
|
||||
});
|
||||
} else {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: "An error occurred while trying to process the request."
|
||||
});
|
||||
}
|
||||
});
|
||||
axiosPost("/objectdetection/rename", "rename an object detection model", {
|
||||
modelPath: model.modelPath.replace("file:", ""),
|
||||
newName: newName
|
||||
});
|
||||
showRenameDialog.value.show = false;
|
||||
};
|
||||
|
||||
@@ -181,36 +115,8 @@ const openExportIndividualModelPrompt = () => {
|
||||
};
|
||||
|
||||
const showNukeDialog = ref(false);
|
||||
const expected = "Delete Models";
|
||||
const yesDeleteMyModelsText = ref("");
|
||||
const nukeModels = () => {
|
||||
axios
|
||||
.post("/objectdetection/nuke")
|
||||
.then(() => {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "Successfully dispatched the clear models command.",
|
||||
color: "success"
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.response) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "The backend is unable to fulfill the request to clear the models.",
|
||||
color: "error"
|
||||
});
|
||||
} else if (error.request) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "Error while trying to process the request! The backend didn't respond.",
|
||||
color: "error"
|
||||
});
|
||||
} else {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "An error occurred while trying to process the request.",
|
||||
color: "error"
|
||||
});
|
||||
}
|
||||
});
|
||||
showNukeDialog.value = false;
|
||||
axiosPost("/objectdetection/nuke", "clear and reset object detection models");
|
||||
};
|
||||
|
||||
const showBulkImportDialog = ref(false);
|
||||
@@ -221,51 +127,27 @@ const handleBulkImport = () => {
|
||||
const formData = new FormData();
|
||||
formData.append("data", importFile.value);
|
||||
|
||||
axios
|
||||
.post("/objectdetection/bulkimport", formData, {
|
||||
headers: { "Content-Type": "multipart/form-data" },
|
||||
onUploadProgress: ({ progress }) => {
|
||||
const uploadPercentage = (progress || 0) * 100.0;
|
||||
if (uploadPercentage < 99.5) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "Object Detection Models Upload in Process, " + uploadPercentage.toFixed(2) + "% complete",
|
||||
color: "secondary",
|
||||
timeout: -1
|
||||
});
|
||||
} else {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "Importing New Object Detection Models...",
|
||||
color: "secondary",
|
||||
timeout: -1
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.then((response) => {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: response.data.text || response.data,
|
||||
color: "success"
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.response) {
|
||||
axiosPost("/objectdetection/bulkimport", "import object detection models", formData, {
|
||||
headers: { "Content-Type": "multipart/form-data" },
|
||||
onUploadProgress: ({ progress }) => {
|
||||
const uploadPercentage = (progress || 0) * 100.0;
|
||||
if (uploadPercentage < 99.5) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: error.response.data.text || error.response.data
|
||||
});
|
||||
} else if (error.request) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: "Error while trying to process the request! The backend didn't respond."
|
||||
message: "Object Detection Models Upload in Progress",
|
||||
color: "secondary",
|
||||
timeout: -1,
|
||||
progressBar: uploadPercentage,
|
||||
progressBarColor: "primary"
|
||||
});
|
||||
} else {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: "An error occurred while trying to process the request."
|
||||
message: "Importing New Object Detection Models...",
|
||||
color: "secondary",
|
||||
timeout: -1
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
showImportDialog.value = false;
|
||||
importFile.value = null;
|
||||
};
|
||||
@@ -485,35 +367,20 @@ const handleBulkImport = () => {
|
||||
</tbody>
|
||||
</v-table>
|
||||
|
||||
<v-dialog v-model="confirmDeleteDialog.show" width="600">
|
||||
<v-card color="surface" dark>
|
||||
<v-card-title>Delete Object Detection Model</v-card-title>
|
||||
<v-card-text class="pt-0">
|
||||
Are you sure you want to delete the model {{ confirmDeleteDialog.model.nickname }}?
|
||||
<v-card-actions class="pt-5 pb-0 pr-0" style="justify-content: flex-end">
|
||||
<v-btn
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
color="buttonPassive"
|
||||
@click="confirmDeleteDialog.show = false"
|
||||
>
|
||||
Cancel
|
||||
</v-btn>
|
||||
<v-btn
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
color="error"
|
||||
@click="deleteModel(confirmDeleteDialog.model)"
|
||||
>
|
||||
Delete
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
<pv-delete-modal
|
||||
v-model="confirmDeleteDialog.show"
|
||||
:width="500"
|
||||
:on-confirm="() => deleteModel(confirmDeleteDialog.model)"
|
||||
title="Delete Object Detection Model"
|
||||
:description="`Are you sure you want to delete the model ${confirmDeleteDialog.model.nickname}?`"
|
||||
delete-text="Delete model"
|
||||
/>
|
||||
|
||||
<v-dialog v-model="showRenameDialog.show" width="600">
|
||||
<v-card color="surface" dark>
|
||||
<v-card-title>Rename Object Detection Model</v-card-title>
|
||||
<v-card-text class="pt-0">
|
||||
Enter a new name for the model {{ showRenameDialog.model.nickname }}:
|
||||
Enter a new name for the model "{{ showRenameDialog.model.nickname }}":
|
||||
<div class="pa-5 pb-0">
|
||||
<v-text-field v-model="showRenameDialog.newName" hide-details label="New Name" variant="underlined" />
|
||||
</div>
|
||||
@@ -569,64 +436,15 @@ const handleBulkImport = () => {
|
||||
</v-row>
|
||||
</div>
|
||||
|
||||
<v-dialog v-model="showNukeDialog" width="800" dark>
|
||||
<v-card color="surface" flat>
|
||||
<v-card-title style="display: flex; justify-content: center">
|
||||
<span class="open-label">
|
||||
<v-icon end color="error" class="open-icon ma-1" size="large">mdi-alert-outline</v-icon>
|
||||
Clear and Reset Object Detection Models
|
||||
<v-icon end color="error" class="open-icon ma-1" size="large">mdi-alert-outline</v-icon>
|
||||
</span>
|
||||
</v-card-title>
|
||||
<v-card-text class="pt-0 pb-10px">
|
||||
<v-row class="align-center text-white">
|
||||
<v-col cols="12" md="6">
|
||||
<span> This will delete ALL OF YOUR MODELS and re-extract the default models. </span>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-btn
|
||||
color="buttonActive"
|
||||
style="float: right"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
@click="openExportPrompt"
|
||||
>
|
||||
<v-icon start class="open-icon" size="large"> mdi-export </v-icon>
|
||||
<span class="open-label">Backup Models</span>
|
||||
<a
|
||||
ref="exportModels"
|
||||
style="color: black; text-decoration: none; display: none"
|
||||
:href="`http://${address}/api/objectdetection/export`"
|
||||
download="photonvision-object-detection-models-export.zip"
|
||||
target="_blank"
|
||||
/>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
<v-card-text class="pt-0 pb-0">
|
||||
<pv-input
|
||||
v-model="yesDeleteMyModelsText"
|
||||
:label="'Type "' + expected + '":'"
|
||||
:label-cols="6"
|
||||
:input-cols="6"
|
||||
/>
|
||||
</v-card-text>
|
||||
<v-card-text class="pt-10px">
|
||||
<v-btn
|
||||
color="error"
|
||||
width="100%"
|
||||
:disabled="yesDeleteMyModelsText.toLowerCase() !== expected.toLowerCase()"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
@click="nukeModels"
|
||||
>
|
||||
<v-icon start class="open-icon" size="large"> mdi-trash-can-outline </v-icon>
|
||||
<span class="open-label">
|
||||
{{ $vuetify.display.mdAndUp ? "Delete models, I have backed up what I need" : "Delete Models" }}
|
||||
</span>
|
||||
</v-btn>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
<pv-delete-modal
|
||||
v-model="showNukeDialog"
|
||||
:on-backup="openExportPrompt"
|
||||
:on-confirm="nukeModels"
|
||||
title="Delete and Reset All Object Detection Models"
|
||||
:description="'This will delete ALL object detection models and re-extract the default object detection models. This action cannot be undone.'"
|
||||
:expected-confirmation-text="'Delete Models'"
|
||||
delete-text="Delete all models"
|
||||
/>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { useStateStore } from "@/stores/StateStore";
|
||||
import type { Resolution } from "@/types/SettingTypes";
|
||||
import axios from "axios";
|
||||
|
||||
export const resolutionsAreEqual = (a: Resolution, b: Resolution) => {
|
||||
return a.height === b.height && a.width === b.width;
|
||||
@@ -18,3 +20,41 @@ export const parseJsonFile = async <T extends Record<string, any>>(file: File):
|
||||
fileReader.readAsText(file);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* A helper function to make POST requests using axios with standardized success and error handling.
|
||||
*
|
||||
* @param url The endpoint URL to which the POST request is sent
|
||||
* @param description A brief description of the request for users, e.g., "import object detection models".
|
||||
* @param data Payload to be sent in the POST request
|
||||
* @param config Optional axios request configuration
|
||||
* @returns A promise that resolves when the POST request is complete
|
||||
*/
|
||||
export const axiosPost = (url: string, description: string, data?: any, config?: any): Promise<void> => {
|
||||
return axios
|
||||
.post(url, data, config)
|
||||
.then(() => {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "Successfully dispatched the request to " + description + ". Waiting for backend to respond",
|
||||
color: "success"
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.response) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "The backend is unable to fulfill the request to " + description + ".",
|
||||
color: "error"
|
||||
});
|
||||
} else if (error.request) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "Error while trying to process the request to " + description + "! The backend didn't respond.",
|
||||
color: "error"
|
||||
});
|
||||
} else {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "An error occurred while trying to process the request to " + description + ".",
|
||||
color: "error"
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -39,6 +39,8 @@ interface StateStore {
|
||||
|
||||
snackbarData: {
|
||||
show: boolean;
|
||||
progressBar: number;
|
||||
progressBarColor: string;
|
||||
message: string;
|
||||
color: string;
|
||||
timeout: number;
|
||||
@@ -86,6 +88,8 @@ export const useStateStore = defineStore("state", {
|
||||
|
||||
snackbarData: {
|
||||
show: false,
|
||||
progressBar: -1,
|
||||
progressBarColor: "info",
|
||||
message: "No Message",
|
||||
color: "info",
|
||||
timeout: 2000
|
||||
@@ -158,11 +162,19 @@ export const useStateStore = defineStore("state", {
|
||||
updateDiscoveredCameras(data: VsmState) {
|
||||
this.vsmState = data;
|
||||
},
|
||||
showSnackbarMessage(data: { message: string; color: string; timeout?: number }) {
|
||||
showSnackbarMessage(data: {
|
||||
message: string;
|
||||
color: string;
|
||||
timeout?: number;
|
||||
progressBar?: number;
|
||||
progressBarColor?: string;
|
||||
}) {
|
||||
this.snackbarData = {
|
||||
show: true,
|
||||
progressBar: data.progressBar || -1,
|
||||
message: data.message,
|
||||
color: data.color,
|
||||
progressBarColor: data.progressBarColor || "",
|
||||
timeout: data.timeout || 2000
|
||||
};
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ export const useCameraSettingsStore = defineStore("cameraSettings", {
|
||||
},
|
||||
// This method only exists due to just how lazy I am and my dislike of consolidating the pipeline type enums (which mind you, suck as is)
|
||||
currentWebsocketPipelineType(): WebsocketPipelineType {
|
||||
return this.currentPipelineType - 2;
|
||||
return this.currentPipelineType - 3;
|
||||
},
|
||||
currentVideoFormat(): VideoFormat {
|
||||
return this.currentCameraSettings.validVideoFormats[this.currentPipelineSettings.cameraVideoModeIndex];
|
||||
@@ -76,6 +76,9 @@ export const useCameraSettingsStore = defineStore("cameraSettings", {
|
||||
isCalibrationMode(): boolean {
|
||||
return this.currentCameraSettings.currentPipelineIndex == WebsocketPipelineType.Calib3d;
|
||||
},
|
||||
isFocusMode(): boolean {
|
||||
return this.currentCameraSettings.currentPipelineIndex == WebsocketPipelineType.FocusCamera;
|
||||
},
|
||||
isCSICamera(): boolean {
|
||||
return this.currentCameraSettings.isCSICamera;
|
||||
},
|
||||
|
||||
@@ -70,6 +70,8 @@ export interface PipelineResult {
|
||||
sequenceID: number;
|
||||
fps: number;
|
||||
latency: number;
|
||||
// Focus pipeline
|
||||
focus?: number;
|
||||
targets: PhotonTarget[];
|
||||
// undefined if multitag failed or non-tag pipeline
|
||||
multitagResult?: MultitagResult;
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import type { WebsocketNumberPair } from "@/types/WebsocketDataTypes";
|
||||
import type { ObjectDetectionModelProperties } from "@/types/SettingTypes";
|
||||
|
||||
/**
|
||||
* The on-wire form of PipelineType.java (the enum is serialized with `ordinal()`)
|
||||
*/
|
||||
export enum PipelineType {
|
||||
DriverMode = 1,
|
||||
Reflective = 2,
|
||||
ColoredShape = 3,
|
||||
AprilTag = 4,
|
||||
Aruco = 5,
|
||||
ObjectDetection = 6
|
||||
DriverMode = 2,
|
||||
Reflective = 3,
|
||||
ColoredShape = 4,
|
||||
AprilTag = 5,
|
||||
Aruco = 6,
|
||||
ObjectDetection = 7
|
||||
}
|
||||
|
||||
export enum AprilTagFamily {
|
||||
|
||||
@@ -110,6 +110,7 @@ export interface IncomingWebsocketData {
|
||||
}
|
||||
|
||||
export enum WebsocketPipelineType {
|
||||
FocusCamera = -3,
|
||||
Calib3d = -2,
|
||||
DriverMode = -1,
|
||||
Reflective = 0,
|
||||
|
||||
@@ -7,16 +7,13 @@ import {
|
||||
PVCameraInfo,
|
||||
type PVCSICameraInfo,
|
||||
type PVFileCameraInfo,
|
||||
type PVUsbCameraInfo,
|
||||
type UiCameraConfiguration
|
||||
type PVUsbCameraInfo
|
||||
} from "@/types/SettingTypes";
|
||||
import { getResolutionString } from "@/lib/PhotonUtils";
|
||||
import { axiosPost, getResolutionString } from "@/lib/PhotonUtils";
|
||||
import PhotonCameraStream from "@/components/app/photon-camera-stream.vue";
|
||||
import PvInput from "@/components/common/pv-input.vue";
|
||||
import PvDeleteModal from "@/components/common/pv-delete-modal.vue";
|
||||
import PvCameraInfoCard from "@/components/common/pv-camera-info-card.vue";
|
||||
import axios from "axios";
|
||||
import PvCameraMatchCard from "@/components/common/pv-camera-match-card.vue";
|
||||
import type { WebsocketCameraSettingsUpdate } from "@/types/WebsocketDataTypes";
|
||||
import { useTheme } from "vuetify";
|
||||
|
||||
const theme = useTheme();
|
||||
@@ -28,33 +25,9 @@ const activateModule = (moduleUniqueName: string) => {
|
||||
if (activatingModule.value) return;
|
||||
activatingModule.value = true;
|
||||
|
||||
axios
|
||||
.post("/utils/activateMatchedCamera", { cameraUniqueName: moduleUniqueName })
|
||||
.then(() => {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "Camera activated successfully",
|
||||
color: "success"
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.response) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "The backend is unable to fulfil the request to activate this camera.",
|
||||
color: "error"
|
||||
});
|
||||
} else if (error.request) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "Error while trying to process the request! The backend didn't respond.",
|
||||
color: "error"
|
||||
});
|
||||
} else {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "An error occurred while trying to process the request.",
|
||||
color: "error"
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => (activatingModule.value = false));
|
||||
axiosPost("/utils/activateMatchedCamera", "activate a matched camera", {
|
||||
cameraUniqueName: moduleUniqueName
|
||||
}).finally(() => (activatingModule.value = false));
|
||||
};
|
||||
|
||||
const assigningCamera = ref(false);
|
||||
@@ -66,106 +39,29 @@ const assignCamera = (cameraInfo: PVCameraInfo) => {
|
||||
cameraInfo: cameraInfo
|
||||
};
|
||||
|
||||
axios
|
||||
.post("/utils/assignUnmatchedCamera", payload)
|
||||
.then(() => {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "Unmatched camera assigned successfully",
|
||||
color: "success"
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.response) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "The backend is unable to fulfil the request to assign this unmatched camera.",
|
||||
color: "error"
|
||||
});
|
||||
} else if (error.request) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "Error while trying to process the request! The backend didn't respond.",
|
||||
color: "error"
|
||||
});
|
||||
} else {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "An error occurred while trying to process the request.",
|
||||
color: "error"
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => (assigningCamera.value = false));
|
||||
axiosPost("/utils/assignUnmatchedCamera", "assign an unmatched camera", payload).finally(
|
||||
() => (assigningCamera.value = false)
|
||||
);
|
||||
};
|
||||
|
||||
const deactivatingModule = ref(false);
|
||||
const deactivateModule = (cameraUniqueName: string) => {
|
||||
if (deactivatingModule.value) return;
|
||||
deactivatingModule.value = true;
|
||||
axios
|
||||
.post("/utils/unassignCamera", { cameraUniqueName: cameraUniqueName })
|
||||
.then(() => {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "Camera deactivated successfully",
|
||||
color: "success"
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.response) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "The backend is unable to fulfil the request to deactivate this camera.",
|
||||
color: "error"
|
||||
});
|
||||
} else if (error.request) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "Error while trying to process the request! The backend didn't respond.",
|
||||
color: "error"
|
||||
});
|
||||
} else {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "An error occurred while trying to process the request.",
|
||||
color: "error"
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => (deactivatingModule.value = false));
|
||||
axiosPost("/utils/unassignCamera", "unassign a camera", { cameraUniqueName: cameraUniqueName }).finally(
|
||||
() => (deactivatingModule.value = false)
|
||||
);
|
||||
};
|
||||
|
||||
const deletingCamera = ref(false);
|
||||
const deleteThisCamera = (cameraName: string) => {
|
||||
if (deletingCamera.value) return;
|
||||
deletingCamera.value = true;
|
||||
const payload = {
|
||||
cameraUniqueName: cameraName
|
||||
};
|
||||
const confirmDeleteDialog = ref({ show: false, nickname: "", cameraUniqueName: "" });
|
||||
const deletingCamera = ref<string | null>(null);
|
||||
|
||||
axios
|
||||
.post("/utils/nukeOneCamera", payload)
|
||||
.then(() => {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "Camera deleted successfully",
|
||||
color: "success"
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.response) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "The backend is unable to fulfil the request to delete this camera.",
|
||||
color: "error"
|
||||
});
|
||||
} else if (error.request) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "Error while trying to process the request! The backend didn't respond.",
|
||||
color: "error"
|
||||
});
|
||||
} else {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "An error occurred while trying to process the request.",
|
||||
color: "error"
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
setCameraDeleting(null);
|
||||
deletingCamera.value = false;
|
||||
});
|
||||
const deleteThisCamera = (cameraUniqueName: string) => {
|
||||
if (deletingCamera.value) return;
|
||||
deletingCamera.value = cameraUniqueName;
|
||||
axiosPost("/utils/nukeOneCamera", "delete a camera", { cameraUniqueName: cameraUniqueName }).finally(() => {
|
||||
deletingCamera.value = null;
|
||||
});
|
||||
};
|
||||
|
||||
const cameraConnected = (uniquePath: string): boolean => {
|
||||
@@ -209,15 +105,6 @@ const setCameraView = (camera: PVCameraInfo | null, isConnected: boolean | null)
|
||||
viewingCamera.value = [camera, isConnected];
|
||||
};
|
||||
|
||||
const viewingDeleteCamera = ref(false);
|
||||
const cameraToDelete = ref<UiCameraConfiguration | WebsocketCameraSettingsUpdate | null>(null);
|
||||
const setCameraDeleting = (camera: UiCameraConfiguration | WebsocketCameraSettingsUpdate | null) => {
|
||||
yesDeleteMySettingsText.value = "";
|
||||
viewingDeleteCamera.value = camera !== null;
|
||||
cameraToDelete.value = camera;
|
||||
};
|
||||
const yesDeleteMySettingsText = ref("");
|
||||
|
||||
/**
|
||||
* Get the connection-type-specific camera info from the given PVCameraInfo object.
|
||||
*/
|
||||
@@ -373,8 +260,16 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
|
||||
class="pa-0"
|
||||
color="error"
|
||||
style="width: 100%"
|
||||
:loading="module.uniqueName === deletingCamera"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
@click="setCameraDeleting(module)"
|
||||
@click="
|
||||
() =>
|
||||
(confirmDeleteDialog = {
|
||||
show: true,
|
||||
nickname: module.nickname,
|
||||
cameraUniqueName: module.uniqueName
|
||||
})
|
||||
"
|
||||
>
|
||||
<v-icon size="x-large">mdi-trash-can-outline</v-icon>
|
||||
</v-btn>
|
||||
@@ -459,8 +354,16 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
|
||||
class="pa-0"
|
||||
color="error"
|
||||
style="width: 100%"
|
||||
:loading="module.uniqueName === deletingCamera"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
@click="setCameraDeleting(module)"
|
||||
@click="
|
||||
() =>
|
||||
(confirmDeleteDialog = {
|
||||
show: true,
|
||||
nickname: module.nickname,
|
||||
cameraUniqueName: module.uniqueName
|
||||
})
|
||||
"
|
||||
>
|
||||
<v-icon size="x-large">mdi-trash-can-outline</v-icon>
|
||||
</v-btn>
|
||||
@@ -564,43 +467,13 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- Camera delete modal -->
|
||||
<v-dialog v-model="viewingDeleteCamera" width="800">
|
||||
<v-card v-if="cameraToDelete !== null" class="dialog-container" color="surface" flat>
|
||||
<v-card-title> Delete {{ cameraToDelete.nickname }}? </v-card-title>
|
||||
<v-card-text class="pb-10px">
|
||||
Are you sure you want to delete "{{ cameraToDelete.nickname }}"? This cannot be undone.
|
||||
</v-card-text>
|
||||
<v-card-text class="pt-0 pb-10px">
|
||||
<pv-input
|
||||
v-model="yesDeleteMySettingsText"
|
||||
:label="'Type "' + cameraToDelete.nickname + '":'"
|
||||
:label-cols="6"
|
||||
:input-cols="6"
|
||||
/>
|
||||
</v-card-text>
|
||||
<v-card-actions class="pa-5 pt-0">
|
||||
<v-btn
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
color="primary"
|
||||
class="text-black"
|
||||
@click="cameraToDelete = null"
|
||||
>
|
||||
Cancel
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="error"
|
||||
:disabled="yesDeleteMySettingsText.toLowerCase() !== cameraToDelete.nickname.toLowerCase()"
|
||||
:loading="deletingCamera"
|
||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||
@click="deleteThisCamera(cameraToDelete.uniqueName)"
|
||||
>
|
||||
<v-icon start class="open-icon" size="large"> mdi-trash-can-outline </v-icon>
|
||||
<span class="open-label">Delete</span>
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
<pv-delete-modal
|
||||
v-model="confirmDeleteDialog.show"
|
||||
title="Delete Camera"
|
||||
:description="`Are you sure you want to delete the camera '${useCameraSettingsStore().currentCameraSettings.nickname}'? This action cannot be undone.`"
|
||||
:expected-confirmation-text="confirmDeleteDialog.nickname"
|
||||
:on-confirm="() => deleteThisCamera(confirmDeleteDialog.cameraUniqueName)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -12,8 +12,13 @@ const cameraViewType = computed<number[]>({
|
||||
// Only show the input stream in Color Picking Mode
|
||||
if (useStateStore().colorPickingMode) return [0];
|
||||
|
||||
// Only show the output stream in Driver Mode or Calibration Mode
|
||||
if (useCameraSettingsStore().isDriverMode || useCameraSettingsStore().isCalibrationMode) return [1];
|
||||
// Only show the output stream in Driver Mode or Calibration Mode or Focus Mode
|
||||
if (
|
||||
useCameraSettingsStore().isDriverMode ||
|
||||
useCameraSettingsStore().isCalibrationMode ||
|
||||
useCameraSettingsStore().isFocusMode
|
||||
)
|
||||
return [1];
|
||||
|
||||
const ret: number[] = [];
|
||||
if (useCameraSettingsStore().currentPipelineSettings.inputShouldShow) {
|
||||
|
||||
@@ -17,8 +17,13 @@ const cameraViewType = computed<number[]>({
|
||||
// Only show the input stream in Color Picking Mode
|
||||
if (useStateStore().colorPickingMode) return [0];
|
||||
|
||||
// Only show the output stream in Driver Mode or Calibration Mode
|
||||
if (useCameraSettingsStore().isDriverMode || useCameraSettingsStore().isCalibrationMode) return [1];
|
||||
// Only show the output stream in Driver Mode or Calibration Mode or Focus Mode
|
||||
if (
|
||||
useCameraSettingsStore().isDriverMode ||
|
||||
useCameraSettingsStore().isCalibrationMode ||
|
||||
useCameraSettingsStore().isFocusMode
|
||||
)
|
||||
return [1];
|
||||
|
||||
const ret: number[] = [];
|
||||
if (useCameraSettingsStore().currentPipelineSettings.inputShouldShow) {
|
||||
|
||||
@@ -24,6 +24,7 @@ import edu.wpi.first.cscore.UsbCameraInfo;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.opencv.core.Size;
|
||||
import org.photonvision.common.dataflow.websocket.UICameraConfiguration;
|
||||
import org.photonvision.common.logging.LogGroup;
|
||||
import org.photonvision.common.logging.Logger;
|
||||
@@ -189,6 +190,23 @@ public class CameraConfiguration {
|
||||
calibrations.add(calibration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a calibration from our list.
|
||||
*
|
||||
* @param calibration The calibration to remove
|
||||
*/
|
||||
public void removeCalibration(Size unrotatedImageSize) {
|
||||
logger.info("deleting calibration " + unrotatedImageSize);
|
||||
calibrations.stream()
|
||||
.filter(it -> it.unrotatedImageSize.equals(unrotatedImageSize))
|
||||
.findAny()
|
||||
.ifPresent(
|
||||
(it) -> {
|
||||
it.release();
|
||||
calibrations.remove(it);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* cscore will auto-reconnect to the camera path we give it. v4l does not guarantee that if i swap
|
||||
* cameras around, the same /dev/videoN ID will be assigned to that camera. So instead default to
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.photonvision.common.logging.Logger;
|
||||
import org.photonvision.common.util.SerializationUtils;
|
||||
import org.photonvision.vision.pipeline.result.CVPipelineResult;
|
||||
import org.photonvision.vision.pipeline.result.CalibrationPipelineResult;
|
||||
import org.photonvision.vision.pipeline.result.FocusPipelineResult;
|
||||
|
||||
public class UIDataPublisher implements CVPipelineResultConsumer {
|
||||
private static final Logger logger = new Logger(UIDataPublisher.class, LogGroup.VisionModule);
|
||||
@@ -77,6 +78,10 @@ public class UIDataPublisher implements CVPipelineResultConsumer {
|
||||
var uiMap = new HashMap<String, HashMap<String, Object>>();
|
||||
uiMap.put(uniqueName, dataMap);
|
||||
|
||||
if (result instanceof FocusPipelineResult focusResult) {
|
||||
dataMap.put("focus", focusResult.focus);
|
||||
}
|
||||
|
||||
DataChangeService.getInstance()
|
||||
.publishEvent(OutgoingUIEvent.wrappedOf("updatePipelineResult", uiMap));
|
||||
lastUIResultUpdateTime = now;
|
||||
|
||||
@@ -90,7 +90,7 @@ public class PhotonArucoDetector implements Releasable {
|
||||
// each detection has a Mat of corners
|
||||
Mat cornerMat = cornerMats.get(i);
|
||||
|
||||
// Aruco detection returns corners (BR, BL, TL, TR).
|
||||
// ArUco detection returns corners (BR, BL, TL, TR).
|
||||
// For parity with AprilTags and photonlib, we want (BL, BR, TR, TL).
|
||||
double[] xCorners = {
|
||||
cornerMat.get(0, 1)[0],
|
||||
|
||||
@@ -77,10 +77,9 @@ public class LibcameraGpuSettables extends VisionSourceSettables {
|
||||
videoModes.put(5, new FPSRatedVideoMode(PixelFormat.kUnknown, 3280 / 2, 2464 / 2, 15, 20, 1));
|
||||
videoModes.put(6, new FPSRatedVideoMode(PixelFormat.kUnknown, 3280 / 4, 2464 / 4, 15, 20, 1));
|
||||
} else if (sensorModel == LibCameraJNI.SensorModel.OV9281) {
|
||||
videoModes.put(0, new FPSRatedVideoMode(PixelFormat.kUnknown, 320, 240, 30, 30, .39));
|
||||
videoModes.put(1, new FPSRatedVideoMode(PixelFormat.kUnknown, 1280 / 2, 800 / 2, 60, 60, 1));
|
||||
videoModes.put(2, new FPSRatedVideoMode(PixelFormat.kUnknown, 640, 480, 65, 90, .39));
|
||||
videoModes.put(3, new FPSRatedVideoMode(PixelFormat.kUnknown, 1280, 800, 60, 60, 1));
|
||||
// Taken from https://www.ovt.com/wp-content/uploads/2022/01/OV9281-OV9282-PB-v1.3-WEB.pdf
|
||||
videoModes.put(0, new FPSRatedVideoMode(PixelFormat.kUnknown, 640, 400, 120, 240, 1));
|
||||
videoModes.put(1, new FPSRatedVideoMode(PixelFormat.kUnknown, 1280, 800, 120, 120, 1));
|
||||
|
||||
} else {
|
||||
if (sensorModel == LibCameraJNI.SensorModel.IMX477) {
|
||||
|
||||
@@ -280,14 +280,14 @@ public class FindBoardCornersPipe
|
||||
}
|
||||
board.matchImagePoints(detectedCornersList, detectedIds, objPoints, imgPoints);
|
||||
|
||||
// draw the charuco board
|
||||
// Draw the ChArUco board
|
||||
Objdetect.drawDetectedCornersCharuco(
|
||||
outFrame, detectedCorners, detectedIds, new Scalar(0, 0, 255)); // Red Text
|
||||
|
||||
imgPoints.copyTo(outBoardCorners);
|
||||
objPoints.copyTo(objPts);
|
||||
|
||||
// Since charuco can still detect without the whole board we need to send "fake" (all
|
||||
// Since ChaArUco can still detect without the whole board we need to send "fake" (all
|
||||
// values less than zero) points and then tell it to ignore that corner by setting the
|
||||
// corresponding level to -1. Calibrate3dPipe deals with piping this into the correct format
|
||||
// for each backend
|
||||
@@ -321,7 +321,7 @@ public class FindBoardCornersPipe
|
||||
detectedCorners.release();
|
||||
detectedIds.release();
|
||||
|
||||
} else { // If not Charuco then do chessboard
|
||||
} else { // If not ChArUco then do chessboard
|
||||
// Reduce the image size to be much more manageable
|
||||
// Note that opencv will copy the frame if no resize is requested; we can skip
|
||||
// this since we
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (C) Photon Vision.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.vision.pipe.impl;
|
||||
|
||||
import org.opencv.core.Core;
|
||||
import org.opencv.core.CvType;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.MatOfDouble;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
import org.photonvision.vision.pipe.CVPipe;
|
||||
|
||||
public class FocusPipe extends CVPipe<Mat, FocusPipe.FocusResult, FocusPipe.FocusParams> {
|
||||
private double maxVariance = 0.0;
|
||||
|
||||
@Override
|
||||
protected FocusResult process(Mat in) {
|
||||
var outputMat = new Mat();
|
||||
|
||||
Imgproc.Laplacian(in, outputMat, CvType.CV_64F, 3);
|
||||
|
||||
var mean = new MatOfDouble();
|
||||
var stddev = new MatOfDouble();
|
||||
Core.meanStdDev(outputMat, mean, stddev);
|
||||
var sd = stddev.get(0, 0)[0];
|
||||
var variance = sd * sd;
|
||||
|
||||
return new FocusResult(outputMat, variance);
|
||||
}
|
||||
|
||||
public static class FocusResult {
|
||||
public final Mat frame;
|
||||
public final double variance;
|
||||
|
||||
public FocusResult(Mat frame, double variance) {
|
||||
this.frame = frame;
|
||||
this.variance = variance;
|
||||
}
|
||||
}
|
||||
|
||||
public static class FocusParams {}
|
||||
}
|
||||
@@ -73,13 +73,13 @@ public class AprilTagPipeline extends CVPipeline<CVPipelineResult, AprilTagPipel
|
||||
settings.threads = Math.max(1, settings.threads);
|
||||
|
||||
// for now, hard code tag width based on enum value
|
||||
// 2023/other: best guess is 6in
|
||||
double tagWidth = Units.inchesToMeters(6);
|
||||
TargetModel tagModel = TargetModel.kAprilTag16h5;
|
||||
if (settings.tagFamily == AprilTagFamily.kTag36h11) {
|
||||
// 2024 tag, 6.5in
|
||||
tagWidth = Units.inchesToMeters(6.5);
|
||||
tagModel = TargetModel.kAprilTag36h11;
|
||||
// From 2024 best guess is 6.5
|
||||
double tagWidth = Units.inchesToMeters(6.5);
|
||||
TargetModel tagModel = TargetModel.kAprilTag36h11;
|
||||
if (settings.tagFamily == AprilTagFamily.kTag16h5) {
|
||||
// 2023 tag, 6in
|
||||
tagWidth = Units.inchesToMeters(6);
|
||||
tagModel = TargetModel.kAprilTag16h5;
|
||||
}
|
||||
|
||||
var config = new AprilTagDetector.Config();
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (C) Photon Vision.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.vision.pipeline;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
import org.photonvision.common.util.math.MathUtils;
|
||||
import org.photonvision.vision.frame.Frame;
|
||||
import org.photonvision.vision.frame.FrameThresholdType;
|
||||
import org.photonvision.vision.opencv.CVMat;
|
||||
import org.photonvision.vision.pipe.impl.CalculateFPSPipe;
|
||||
import org.photonvision.vision.pipe.impl.FocusPipe;
|
||||
import org.photonvision.vision.pipe.impl.ResizeImagePipe;
|
||||
import org.photonvision.vision.pipeline.result.FocusPipelineResult;
|
||||
|
||||
public class FocusPipeline extends CVPipeline<FocusPipelineResult, FocusPipelineSettings> {
|
||||
private final FocusPipe focusPipe = new FocusPipe();
|
||||
private final CalculateFPSPipe calculateFPSPipe = new CalculateFPSPipe();
|
||||
private final ResizeImagePipe resizeImagePipe = new ResizeImagePipe();
|
||||
|
||||
private static final FrameThresholdType PROCESSING_TYPE = FrameThresholdType.NONE;
|
||||
|
||||
public FocusPipeline() {
|
||||
super(PROCESSING_TYPE);
|
||||
settings = new FocusPipelineSettings();
|
||||
}
|
||||
|
||||
public FocusPipeline(FocusPipelineSettings settings) {
|
||||
super(PROCESSING_TYPE);
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setPipeParamsImpl() {
|
||||
resizeImagePipe.setParams(
|
||||
new ResizeImagePipe.ResizeImageParams(settings.streamingFrameDivisor));
|
||||
}
|
||||
|
||||
@Override
|
||||
public FocusPipelineResult process(Frame frame, FocusPipelineSettings settings) {
|
||||
long totalNanos = 0;
|
||||
|
||||
var inputMat = frame.colorImage.getMat();
|
||||
boolean emptyIn = inputMat.empty();
|
||||
Mat displayMat = new Mat();
|
||||
double variance = 0.0;
|
||||
|
||||
if (!emptyIn) {
|
||||
totalNanos += resizeImagePipe.run(inputMat).nanosElapsed;
|
||||
|
||||
var focusResult = focusPipe.run(inputMat);
|
||||
totalNanos += focusResult.nanosElapsed;
|
||||
variance = focusResult.output.variance;
|
||||
displayMat = focusResult.output.frame;
|
||||
}
|
||||
|
||||
var fpsResult = calculateFPSPipe.run(null);
|
||||
var fps = fpsResult.output;
|
||||
|
||||
var processedCVMat = new CVMat(displayMat);
|
||||
|
||||
return new FocusPipelineResult(
|
||||
frame.sequenceID,
|
||||
MathUtils.nanosToMillis(totalNanos),
|
||||
fps,
|
||||
new Frame(
|
||||
frame.sequenceID,
|
||||
frame.colorImage,
|
||||
processedCVMat,
|
||||
frame.type,
|
||||
frame.frameStaticProperties),
|
||||
variance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
// we never actually need to give resources up since pipelinemanager only makes
|
||||
// one of us
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) Photon Vision.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.vision.pipeline;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
import org.photonvision.vision.processes.PipelineManager;
|
||||
|
||||
@JsonTypeName("FocusPipelineSettings")
|
||||
public class FocusPipelineSettings extends CVPipelineSettings {
|
||||
public FocusPipelineSettings() {
|
||||
super();
|
||||
pipelineNickname = "Focus Camera";
|
||||
pipelineIndex = PipelineManager.FOCUS_INDEX;
|
||||
pipelineType = PipelineType.FocusCamera;
|
||||
inputShouldShow = true;
|
||||
cameraAutoExposure = true;
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ package org.photonvision.vision.pipeline;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public enum PipelineType {
|
||||
FocusCamera(-3, FocusPipeline.class),
|
||||
Calib3d(-2, Calibrate3dPipeline.class),
|
||||
DriverMode(-1, DriverModePipeline.class),
|
||||
Reflective(0, ReflectivePipeline.class),
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (C) Photon Vision.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.vision.pipeline.result;
|
||||
|
||||
import java.util.List;
|
||||
import org.photonvision.vision.frame.Frame;
|
||||
|
||||
public class FocusPipelineResult extends CVPipelineResult {
|
||||
public final double focus;
|
||||
|
||||
public FocusPipelineResult(
|
||||
long seq, double latencyNanos, double fps, Frame outputFrame, double focus) {
|
||||
super(seq, latencyNanos, fps, List.of(), outputFrame);
|
||||
this.focus = focus;
|
||||
}
|
||||
}
|
||||
@@ -36,10 +36,12 @@ public class PipelineManager {
|
||||
private static final Logger logger = new Logger(PipelineManager.class, LogGroup.VisionModule);
|
||||
|
||||
public static final int DRIVERMODE_INDEX = -1;
|
||||
public static final int FOCUS_INDEX = -3;
|
||||
public static final int CAL_3D_INDEX = -2;
|
||||
|
||||
protected final List<CVPipelineSettings> userPipelineSettings;
|
||||
protected final Calibrate3dPipeline calibration3dPipeline;
|
||||
protected final FocusPipeline focusPipeline = new FocusPipeline();
|
||||
protected final DriverModePipeline driverModePipeline = new DriverModePipeline();
|
||||
|
||||
/** Index of the currently active pipeline. Defaults to 0. */
|
||||
@@ -93,6 +95,7 @@ public class PipelineManager {
|
||||
return switch (index) {
|
||||
case DRIVERMODE_INDEX -> driverModePipeline.getSettings();
|
||||
case CAL_3D_INDEX -> calibration3dPipeline.getSettings();
|
||||
case FOCUS_INDEX -> focusPipeline.getSettings();
|
||||
default -> {
|
||||
for (var setting : userPipelineSettings) {
|
||||
if (setting.pipelineIndex == index) yield setting;
|
||||
@@ -112,6 +115,7 @@ public class PipelineManager {
|
||||
return switch (index) {
|
||||
case DRIVERMODE_INDEX -> driverModePipeline.getSettings().pipelineNickname;
|
||||
case CAL_3D_INDEX -> calibration3dPipeline.getSettings().pipelineNickname;
|
||||
case FOCUS_INDEX -> focusPipeline.getSettings().pipelineNickname;
|
||||
default -> {
|
||||
for (var setting : userPipelineSettings) {
|
||||
if (setting.pipelineIndex == index) yield setting.pipelineNickname;
|
||||
@@ -153,6 +157,7 @@ public class PipelineManager {
|
||||
return switch (currentPipelineIndex) {
|
||||
case CAL_3D_INDEX -> calibration3dPipeline;
|
||||
case DRIVERMODE_INDEX -> driverModePipeline;
|
||||
case FOCUS_INDEX -> focusPipeline;
|
||||
// Just return the current user pipeline, we're not on a built-in one
|
||||
default -> currentUserPipeline;
|
||||
};
|
||||
@@ -253,7 +258,7 @@ public class PipelineManager {
|
||||
new AprilTagPipeline((AprilTagPipelineSettings) desiredPipelineSettings);
|
||||
}
|
||||
case Aruco -> {
|
||||
logger.debug("Creating Aruco Pipeline");
|
||||
logger.debug("Creating ArUco Pipeline");
|
||||
currentUserPipeline = new ArucoPipeline((ArucoPipelineSettings) desiredPipelineSettings);
|
||||
}
|
||||
case ObjectDetection -> {
|
||||
@@ -261,7 +266,7 @@ public class PipelineManager {
|
||||
currentUserPipeline =
|
||||
new ObjectDetectionPipeline((ObjectDetectionPipelineSettings) desiredPipelineSettings);
|
||||
}
|
||||
case Calib3d, DriverMode -> {}
|
||||
case Calib3d, DriverMode, FocusCamera -> {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,7 +340,7 @@ public class PipelineManager {
|
||||
case AprilTag -> new AprilTagPipelineSettings();
|
||||
case Aruco -> new ArucoPipelineSettings();
|
||||
case ObjectDetection -> new ObjectDetectionPipelineSettings();
|
||||
case Calib3d, DriverMode -> {
|
||||
case Calib3d, DriverMode, FocusCamera -> {
|
||||
logger.error("Got invalid pipeline type: " + type);
|
||||
yield null;
|
||||
}
|
||||
|
||||
@@ -680,6 +680,16 @@ public class VisionModule {
|
||||
saveAndBroadcastAll();
|
||||
}
|
||||
|
||||
public void removeCalibrationFromConfig(Size unrotatedImageSize) {
|
||||
if (unrotatedImageSize != null) {
|
||||
visionSource.getSettables().removeCalibration(unrotatedImageSize);
|
||||
} else {
|
||||
logger.error("Got null size?");
|
||||
}
|
||||
|
||||
saveAndBroadcastAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add/remove quirks from the camera we're controlling
|
||||
*
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.photonvision.vision.processes;
|
||||
|
||||
import edu.wpi.first.cscore.VideoMode;
|
||||
import java.util.HashMap;
|
||||
import org.opencv.core.Size;
|
||||
import org.photonvision.common.configuration.CameraConfiguration;
|
||||
import org.photonvision.common.logging.LogGroup;
|
||||
import org.photonvision.common.logging.Logger;
|
||||
@@ -120,6 +121,11 @@ public abstract class VisionSourceSettables {
|
||||
calculateFrameStaticProps();
|
||||
}
|
||||
|
||||
public void removeCalibration(Size unrotatedImageSize) {
|
||||
configuration.removeCalibration(unrotatedImageSize);
|
||||
calculateFrameStaticProps();
|
||||
}
|
||||
|
||||
protected void calculateFrameStaticProps() {
|
||||
var videoMode = getCurrentVideoMode();
|
||||
this.frameStaticProperties =
|
||||
|
||||
@@ -25,6 +25,7 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.photonvision.common.util.TestUtils;
|
||||
|
||||
public class NetworkConfigTest {
|
||||
@Test
|
||||
@@ -39,13 +40,13 @@ public class NetworkConfigTest {
|
||||
@Test
|
||||
public void testDeserializeTeamNumberOrNtServerAddress() {
|
||||
{
|
||||
var folder = Path.of("test-resources/network-old-team-number");
|
||||
var folder = TestUtils.getResourcesFolderPath(true).resolve("network-old-team-number");
|
||||
var configMgr = new ConfigManager(folder, new LegacyConfigProvider(folder));
|
||||
configMgr.load();
|
||||
assertEquals("9999", configMgr.getConfig().getNetworkConfig().ntServerAddress);
|
||||
}
|
||||
{
|
||||
var folder = Path.of("test-resources/network-new-team-number");
|
||||
var folder = TestUtils.getResourcesFolderPath(true).resolve("network-new-team-number");
|
||||
var configMgr = new ConfigManager(folder, new LegacyConfigProvider(folder));
|
||||
configMgr.load();
|
||||
assertEquals("9999", configMgr.getConfig().getNetworkConfig().ntServerAddress);
|
||||
|
||||
@@ -109,7 +109,6 @@ public class AprilTagTest {
|
||||
pipeline.getSettings().solvePNPEnabled = true;
|
||||
pipeline.getSettings().cornerDetectionAccuracyPercentage = 4;
|
||||
pipeline.getSettings().cornerDetectionUseConvexHulls = true;
|
||||
pipeline.getSettings().targetModel = TargetModel.kAprilTag6p5in_36h11;
|
||||
pipeline.getSettings().tagFamily = AprilTagFamily.kTag16h5;
|
||||
|
||||
var frameProvider =
|
||||
|
||||
20
photon-lib/py/docs/Makefile
Normal file
20
photon-lib/py/docs/Makefile
Normal file
@@ -0,0 +1,20 @@
|
||||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line, and also
|
||||
# from the environment for the first two.
|
||||
SPHINXOPTS ?=
|
||||
SPHINXBUILD ?= sphinx-build
|
||||
SOURCEDIR = source
|
||||
BUILDDIR = build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
5
photon-lib/py/docs/_stubs/wpimath/__init__.py
Normal file
5
photon-lib/py/docs/_stubs/wpimath/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
# Minimal wpimath stub for Sphinx docs build
|
||||
from . import geometry
|
||||
from . import units
|
||||
|
||||
__all__ = ["geometry", "units"]
|
||||
4
photon-lib/py/docs/_stubs/wpimath/_init__wpimath.py
Normal file
4
photon-lib/py/docs/_stubs/wpimath/_init__wpimath.py
Normal file
@@ -0,0 +1,4 @@
|
||||
# Stub module to match wpimath compiled module names
|
||||
# This file exists so imports like `wpimath._init__wpimath` succeed during docs build.
|
||||
|
||||
# no-op
|
||||
130
photon-lib/py/docs/_stubs/wpimath/geometry.py
Normal file
130
photon-lib/py/docs/_stubs/wpimath/geometry.py
Normal file
@@ -0,0 +1,130 @@
|
||||
# Minimal geometry stubs for Sphinx documentation
|
||||
|
||||
class Rotation3d:
|
||||
def __init__(self, roll=0.0, pitch=0.0, yaw=0.0):
|
||||
# store yaw as the primary rotation for simple stubs
|
||||
self.roll = roll
|
||||
self.pitch = pitch
|
||||
self.yaw = yaw
|
||||
|
||||
def toRotation2d(self):
|
||||
# convert yaw to a Rotation2d for simple compatibility in docs build
|
||||
return Rotation2d(self.yaw)
|
||||
|
||||
class Translation3d:
|
||||
def __init__(self, x=0.0, y=0.0, z=0.0):
|
||||
# Support both (x, y, z) and (distance, Rotation3d) forms used by the real wpimath
|
||||
# If y is a Rotation3d, compute a point at 'distance' along its yaw/pitch
|
||||
try:
|
||||
from math import cos, sin
|
||||
except Exception:
|
||||
def cos(x):
|
||||
return x
|
||||
def sin(x):
|
||||
return x
|
||||
|
||||
if hasattr(y, "yaw") and hasattr(y, "pitch"):
|
||||
# interpret constructor as Translation3d(distance, Rotation3d)
|
||||
distance = float(x)
|
||||
pitch = float(getattr(y, "pitch", 0.0))
|
||||
yaw = float(getattr(y, "yaw", 0.0))
|
||||
# approximate spherical -> cartesian
|
||||
self._x = distance * cos(pitch) * cos(yaw)
|
||||
self._y = distance * cos(pitch) * sin(yaw)
|
||||
self._z = distance * sin(pitch)
|
||||
else:
|
||||
self._x = float(x)
|
||||
self._y = float(y)
|
||||
self._z = float(z)
|
||||
|
||||
def X(self):
|
||||
return self._x
|
||||
|
||||
def Y(self):
|
||||
return self._y
|
||||
|
||||
def Z(self):
|
||||
return self._z
|
||||
|
||||
class Pose3d:
|
||||
def __init__(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
class Rotation2d:
|
||||
def __init__(self, *args):
|
||||
# Accept several initialization forms used in the real wpimath Rotation2d
|
||||
# - Rotation2d(angle)
|
||||
# - Rotation2d(fx, xOffset) used by SimCameraProperties.getPixelYaw
|
||||
if len(args) == 0:
|
||||
self._angle = 0.0
|
||||
elif len(args) == 1:
|
||||
self._angle = float(args[0])
|
||||
else:
|
||||
# fallback: when called with fx, xOffset, approximate angle as 0.0
|
||||
self._angle = 0.0
|
||||
|
||||
def degrees(self):
|
||||
from math import degrees
|
||||
|
||||
return degrees(self._angle)
|
||||
|
||||
def radians(self):
|
||||
return float(self._angle)
|
||||
|
||||
def __add__(self, other):
|
||||
# allow Rotation2d + Rotation2d or Rotation2d + numeric
|
||||
if hasattr(other, "_angle"):
|
||||
return Rotation2d(self._angle + float(other._angle))
|
||||
try:
|
||||
return Rotation2d(self._angle + float(other))
|
||||
except Exception:
|
||||
return NotImplemented
|
||||
|
||||
def __radd__(self, other):
|
||||
# numeric + Rotation2d
|
||||
return self.__add__(other)
|
||||
|
||||
def __sub__(self, other):
|
||||
if hasattr(other, "_angle"):
|
||||
return Rotation2d(self._angle - float(other._angle))
|
||||
try:
|
||||
return Rotation2d(self._angle - float(other))
|
||||
except Exception:
|
||||
return NotImplemented
|
||||
|
||||
def __neg__(self):
|
||||
return Rotation2d(-self._angle)
|
||||
|
||||
def __repr__(self):
|
||||
return f"Rotation2d({self._angle})"
|
||||
|
||||
class Translation2d:
|
||||
def __init__(self, x=0.0, y=0.0):
|
||||
self._x = float(x)
|
||||
self._y = float(y)
|
||||
|
||||
def X(self):
|
||||
return self._x
|
||||
|
||||
def Y(self):
|
||||
return self._y
|
||||
|
||||
class Pose2d:
|
||||
def __init__(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "Pose2d()"
|
||||
|
||||
|
||||
class Transform3d:
|
||||
def __init__(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
class Quaternion:
|
||||
def __init__(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
# Expose names commonly used by photonlibpy
|
||||
__all__ = ["Rotation3d", "Translation3d", "Pose3d", "Rotation2d", "Translation2d", "Pose2d"]
|
||||
12
photon-lib/py/docs/_stubs/wpimath/interpolation.py
Normal file
12
photon-lib/py/docs/_stubs/wpimath/interpolation.py
Normal file
@@ -0,0 +1,12 @@
|
||||
# Minimal interpolation stub for docs
|
||||
class TimeInterpolatableRotation2dBuffer:
|
||||
def __init__(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def addSample(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def sample(self, *args, **kwargs):
|
||||
return None
|
||||
|
||||
__all__ = ["TimeInterpolatableRotation2dBuffer"]
|
||||
@@ -0,0 +1,3 @@
|
||||
from ._interpolation import *
|
||||
|
||||
__all__ = ["TimeInterpolatableRotation2dBuffer"]
|
||||
@@ -0,0 +1,25 @@
|
||||
# Minimal interpolation submodule stub for docs
|
||||
class TimeInterpolatableRotation2dBuffer:
|
||||
def __init__(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def addSample(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def sample(self, *args, **kwargs):
|
||||
return None
|
||||
|
||||
|
||||
class TimeInterpolatablePose3dBuffer:
|
||||
def __init__(self, *args, **kwargs):
|
||||
# buffer of Pose3d-like objects for docs import
|
||||
pass
|
||||
|
||||
def addSample(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def sample(self, *args, **kwargs):
|
||||
return None
|
||||
|
||||
|
||||
__all__ = ["TimeInterpolatableRotation2dBuffer", "TimeInterpolatablePose3dBuffer"]
|
||||
31
photon-lib/py/docs/_stubs/wpimath/units.py
Normal file
31
photon-lib/py/docs/_stubs/wpimath/units.py
Normal file
@@ -0,0 +1,31 @@
|
||||
"""Minimal wpimath.units stub for documentation builds."""
|
||||
|
||||
def degreesToRadians(deg: float) -> float:
|
||||
from math import pi
|
||||
|
||||
return deg * (pi / 180.0)
|
||||
|
||||
|
||||
# Represent seconds as a float alias for annotations
|
||||
seconds = float
|
||||
|
||||
__all__ = ["degreesToRadians", "seconds"]
|
||||
|
||||
# Common unit aliases used in type annotations in WPILib stubs
|
||||
meters = float
|
||||
meters_per_second = float
|
||||
meters_per_second_squared = float
|
||||
kilograms = float
|
||||
kilogram_square_meters = float
|
||||
|
||||
__all__.extend([
|
||||
"meters",
|
||||
"meters_per_second",
|
||||
"meters_per_second_squared",
|
||||
"kilograms",
|
||||
"kilogram_square_meters",
|
||||
"hertz",
|
||||
])
|
||||
|
||||
# frequency
|
||||
hertz = float
|
||||
35
photon-lib/py/docs/make.bat
Normal file
35
photon-lib/py/docs/make.bat
Normal file
@@ -0,0 +1,35 @@
|
||||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set SOURCEDIR=source
|
||||
set BUILDDIR=build
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.https://www.sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
|
||||
:end
|
||||
popd
|
||||
3
photon-lib/py/docs/requirements.txt
Normal file
3
photon-lib/py/docs/requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
sphinx==7.2.6
|
||||
sphinx-autodoc-typehints==1.25.2
|
||||
sphinx-rtd-theme==1.3.0
|
||||
53
photon-lib/py/docs/source/conf.py
Normal file
53
photon-lib/py/docs/source/conf.py
Normal file
@@ -0,0 +1,53 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
# This adds the 'py/' directory to the Python path
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")))
|
||||
|
||||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# For the full list of built-in configuration values, see the documentation:
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
||||
|
||||
project = "PhotonVision"
|
||||
copyright = "2025, Matt Morley, Banks Troutman"
|
||||
author = "Matt Morley, Banks Troutman"
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
||||
|
||||
extensions = [
|
||||
"sphinx.ext.autodoc",
|
||||
"sphinx.ext.napoleon", # for Google/NumPy docstrings
|
||||
"sphinx_autodoc_typehints", # for type hints in docs
|
||||
]
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(
|
||||
0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "_stubs"))
|
||||
) # add docs stubs first so they shadow unavailable third-party packages
|
||||
|
||||
sys.path.insert(
|
||||
0, os.path.abspath("../../photonlibpy")
|
||||
) # adjust based on your project layout
|
||||
# Mock imports that aren't available in the docs build environment so autodoc
|
||||
# can import the local modules even if optional runtime deps (like wpimath)
|
||||
# aren't installed. Add other names here if you see warnings for missing
|
||||
# third-party packages during the build.
|
||||
autodoc_mock_imports = [
|
||||
"wpilib",
|
||||
]
|
||||
templates_path = ["_templates"]
|
||||
exclude_patterns = []
|
||||
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
||||
|
||||
html_theme = "sphinx_rtd_theme"
|
||||
html_static_path = ["_static"]
|
||||
17
photon-lib/py/docs/source/index.rst
Normal file
17
photon-lib/py/docs/source/index.rst
Normal file
@@ -0,0 +1,17 @@
|
||||
.. PhotonVision documentation master file, created by
|
||||
sphinx-quickstart on Fri May 9 12:16:37 2025.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
PhotonLib Python Documentation
|
||||
===============================
|
||||
|
||||
|
||||
The main documentation for PhotonVision can be found at `photonvision.org <https://photonvision.org>`_.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Contents:
|
||||
|
||||
modules
|
||||
photonlibpy
|
||||
7
photon-lib/py/docs/source/modules.rst
Normal file
7
photon-lib/py/docs/source/modules.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
photonlibpy
|
||||
===========
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 4
|
||||
|
||||
photonlibpy
|
||||
53
photon-lib/py/docs/source/photonlibpy.estimation.rst
Normal file
53
photon-lib/py/docs/source/photonlibpy.estimation.rst
Normal file
@@ -0,0 +1,53 @@
|
||||
photonlibpy.estimation package
|
||||
==============================
|
||||
|
||||
Submodules
|
||||
----------
|
||||
|
||||
photonlibpy.estimation.cameraTargetRelation module
|
||||
--------------------------------------------------
|
||||
|
||||
.. automodule:: photonlibpy.estimation.cameraTargetRelation
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
photonlibpy.estimation.openCVHelp module
|
||||
----------------------------------------
|
||||
|
||||
.. automodule:: photonlibpy.estimation.openCVHelp
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
photonlibpy.estimation.rotTrlTransform3d module
|
||||
-----------------------------------------------
|
||||
|
||||
.. automodule:: photonlibpy.estimation.rotTrlTransform3d
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
photonlibpy.estimation.targetModel module
|
||||
-----------------------------------------
|
||||
|
||||
.. automodule:: photonlibpy.estimation.targetModel
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
photonlibpy.estimation.visionEstimation module
|
||||
----------------------------------------------
|
||||
|
||||
.. automodule:: photonlibpy.estimation.visionEstimation
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Module contents
|
||||
---------------
|
||||
|
||||
.. automodule:: photonlibpy.estimation
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
61
photon-lib/py/docs/source/photonlibpy.generated.rst
Normal file
61
photon-lib/py/docs/source/photonlibpy.generated.rst
Normal file
@@ -0,0 +1,61 @@
|
||||
photonlibpy.generated package
|
||||
=============================
|
||||
|
||||
Submodules
|
||||
----------
|
||||
|
||||
photonlibpy.generated.MultiTargetPNPResultSerde module
|
||||
------------------------------------------------------
|
||||
|
||||
.. automodule:: photonlibpy.generated.MultiTargetPNPResultSerde
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
photonlibpy.generated.PhotonPipelineMetadataSerde module
|
||||
--------------------------------------------------------
|
||||
|
||||
.. automodule:: photonlibpy.generated.PhotonPipelineMetadataSerde
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
photonlibpy.generated.PhotonPipelineResultSerde module
|
||||
------------------------------------------------------
|
||||
|
||||
.. automodule:: photonlibpy.generated.PhotonPipelineResultSerde
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
photonlibpy.generated.PhotonTrackedTargetSerde module
|
||||
-----------------------------------------------------
|
||||
|
||||
.. automodule:: photonlibpy.generated.PhotonTrackedTargetSerde
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
photonlibpy.generated.PnpResultSerde module
|
||||
-------------------------------------------
|
||||
|
||||
.. automodule:: photonlibpy.generated.PnpResultSerde
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
photonlibpy.generated.TargetCornerSerde module
|
||||
----------------------------------------------
|
||||
|
||||
.. automodule:: photonlibpy.generated.TargetCornerSerde
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Module contents
|
||||
---------------
|
||||
|
||||
.. automodule:: photonlibpy.generated
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
21
photon-lib/py/docs/source/photonlibpy.networktables.rst
Normal file
21
photon-lib/py/docs/source/photonlibpy.networktables.rst
Normal file
@@ -0,0 +1,21 @@
|
||||
photonlibpy.networktables package
|
||||
=================================
|
||||
|
||||
Submodules
|
||||
----------
|
||||
|
||||
photonlibpy.networktables.NTTopicSet module
|
||||
-------------------------------------------
|
||||
|
||||
.. automodule:: photonlibpy.networktables.NTTopicSet
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Module contents
|
||||
---------------
|
||||
|
||||
.. automodule:: photonlibpy.networktables
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
58
photon-lib/py/docs/source/photonlibpy.rst
Normal file
58
photon-lib/py/docs/source/photonlibpy.rst
Normal file
@@ -0,0 +1,58 @@
|
||||
photonlibpy package
|
||||
===================
|
||||
|
||||
Subpackages
|
||||
-----------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 4
|
||||
|
||||
photonlibpy.estimation
|
||||
photonlibpy.generated
|
||||
photonlibpy.networktables
|
||||
photonlibpy.simulation
|
||||
photonlibpy.targeting
|
||||
photonlibpy.timesync
|
||||
|
||||
Submodules
|
||||
----------
|
||||
|
||||
photonlibpy.estimatedRobotPose module
|
||||
-------------------------------------
|
||||
|
||||
.. automodule:: photonlibpy.estimatedRobotPose
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
photonlibpy.packet module
|
||||
-------------------------
|
||||
|
||||
.. automodule:: photonlibpy.packet
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
photonlibpy.photonCamera module
|
||||
-------------------------------
|
||||
|
||||
.. automodule:: photonlibpy.photonCamera
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
photonlibpy.photonPoseEstimator module
|
||||
--------------------------------------
|
||||
|
||||
.. automodule:: photonlibpy.photonPoseEstimator
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Module contents
|
||||
---------------
|
||||
|
||||
.. automodule:: photonlibpy
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
53
photon-lib/py/docs/source/photonlibpy.simulation.rst
Normal file
53
photon-lib/py/docs/source/photonlibpy.simulation.rst
Normal file
@@ -0,0 +1,53 @@
|
||||
photonlibpy.simulation package
|
||||
==============================
|
||||
|
||||
Submodules
|
||||
----------
|
||||
|
||||
photonlibpy.simulation.photonCameraSim module
|
||||
---------------------------------------------
|
||||
|
||||
.. automodule:: photonlibpy.simulation.photonCameraSim
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
photonlibpy.simulation.simCameraProperties module
|
||||
-------------------------------------------------
|
||||
|
||||
.. automodule:: photonlibpy.simulation.simCameraProperties
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
photonlibpy.simulation.videoSimUtil module
|
||||
------------------------------------------
|
||||
|
||||
.. automodule:: photonlibpy.simulation.videoSimUtil
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
photonlibpy.simulation.visionSystemSim module
|
||||
---------------------------------------------
|
||||
|
||||
.. automodule:: photonlibpy.simulation.visionSystemSim
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
photonlibpy.simulation.visionTargetSim module
|
||||
---------------------------------------------
|
||||
|
||||
.. automodule:: photonlibpy.simulation.visionTargetSim
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Module contents
|
||||
---------------
|
||||
|
||||
.. automodule:: photonlibpy.simulation
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
45
photon-lib/py/docs/source/photonlibpy.targeting.rst
Normal file
45
photon-lib/py/docs/source/photonlibpy.targeting.rst
Normal file
@@ -0,0 +1,45 @@
|
||||
photonlibpy.targeting package
|
||||
=============================
|
||||
|
||||
Submodules
|
||||
----------
|
||||
|
||||
photonlibpy.targeting.TargetCorner module
|
||||
-----------------------------------------
|
||||
|
||||
.. automodule:: photonlibpy.targeting.TargetCorner
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
photonlibpy.targeting.multiTargetPNPResult module
|
||||
-------------------------------------------------
|
||||
|
||||
.. automodule:: photonlibpy.targeting.multiTargetPNPResult
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
photonlibpy.targeting.photonPipelineResult module
|
||||
-------------------------------------------------
|
||||
|
||||
.. automodule:: photonlibpy.targeting.photonPipelineResult
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
photonlibpy.targeting.photonTrackedTarget module
|
||||
------------------------------------------------
|
||||
|
||||
.. automodule:: photonlibpy.targeting.photonTrackedTarget
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Module contents
|
||||
---------------
|
||||
|
||||
.. automodule:: photonlibpy.targeting
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
21
photon-lib/py/docs/source/photonlibpy.timesync.rst
Normal file
21
photon-lib/py/docs/source/photonlibpy.timesync.rst
Normal file
@@ -0,0 +1,21 @@
|
||||
photonlibpy.timesync package
|
||||
============================
|
||||
|
||||
Submodules
|
||||
----------
|
||||
|
||||
photonlibpy.timesync.timeSyncServer module
|
||||
------------------------------------------
|
||||
|
||||
.. automodule:: photonlibpy.timesync.timeSyncServer
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Module contents
|
||||
---------------
|
||||
|
||||
.. automodule:: photonlibpy.timesync
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -70,19 +70,11 @@ class Packet:
|
||||
return retVal
|
||||
|
||||
def getData(self) -> bytes:
|
||||
"""
|
||||
* Returns the packet data.
|
||||
*
|
||||
* @return The packet data.
|
||||
"""
|
||||
"""Return the packet data."""
|
||||
return self.packetData
|
||||
|
||||
def setData(self, data: bytes):
|
||||
"""
|
||||
* Sets the packet data.
|
||||
*
|
||||
* @param data The packet data.
|
||||
"""
|
||||
"""Set the packet data."""
|
||||
self.clear()
|
||||
self.packetData = data
|
||||
self.size = len(self.packetData)
|
||||
@@ -101,74 +93,42 @@ class Packet:
|
||||
return value
|
||||
|
||||
def decode8(self) -> int:
|
||||
"""
|
||||
* Returns a single decoded byte from the packet.
|
||||
*
|
||||
* @return A decoded byte from the packet.
|
||||
"""
|
||||
"""Return a single decoded byte from the packet."""
|
||||
return self._decodeGeneric("<b", 1)
|
||||
|
||||
def decode16(self) -> int:
|
||||
"""
|
||||
* Returns a single decoded short from the packet.
|
||||
*
|
||||
* @return A decoded short from the packet.
|
||||
"""
|
||||
"""Return a single decoded short from the packet."""
|
||||
return self._decodeGeneric("<h", 2)
|
||||
|
||||
def decodeInt(self) -> int:
|
||||
"""
|
||||
* Returns a decoded int (32 bytes) from the packet.
|
||||
*
|
||||
* @return A decoded int from the packet.
|
||||
"""
|
||||
"""Return a decoded 32-bit integer from the packet."""
|
||||
return self._decodeGeneric("<l", 4)
|
||||
|
||||
def decodeFloat(self) -> float:
|
||||
"""
|
||||
* Returns a decoded float from the packet.
|
||||
*
|
||||
* @return A decoded float from the packet.
|
||||
"""
|
||||
"""Return a decoded float from the packet."""
|
||||
return self._decodeGeneric("<f", 4)
|
||||
|
||||
def decodeLong(self) -> int:
|
||||
"""
|
||||
* Returns a decoded int64 from the packet.
|
||||
*
|
||||
* @return A decoded int64 from the packet.
|
||||
"""
|
||||
"""Return a decoded 64-bit integer from the packet."""
|
||||
return self._decodeGeneric("<q", 8)
|
||||
|
||||
def decodeDouble(self) -> float:
|
||||
"""
|
||||
* Returns a decoded double from the packet.
|
||||
*
|
||||
* @return A decoded double from the packet.
|
||||
"""
|
||||
"""Return a decoded double from the packet."""
|
||||
return self._decodeGeneric("<d", 8)
|
||||
|
||||
def decodeBoolean(self) -> bool:
|
||||
"""
|
||||
* Returns a decoded boolean from the packet.
|
||||
*
|
||||
* @return A decoded boolean from the packet.
|
||||
"""
|
||||
"""Return a decoded boolean from the packet."""
|
||||
return self.decode8() == 1
|
||||
|
||||
def decodeDoubleArray(self, length: int) -> list[float]:
|
||||
"""
|
||||
* Returns a decoded array of floats from the packet.
|
||||
"""
|
||||
"""Return a decoded list of doubles of the given length from the packet."""
|
||||
ret = []
|
||||
for _ in range(length):
|
||||
ret.append(self.decodeDouble())
|
||||
return ret
|
||||
|
||||
def decodeShortList(self) -> list[int]:
|
||||
"""
|
||||
* Returns a decoded array of shorts from the packet.
|
||||
"""
|
||||
"""Return a decoded list of shorts from the packet (length-prefixed)."""
|
||||
length = self.decode8()
|
||||
ret = []
|
||||
for _ in range(length):
|
||||
@@ -176,11 +136,7 @@ class Packet:
|
||||
return ret
|
||||
|
||||
def decodeTransform(self) -> Transform3d:
|
||||
"""
|
||||
* Returns a decoded Transform3d
|
||||
*
|
||||
* @return A decoded Tansform3d from the packet.
|
||||
"""
|
||||
"""Return a decoded Transform3d from the packet."""
|
||||
x = self.decodeDouble()
|
||||
y = self.decodeDouble()
|
||||
z = self.decodeDouble()
|
||||
|
||||
@@ -261,18 +261,18 @@ class PhotonPoseEstimator:
|
||||
def update(
|
||||
self, cameraResult: Optional[PhotonPipelineResult] = None
|
||||
) -> Optional[EstimatedRobotPose]:
|
||||
"""
|
||||
Updates the estimated position of the robot. Returns empty if:
|
||||
"""Update the estimated robot position.
|
||||
|
||||
- The timestamp of the provided pipeline result is the same as in the previous call to
|
||||
``update()``.
|
||||
Returns empty if one of the following is true:
|
||||
|
||||
- No targets were found in the pipeline results.
|
||||
- The timestamp of the provided pipeline result is the same as in the previous call to
|
||||
``update()``.
|
||||
- No targets were found in the pipeline results.
|
||||
|
||||
:param cameraResult: The latest pipeline result from the camera
|
||||
:param cameraResult: The latest pipeline result from the camera.
|
||||
|
||||
:returns: an :class:`EstimatedRobotPose` with an estimated pose, timestamp, and targets used to
|
||||
create the estimate.
|
||||
:returns: An :class:`EstimatedRobotPose` with an estimated pose, timestamp, and targets used
|
||||
to create the estimate, or ``None`` if no estimate could be made.
|
||||
"""
|
||||
if not cameraResult:
|
||||
if not self._camera:
|
||||
|
||||
@@ -306,14 +306,13 @@ class SimCameraProperties:
|
||||
:param a: The initial translation of the line
|
||||
:param b: The final translation of the line
|
||||
|
||||
:returns: A Pair of Doubles. The values may be null:
|
||||
:returns: A pair of floats (t_min, t_max) where each may be ``None``:
|
||||
|
||||
- {Double, Double} : Two parametrized values(t), minimum first, representing which
|
||||
segment of the line is visible in the camera frustum.
|
||||
- {Double, null} : One value(t) representing a single intersection point. For example,
|
||||
the line only intersects the intersection of two adjacent viewplanes.
|
||||
- {null, null} : No values. The line segment is not visible in the camera frustum.
|
||||
"""
|
||||
- ``(float, float)``: Two t values (minimum first) representing the visible segment.
|
||||
- ``(float, None)``: A single intersection point.
|
||||
- ``(None, None)``: No intersection; the segment is not visible.
|
||||
|
||||
"""
|
||||
|
||||
# translations relative to the camera
|
||||
relA = camRt.applyTranslation(a)
|
||||
|
||||
@@ -24,9 +24,6 @@
|
||||
|
||||
#include "photon/PhotonCamera.h"
|
||||
|
||||
#include <hal/FRCUsageReporting.h>
|
||||
#include <net/TimeSyncServer.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
@@ -36,6 +33,8 @@
|
||||
#include <frc/Errors.h>
|
||||
#include <frc/RobotController.h>
|
||||
#include <frc/Timer.h>
|
||||
#include <hal/FRCUsageReporting.h>
|
||||
#include <net/TimeSyncServer.h>
|
||||
#include <opencv2/core.hpp>
|
||||
#include <opencv2/core/mat.hpp>
|
||||
#include <wpi/json.h>
|
||||
|
||||
@@ -24,8 +24,6 @@
|
||||
|
||||
#include "photon/PhotonPoseEstimator.h"
|
||||
|
||||
#include <hal/FRCUsageReporting.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
@@ -41,6 +39,7 @@
|
||||
#include <frc/geometry/Pose3d.h>
|
||||
#include <frc/geometry/Rotation3d.h>
|
||||
#include <frc/geometry/Transform3d.h>
|
||||
#include <hal/FRCUsageReporting.h>
|
||||
#include <opencv2/calib3d.hpp>
|
||||
#include <opencv2/core/mat.hpp>
|
||||
#include <opencv2/core/types.hpp>
|
||||
|
||||
@@ -24,7 +24,16 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <cameraserver/CameraServer.h>
|
||||
#include <frc/Timer.h>
|
||||
#include <frc/apriltag/AprilTagFieldLayout.h>
|
||||
#include <frc/apriltag/AprilTagFields.h>
|
||||
#include <photon/PhotonCamera.h>
|
||||
#include <photon/PhotonTargetSortMode.h>
|
||||
#include <photon/estimation/CameraTargetRelation.h>
|
||||
@@ -33,16 +42,6 @@
|
||||
#include <photon/simulation/SimCameraProperties.h>
|
||||
#include <photon/simulation/VideoSimUtil.h>
|
||||
#include <photon/simulation/VisionTargetSim.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <frc/Timer.h>
|
||||
#include <frc/apriltag/AprilTagFieldLayout.h>
|
||||
#include <frc/apriltag/AprilTagFields.h>
|
||||
#include <units/math.h>
|
||||
#include <wpi/timestamp.h>
|
||||
|
||||
|
||||
@@ -24,8 +24,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <photon/estimation/OpenCVHelp.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <random>
|
||||
#include <string>
|
||||
@@ -37,6 +35,7 @@
|
||||
#include <frc/MathUtil.h>
|
||||
#include <frc/geometry/Rotation2d.h>
|
||||
#include <frc/geometry/Translation3d.h>
|
||||
#include <photon/estimation/OpenCVHelp.h>
|
||||
#include <units/frequency.h>
|
||||
#include <units/time.h>
|
||||
|
||||
|
||||
@@ -24,8 +24,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cscore_cv.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
#include <string>
|
||||
@@ -33,6 +31,7 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <cscore_cv.h>
|
||||
#include <frc/apriltag/AprilTag.h>
|
||||
#include <opencv2/core.hpp>
|
||||
#include <opencv2/imgcodecs.hpp>
|
||||
|
||||
@@ -22,20 +22,19 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/ranges.h>
|
||||
#include <frc/smartdashboard/SmartDashboard.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <hal/HAL.h>
|
||||
#include <net/TimeSyncClient.h>
|
||||
#include <net/TimeSyncServer.h>
|
||||
#include <networktables/NetworkTableInstance.h>
|
||||
#include <photon/PhotonCamera.h>
|
||||
#include <photon/simulation/PhotonCameraSim.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <frc/smartdashboard/SmartDashboard.h>
|
||||
#include <networktables/NetworkTableInstance.h>
|
||||
|
||||
TEST(TimeSyncProtocolTest, Smoketest) {
|
||||
using namespace wpi::tsp;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "photon/PhotonPoseEstimator.h"
|
||||
|
||||
#include <map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@@ -30,13 +32,12 @@
|
||||
#include <frc/geometry/Pose3d.h>
|
||||
#include <frc/geometry/Rotation3d.h>
|
||||
#include <frc/geometry/Transform3d.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <units/angle.h>
|
||||
#include <units/length.h>
|
||||
#include <wpi/SmallVector.h>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "photon/PhotonCamera.h"
|
||||
#include "photon/PhotonPoseEstimator.h"
|
||||
#include "photon/dataflow/structures/Packet.h"
|
||||
#include "photon/simulation/PhotonCameraSim.h"
|
||||
#include "photon/simulation/SimCameraProperties.h"
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "photon/PhotonUtils.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
TEST(PhotonUtilsTest, Include) {}
|
||||
|
||||
@@ -24,8 +24,9 @@
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "PhotonVersion.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
TEST(VersionTest, PrintVersion) {
|
||||
std::cout << photon::PhotonVersion::versionString << std::endl;
|
||||
|
||||
@@ -22,16 +22,17 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "photon/simulation/VisionSystemSim.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <wpi/deprecated.h>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "photon/PhotonUtils.h"
|
||||
#include "photon/simulation/VisionSystemSim.h"
|
||||
|
||||
// Ignore GetLatestResult warnings
|
||||
WPI_IGNORE_DEPRECATED
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user