mirror of
https://github.com/PhotonVision/photonvision
synced 2026-06-24 01:31:44 +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
|
- BOOST_FOREACH
|
||||||
IncludeBlocks: Regroup
|
IncludeBlocks: Regroup
|
||||||
IncludeCategories:
|
IncludeCategories:
|
||||||
- Regex: '^<ext/.*\.h>'
|
# C standard library headers
|
||||||
Priority: 2
|
#
|
||||||
SortPriority: 0
|
# https://en.cppreference.com/w/cpp/header:
|
||||||
- Regex: '^<.*\.h>'
|
# * C compatibility headers
|
||||||
Priority: 1
|
# * Special C compatibility headers
|
||||||
SortPriority: 0
|
# * Empty C headers
|
||||||
- Regex: '^<.*'
|
# * Meaningless C headers
|
||||||
Priority: 2
|
# * Unsupported C headers
|
||||||
SortPriority: 0
|
- 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)>'
|
||||||
- Regex: '.*'
|
Priority: 1
|
||||||
Priority: 3
|
# C++ standard library headers (lowercase and underscores with no .h suffix)
|
||||||
SortPriority: 0
|
- Regex: '^<[a-z_]+>'
|
||||||
IncludeIsMainRegex: '([-_](test|unittest))?$'
|
Priority: 2
|
||||||
|
# Other library headers (angle brackets)
|
||||||
|
- Regex: '^<.*'
|
||||||
|
Priority: 3
|
||||||
|
# Project headers (double quotes)
|
||||||
|
- Regex: '^".*'
|
||||||
|
Priority: 4
|
||||||
|
IncludeIsMainRegex: '(Test|_test)?$'
|
||||||
IncludeIsMainSourceRegex: ''
|
IncludeIsMainSourceRegex: ''
|
||||||
IndentCaseLabels: true
|
IndentCaseLabels: true
|
||||||
IndentGotoLabels: true
|
IndentGotoLabels: true
|
||||||
@@ -136,7 +143,7 @@ RawStringFormats:
|
|||||||
CanonicalDelimiter: ''
|
CanonicalDelimiter: ''
|
||||||
BasedOnStyle: google
|
BasedOnStyle: google
|
||||||
ReflowComments: true
|
ReflowComments: true
|
||||||
SortIncludes: false
|
SortIncludes: true
|
||||||
SortUsingDeclarations: true
|
SortUsingDeclarations: true
|
||||||
SpaceAfterCStyleCast: false
|
SpaceAfterCStyleCast: false
|
||||||
SpaceAfterLogicalNot: false
|
SpaceAfterLogicalNot: false
|
||||||
|
|||||||
1
.github/pull_request_template.md
vendored
1
.github/pull_request_template.md
vendored
@@ -11,6 +11,7 @@
|
|||||||
Merge checklist:
|
Merge checklist:
|
||||||
- [ ] Pull Request title is [short, imperative summary](https://cbea.ms/git-commit/) of proposed changes
|
- [ ] Pull Request title is [short, imperative summary](https://cbea.ms/git-commit/) of proposed changes
|
||||||
- [ ] The description documents the _what_ and _why_
|
- [ ] The description documents the _what_ and _why_
|
||||||
|
- [ ] 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 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 photon-serde, all messages have been regenerated and hashes have not changed unexpectedly
|
||||||
- [ ] If this PR touches configuration, this is backwards compatible with settings back to v2025.3.2
|
- [ ] If this PR touches 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
|
run: sudo apt-get update && sudo apt-get install -y libcholmod3 liblapack3 libsuitesparseconfig5
|
||||||
- name: Gradle Build
|
- name: Gradle Build
|
||||||
run: ./gradlew photon-targeting:build photon-core:build photon-server:build -x check
|
run: ./gradlew photon-targeting:build photon-core:build photon-server:build -x check
|
||||||
- name: Gradle Tests
|
- name: Gradle Tests and Coverage
|
||||||
run: ./gradlew testHeadless --stacktrace
|
run: ./gradlew test jacocoTestReport --stacktrace
|
||||||
- name: Gradle Coverage
|
|
||||||
run: ./gradlew jacocoTestReport
|
|
||||||
build-offline-docs:
|
build-offline-docs:
|
||||||
name: "Build Offline Docs"
|
name: "Build Offline Docs"
|
||||||
runs-on: ubuntu-22.04
|
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:
|
with:
|
||||||
python-version: 3.11
|
python-version: 3.11
|
||||||
- name: Install wpiformat
|
- name: Install wpiformat
|
||||||
run: pip3 install wpiformat==2025.34
|
run: pip3 install wpiformat==2025.75
|
||||||
- name: Run
|
- name: Run
|
||||||
run: wpiformat
|
run: wpiformat
|
||||||
- name: Check output
|
- 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
|
run: pnpm run build-demo
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: built-demo
|
name: demo
|
||||||
path: photon-client/dist/
|
path: photon-client/dist/
|
||||||
|
|
||||||
run_java_cpp_docs:
|
run_java_cpp_docs:
|
||||||
@@ -74,9 +74,39 @@ jobs:
|
|||||||
name: docs-java-cpp
|
name: docs-java-cpp
|
||||||
path: photon-docs/build/docs
|
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:
|
publish_api_docs:
|
||||||
name: 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
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
# Download docs artifact
|
# Download docs artifact
|
||||||
@@ -85,7 +115,7 @@ jobs:
|
|||||||
pattern: docs-*
|
pattern: docs-*
|
||||||
- run: find .
|
- run: find .
|
||||||
- name: Publish Docs To Development
|
- name: Publish Docs To Development
|
||||||
if: github.ref == 'refs/heads/main'
|
# if: github.ref == 'refs/heads/main'
|
||||||
uses: up9cloud/action-rsync@v1.4
|
uses: up9cloud/action-rsync@v1.4
|
||||||
env:
|
env:
|
||||||
HOST: ${{ secrets.WEBMASTER_SSH_HOST }}
|
HOST: ${{ secrets.WEBMASTER_SSH_HOST }}
|
||||||
@@ -108,7 +138,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: built-demo
|
name: demo
|
||||||
- run: find .
|
- run: find .
|
||||||
- name: Publish demo
|
- name: Publish demo
|
||||||
if: github.ref == 'refs/heads/main'
|
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
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
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
|
- name: Build wheel
|
||||||
working-directory: ./photon-lib/py
|
working-directory: ./photon-lib/py
|
||||||
|
|||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -142,9 +142,13 @@ venv
|
|||||||
.venv/*
|
.venv/*
|
||||||
.venv
|
.venv
|
||||||
networktables.json
|
networktables.json
|
||||||
|
|
||||||
# Web stuff
|
# Web stuff
|
||||||
photon-server/src/main/resources/web/*
|
photon-server/src/main/resources/web/*
|
||||||
node_modules
|
node_modules
|
||||||
dist
|
dist
|
||||||
components.d.ts
|
components.d.ts
|
||||||
|
|
||||||
|
# Py docs stuff
|
||||||
|
photon-lib/py/docs/build
|
||||||
photon-server/src/main/resources/web/index.html
|
photon-server/src/main/resources/web/index.html
|
||||||
|
|||||||
@@ -1,41 +1,25 @@
|
|||||||
cppHeaderFileInclude {
|
cppHeaderFileInclude {
|
||||||
\.h$
|
\.h$
|
||||||
\.hpp$
|
|
||||||
\.inc$
|
|
||||||
\.inl$
|
|
||||||
}
|
|
||||||
|
|
||||||
cppSrcFileInclude {
|
|
||||||
\.cpp$
|
|
||||||
}
|
}
|
||||||
|
|
||||||
modifiableFileExclude {
|
modifiableFileExclude {
|
||||||
\.jpg$
|
|
||||||
\.jpeg$
|
|
||||||
\.png$
|
|
||||||
\.gif$
|
|
||||||
\.so$
|
|
||||||
\.dll$
|
\.dll$
|
||||||
\.webp$
|
\.gif$
|
||||||
\.ico$
|
\.ico$
|
||||||
\.rknn$
|
\.jpeg$
|
||||||
\.tflite$
|
\.jpg$
|
||||||
\.mp4$
|
\.mp4$
|
||||||
|
\.pdf$
|
||||||
|
\.png$
|
||||||
|
\.rknn$
|
||||||
|
\.so$
|
||||||
|
\.svg$
|
||||||
|
\.tflite$
|
||||||
\.ttf$
|
\.ttf$
|
||||||
|
\.webp$
|
||||||
\.woff2$
|
\.woff2$
|
||||||
gradlew
|
gradlew
|
||||||
photon-lib/py/photonlibpy/generated/
|
photon-lib/py/photonlibpy/generated/
|
||||||
photon-targeting/src/main/native/cpp/photon/constrained_solvepnp/generate/
|
|
||||||
photon-targeting/src/generated/
|
photon-targeting/src/generated/
|
||||||
}
|
photon-targeting/src/main/native/cpp/photon/constrained_solvepnp/generate/
|
||||||
|
|
||||||
includeProject {
|
|
||||||
^photonLib/
|
|
||||||
}
|
|
||||||
|
|
||||||
includeOtherLibs {
|
|
||||||
^frc/
|
|
||||||
^networktables/
|
|
||||||
^units/
|
|
||||||
^wpi/
|
|
||||||
}
|
}
|
||||||
@@ -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/)
|
- Photon UI demo: [http://photonvision.global/](http://photonvision.global/)
|
||||||
- Javadocs: [javadocs.photonvision.org](https://javadocs.photonvision.org)
|
- Javadocs: [javadocs.photonvision.org](https://javadocs.photonvision.org)
|
||||||
- C++ Doxygen [cppdocs.photonvision.org](https://cppdocs.photonvision.org)
|
- C++ Doxygen [cppdocs.photonvision.org](https://cppdocs.photonvision.org)
|
||||||
|
- Python Documentation [pydocs.photonvision.org](https://pydocs.photonvision.org)
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ ext {
|
|||||||
javalinVersion = "6.7.0"
|
javalinVersion = "6.7.0"
|
||||||
libcameraDriverVersion = "v2025.0.4"
|
libcameraDriverVersion = "v2025.0.4"
|
||||||
rknnVersion = "dev-v2025.0.0-5-g666c0c6"
|
rknnVersion = "dev-v2025.0.0-5-g666c0c6"
|
||||||
rubikVersion = "dev-v2025.1.0-8-g067a316"
|
rubikVersion = "dev-v2025.1.0-6-g4a5e508"
|
||||||
frcYear = "2025"
|
frcYear = "2025"
|
||||||
mrcalVersion = "v2025.0.0";
|
mrcalVersion = "v2025.0.0";
|
||||||
|
|
||||||
@@ -92,7 +92,7 @@ spotless {
|
|||||||
format 'misc', {
|
format 'misc', {
|
||||||
target fileTree('.') {
|
target fileTree('.') {
|
||||||
include '**/*.md', '**/.gitignore'
|
include '**/*.md', '**/.gitignore'
|
||||||
exclude '**/build/**', '**/build-*/**'
|
exclude '**/build/**', '**/build-*/**', '**/node_modules/**'
|
||||||
}
|
}
|
||||||
trimTrailingWhitespace()
|
trimTrailingWhitespace()
|
||||||
indentWithSpaces(2)
|
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
|
## 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
|
```{image} images/apriltag.png
|
||||||
:align: center
|
:align: center
|
||||||
@@ -12,7 +12,7 @@ You are now able to detect and track AprilTags in 2D (yaw, pitch, roll, etc.). I
|
|||||||
|
|
||||||
## Tuning AprilTags
|
## 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
|
```{image} images/apriltag-tune.png
|
||||||
:align: center
|
:align: center
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ Note that both of these pipeline types detect AprilTag markers and are just two
|
|||||||
|
|
||||||
## AprilTag
|
## 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.
|
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}
|
:::{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.
|
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
|
## 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:
|
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.
|
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.
|
### 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.
|
### 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}
|
:::{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.
|
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.
|
### 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).
|
:::{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}
|
:::{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.
|
### 4. Take at calibration images from various angles.
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
# Arducam Cameras
|
# 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.
|
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
|
## 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).
|
[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
|
## Compiling Instructions
|
||||||
|
|
||||||
### Getting the Source Code
|
### 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.
|
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
|
### 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:
|
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}
|
```{toctree}
|
||||||
building-photon
|
building-photon
|
||||||
building-docs
|
building-docs
|
||||||
|
linting
|
||||||
developer-docs/index
|
developer-docs/index
|
||||||
design-descriptions/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 Robot
|
||||||
- A camera mounted rigidly to the robot's frame, centered and pointed forward.
|
- 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.
|
- [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
|
## 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.
|
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.
|
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.
|
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.
|
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
|
## 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.
|
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.
|
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.
|
- 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
|
## Print the Calibration Target
|
||||||
|
|
||||||
- Downloaded from our [demo site](http://photonvision.global/#/cameras), or directly from your coprocessors cameras tab.
|
- Downloaded from our [demo site](http://photonvision.global/#/cameras), or directly from your coprocessors cameras tab.
|
||||||
- Use the Charuco calibration board:
|
- Use the ChArUco calibration board:
|
||||||
- Board Type: Charuco
|
- Board Type: ChAruCo
|
||||||
- Tag Family: 4x4
|
- Tag Family: 4x4
|
||||||
- Pattern Spacing: 1.00in
|
- Pattern Spacing: 1.00in
|
||||||
- Marker Size: 0.75in
|
- 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
|
networking
|
||||||
camera-matching
|
camera-matching
|
||||||
camera-calibration
|
camera-calibration
|
||||||
|
camera-focusing
|
||||||
quick-configure
|
quick-configure
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -138,5 +138,7 @@ docs/contributing/index
|
|||||||
|
|
||||||
Java <https://javadocs.photonvision.org>
|
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",
|
"build-demo": "vite build --mode demo",
|
||||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix",
|
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix",
|
||||||
"format": "prettier --write src/",
|
"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/"
|
"format-ci": "prettier --check src/"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -13,5 +13,15 @@ import { useStateStore } from "@/stores/StateStore";
|
|||||||
<p style="padding: 0; margin: 0; text-align: center">
|
<p style="padding: 0; margin: 0; text-align: center">
|
||||||
{{ useStateStore().snackbarData.message }}
|
{{ useStateStore().snackbarData.message }}
|
||||||
</p>
|
</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>
|
</v-snackbar>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ const downloadCalibBoard = async () => {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case CalibrationBoardTypes.Charuco:
|
case CalibrationBoardTypes.Charuco:
|
||||||
// Add pregenerated charuco
|
// Add pregenerated ChArUco
|
||||||
const charucoImage = new Image();
|
const charucoImage = new Image();
|
||||||
charucoImage.src = CharucoImage;
|
charucoImage.src = CharucoImage;
|
||||||
doc.addImage(charucoImage, "PNG", 0.25, 1.5, 8, 8);
|
doc.addImage(charucoImage, "PNG", 0.25, 1.5, 8, 8);
|
||||||
@@ -297,20 +297,30 @@ const setSelectedVideoFormat = (format: VideoFormat) => {
|
|||||||
:select-cols="8"
|
:select-cols="8"
|
||||||
:disabled="isCalibrating"
|
:disabled="isCalibrating"
|
||||||
tooltip="Resolution to calibrate at (you will have to calibrate every resolution you use 3D mode on)"
|
tooltip="Resolution to calibrate at (you will have to calibrate every resolution you use 3D mode on)"
|
||||||
|
:items="getUniqueVideoResolutionStrings()"
|
||||||
@update:model-value="
|
@update:model-value="
|
||||||
useStateStore().calibrationData.videoFormatIndex =
|
useStateStore().calibrationData.videoFormatIndex =
|
||||||
getUniqueVideoResolutionStrings().find((v) => v.value === $event)?.value || 0
|
getUniqueVideoResolutionStrings().find((v) => v.value === $event)?.value || 0
|
||||||
"
|
"
|
||||||
:items="getUniqueVideoResolutionStrings()"
|
|
||||||
/>
|
/>
|
||||||
<pv-select
|
<pv-select
|
||||||
v-model="boardType"
|
v-model="boardType"
|
||||||
label="Board Type"
|
label="Board Type"
|
||||||
tooltip="Calibration board pattern to use"
|
tooltip="Calibration board pattern to use"
|
||||||
:select-cols="8"
|
:select-cols="8"
|
||||||
:items="['Chessboard', 'Charuco']"
|
:items="['Chessboard', 'ChArUco']"
|
||||||
:disabled="isCalibrating"
|
: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
|
<pv-select
|
||||||
v-if="boardType !== CalibrationBoardTypes.Charuco"
|
v-if="boardType !== CalibrationBoardTypes.Charuco"
|
||||||
v-model="useCameraSettingsStore().currentPipelineSettings.streamingFrameDivisor"
|
v-model="useCameraSettingsStore().currentPipelineSettings.streamingFrameDivisor"
|
||||||
@@ -326,7 +336,7 @@ const setSelectedVideoFormat = (format: VideoFormat) => {
|
|||||||
v-if="boardType === CalibrationBoardTypes.Charuco"
|
v-if="boardType === CalibrationBoardTypes.Charuco"
|
||||||
v-model="tagFamily"
|
v-model="tagFamily"
|
||||||
label="Tag Family"
|
label="Tag Family"
|
||||||
tooltip="Dictionary of aruco markers on the charuco board"
|
tooltip="Dictionary of ArUco markers on the ChArUco board"
|
||||||
:select-cols="8"
|
:select-cols="8"
|
||||||
:items="['Dict_4X4_1000', 'Dict_5X5_1000', 'Dict_6X6_1000', 'Dict_7X7_1000']"
|
:items="['Dict_4X4_1000', 'Dict_5X5_1000', 'Dict_6X6_1000', 'Dict_7X7_1000']"
|
||||||
:disabled="isCalibrating"
|
:disabled="isCalibrating"
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ import type { CameraCalibrationResult, VideoFormat } from "@/types/SettingTypes"
|
|||||||
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
|
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
|
||||||
import { useStateStore } from "@/stores/StateStore";
|
import { useStateStore } from "@/stores/StateStore";
|
||||||
import { computed, inject, ref } from "vue";
|
import { computed, inject, ref } from "vue";
|
||||||
import { getResolutionString, parseJsonFile } from "@/lib/PhotonUtils";
|
import { axiosPost, getResolutionString, parseJsonFile } from "@/lib/PhotonUtils";
|
||||||
import { useTheme } from "vuetify";
|
import { useTheme } from "vuetify";
|
||||||
|
import PvDeleteModal from "@/components/common/pv-delete-modal.vue";
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
@@ -12,6 +13,16 @@ const props = defineProps<{
|
|||||||
videoFormat: VideoFormat;
|
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 exportCalibration = ref();
|
||||||
const openExportCalibrationPrompt = () => {
|
const openExportCalibrationPrompt = () => {
|
||||||
exportCalibration.value.click();
|
exportCalibration.value.click();
|
||||||
@@ -93,18 +104,19 @@ const calibrationImageURL = (index: number) =>
|
|||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<v-card color="surface" dark>
|
<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-col cols="12" md="6">
|
||||||
<v-card-title class="pa-0"> Calibration Details </v-card-title>
|
<v-card-title class="pa-0"> Calibration Details </v-card-title>
|
||||||
</v-col>
|
</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
|
<v-btn
|
||||||
color="buttonPassive"
|
color="buttonPassive"
|
||||||
style="width: 100%"
|
class="mr-2"
|
||||||
|
style="flex: 1"
|
||||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||||
@click="openUploadPhotonCalibJsonPrompt"
|
@click="openUploadPhotonCalibJsonPrompt"
|
||||||
>
|
>
|
||||||
<v-icon start size="large"> mdi-import</v-icon>
|
<v-icon start size="large">mdi-import</v-icon>
|
||||||
<span>Import</span>
|
<span>Import</span>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<input
|
<input
|
||||||
@@ -114,12 +126,11 @@ const calibrationImageURL = (index: number) =>
|
|||||||
style="display: none"
|
style="display: none"
|
||||||
@change="importCalibration"
|
@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
|
<v-btn
|
||||||
color="buttonPassive"
|
color="buttonPassive"
|
||||||
|
class="mr-2"
|
||||||
:disabled="!currentCalibrationCoeffs"
|
:disabled="!currentCalibrationCoeffs"
|
||||||
style="width: 100%"
|
style="flex: 1"
|
||||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
||||||
@click="openExportCalibrationPrompt"
|
@click="openExportCalibrationPrompt"
|
||||||
>
|
>
|
||||||
@@ -132,6 +143,16 @@ const calibrationImageURL = (index: number) =>
|
|||||||
:href="exportCalibrationURL"
|
:href="exportCalibrationURL"
|
||||||
target="_blank"
|
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>
|
</v-col>
|
||||||
</div>
|
</div>
|
||||||
<v-card-title class="pt-0 pb-0"
|
<v-card-title class="pt-0 pb-0"
|
||||||
@@ -289,6 +310,14 @@ const calibrationImageURL = (index: number) =>
|
|||||||
</v-data-table>
|
</v-data-table>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
</v-card>
|
</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>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import PvSelect, { type SelectItem } from "@/components/common/pv-select.vue";
|
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 PvNumberInput from "@/components/common/pv-number-input.vue";
|
||||||
|
import PvSwitch from "@/components/common/pv-switch.vue";
|
||||||
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
|
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
|
||||||
import { useStateStore } from "@/stores/StateStore";
|
import { useStateStore } from "@/stores/StateStore";
|
||||||
import { computed, ref, watchEffect } from "vue";
|
import { computed, ref, watchEffect } from "vue";
|
||||||
import { type CameraSettingsChangeRequest, ValidQuirks } from "@/types/SettingTypes";
|
import { type CameraSettingsChangeRequest, ValidQuirks } from "@/types/SettingTypes";
|
||||||
import axios from "axios";
|
|
||||||
import { useTheme } from "vuetify";
|
import { useTheme } from "vuetify";
|
||||||
|
import { axiosPost } from "@/lib/PhotonUtils";
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
@@ -15,7 +16,14 @@ const tempSettingsStruct = ref<CameraSettingsChangeRequest>({
|
|||||||
fov: useCameraSettingsStore().currentCameraSettings.fov.value,
|
fov: useCameraSettingsStore().currentCameraSettings.fov.value,
|
||||||
quirksToChange: Object.assign({}, useCameraSettingsStore().currentCameraSettings.cameraQuirks.quirks)
|
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>({
|
const arducamSelectWrapper = computed<number>({
|
||||||
get: () => {
|
get: () => {
|
||||||
if (tempSettingsStruct.value.quirksToChange.ArduOV9281Controls) return 1;
|
if (tempSettingsStruct.value.quirksToChange.ArduOV9281Controls) return 1;
|
||||||
@@ -112,44 +120,10 @@ watchEffect(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const showDeleteCamera = ref(false);
|
const showDeleteCamera = ref(false);
|
||||||
const yesDeleteMySettingsText = ref("");
|
|
||||||
const deletingCamera = ref(false);
|
|
||||||
const deleteThisCamera = () => {
|
const deleteThisCamera = () => {
|
||||||
if (deletingCamera.value) return;
|
axiosPost("/utils/nukeOneCamera", "delete this camera", {
|
||||||
deletingCamera.value = true;
|
cameraUniqueName: useStateStore().currentCameraUniqueName
|
||||||
|
});
|
||||||
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;
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
const wrappedCameras = computed<SelectItem[]>(() =>
|
const wrappedCameras = computed<SelectItem[]>(() =>
|
||||||
Object.keys(useCameraSettingsStore().cameras).map((cameraUniqueName) => ({
|
Object.keys(useCameraSettingsStore().cameras).map((cameraUniqueName) => ({
|
||||||
@@ -192,6 +166,11 @@ const wrappedCameras = computed<SelectItem[]>(() =>
|
|||||||
]"
|
]"
|
||||||
:select-cols="8"
|
: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>
|
||||||
<v-card-text class="d-flex pt-0">
|
<v-card-text class="d-flex pt-0">
|
||||||
<v-col cols="6" class="pa-0 pr-2">
|
<v-col cols="6" class="pa-0 pr-2">
|
||||||
@@ -221,45 +200,13 @@ const wrappedCameras = computed<SelectItem[]>(() =>
|
|||||||
</v-col>
|
</v-col>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
|
|
||||||
<v-dialog v-model="showDeleteCamera" width="800">
|
<pv-delete-modal
|
||||||
<v-card color="surface" flat>
|
v-model="showDeleteCamera"
|
||||||
<v-card-title> Delete {{ useCameraSettingsStore().currentCameraSettings.nickname }}? </v-card-title>
|
title="Delete Camera"
|
||||||
<v-card-text class="pt-0 pb-10px">
|
:description="`Are you sure you want to delete the camera '${useCameraSettingsStore().currentCameraSettings.nickname}'? This action cannot be undone.`"
|
||||||
Are you sure you want to delete "{{ useCameraSettingsStore().currentCameraSettings.nickname }}"? This cannot
|
:expected-confirmation-text="useCameraSettingsStore().currentCameraSettings.nickname"
|
||||||
be undone.
|
:on-confirm="deleteThisCamera"
|
||||||
</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>
|
|
||||||
</v-card>
|
</v-card>
|
||||||
</template>
|
</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">
|
<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>
|
<span class="pr-1">Camera not connected</span>
|
||||||
</v-chip>
|
</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-switch
|
||||||
v-model="driverMode"
|
v-model="driverMode"
|
||||||
:disabled="useCameraSettingsStore().isCalibrationMode || useCameraSettingsStore().pipelineNames.length === 0"
|
:disabled="useCameraSettingsStore().isCalibrationMode || useCameraSettingsStore().pipelineNames.length === 0"
|
||||||
@@ -95,7 +104,11 @@ const fpsTooLow = computed<boolean>(() => {
|
|||||||
color="buttonPassive"
|
color="buttonPassive"
|
||||||
class="fill"
|
class="fill"
|
||||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
: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>
|
<v-icon start class="mode-btn-icon" size="large">mdi-import</v-icon>
|
||||||
<span class="mode-btn-label">Raw</span>
|
<span class="mode-btn-label">Raw</span>
|
||||||
@@ -104,7 +117,11 @@ const fpsTooLow = computed<boolean>(() => {
|
|||||||
color="buttonPassive"
|
color="buttonPassive"
|
||||||
class="fill"
|
class="fill"
|
||||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
: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>
|
<v-icon start class="mode-btn-icon" size="large">mdi-export</v-icon>
|
||||||
<span class="mode-btn-label">Processed</span>
|
<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 { PipelineType } from "@/types/PipelineTypes";
|
||||||
import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore";
|
import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore";
|
||||||
import { useTheme } from "vuetify";
|
import { useTheme } from "vuetify";
|
||||||
|
import PvDeleteModal from "@/components/common/pv-delete-modal.vue";
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
@@ -92,6 +93,9 @@ const pipelineNamesWrapper = computed<SelectItem[]>(() => {
|
|||||||
if (useCameraSettingsStore().isDriverMode) {
|
if (useCameraSettingsStore().isDriverMode) {
|
||||||
pipelineNames.push({ name: "Driver Mode", value: WebsocketPipelineType.DriverMode });
|
pipelineNames.push({ name: "Driver Mode", value: WebsocketPipelineType.DriverMode });
|
||||||
}
|
}
|
||||||
|
if (useCameraSettingsStore().isFocusMode) {
|
||||||
|
pipelineNames.push({ name: "Focus Mode", value: WebsocketPipelineType.FocusCamera });
|
||||||
|
}
|
||||||
if (useCameraSettingsStore().isCalibrationMode) {
|
if (useCameraSettingsStore().isCalibrationMode) {
|
||||||
pipelineNames.push({ name: "3D Calibration Mode", value: WebsocketPipelineType.Calib3d });
|
pipelineNames.push({ name: "3D Calibration Mode", value: WebsocketPipelineType.Calib3d });
|
||||||
}
|
}
|
||||||
@@ -130,7 +134,7 @@ const validNewPipelineTypes = computed(() => {
|
|||||||
{ name: "Reflective", value: WebsocketPipelineType.Reflective },
|
{ name: "Reflective", value: WebsocketPipelineType.Reflective },
|
||||||
{ name: "Colored Shape", value: WebsocketPipelineType.ColoredShape },
|
{ name: "Colored Shape", value: WebsocketPipelineType.ColoredShape },
|
||||||
{ name: "AprilTag", value: WebsocketPipelineType.AprilTag },
|
{ name: "AprilTag", value: WebsocketPipelineType.AprilTag },
|
||||||
{ name: "Aruco", value: WebsocketPipelineType.Aruco }
|
{ name: "ArUco", value: WebsocketPipelineType.Aruco }
|
||||||
];
|
];
|
||||||
if (useSettingsStore().general.supportedBackends.length > 0) {
|
if (useSettingsStore().general.supportedBackends.length > 0) {
|
||||||
pipelineTypes.push({ name: "Object Detection", value: WebsocketPipelineType.ObjectDetection });
|
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: "Reflective", value: WebsocketPipelineType.Reflective },
|
||||||
{ name: "Colored Shape", value: WebsocketPipelineType.ColoredShape },
|
{ name: "Colored Shape", value: WebsocketPipelineType.ColoredShape },
|
||||||
{ name: "AprilTag", value: WebsocketPipelineType.AprilTag },
|
{ name: "AprilTag", value: WebsocketPipelineType.AprilTag },
|
||||||
{ name: "Aruco", value: WebsocketPipelineType.Aruco }
|
{ name: "ArUco", value: WebsocketPipelineType.Aruco }
|
||||||
];
|
];
|
||||||
if (useSettingsStore().general.supportedBackends.length > 0) {
|
if (useSettingsStore().general.supportedBackends.length > 0) {
|
||||||
pipelineTypes.push({ name: "Object Detection", value: WebsocketPipelineType.ObjectDetection });
|
pipelineTypes.push({ name: "Object Detection", value: WebsocketPipelineType.ObjectDetection });
|
||||||
@@ -177,6 +181,9 @@ const pipelineTypesWrapper = computed<{ name: string; value: number }[]>(() => {
|
|||||||
if (useCameraSettingsStore().isDriverMode) {
|
if (useCameraSettingsStore().isDriverMode) {
|
||||||
pipelineTypes.push({ name: "Driver Mode", value: WebsocketPipelineType.DriverMode });
|
pipelineTypes.push({ name: "Driver Mode", value: WebsocketPipelineType.DriverMode });
|
||||||
}
|
}
|
||||||
|
if (useCameraSettingsStore().isFocusMode) {
|
||||||
|
pipelineTypes.push({ name: "Focus Mode", value: WebsocketPipelineType.FocusCamera });
|
||||||
|
}
|
||||||
if (useCameraSettingsStore().isCalibrationMode) {
|
if (useCameraSettingsStore().isCalibrationMode) {
|
||||||
pipelineTypes.push({ name: "3D Calibration Mode", value: WebsocketPipelineType.Calib3d });
|
pipelineTypes.push({ name: "3D Calibration Mode", value: WebsocketPipelineType.Calib3d });
|
||||||
}
|
}
|
||||||
@@ -187,6 +194,7 @@ const pipelineType = ref<WebsocketPipelineType>(useCameraSettingsStore().current
|
|||||||
const currentPipelineType = computed<WebsocketPipelineType>({
|
const currentPipelineType = computed<WebsocketPipelineType>({
|
||||||
get: () => {
|
get: () => {
|
||||||
if (useCameraSettingsStore().isDriverMode) return WebsocketPipelineType.DriverMode;
|
if (useCameraSettingsStore().isDriverMode) return WebsocketPipelineType.DriverMode;
|
||||||
|
if (useCameraSettingsStore().isFocusMode) return WebsocketPipelineType.FocusCamera;
|
||||||
if (useCameraSettingsStore().isCalibrationMode) return WebsocketPipelineType.Calib3d;
|
if (useCameraSettingsStore().isCalibrationMode) return WebsocketPipelineType.Calib3d;
|
||||||
return pipelineType.value;
|
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"
|
tooltip="Each pipeline runs on a camera output and stores a unique set of processing settings"
|
||||||
:disabled="
|
:disabled="
|
||||||
useCameraSettingsStore().isDriverMode ||
|
useCameraSettingsStore().isDriverMode ||
|
||||||
|
useCameraSettingsStore().isFocusMode ||
|
||||||
useCameraSettingsStore().isCalibrationMode ||
|
useCameraSettingsStore().isCalibrationMode ||
|
||||||
!useCameraSettingsStore().hasConnected
|
!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"
|
tooltip="Changes the pipeline type, which changes the type of processing that will happen on input frames"
|
||||||
:disabled="
|
:disabled="
|
||||||
useCameraSettingsStore().isDriverMode ||
|
useCameraSettingsStore().isDriverMode ||
|
||||||
|
useCameraSettingsStore().isFocusMode ||
|
||||||
useCameraSettingsStore().isCalibrationMode ||
|
useCameraSettingsStore().isCalibrationMode ||
|
||||||
!useCameraSettingsStore().hasConnected
|
!useCameraSettingsStore().hasConnected
|
||||||
"
|
"
|
||||||
@@ -413,33 +423,13 @@ const wrappedCameras = computed<SelectItem[]>(() =>
|
|||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
<v-dialog v-model="showPipelineDeletionConfirmationDialog" width="500">
|
<pv-delete-modal
|
||||||
<v-card color="surface">
|
v-model="showPipelineDeletionConfirmationDialog"
|
||||||
<v-card-title class="pb-0">Delete Pipeline</v-card-title>
|
:width="500"
|
||||||
<v-card-text>
|
title="Delete Pipeline"
|
||||||
Are you sure you want to delete
|
description="Are you sure you want to delete the current pipeline? This action cannot be undone."
|
||||||
<span style="color: white">"{{ useCameraSettingsStore().currentPipelineSettings.pipelineNickname }}"</span>?
|
:on-confirm="confirmDeleteCurrentPipeline"
|
||||||
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>
|
|
||||||
<v-dialog v-model="showPipelineTypeChangeDialog" persistent width="600">
|
<v-dialog v-model="showPipelineTypeChangeDialog" persistent width="600">
|
||||||
<v-card color="surface" dark>
|
<v-card color="surface" dark>
|
||||||
<v-card-title class="pb-0">Change Pipeline Type</v-card-title>
|
<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 },
|
thresholdTab: { tabName: "Threshold", component: ThresholdTab },
|
||||||
contoursTab: { tabName: "Contours", component: ContoursTab },
|
contoursTab: { tabName: "Contours", component: ContoursTab },
|
||||||
apriltagTab: { tabName: "AprilTag", component: AprilTagTab },
|
apriltagTab: { tabName: "AprilTag", component: AprilTagTab },
|
||||||
arucoTab: { tabName: "Aruco", component: ArucoTab },
|
arucoTab: { tabName: "ArUco", component: ArucoTab },
|
||||||
objectDetectionTab: { tabName: "Object Detection", component: ObjectDetectionTab },
|
objectDetectionTab: { tabName: "Object Detection", component: ObjectDetectionTab },
|
||||||
outputTab: { tabName: "Output", component: OutputTab },
|
outputTab: { tabName: "Output", component: OutputTab },
|
||||||
targetsTab: { tabName: "Targets", component: TargetsTab },
|
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 === "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 || 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
|
!(!isAprilTag && tabConfig.tabName === "AprilTag") && //Filter out apriltag unless we actually are doing AprilTags
|
||||||
!(!isAruco && tabConfig.tabName === "Aruco") &&
|
!(!isAruco && tabConfig.tabName === "ArUco") &&
|
||||||
!(!isObjectDetection && tabConfig.tabName === "Object Detection") //Filter out aruco unless we actually are doing Aruco
|
!(!isObjectDetection && tabConfig.tabName === "Object Detection") //Filter out ArUco unless we actually are doing ArUco
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.filter((it) => it.length); // Remove empty tab groups
|
.filter((it) => it.length); // Remove empty tab groups
|
||||||
|
|||||||
@@ -21,7 +21,9 @@ const processingMode = computed<number>({
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<v-card
|
<v-card
|
||||||
:disabled="useCameraSettingsStore().isDriverMode || useStateStore().colorPickingMode"
|
:disabled="
|
||||||
|
useCameraSettingsStore().isDriverMode || useCameraSettingsStore().isFocusMode || useStateStore().colorPickingMode
|
||||||
|
"
|
||||||
class="mt-3 rounded-12"
|
class="mt-3 rounded-12"
|
||||||
color="surface"
|
color="surface"
|
||||||
style="flex-grow: 1; display: flex; flex-direction: column"
|
style="flex-grow: 1; display: flex; flex-direction: column"
|
||||||
|
|||||||
@@ -2,60 +2,17 @@
|
|||||||
import { inject, ref } from "vue";
|
import { inject, ref } from "vue";
|
||||||
import { useStateStore } from "@/stores/StateStore";
|
import { useStateStore } from "@/stores/StateStore";
|
||||||
import PvSelect from "@/components/common/pv-select.vue";
|
import PvSelect from "@/components/common/pv-select.vue";
|
||||||
import PvInput from "@/components/common/pv-input.vue";
|
import PvDeleteModal from "@/components/common/pv-delete-modal.vue";
|
||||||
import axios from "axios";
|
|
||||||
import { useTheme } from "vuetify";
|
import { useTheme } from "vuetify";
|
||||||
|
import { axiosPost } from "@/lib/PhotonUtils";
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const restartProgram = () => {
|
const restartProgram = () => {
|
||||||
axios
|
axiosPost("/utils/restartProgram", "restart PhotonVision");
|
||||||
.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"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
const restartDevice = () => {
|
const restartDevice = () => {
|
||||||
axios
|
axiosPost("/utils/restartDevice", "restart the device");
|
||||||
.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"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const address = inject<string>("backendHost");
|
const address = inject<string>("backendHost");
|
||||||
@@ -77,47 +34,27 @@ const handleOfflineUpdate = () => {
|
|||||||
timeout: -1
|
timeout: -1
|
||||||
});
|
});
|
||||||
|
|
||||||
axios
|
axiosPost("/utils/offlineUpdate", "upload new software", formData, {
|
||||||
.post("/utils/offlineUpdate", formData, {
|
headers: { "Content-Type": "multipart/form-data" },
|
||||||
headers: { "Content-Type": "multipart/form-data" },
|
onUploadProgress: ({ progress }) => {
|
||||||
onUploadProgress: ({ progress }) => {
|
const uploadPercentage = (progress || 0) * 100.0;
|
||||||
const uploadPercentage = (progress || 0) * 100.0;
|
if (uploadPercentage < 99.5) {
|
||||||
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) {
|
|
||||||
useStateStore().showSnackbarMessage({
|
useStateStore().showSnackbarMessage({
|
||||||
color: "error",
|
message: "New Software Upload in Progress",
|
||||||
message: error.response.data.text || error.response.data
|
color: "secondary",
|
||||||
});
|
timeout: -1,
|
||||||
} else if (error.request) {
|
progressBar: uploadPercentage,
|
||||||
useStateStore().showSnackbarMessage({
|
progressBarColor: "primary"
|
||||||
color: "error",
|
|
||||||
message: "Error while trying to process the request! The backend didn't respond."
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
useStateStore().showSnackbarMessage({
|
useStateStore().showSnackbarMessage({
|
||||||
color: "error",
|
message: "Installing uploaded software...",
|
||||||
message: "An error occurred while trying to process the request."
|
color: "secondary",
|
||||||
|
timeout: -1
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const exportLogFile = ref();
|
const exportLogFile = ref();
|
||||||
@@ -166,29 +103,9 @@ const handleSettingsImport = () => {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
axios
|
axiosPost(`/settings${settingsEndpoint}`, "import settings", formData, {
|
||||||
.post(`/settings${settingsEndpoint}`, formData, { headers: { "Content-Type": "multipart/form-data" } })
|
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."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
showImportDialog.value = false;
|
showImportDialog.value = false;
|
||||||
importType.value = undefined;
|
importType.value = undefined;
|
||||||
@@ -196,36 +113,8 @@ const handleSettingsImport = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const showFactoryReset = ref(false);
|
const showFactoryReset = ref(false);
|
||||||
const expected = "Delete Everything";
|
|
||||||
const yesDeleteMySettingsText = ref("");
|
|
||||||
const nukePhotonConfigDirectory = () => {
|
const nukePhotonConfigDirectory = () => {
|
||||||
axios
|
axiosPost("/utils/nukeConfigDirectory", "delete the config directory");
|
||||||
.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;
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -387,63 +276,15 @@ const nukePhotonConfigDirectory = () => {
|
|||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</div>
|
</div>
|
||||||
<v-dialog v-model="showFactoryReset" width="800" dark>
|
<pv-delete-modal
|
||||||
<v-card color="surface" flat>
|
v-model="showFactoryReset"
|
||||||
<v-card-title style="display: flex; justify-content: center">
|
title="Factory Reset PhotonVision"
|
||||||
<span class="open-label">
|
description="This will delete all settings and configurations stored on this device, including network settings. This action cannot be undone."
|
||||||
<v-icon end color="red" class="open-icon ma-1" size="large">mdi-alert-outline</v-icon>
|
expected-confirmation-text="Delete Everything"
|
||||||
Factory Reset PhotonVision
|
:on-confirm="nukePhotonConfigDirectory"
|
||||||
<v-icon end color="red" class="open-icon ma-1" size="large">mdi-alert-outline</v-icon>
|
:on-backup="openExportSettingsPrompt"
|
||||||
</span>
|
delete-text="Factory reset"
|
||||||
</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>
|
|
||||||
</v-card>
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -315,23 +315,23 @@ watchEffect(() => {
|
|||||||
<v-col class="text-center">
|
<v-col class="text-center">
|
||||||
Background
|
Background
|
||||||
<v-color-picker
|
<v-color-picker
|
||||||
|
v-model:model-value="backgroundColor"
|
||||||
class="ma-auto pt-3"
|
class="ma-auto pt-3"
|
||||||
elevation="0"
|
elevation="0"
|
||||||
mode="hex"
|
mode="hex"
|
||||||
:modes="['hex']"
|
:modes="['hex']"
|
||||||
v-model:model-value="backgroundColor"
|
@update:model-value="(hex) => setThemeColor(theme, 'background', hex)"
|
||||||
v-on:update:model-value="(hex) => setThemeColor(theme, 'background', hex)"
|
|
||||||
></v-color-picker>
|
></v-color-picker>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col class="text-center">
|
<v-col class="text-center">
|
||||||
Surface
|
Surface
|
||||||
<v-color-picker
|
<v-color-picker
|
||||||
|
v-model:model-value="surfaceColor"
|
||||||
class="ma-auto pt-3"
|
class="ma-auto pt-3"
|
||||||
elevation="0"
|
elevation="0"
|
||||||
mode="hex"
|
mode="hex"
|
||||||
:modes="['hex']"
|
:modes="['hex']"
|
||||||
v-model:model-value="surfaceColor"
|
@update:model-value="(hex) => setThemeColor(theme, 'surface', hex)"
|
||||||
v-on:update:model-value="(hex) => setThemeColor(theme, 'surface', hex)"
|
|
||||||
></v-color-picker>
|
></v-color-picker>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
@@ -339,23 +339,23 @@ watchEffect(() => {
|
|||||||
<v-col class="text-center">
|
<v-col class="text-center">
|
||||||
Primary
|
Primary
|
||||||
<v-color-picker
|
<v-color-picker
|
||||||
|
v-model:model-value="primaryColor"
|
||||||
class="ma-auto pt-3"
|
class="ma-auto pt-3"
|
||||||
elevation="0"
|
elevation="0"
|
||||||
mode="hex"
|
mode="hex"
|
||||||
:modes="['hex']"
|
:modes="['hex']"
|
||||||
v-model:model-value="primaryColor"
|
@update:model-value="(hex) => setThemeColor(theme, 'primary', hex)"
|
||||||
v-on:update:model-value="(hex) => setThemeColor(theme, 'primary', hex)"
|
|
||||||
></v-color-picker>
|
></v-color-picker>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col class="text-center">
|
<v-col class="text-center">
|
||||||
Secondary
|
Secondary
|
||||||
<v-color-picker
|
<v-color-picker
|
||||||
|
v-model:model-value="secondaryColor"
|
||||||
class="ma-auto pt-3"
|
class="ma-auto pt-3"
|
||||||
elevation="0"
|
elevation="0"
|
||||||
mode="hex"
|
mode="hex"
|
||||||
:modes="['hex']"
|
:modes="['hex']"
|
||||||
v-model:model-value="secondaryColor"
|
@update:model-value="(hex) => setThemeColor(theme, 'secondary', hex)"
|
||||||
v-on:update:model-value="(hex) => setThemeColor(theme, 'secondary', hex)"
|
|
||||||
></v-color-picker>
|
></v-color-picker>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, inject } from "vue";
|
import { ref, computed, inject } from "vue";
|
||||||
import axios from "axios";
|
|
||||||
import { useStateStore } from "@/stores/StateStore";
|
import { useStateStore } from "@/stores/StateStore";
|
||||||
import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore";
|
import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore";
|
||||||
import type { ObjectDetectionModelProperties } from "@/types/SettingTypes";
|
import { type ObjectDetectionModelProperties } from "@/types/SettingTypes";
|
||||||
import pvInput from "@/components/common/pv-input.vue";
|
import PvDeleteModal from "@/components/common/pv-delete-modal.vue";
|
||||||
import { useTheme } from "vuetify";
|
import { useTheme } from "vuetify";
|
||||||
|
import { axiosPost } from "@/lib/PhotonUtils";
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const showImportDialog = ref(false);
|
const showImportDialog = ref(false);
|
||||||
@@ -43,34 +43,25 @@ const handleImport = async () => {
|
|||||||
timeout: -1
|
timeout: -1
|
||||||
});
|
});
|
||||||
|
|
||||||
axios
|
axiosPost("/objectdetection/import", "import an object detection model", formData, {
|
||||||
.post("/objectdetection/import", formData, {
|
headers: { "Content-Type": "multipart/form-data" },
|
||||||
headers: { "Content-Type": "multipart/form-data" }
|
onUploadProgress: ({ progress }) => {
|
||||||
})
|
const uploadPercentage = (progress || 0) * 100.0;
|
||||||
.then((response) => {
|
if (uploadPercentage < 99.5) {
|
||||||
useStateStore().showSnackbarMessage({
|
|
||||||
message: response.data.text || response.data,
|
|
||||||
color: "success"
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
if (error.response) {
|
|
||||||
useStateStore().showSnackbarMessage({
|
useStateStore().showSnackbarMessage({
|
||||||
color: "error",
|
message: "Object Detection Model Upload in Process, " + uploadPercentage.toFixed(2) + "% complete",
|
||||||
message: error.response.data.text || error.response.data
|
color: "secondary",
|
||||||
});
|
timeout: -1
|
||||||
} else if (error.request) {
|
|
||||||
useStateStore().showSnackbarMessage({
|
|
||||||
color: "error",
|
|
||||||
message: "Error while trying to process the request! The backend didn't respond."
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
useStateStore().showSnackbarMessage({
|
useStateStore().showSnackbarMessage({
|
||||||
color: "error",
|
message: "Processing uploaded Object Detection Model...",
|
||||||
message: "An error occurred while trying to process the request."
|
color: "secondary",
|
||||||
|
timeout: -1
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
|
||||||
showImportDialog.value = false;
|
showImportDialog.value = false;
|
||||||
|
|
||||||
@@ -82,41 +73,9 @@ const handleImport = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const deleteModel = async (model: ObjectDetectionModelProperties) => {
|
const deleteModel = async (model: ObjectDetectionModelProperties) => {
|
||||||
useStateStore().showSnackbarMessage({
|
axiosPost("/objectdetection/delete", "delete an object detection model", {
|
||||||
message: "Deleting Object Detection Model...",
|
modelPath: model.modelPath
|
||||||
color: "secondary",
|
|
||||||
timeout: -1
|
|
||||||
});
|
});
|
||||||
|
|
||||||
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) => {
|
const renameModel = async (model: ObjectDetectionModelProperties, newName: string) => {
|
||||||
@@ -126,35 +85,10 @@ const renameModel = async (model: ObjectDetectionModelProperties, newName: strin
|
|||||||
timeout: -1
|
timeout: -1
|
||||||
});
|
});
|
||||||
|
|
||||||
axios
|
axiosPost("/objectdetection/rename", "rename an object detection model", {
|
||||||
.post("/objectdetection/rename", {
|
modelPath: model.modelPath.replace("file:", ""),
|
||||||
modelPath: model.modelPath.replace("file:", ""),
|
newName: newName
|
||||||
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."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
showRenameDialog.value.show = false;
|
showRenameDialog.value.show = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -181,36 +115,8 @@ const openExportIndividualModelPrompt = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const showNukeDialog = ref(false);
|
const showNukeDialog = ref(false);
|
||||||
const expected = "Delete Models";
|
|
||||||
const yesDeleteMyModelsText = ref("");
|
|
||||||
const nukeModels = () => {
|
const nukeModels = () => {
|
||||||
axios
|
axiosPost("/objectdetection/nuke", "clear and reset object detection models");
|
||||||
.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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const showBulkImportDialog = ref(false);
|
const showBulkImportDialog = ref(false);
|
||||||
@@ -221,51 +127,27 @@ const handleBulkImport = () => {
|
|||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("data", importFile.value);
|
formData.append("data", importFile.value);
|
||||||
|
|
||||||
axios
|
axiosPost("/objectdetection/bulkimport", "import object detection models", formData, {
|
||||||
.post("/objectdetection/bulkimport", formData, {
|
headers: { "Content-Type": "multipart/form-data" },
|
||||||
headers: { "Content-Type": "multipart/form-data" },
|
onUploadProgress: ({ progress }) => {
|
||||||
onUploadProgress: ({ progress }) => {
|
const uploadPercentage = (progress || 0) * 100.0;
|
||||||
const uploadPercentage = (progress || 0) * 100.0;
|
if (uploadPercentage < 99.5) {
|
||||||
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) {
|
|
||||||
useStateStore().showSnackbarMessage({
|
useStateStore().showSnackbarMessage({
|
||||||
color: "error",
|
message: "Object Detection Models Upload in Progress",
|
||||||
message: error.response.data.text || error.response.data
|
color: "secondary",
|
||||||
});
|
timeout: -1,
|
||||||
} else if (error.request) {
|
progressBar: uploadPercentage,
|
||||||
useStateStore().showSnackbarMessage({
|
progressBarColor: "primary"
|
||||||
color: "error",
|
|
||||||
message: "Error while trying to process the request! The backend didn't respond."
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
useStateStore().showSnackbarMessage({
|
useStateStore().showSnackbarMessage({
|
||||||
color: "error",
|
message: "Importing New Object Detection Models...",
|
||||||
message: "An error occurred while trying to process the request."
|
color: "secondary",
|
||||||
|
timeout: -1
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
});
|
||||||
showImportDialog.value = false;
|
showImportDialog.value = false;
|
||||||
importFile.value = null;
|
importFile.value = null;
|
||||||
};
|
};
|
||||||
@@ -485,35 +367,20 @@ const handleBulkImport = () => {
|
|||||||
</tbody>
|
</tbody>
|
||||||
</v-table>
|
</v-table>
|
||||||
|
|
||||||
<v-dialog v-model="confirmDeleteDialog.show" width="600">
|
<pv-delete-modal
|
||||||
<v-card color="surface" dark>
|
v-model="confirmDeleteDialog.show"
|
||||||
<v-card-title>Delete Object Detection Model</v-card-title>
|
:width="500"
|
||||||
<v-card-text class="pt-0">
|
:on-confirm="() => deleteModel(confirmDeleteDialog.model)"
|
||||||
Are you sure you want to delete the model {{ confirmDeleteDialog.model.nickname }}?
|
title="Delete Object Detection Model"
|
||||||
<v-card-actions class="pt-5 pb-0 pr-0" style="justify-content: flex-end">
|
:description="`Are you sure you want to delete the model ${confirmDeleteDialog.model.nickname}?`"
|
||||||
<v-btn
|
delete-text="Delete model"
|
||||||
: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>
|
|
||||||
<v-dialog v-model="showRenameDialog.show" width="600">
|
<v-dialog v-model="showRenameDialog.show" width="600">
|
||||||
<v-card color="surface" dark>
|
<v-card color="surface" dark>
|
||||||
<v-card-title>Rename Object Detection Model</v-card-title>
|
<v-card-title>Rename Object Detection Model</v-card-title>
|
||||||
<v-card-text class="pt-0">
|
<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">
|
<div class="pa-5 pb-0">
|
||||||
<v-text-field v-model="showRenameDialog.newName" hide-details label="New Name" variant="underlined" />
|
<v-text-field v-model="showRenameDialog.newName" hide-details label="New Name" variant="underlined" />
|
||||||
</div>
|
</div>
|
||||||
@@ -569,64 +436,15 @@ const handleBulkImport = () => {
|
|||||||
</v-row>
|
</v-row>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<v-dialog v-model="showNukeDialog" width="800" dark>
|
<pv-delete-modal
|
||||||
<v-card color="surface" flat>
|
v-model="showNukeDialog"
|
||||||
<v-card-title style="display: flex; justify-content: center">
|
:on-backup="openExportPrompt"
|
||||||
<span class="open-label">
|
:on-confirm="nukeModels"
|
||||||
<v-icon end color="error" class="open-icon ma-1" size="large">mdi-alert-outline</v-icon>
|
title="Delete and Reset All Object Detection Models"
|
||||||
Clear and Reset Object Detection Models
|
:description="'This will delete ALL object detection models and re-extract the default object detection models. This action cannot be undone.'"
|
||||||
<v-icon end color="error" class="open-icon ma-1" size="large">mdi-alert-outline</v-icon>
|
:expected-confirmation-text="'Delete Models'"
|
||||||
</span>
|
delete-text="Delete all models"
|
||||||
</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>
|
|
||||||
</v-card>
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
import { useStateStore } from "@/stores/StateStore";
|
||||||
import type { Resolution } from "@/types/SettingTypes";
|
import type { Resolution } from "@/types/SettingTypes";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
export const resolutionsAreEqual = (a: Resolution, b: Resolution) => {
|
export const resolutionsAreEqual = (a: Resolution, b: Resolution) => {
|
||||||
return a.height === b.height && a.width === b.width;
|
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);
|
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: {
|
snackbarData: {
|
||||||
show: boolean;
|
show: boolean;
|
||||||
|
progressBar: number;
|
||||||
|
progressBarColor: string;
|
||||||
message: string;
|
message: string;
|
||||||
color: string;
|
color: string;
|
||||||
timeout: number;
|
timeout: number;
|
||||||
@@ -86,6 +88,8 @@ export const useStateStore = defineStore("state", {
|
|||||||
|
|
||||||
snackbarData: {
|
snackbarData: {
|
||||||
show: false,
|
show: false,
|
||||||
|
progressBar: -1,
|
||||||
|
progressBarColor: "info",
|
||||||
message: "No Message",
|
message: "No Message",
|
||||||
color: "info",
|
color: "info",
|
||||||
timeout: 2000
|
timeout: 2000
|
||||||
@@ -158,11 +162,19 @@ export const useStateStore = defineStore("state", {
|
|||||||
updateDiscoveredCameras(data: VsmState) {
|
updateDiscoveredCameras(data: VsmState) {
|
||||||
this.vsmState = data;
|
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 = {
|
this.snackbarData = {
|
||||||
show: true,
|
show: true,
|
||||||
|
progressBar: data.progressBar || -1,
|
||||||
message: data.message,
|
message: data.message,
|
||||||
color: data.color,
|
color: data.color,
|
||||||
|
progressBarColor: data.progressBarColor || "",
|
||||||
timeout: data.timeout || 2000
|
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)
|
// 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 {
|
currentWebsocketPipelineType(): WebsocketPipelineType {
|
||||||
return this.currentPipelineType - 2;
|
return this.currentPipelineType - 3;
|
||||||
},
|
},
|
||||||
currentVideoFormat(): VideoFormat {
|
currentVideoFormat(): VideoFormat {
|
||||||
return this.currentCameraSettings.validVideoFormats[this.currentPipelineSettings.cameraVideoModeIndex];
|
return this.currentCameraSettings.validVideoFormats[this.currentPipelineSettings.cameraVideoModeIndex];
|
||||||
@@ -76,6 +76,9 @@ export const useCameraSettingsStore = defineStore("cameraSettings", {
|
|||||||
isCalibrationMode(): boolean {
|
isCalibrationMode(): boolean {
|
||||||
return this.currentCameraSettings.currentPipelineIndex == WebsocketPipelineType.Calib3d;
|
return this.currentCameraSettings.currentPipelineIndex == WebsocketPipelineType.Calib3d;
|
||||||
},
|
},
|
||||||
|
isFocusMode(): boolean {
|
||||||
|
return this.currentCameraSettings.currentPipelineIndex == WebsocketPipelineType.FocusCamera;
|
||||||
|
},
|
||||||
isCSICamera(): boolean {
|
isCSICamera(): boolean {
|
||||||
return this.currentCameraSettings.isCSICamera;
|
return this.currentCameraSettings.isCSICamera;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -70,6 +70,8 @@ export interface PipelineResult {
|
|||||||
sequenceID: number;
|
sequenceID: number;
|
||||||
fps: number;
|
fps: number;
|
||||||
latency: number;
|
latency: number;
|
||||||
|
// Focus pipeline
|
||||||
|
focus?: number;
|
||||||
targets: PhotonTarget[];
|
targets: PhotonTarget[];
|
||||||
// undefined if multitag failed or non-tag pipeline
|
// undefined if multitag failed or non-tag pipeline
|
||||||
multitagResult?: MultitagResult;
|
multitagResult?: MultitagResult;
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
import type { WebsocketNumberPair } from "@/types/WebsocketDataTypes";
|
import type { WebsocketNumberPair } from "@/types/WebsocketDataTypes";
|
||||||
import type { ObjectDetectionModelProperties } from "@/types/SettingTypes";
|
import type { ObjectDetectionModelProperties } from "@/types/SettingTypes";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The on-wire form of PipelineType.java (the enum is serialized with `ordinal()`)
|
||||||
|
*/
|
||||||
export enum PipelineType {
|
export enum PipelineType {
|
||||||
DriverMode = 1,
|
DriverMode = 2,
|
||||||
Reflective = 2,
|
Reflective = 3,
|
||||||
ColoredShape = 3,
|
ColoredShape = 4,
|
||||||
AprilTag = 4,
|
AprilTag = 5,
|
||||||
Aruco = 5,
|
Aruco = 6,
|
||||||
ObjectDetection = 6
|
ObjectDetection = 7
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AprilTagFamily {
|
export enum AprilTagFamily {
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ export interface IncomingWebsocketData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum WebsocketPipelineType {
|
export enum WebsocketPipelineType {
|
||||||
|
FocusCamera = -3,
|
||||||
Calib3d = -2,
|
Calib3d = -2,
|
||||||
DriverMode = -1,
|
DriverMode = -1,
|
||||||
Reflective = 0,
|
Reflective = 0,
|
||||||
|
|||||||
@@ -7,16 +7,13 @@ import {
|
|||||||
PVCameraInfo,
|
PVCameraInfo,
|
||||||
type PVCSICameraInfo,
|
type PVCSICameraInfo,
|
||||||
type PVFileCameraInfo,
|
type PVFileCameraInfo,
|
||||||
type PVUsbCameraInfo,
|
type PVUsbCameraInfo
|
||||||
type UiCameraConfiguration
|
|
||||||
} from "@/types/SettingTypes";
|
} from "@/types/SettingTypes";
|
||||||
import { getResolutionString } from "@/lib/PhotonUtils";
|
import { axiosPost, getResolutionString } from "@/lib/PhotonUtils";
|
||||||
import PhotonCameraStream from "@/components/app/photon-camera-stream.vue";
|
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 PvCameraInfoCard from "@/components/common/pv-camera-info-card.vue";
|
||||||
import axios from "axios";
|
|
||||||
import PvCameraMatchCard from "@/components/common/pv-camera-match-card.vue";
|
import PvCameraMatchCard from "@/components/common/pv-camera-match-card.vue";
|
||||||
import type { WebsocketCameraSettingsUpdate } from "@/types/WebsocketDataTypes";
|
|
||||||
import { useTheme } from "vuetify";
|
import { useTheme } from "vuetify";
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@@ -28,33 +25,9 @@ const activateModule = (moduleUniqueName: string) => {
|
|||||||
if (activatingModule.value) return;
|
if (activatingModule.value) return;
|
||||||
activatingModule.value = true;
|
activatingModule.value = true;
|
||||||
|
|
||||||
axios
|
axiosPost("/utils/activateMatchedCamera", "activate a matched camera", {
|
||||||
.post("/utils/activateMatchedCamera", { cameraUniqueName: moduleUniqueName })
|
cameraUniqueName: moduleUniqueName
|
||||||
.then(() => {
|
}).finally(() => (activatingModule.value = false));
|
||||||
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));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const assigningCamera = ref(false);
|
const assigningCamera = ref(false);
|
||||||
@@ -66,106 +39,29 @@ const assignCamera = (cameraInfo: PVCameraInfo) => {
|
|||||||
cameraInfo: cameraInfo
|
cameraInfo: cameraInfo
|
||||||
};
|
};
|
||||||
|
|
||||||
axios
|
axiosPost("/utils/assignUnmatchedCamera", "assign an unmatched camera", payload).finally(
|
||||||
.post("/utils/assignUnmatchedCamera", payload)
|
() => (assigningCamera.value = false)
|
||||||
.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));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const deactivatingModule = ref(false);
|
const deactivatingModule = ref(false);
|
||||||
const deactivateModule = (cameraUniqueName: string) => {
|
const deactivateModule = (cameraUniqueName: string) => {
|
||||||
if (deactivatingModule.value) return;
|
if (deactivatingModule.value) return;
|
||||||
deactivatingModule.value = true;
|
deactivatingModule.value = true;
|
||||||
axios
|
axiosPost("/utils/unassignCamera", "unassign a camera", { cameraUniqueName: cameraUniqueName }).finally(
|
||||||
.post("/utils/unassignCamera", { cameraUniqueName: cameraUniqueName })
|
() => (deactivatingModule.value = false)
|
||||||
.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));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const deletingCamera = ref(false);
|
const confirmDeleteDialog = ref({ show: false, nickname: "", cameraUniqueName: "" });
|
||||||
const deleteThisCamera = (cameraName: string) => {
|
const deletingCamera = ref<string | null>(null);
|
||||||
if (deletingCamera.value) return;
|
|
||||||
deletingCamera.value = true;
|
|
||||||
const payload = {
|
|
||||||
cameraUniqueName: cameraName
|
|
||||||
};
|
|
||||||
|
|
||||||
axios
|
const deleteThisCamera = (cameraUniqueName: string) => {
|
||||||
.post("/utils/nukeOneCamera", payload)
|
if (deletingCamera.value) return;
|
||||||
.then(() => {
|
deletingCamera.value = cameraUniqueName;
|
||||||
useStateStore().showSnackbarMessage({
|
axiosPost("/utils/nukeOneCamera", "delete a camera", { cameraUniqueName: cameraUniqueName }).finally(() => {
|
||||||
message: "Camera deleted successfully",
|
deletingCamera.value = null;
|
||||||
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 cameraConnected = (uniquePath: string): boolean => {
|
const cameraConnected = (uniquePath: string): boolean => {
|
||||||
@@ -209,15 +105,6 @@ const setCameraView = (camera: PVCameraInfo | null, isConnected: boolean | null)
|
|||||||
viewingCamera.value = [camera, isConnected];
|
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.
|
* 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"
|
class="pa-0"
|
||||||
color="error"
|
color="error"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
|
:loading="module.uniqueName === deletingCamera"
|
||||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
: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-icon size="x-large">mdi-trash-can-outline</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
@@ -459,8 +354,16 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
|
|||||||
class="pa-0"
|
class="pa-0"
|
||||||
color="error"
|
color="error"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
|
:loading="module.uniqueName === deletingCamera"
|
||||||
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
|
: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-icon size="x-large">mdi-trash-can-outline</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
@@ -564,43 +467,13 @@ const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => {
|
|||||||
</v-card>
|
</v-card>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
|
|
||||||
<!-- Camera delete modal -->
|
<pv-delete-modal
|
||||||
<v-dialog v-model="viewingDeleteCamera" width="800">
|
v-model="confirmDeleteDialog.show"
|
||||||
<v-card v-if="cameraToDelete !== null" class="dialog-container" color="surface" flat>
|
title="Delete Camera"
|
||||||
<v-card-title> Delete {{ cameraToDelete.nickname }}? </v-card-title>
|
:description="`Are you sure you want to delete the camera '${useCameraSettingsStore().currentCameraSettings.nickname}'? This action cannot be undone.`"
|
||||||
<v-card-text class="pb-10px">
|
:expected-confirmation-text="confirmDeleteDialog.nickname"
|
||||||
Are you sure you want to delete "{{ cameraToDelete.nickname }}"? This cannot be undone.
|
:on-confirm="() => deleteThisCamera(confirmDeleteDialog.cameraUniqueName)"
|
||||||
</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>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,13 @@ const cameraViewType = computed<number[]>({
|
|||||||
// Only show the input stream in Color Picking Mode
|
// Only show the input stream in Color Picking Mode
|
||||||
if (useStateStore().colorPickingMode) return [0];
|
if (useStateStore().colorPickingMode) return [0];
|
||||||
|
|
||||||
// Only show the output stream in Driver Mode or Calibration Mode
|
// Only show the output stream in Driver Mode or Calibration Mode or Focus Mode
|
||||||
if (useCameraSettingsStore().isDriverMode || useCameraSettingsStore().isCalibrationMode) return [1];
|
if (
|
||||||
|
useCameraSettingsStore().isDriverMode ||
|
||||||
|
useCameraSettingsStore().isCalibrationMode ||
|
||||||
|
useCameraSettingsStore().isFocusMode
|
||||||
|
)
|
||||||
|
return [1];
|
||||||
|
|
||||||
const ret: number[] = [];
|
const ret: number[] = [];
|
||||||
if (useCameraSettingsStore().currentPipelineSettings.inputShouldShow) {
|
if (useCameraSettingsStore().currentPipelineSettings.inputShouldShow) {
|
||||||
|
|||||||
@@ -17,8 +17,13 @@ const cameraViewType = computed<number[]>({
|
|||||||
// Only show the input stream in Color Picking Mode
|
// Only show the input stream in Color Picking Mode
|
||||||
if (useStateStore().colorPickingMode) return [0];
|
if (useStateStore().colorPickingMode) return [0];
|
||||||
|
|
||||||
// Only show the output stream in Driver Mode or Calibration Mode
|
// Only show the output stream in Driver Mode or Calibration Mode or Focus Mode
|
||||||
if (useCameraSettingsStore().isDriverMode || useCameraSettingsStore().isCalibrationMode) return [1];
|
if (
|
||||||
|
useCameraSettingsStore().isDriverMode ||
|
||||||
|
useCameraSettingsStore().isCalibrationMode ||
|
||||||
|
useCameraSettingsStore().isFocusMode
|
||||||
|
)
|
||||||
|
return [1];
|
||||||
|
|
||||||
const ret: number[] = [];
|
const ret: number[] = [];
|
||||||
if (useCameraSettingsStore().currentPipelineSettings.inputShouldShow) {
|
if (useCameraSettingsStore().currentPipelineSettings.inputShouldShow) {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import edu.wpi.first.cscore.UsbCameraInfo;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import org.opencv.core.Size;
|
||||||
import org.photonvision.common.dataflow.websocket.UICameraConfiguration;
|
import org.photonvision.common.dataflow.websocket.UICameraConfiguration;
|
||||||
import org.photonvision.common.logging.LogGroup;
|
import org.photonvision.common.logging.LogGroup;
|
||||||
import org.photonvision.common.logging.Logger;
|
import org.photonvision.common.logging.Logger;
|
||||||
@@ -189,6 +190,23 @@ public class CameraConfiguration {
|
|||||||
calibrations.add(calibration);
|
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
|
* 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
|
* 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.common.util.SerializationUtils;
|
||||||
import org.photonvision.vision.pipeline.result.CVPipelineResult;
|
import org.photonvision.vision.pipeline.result.CVPipelineResult;
|
||||||
import org.photonvision.vision.pipeline.result.CalibrationPipelineResult;
|
import org.photonvision.vision.pipeline.result.CalibrationPipelineResult;
|
||||||
|
import org.photonvision.vision.pipeline.result.FocusPipelineResult;
|
||||||
|
|
||||||
public class UIDataPublisher implements CVPipelineResultConsumer {
|
public class UIDataPublisher implements CVPipelineResultConsumer {
|
||||||
private static final Logger logger = new Logger(UIDataPublisher.class, LogGroup.VisionModule);
|
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>>();
|
var uiMap = new HashMap<String, HashMap<String, Object>>();
|
||||||
uiMap.put(uniqueName, dataMap);
|
uiMap.put(uniqueName, dataMap);
|
||||||
|
|
||||||
|
if (result instanceof FocusPipelineResult focusResult) {
|
||||||
|
dataMap.put("focus", focusResult.focus);
|
||||||
|
}
|
||||||
|
|
||||||
DataChangeService.getInstance()
|
DataChangeService.getInstance()
|
||||||
.publishEvent(OutgoingUIEvent.wrappedOf("updatePipelineResult", uiMap));
|
.publishEvent(OutgoingUIEvent.wrappedOf("updatePipelineResult", uiMap));
|
||||||
lastUIResultUpdateTime = now;
|
lastUIResultUpdateTime = now;
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ public class PhotonArucoDetector implements Releasable {
|
|||||||
// each detection has a Mat of corners
|
// each detection has a Mat of corners
|
||||||
Mat cornerMat = cornerMats.get(i);
|
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).
|
// For parity with AprilTags and photonlib, we want (BL, BR, TR, TL).
|
||||||
double[] xCorners = {
|
double[] xCorners = {
|
||||||
cornerMat.get(0, 1)[0],
|
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(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));
|
videoModes.put(6, new FPSRatedVideoMode(PixelFormat.kUnknown, 3280 / 4, 2464 / 4, 15, 20, 1));
|
||||||
} else if (sensorModel == LibCameraJNI.SensorModel.OV9281) {
|
} else if (sensorModel == LibCameraJNI.SensorModel.OV9281) {
|
||||||
videoModes.put(0, new FPSRatedVideoMode(PixelFormat.kUnknown, 320, 240, 30, 30, .39));
|
// Taken from https://www.ovt.com/wp-content/uploads/2022/01/OV9281-OV9282-PB-v1.3-WEB.pdf
|
||||||
videoModes.put(1, new FPSRatedVideoMode(PixelFormat.kUnknown, 1280 / 2, 800 / 2, 60, 60, 1));
|
videoModes.put(0, new FPSRatedVideoMode(PixelFormat.kUnknown, 640, 400, 120, 240, 1));
|
||||||
videoModes.put(2, new FPSRatedVideoMode(PixelFormat.kUnknown, 640, 480, 65, 90, .39));
|
videoModes.put(1, new FPSRatedVideoMode(PixelFormat.kUnknown, 1280, 800, 120, 120, 1));
|
||||||
videoModes.put(3, new FPSRatedVideoMode(PixelFormat.kUnknown, 1280, 800, 60, 60, 1));
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (sensorModel == LibCameraJNI.SensorModel.IMX477) {
|
if (sensorModel == LibCameraJNI.SensorModel.IMX477) {
|
||||||
|
|||||||
@@ -280,14 +280,14 @@ public class FindBoardCornersPipe
|
|||||||
}
|
}
|
||||||
board.matchImagePoints(detectedCornersList, detectedIds, objPoints, imgPoints);
|
board.matchImagePoints(detectedCornersList, detectedIds, objPoints, imgPoints);
|
||||||
|
|
||||||
// draw the charuco board
|
// Draw the ChArUco board
|
||||||
Objdetect.drawDetectedCornersCharuco(
|
Objdetect.drawDetectedCornersCharuco(
|
||||||
outFrame, detectedCorners, detectedIds, new Scalar(0, 0, 255)); // Red Text
|
outFrame, detectedCorners, detectedIds, new Scalar(0, 0, 255)); // Red Text
|
||||||
|
|
||||||
imgPoints.copyTo(outBoardCorners);
|
imgPoints.copyTo(outBoardCorners);
|
||||||
objPoints.copyTo(objPts);
|
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
|
// 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
|
// corresponding level to -1. Calibrate3dPipe deals with piping this into the correct format
|
||||||
// for each backend
|
// for each backend
|
||||||
@@ -321,7 +321,7 @@ public class FindBoardCornersPipe
|
|||||||
detectedCorners.release();
|
detectedCorners.release();
|
||||||
detectedIds.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
|
// Reduce the image size to be much more manageable
|
||||||
// Note that opencv will copy the frame if no resize is requested; we can skip
|
// Note that opencv will copy the frame if no resize is requested; we can skip
|
||||||
// this since we
|
// 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);
|
settings.threads = Math.max(1, settings.threads);
|
||||||
|
|
||||||
// for now, hard code tag width based on enum value
|
// for now, hard code tag width based on enum value
|
||||||
// 2023/other: best guess is 6in
|
// From 2024 best guess is 6.5
|
||||||
double tagWidth = Units.inchesToMeters(6);
|
double tagWidth = Units.inchesToMeters(6.5);
|
||||||
TargetModel tagModel = TargetModel.kAprilTag16h5;
|
TargetModel tagModel = TargetModel.kAprilTag36h11;
|
||||||
if (settings.tagFamily == AprilTagFamily.kTag36h11) {
|
if (settings.tagFamily == AprilTagFamily.kTag16h5) {
|
||||||
// 2024 tag, 6.5in
|
// 2023 tag, 6in
|
||||||
tagWidth = Units.inchesToMeters(6.5);
|
tagWidth = Units.inchesToMeters(6);
|
||||||
tagModel = TargetModel.kAprilTag36h11;
|
tagModel = TargetModel.kAprilTag16h5;
|
||||||
}
|
}
|
||||||
|
|
||||||
var config = new AprilTagDetector.Config();
|
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")
|
@SuppressWarnings("rawtypes")
|
||||||
public enum PipelineType {
|
public enum PipelineType {
|
||||||
|
FocusCamera(-3, FocusPipeline.class),
|
||||||
Calib3d(-2, Calibrate3dPipeline.class),
|
Calib3d(-2, Calibrate3dPipeline.class),
|
||||||
DriverMode(-1, DriverModePipeline.class),
|
DriverMode(-1, DriverModePipeline.class),
|
||||||
Reflective(0, ReflectivePipeline.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);
|
private static final Logger logger = new Logger(PipelineManager.class, LogGroup.VisionModule);
|
||||||
|
|
||||||
public static final int DRIVERMODE_INDEX = -1;
|
public static final int DRIVERMODE_INDEX = -1;
|
||||||
|
public static final int FOCUS_INDEX = -3;
|
||||||
public static final int CAL_3D_INDEX = -2;
|
public static final int CAL_3D_INDEX = -2;
|
||||||
|
|
||||||
protected final List<CVPipelineSettings> userPipelineSettings;
|
protected final List<CVPipelineSettings> userPipelineSettings;
|
||||||
protected final Calibrate3dPipeline calibration3dPipeline;
|
protected final Calibrate3dPipeline calibration3dPipeline;
|
||||||
|
protected final FocusPipeline focusPipeline = new FocusPipeline();
|
||||||
protected final DriverModePipeline driverModePipeline = new DriverModePipeline();
|
protected final DriverModePipeline driverModePipeline = new DriverModePipeline();
|
||||||
|
|
||||||
/** Index of the currently active pipeline. Defaults to 0. */
|
/** Index of the currently active pipeline. Defaults to 0. */
|
||||||
@@ -93,6 +95,7 @@ public class PipelineManager {
|
|||||||
return switch (index) {
|
return switch (index) {
|
||||||
case DRIVERMODE_INDEX -> driverModePipeline.getSettings();
|
case DRIVERMODE_INDEX -> driverModePipeline.getSettings();
|
||||||
case CAL_3D_INDEX -> calibration3dPipeline.getSettings();
|
case CAL_3D_INDEX -> calibration3dPipeline.getSettings();
|
||||||
|
case FOCUS_INDEX -> focusPipeline.getSettings();
|
||||||
default -> {
|
default -> {
|
||||||
for (var setting : userPipelineSettings) {
|
for (var setting : userPipelineSettings) {
|
||||||
if (setting.pipelineIndex == index) yield setting;
|
if (setting.pipelineIndex == index) yield setting;
|
||||||
@@ -112,6 +115,7 @@ public class PipelineManager {
|
|||||||
return switch (index) {
|
return switch (index) {
|
||||||
case DRIVERMODE_INDEX -> driverModePipeline.getSettings().pipelineNickname;
|
case DRIVERMODE_INDEX -> driverModePipeline.getSettings().pipelineNickname;
|
||||||
case CAL_3D_INDEX -> calibration3dPipeline.getSettings().pipelineNickname;
|
case CAL_3D_INDEX -> calibration3dPipeline.getSettings().pipelineNickname;
|
||||||
|
case FOCUS_INDEX -> focusPipeline.getSettings().pipelineNickname;
|
||||||
default -> {
|
default -> {
|
||||||
for (var setting : userPipelineSettings) {
|
for (var setting : userPipelineSettings) {
|
||||||
if (setting.pipelineIndex == index) yield setting.pipelineNickname;
|
if (setting.pipelineIndex == index) yield setting.pipelineNickname;
|
||||||
@@ -153,6 +157,7 @@ public class PipelineManager {
|
|||||||
return switch (currentPipelineIndex) {
|
return switch (currentPipelineIndex) {
|
||||||
case CAL_3D_INDEX -> calibration3dPipeline;
|
case CAL_3D_INDEX -> calibration3dPipeline;
|
||||||
case DRIVERMODE_INDEX -> driverModePipeline;
|
case DRIVERMODE_INDEX -> driverModePipeline;
|
||||||
|
case FOCUS_INDEX -> focusPipeline;
|
||||||
// Just return the current user pipeline, we're not on a built-in one
|
// Just return the current user pipeline, we're not on a built-in one
|
||||||
default -> currentUserPipeline;
|
default -> currentUserPipeline;
|
||||||
};
|
};
|
||||||
@@ -253,7 +258,7 @@ public class PipelineManager {
|
|||||||
new AprilTagPipeline((AprilTagPipelineSettings) desiredPipelineSettings);
|
new AprilTagPipeline((AprilTagPipelineSettings) desiredPipelineSettings);
|
||||||
}
|
}
|
||||||
case Aruco -> {
|
case Aruco -> {
|
||||||
logger.debug("Creating Aruco Pipeline");
|
logger.debug("Creating ArUco Pipeline");
|
||||||
currentUserPipeline = new ArucoPipeline((ArucoPipelineSettings) desiredPipelineSettings);
|
currentUserPipeline = new ArucoPipeline((ArucoPipelineSettings) desiredPipelineSettings);
|
||||||
}
|
}
|
||||||
case ObjectDetection -> {
|
case ObjectDetection -> {
|
||||||
@@ -261,7 +266,7 @@ public class PipelineManager {
|
|||||||
currentUserPipeline =
|
currentUserPipeline =
|
||||||
new ObjectDetectionPipeline((ObjectDetectionPipelineSettings) desiredPipelineSettings);
|
new ObjectDetectionPipeline((ObjectDetectionPipelineSettings) desiredPipelineSettings);
|
||||||
}
|
}
|
||||||
case Calib3d, DriverMode -> {}
|
case Calib3d, DriverMode, FocusCamera -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,7 +340,7 @@ public class PipelineManager {
|
|||||||
case AprilTag -> new AprilTagPipelineSettings();
|
case AprilTag -> new AprilTagPipelineSettings();
|
||||||
case Aruco -> new ArucoPipelineSettings();
|
case Aruco -> new ArucoPipelineSettings();
|
||||||
case ObjectDetection -> new ObjectDetectionPipelineSettings();
|
case ObjectDetection -> new ObjectDetectionPipelineSettings();
|
||||||
case Calib3d, DriverMode -> {
|
case Calib3d, DriverMode, FocusCamera -> {
|
||||||
logger.error("Got invalid pipeline type: " + type);
|
logger.error("Got invalid pipeline type: " + type);
|
||||||
yield null;
|
yield null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -680,6 +680,16 @@ public class VisionModule {
|
|||||||
saveAndBroadcastAll();
|
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
|
* 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 edu.wpi.first.cscore.VideoMode;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import org.opencv.core.Size;
|
||||||
import org.photonvision.common.configuration.CameraConfiguration;
|
import org.photonvision.common.configuration.CameraConfiguration;
|
||||||
import org.photonvision.common.logging.LogGroup;
|
import org.photonvision.common.logging.LogGroup;
|
||||||
import org.photonvision.common.logging.Logger;
|
import org.photonvision.common.logging.Logger;
|
||||||
@@ -120,6 +121,11 @@ public abstract class VisionSourceSettables {
|
|||||||
calculateFrameStaticProps();
|
calculateFrameStaticProps();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeCalibration(Size unrotatedImageSize) {
|
||||||
|
configuration.removeCalibration(unrotatedImageSize);
|
||||||
|
calculateFrameStaticProps();
|
||||||
|
}
|
||||||
|
|
||||||
protected void calculateFrameStaticProps() {
|
protected void calculateFrameStaticProps() {
|
||||||
var videoMode = getCurrentVideoMode();
|
var videoMode = getCurrentVideoMode();
|
||||||
this.frameStaticProperties =
|
this.frameStaticProperties =
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import java.io.File;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.photonvision.common.util.TestUtils;
|
||||||
|
|
||||||
public class NetworkConfigTest {
|
public class NetworkConfigTest {
|
||||||
@Test
|
@Test
|
||||||
@@ -39,13 +40,13 @@ public class NetworkConfigTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testDeserializeTeamNumberOrNtServerAddress() {
|
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));
|
var configMgr = new ConfigManager(folder, new LegacyConfigProvider(folder));
|
||||||
configMgr.load();
|
configMgr.load();
|
||||||
assertEquals("9999", configMgr.getConfig().getNetworkConfig().ntServerAddress);
|
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));
|
var configMgr = new ConfigManager(folder, new LegacyConfigProvider(folder));
|
||||||
configMgr.load();
|
configMgr.load();
|
||||||
assertEquals("9999", configMgr.getConfig().getNetworkConfig().ntServerAddress);
|
assertEquals("9999", configMgr.getConfig().getNetworkConfig().ntServerAddress);
|
||||||
|
|||||||
@@ -109,7 +109,6 @@ public class AprilTagTest {
|
|||||||
pipeline.getSettings().solvePNPEnabled = true;
|
pipeline.getSettings().solvePNPEnabled = true;
|
||||||
pipeline.getSettings().cornerDetectionAccuracyPercentage = 4;
|
pipeline.getSettings().cornerDetectionAccuracyPercentage = 4;
|
||||||
pipeline.getSettings().cornerDetectionUseConvexHulls = true;
|
pipeline.getSettings().cornerDetectionUseConvexHulls = true;
|
||||||
pipeline.getSettings().targetModel = TargetModel.kAprilTag6p5in_36h11;
|
|
||||||
pipeline.getSettings().tagFamily = AprilTagFamily.kTag16h5;
|
pipeline.getSettings().tagFamily = AprilTagFamily.kTag16h5;
|
||||||
|
|
||||||
var frameProvider =
|
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
|
return retVal
|
||||||
|
|
||||||
def getData(self) -> bytes:
|
def getData(self) -> bytes:
|
||||||
"""
|
"""Return the packet data."""
|
||||||
* Returns the packet data.
|
|
||||||
*
|
|
||||||
* @return The packet data.
|
|
||||||
"""
|
|
||||||
return self.packetData
|
return self.packetData
|
||||||
|
|
||||||
def setData(self, data: bytes):
|
def setData(self, data: bytes):
|
||||||
"""
|
"""Set the packet data."""
|
||||||
* Sets the packet data.
|
|
||||||
*
|
|
||||||
* @param data The packet data.
|
|
||||||
"""
|
|
||||||
self.clear()
|
self.clear()
|
||||||
self.packetData = data
|
self.packetData = data
|
||||||
self.size = len(self.packetData)
|
self.size = len(self.packetData)
|
||||||
@@ -101,74 +93,42 @@ class Packet:
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
def decode8(self) -> int:
|
def decode8(self) -> int:
|
||||||
"""
|
"""Return a single decoded byte from the packet."""
|
||||||
* Returns a single decoded byte from the packet.
|
|
||||||
*
|
|
||||||
* @return A decoded byte from the packet.
|
|
||||||
"""
|
|
||||||
return self._decodeGeneric("<b", 1)
|
return self._decodeGeneric("<b", 1)
|
||||||
|
|
||||||
def decode16(self) -> int:
|
def decode16(self) -> int:
|
||||||
"""
|
"""Return a single decoded short from the packet."""
|
||||||
* Returns a single decoded short from the packet.
|
|
||||||
*
|
|
||||||
* @return A decoded short from the packet.
|
|
||||||
"""
|
|
||||||
return self._decodeGeneric("<h", 2)
|
return self._decodeGeneric("<h", 2)
|
||||||
|
|
||||||
def decodeInt(self) -> int:
|
def decodeInt(self) -> int:
|
||||||
"""
|
"""Return a decoded 32-bit integer from the packet."""
|
||||||
* Returns a decoded int (32 bytes) from the packet.
|
|
||||||
*
|
|
||||||
* @return A decoded int from the packet.
|
|
||||||
"""
|
|
||||||
return self._decodeGeneric("<l", 4)
|
return self._decodeGeneric("<l", 4)
|
||||||
|
|
||||||
def decodeFloat(self) -> float:
|
def decodeFloat(self) -> float:
|
||||||
"""
|
"""Return a decoded float from the packet."""
|
||||||
* Returns a decoded float from the packet.
|
|
||||||
*
|
|
||||||
* @return A decoded float from the packet.
|
|
||||||
"""
|
|
||||||
return self._decodeGeneric("<f", 4)
|
return self._decodeGeneric("<f", 4)
|
||||||
|
|
||||||
def decodeLong(self) -> int:
|
def decodeLong(self) -> int:
|
||||||
"""
|
"""Return a decoded 64-bit integer from the packet."""
|
||||||
* Returns a decoded int64 from the packet.
|
|
||||||
*
|
|
||||||
* @return A decoded int64 from the packet.
|
|
||||||
"""
|
|
||||||
return self._decodeGeneric("<q", 8)
|
return self._decodeGeneric("<q", 8)
|
||||||
|
|
||||||
def decodeDouble(self) -> float:
|
def decodeDouble(self) -> float:
|
||||||
"""
|
"""Return a decoded double from the packet."""
|
||||||
* Returns a decoded double from the packet.
|
|
||||||
*
|
|
||||||
* @return A decoded double from the packet.
|
|
||||||
"""
|
|
||||||
return self._decodeGeneric("<d", 8)
|
return self._decodeGeneric("<d", 8)
|
||||||
|
|
||||||
def decodeBoolean(self) -> bool:
|
def decodeBoolean(self) -> bool:
|
||||||
"""
|
"""Return a decoded boolean from the packet."""
|
||||||
* Returns a decoded boolean from the packet.
|
|
||||||
*
|
|
||||||
* @return A decoded boolean from the packet.
|
|
||||||
"""
|
|
||||||
return self.decode8() == 1
|
return self.decode8() == 1
|
||||||
|
|
||||||
def decodeDoubleArray(self, length: int) -> list[float]:
|
def decodeDoubleArray(self, length: int) -> list[float]:
|
||||||
"""
|
"""Return a decoded list of doubles of the given length from the packet."""
|
||||||
* Returns a decoded array of floats from the packet.
|
|
||||||
"""
|
|
||||||
ret = []
|
ret = []
|
||||||
for _ in range(length):
|
for _ in range(length):
|
||||||
ret.append(self.decodeDouble())
|
ret.append(self.decodeDouble())
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def decodeShortList(self) -> list[int]:
|
def decodeShortList(self) -> list[int]:
|
||||||
"""
|
"""Return a decoded list of shorts from the packet (length-prefixed)."""
|
||||||
* Returns a decoded array of shorts from the packet.
|
|
||||||
"""
|
|
||||||
length = self.decode8()
|
length = self.decode8()
|
||||||
ret = []
|
ret = []
|
||||||
for _ in range(length):
|
for _ in range(length):
|
||||||
@@ -176,11 +136,7 @@ class Packet:
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
def decodeTransform(self) -> Transform3d:
|
def decodeTransform(self) -> Transform3d:
|
||||||
"""
|
"""Return a decoded Transform3d from the packet."""
|
||||||
* Returns a decoded Transform3d
|
|
||||||
*
|
|
||||||
* @return A decoded Tansform3d from the packet.
|
|
||||||
"""
|
|
||||||
x = self.decodeDouble()
|
x = self.decodeDouble()
|
||||||
y = self.decodeDouble()
|
y = self.decodeDouble()
|
||||||
z = self.decodeDouble()
|
z = self.decodeDouble()
|
||||||
|
|||||||
@@ -261,18 +261,18 @@ class PhotonPoseEstimator:
|
|||||||
def update(
|
def update(
|
||||||
self, cameraResult: Optional[PhotonPipelineResult] = None
|
self, cameraResult: Optional[PhotonPipelineResult] = None
|
||||||
) -> Optional[EstimatedRobotPose]:
|
) -> Optional[EstimatedRobotPose]:
|
||||||
"""
|
"""Update the estimated robot position.
|
||||||
Updates the estimated position of the robot. Returns empty if:
|
|
||||||
|
|
||||||
- The timestamp of the provided pipeline result is the same as in the previous call to
|
Returns empty if one of the following is true:
|
||||||
``update()``.
|
|
||||||
|
|
||||||
- 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
|
:returns: An :class:`EstimatedRobotPose` with an estimated pose, timestamp, and targets used
|
||||||
create the estimate.
|
to create the estimate, or ``None`` if no estimate could be made.
|
||||||
"""
|
"""
|
||||||
if not cameraResult:
|
if not cameraResult:
|
||||||
if not self._camera:
|
if not self._camera:
|
||||||
|
|||||||
@@ -306,14 +306,13 @@ class SimCameraProperties:
|
|||||||
:param a: The initial translation of the line
|
:param a: The initial translation of the line
|
||||||
:param b: The final 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
|
- ``(float, float)``: Two t values (minimum first) representing the visible segment.
|
||||||
segment of the line is visible in the camera frustum.
|
- ``(float, None)``: A single intersection point.
|
||||||
- {Double, null} : One value(t) representing a single intersection point. For example,
|
- ``(None, None)``: No intersection; the segment is not visible.
|
||||||
the line only intersects the intersection of two adjacent viewplanes.
|
|
||||||
- {null, null} : No values. The line segment is not visible in the camera frustum.
|
"""
|
||||||
"""
|
|
||||||
|
|
||||||
# translations relative to the camera
|
# translations relative to the camera
|
||||||
relA = camRt.applyTranslation(a)
|
relA = camRt.applyTranslation(a)
|
||||||
|
|||||||
@@ -24,9 +24,6 @@
|
|||||||
|
|
||||||
#include "photon/PhotonCamera.h"
|
#include "photon/PhotonCamera.h"
|
||||||
|
|
||||||
#include <hal/FRCUsageReporting.h>
|
|
||||||
#include <net/TimeSyncServer.h>
|
|
||||||
|
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
@@ -36,6 +33,8 @@
|
|||||||
#include <frc/Errors.h>
|
#include <frc/Errors.h>
|
||||||
#include <frc/RobotController.h>
|
#include <frc/RobotController.h>
|
||||||
#include <frc/Timer.h>
|
#include <frc/Timer.h>
|
||||||
|
#include <hal/FRCUsageReporting.h>
|
||||||
|
#include <net/TimeSyncServer.h>
|
||||||
#include <opencv2/core.hpp>
|
#include <opencv2/core.hpp>
|
||||||
#include <opencv2/core/mat.hpp>
|
#include <opencv2/core/mat.hpp>
|
||||||
#include <wpi/json.h>
|
#include <wpi/json.h>
|
||||||
|
|||||||
@@ -24,8 +24,6 @@
|
|||||||
|
|
||||||
#include "photon/PhotonPoseEstimator.h"
|
#include "photon/PhotonPoseEstimator.h"
|
||||||
|
|
||||||
#include <hal/FRCUsageReporting.h>
|
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
@@ -41,6 +39,7 @@
|
|||||||
#include <frc/geometry/Pose3d.h>
|
#include <frc/geometry/Pose3d.h>
|
||||||
#include <frc/geometry/Rotation3d.h>
|
#include <frc/geometry/Rotation3d.h>
|
||||||
#include <frc/geometry/Transform3d.h>
|
#include <frc/geometry/Transform3d.h>
|
||||||
|
#include <hal/FRCUsageReporting.h>
|
||||||
#include <opencv2/calib3d.hpp>
|
#include <opencv2/calib3d.hpp>
|
||||||
#include <opencv2/core/mat.hpp>
|
#include <opencv2/core/mat.hpp>
|
||||||
#include <opencv2/core/types.hpp>
|
#include <opencv2/core/types.hpp>
|
||||||
|
|||||||
@@ -24,7 +24,16 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <limits>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include <cameraserver/CameraServer.h>
|
#include <cameraserver/CameraServer.h>
|
||||||
|
#include <frc/Timer.h>
|
||||||
|
#include <frc/apriltag/AprilTagFieldLayout.h>
|
||||||
|
#include <frc/apriltag/AprilTagFields.h>
|
||||||
#include <photon/PhotonCamera.h>
|
#include <photon/PhotonCamera.h>
|
||||||
#include <photon/PhotonTargetSortMode.h>
|
#include <photon/PhotonTargetSortMode.h>
|
||||||
#include <photon/estimation/CameraTargetRelation.h>
|
#include <photon/estimation/CameraTargetRelation.h>
|
||||||
@@ -33,16 +42,6 @@
|
|||||||
#include <photon/simulation/SimCameraProperties.h>
|
#include <photon/simulation/SimCameraProperties.h>
|
||||||
#include <photon/simulation/VideoSimUtil.h>
|
#include <photon/simulation/VideoSimUtil.h>
|
||||||
#include <photon/simulation/VisionTargetSim.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 <units/math.h>
|
||||||
#include <wpi/timestamp.h>
|
#include <wpi/timestamp.h>
|
||||||
|
|
||||||
|
|||||||
@@ -24,8 +24,6 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <photon/estimation/OpenCVHelp.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <random>
|
#include <random>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -37,6 +35,7 @@
|
|||||||
#include <frc/MathUtil.h>
|
#include <frc/MathUtil.h>
|
||||||
#include <frc/geometry/Rotation2d.h>
|
#include <frc/geometry/Rotation2d.h>
|
||||||
#include <frc/geometry/Translation3d.h>
|
#include <frc/geometry/Translation3d.h>
|
||||||
|
#include <photon/estimation/OpenCVHelp.h>
|
||||||
#include <units/frequency.h>
|
#include <units/frequency.h>
|
||||||
#include <units/time.h>
|
#include <units/time.h>
|
||||||
|
|
||||||
|
|||||||
@@ -24,8 +24,6 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cscore_cv.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -33,6 +31,7 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include <cscore_cv.h>
|
||||||
#include <frc/apriltag/AprilTag.h>
|
#include <frc/apriltag/AprilTag.h>
|
||||||
#include <opencv2/core.hpp>
|
#include <opencv2/core.hpp>
|
||||||
#include <opencv2/imgcodecs.hpp>
|
#include <opencv2/imgcodecs.hpp>
|
||||||
|
|||||||
@@ -22,20 +22,19 @@
|
|||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include <fmt/ranges.h>
|
#include <fmt/ranges.h>
|
||||||
|
#include <frc/smartdashboard/SmartDashboard.h>
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
#include <hal/HAL.h>
|
#include <hal/HAL.h>
|
||||||
#include <net/TimeSyncClient.h>
|
#include <net/TimeSyncClient.h>
|
||||||
#include <net/TimeSyncServer.h>
|
#include <net/TimeSyncServer.h>
|
||||||
|
#include <networktables/NetworkTableInstance.h>
|
||||||
#include <photon/PhotonCamera.h>
|
#include <photon/PhotonCamera.h>
|
||||||
#include <photon/simulation/PhotonCameraSim.h>
|
#include <photon/simulation/PhotonCameraSim.h>
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <frc/smartdashboard/SmartDashboard.h>
|
|
||||||
#include <networktables/NetworkTableInstance.h>
|
|
||||||
|
|
||||||
TEST(TimeSyncProtocolTest, Smoketest) {
|
TEST(TimeSyncProtocolTest, Smoketest) {
|
||||||
using namespace wpi::tsp;
|
using namespace wpi::tsp;
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "photon/PhotonPoseEstimator.h"
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -30,13 +32,12 @@
|
|||||||
#include <frc/geometry/Pose3d.h>
|
#include <frc/geometry/Pose3d.h>
|
||||||
#include <frc/geometry/Rotation3d.h>
|
#include <frc/geometry/Rotation3d.h>
|
||||||
#include <frc/geometry/Transform3d.h>
|
#include <frc/geometry/Transform3d.h>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
#include <units/angle.h>
|
#include <units/angle.h>
|
||||||
#include <units/length.h>
|
#include <units/length.h>
|
||||||
#include <wpi/SmallVector.h>
|
#include <wpi/SmallVector.h>
|
||||||
|
|
||||||
#include "gtest/gtest.h"
|
|
||||||
#include "photon/PhotonCamera.h"
|
#include "photon/PhotonCamera.h"
|
||||||
#include "photon/PhotonPoseEstimator.h"
|
|
||||||
#include "photon/dataflow/structures/Packet.h"
|
#include "photon/dataflow/structures/Packet.h"
|
||||||
#include "photon/simulation/PhotonCameraSim.h"
|
#include "photon/simulation/PhotonCameraSim.h"
|
||||||
#include "photon/simulation/SimCameraProperties.h"
|
#include "photon/simulation/SimCameraProperties.h"
|
||||||
|
|||||||
@@ -22,7 +22,8 @@
|
|||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "gtest/gtest.h"
|
|
||||||
#include "photon/PhotonUtils.h"
|
#include "photon/PhotonUtils.h"
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
TEST(PhotonUtilsTest, Include) {}
|
TEST(PhotonUtilsTest, Include) {}
|
||||||
|
|||||||
@@ -24,8 +24,9 @@
|
|||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
#include "PhotonVersion.h"
|
#include "PhotonVersion.h"
|
||||||
#include "gtest/gtest.h"
|
|
||||||
|
|
||||||
TEST(VersionTest, PrintVersion) {
|
TEST(VersionTest, PrintVersion) {
|
||||||
std::cout << photon::PhotonVersion::versionString << std::endl;
|
std::cout << photon::PhotonVersion::versionString << std::endl;
|
||||||
|
|||||||
@@ -22,16 +22,17 @@
|
|||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "photon/simulation/VisionSystemSim.h"
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
#include <wpi/deprecated.h>
|
#include <wpi/deprecated.h>
|
||||||
|
|
||||||
#include "gtest/gtest.h"
|
|
||||||
#include "photon/PhotonUtils.h"
|
#include "photon/PhotonUtils.h"
|
||||||
#include "photon/simulation/VisionSystemSim.h"
|
|
||||||
|
|
||||||
// Ignore GetLatestResult warnings
|
// Ignore GetLatestResult warnings
|
||||||
WPI_IGNORE_DEPRECATED
|
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