Compare commits

..

11 Commits

Author SHA1 Message Date
Sam Freund
bab156312c cancel the workflow if build-gradle fails and unblock previously dependent jobs (#2525) 2026-06-29 21:58:45 -07:00
Jade
8f560e5b1f Refactor Rubik OD code to generic TFLite OD code (#2516) 2026-06-29 17:02:47 +00:00
Sam Freund
bd8fa28ab7 move typechecking to lint-format (#2526) 2026-06-29 16:01:33 +00:00
Sam Freund
6155fb9534 Update playwright (#2522) 2026-06-29 10:41:09 -04:00
Sam Freund
10f38268e6 Evaluate IMAGE_VERSION when passing into image-runner (#2521)
If we don't evaluate IMAGE_VERSION when passing it into the image-runner
action, cache gets the variable name instead of the actual value. This
PR also cleans up the really long URLs in the matrix entries since it's
always the same.
2026-06-23 22:08:07 -07:00
Sam Freund
d3503226b3 cache rubik image (#2520)
Pursuant to caching being added in
https://github.com/photonvision/photonvision/photon-image-runner, we can
now cache images for our action. We're only caching the rubik image as
we need to wait for all of them to finish anyways, and caching the other
ones as well won't gain time.

Also, downgrade to windows 2022 cause MSVC ICE bugs. Also also, bump
pnpm to 11 in actions cause windows using the version as part of the
path for caches (why? WHY??)

---------

Co-authored-by: Matt Morley <matthew.morley.ca@gmail.com>
2026-06-23 20:08:55 -07:00
Sam Freund
0e2563111c refactor OD model UID to path (#2486) 2026-06-23 02:15:57 -04:00
Alan Everett
0a07263f74 Clean up TargetModel.java (#2460)
## Description

`TargetModel.java` had some leftover code from #139 still present. This
cleans up everything unused and also consolidates some redundant code to
make the class more readable.

## Meta

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

---------

Co-authored-by: Matt Morley <matthew.morley.ca@gmail.com>
2026-06-23 01:55:39 -04:00
Alan Everett
e41be8e858 Store calibration board measurements in native units (#2480) 2026-06-22 22:31:44 -07:00
Gold856
bd9f899514 Shorten registration of CorsPlugin (#2518) 2026-06-22 22:28:40 -07:00
Alan Everett
c04c8d76ed Use Avaje Jsonb for Javalin JSON (#2512) 2026-06-09 17:20:21 -07:00
26 changed files with 294 additions and 274 deletions

View File

@@ -91,27 +91,6 @@ jobs:
# ./gradlew build # ./gradlew build
# ./gradlew clean # ./gradlew clean
typecheck-client:
name: "Typecheck Client"
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Install pnpm
uses: pnpm/action-setup@v5
with:
version: 10
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm
cache-dependency-path: photon-client/pnpm-lock.yaml
- name: Typecheck Client
working-directory: photon-client
run: |
pnpm install --frozen-lockfile
pnpm type-check
playwright-tests: playwright-tests:
name: "Playwright E2E tests" name: "Playwright E2E tests"
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
@@ -125,19 +104,20 @@ jobs:
distribution: temurin distribution: temurin
- uses: pnpm/action-setup@v5 - uses: pnpm/action-setup@v5
with: with:
version: 10 version: 11
- uses: actions/setup-node@v6 - uses: actions/setup-node@v6
with: with:
cache: pnpm cache: pnpm
cache-dependency-path: photon-client/pnpm-lock.yaml cache-dependency-path: photon-client/pnpm-lock.yaml
node-version: 24 node-version: 24
- name: Setup tests - parallel:
working-directory: photon-client - name: Setup tests
run: | working-directory: photon-client
pnpm install run: |
pnpm test-setup pnpm install
- name: Prebuild Gradle pnpm test-setup
run: ./gradlew photon-targeting:build photon-core:build photon-server:build -x check - name: Prebuild Gradle
run: ./gradlew photon-targeting:build photon-core:build photon-server:build -x check
- name: Run Playwright tests - name: Run Playwright tests
working-directory: photon-client working-directory: photon-client
run: pnpm test run: pnpm test
@@ -164,7 +144,7 @@ jobs:
distribution: temurin distribution: temurin
- uses: pnpm/action-setup@v5 - uses: pnpm/action-setup@v5
with: with:
version: 10 version: 11
- uses: actions/setup-node@v6 - uses: actions/setup-node@v6
with: with:
cache: pnpm cache: pnpm
@@ -174,6 +154,19 @@ jobs:
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 and Coverage - name: Gradle Tests and Coverage
run: ./gradlew test jacocoTestReport --stacktrace run: ./gradlew test jacocoTestReport --stacktrace
- name: Cancel on failure
if: failure()
uses: actions/github-script@v6
with:
script: |
const { owner, repo } = context.repo;
const runId = context.runId;
await github.rest.actions.cancelWorkflowRun({
owner,
repo,
run_id: runId
});
build-offline-docs: build-offline-docs:
name: "Build Offline Docs" name: "Build Offline Docs"
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
@@ -337,7 +330,7 @@ jobs:
path: output/*.zip path: output/*.zip
build-package-linux: build-package-linux:
needs: [build-gradle, build-offline-docs] needs: [build-offline-docs]
strategy: strategy:
fail-fast: false fail-fast: false
@@ -363,7 +356,7 @@ jobs:
distribution: temurin distribution: temurin
- uses: pnpm/action-setup@v5 - uses: pnpm/action-setup@v5
with: with:
version: 10 version: 11
- uses: actions/setup-node@v6 - uses: actions/setup-node@v6
with: with:
node-version: 24 node-version: 24
@@ -390,7 +383,7 @@ jobs:
path: photon-targeting/build/libs path: photon-targeting/build/libs
build-package-macos: build-package-macos:
needs: [build-gradle, build-offline-docs] needs: [build-offline-docs]
strategy: strategy:
fail-fast: false fail-fast: false
@@ -409,13 +402,13 @@ jobs:
steps: *build-package-steps steps: *build-package-steps
build-package-windows: build-package-windows:
needs: [build-gradle, build-offline-docs] needs: [build-offline-docs]
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
include: include:
- os: windows-latest - os: windows-2022
artifact-name: Win artifact-name: Win
arch-override: winx86-64 arch-override: winx86-64
@@ -434,7 +427,7 @@ jobs:
- os: ubuntu-24.04 - os: ubuntu-24.04
artifact-name: photonvision-*-linuxx86-64.jar artifact-name: photonvision-*-linuxx86-64.jar
extraOpts: -Djdk.lang.Process.launchMechanism=vfork extraOpts: -Djdk.lang.Process.launchMechanism=vfork
- os: windows-latest - os: windows-2022
artifact-name: photonvision-*-winx86-64.jar artifact-name: photonvision-*-winx86-64.jar
- os: macos-latest - os: macos-latest
artifact-name: photonvision-*-macarm64.jar artifact-name: photonvision-*-macarm64.jar
@@ -474,11 +467,11 @@ jobs:
fi fi
echo "=== Second run ===" echo "=== Second run ==="
java -jar ${{ matrix.extraOpts }} *.jar --smoketest java -jar ${{ matrix.extraOpts }} *.jar --smoketest
if: ${{ (matrix.os) != 'windows-latest' }} if: ${{ (matrix.os) != 'windows-2022' }}
- run: | - run: |
ls *.jar | %{ Write-Host "Running $($_.Name)"; Start-Process "java" -ArgumentList "-jar `"$($_.FullName)`" --smoketest" -NoNewWindow -Wait; break } ls *.jar | %{ Write-Host "Running $($_.Name)"; Start-Process "java" -ArgumentList "-jar `"$($_.FullName)`" --smoketest" -NoNewWindow -Wait; break }
ls *.jar | %{ Write-Host "Running $($_.Name)"; Start-Process "java" -ArgumentList "-jar `"$($_.FullName)`" --smoketest" -NoNewWindow -Wait; break } ls *.jar | %{ Write-Host "Running $($_.Name)"; Start-Process "java" -ArgumentList "-jar `"$($_.FullName)`" --smoketest" -NoNewWindow -Wait; break }
if: ${{ (matrix.os) == 'windows-latest' }} if: ${{ (matrix.os) == 'windows-2022' }}
build-image: build-image:
needs: [build-package-linux] needs: [build-package-linux]
@@ -490,67 +483,68 @@ jobs:
- os: ubuntu-24.04-arm - os: ubuntu-24.04-arm
image_suffix: RaspberryPi image_suffix: RaspberryPi
plat_override: LINUX_RASPBIAN64 plat_override: LINUX_RASPBIAN64
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_raspi.img.xz image_name: photonvision_raspi.img.xz
minimum_free_mb: 100 minimum_free_mb: 100
- os: ubuntu-24.04-arm - os: ubuntu-24.04-arm
image_suffix: limelight2 image_suffix: limelight2
plat_override: LINUX_RASPBIAN64 plat_override: LINUX_RASPBIAN64
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_limelight.img.xz image_name: photonvision_limelight.img.xz
minimum_free_mb: 100 minimum_free_mb: 100
- os: ubuntu-24.04-arm - os: ubuntu-24.04-arm
image_suffix: limelight3 image_suffix: limelight3
plat_override: LINUX_RASPBIAN64 plat_override: LINUX_RASPBIAN64
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_limelight3.img.xz image_name: photonvision_limelight3.img.xz
minimum_free_mb: 100 minimum_free_mb: 100
- os: ubuntu-24.04-arm - os: ubuntu-24.04-arm
image_suffix: limelight3G image_suffix: limelight3G
plat_override: LINUX_RASPBIAN64 plat_override: LINUX_RASPBIAN64
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_limelight3g.img.xz image_name: photonvision_limelight3g.img.xz
minimum_free_mb: 100 minimum_free_mb: 100
- os: ubuntu-24.04-arm - os: ubuntu-24.04-arm
image_suffix: limelight4 image_suffix: limelight4
plat_override: LINUX_RASPBIAN64 plat_override: LINUX_RASPBIAN64
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_limelight4.img.xz image_name: photonvision_limelight4.img.xz
minimum_free_mb: 100 minimum_free_mb: 100
- os: ubuntu-24.04-arm - os: ubuntu-24.04-arm
image_suffix: luma_p1 image_suffix: luma_p1
plat_override: LINUX_RASPBIAN64 plat_override: LINUX_RASPBIAN64
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_luma_p1.img.xz image_name: photonvision_luma_p1.img.xz
minimum_free_mb: 100 minimum_free_mb: 100
- os: ubuntu-24.04-arm - os: ubuntu-24.04-arm
image_suffix: orangepi5 image_suffix: orangepi5
plat_override: LINUX_RK3588_64 plat_override: LINUX_RK3588_64
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_opi5.img.xz image_name: photonvision_opi5.img.xz
minimum_free_mb: 1024 minimum_free_mb: 1024
- os: ubuntu-24.04-arm - os: ubuntu-24.04-arm
image_suffix: orangepi5b image_suffix: orangepi5b
plat_override: LINUX_RK3588_64 plat_override: LINUX_RK3588_64
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_opi5b.img.xz image_name: photonvision_opi5b.img.xz
minimum_free_mb: 1024 minimum_free_mb: 1024
- os: ubuntu-24.04-arm - os: ubuntu-24.04-arm
image_suffix: orangepi5plus image_suffix: orangepi5plus
plat_override: LINUX_RK3588_64 plat_override: LINUX_RK3588_64
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_opi5plus.img.xz image_name: photonvision_opi5plus.img.xz
minimum_free_mb: 1024 minimum_free_mb: 1024
- os: ubuntu-24.04-arm - os: ubuntu-24.04-arm
image_suffix: orangepi5pro image_suffix: orangepi5pro
plat_override: LINUX_RK3588_64 plat_override: LINUX_RK3588_64
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_opi5pro.img.xz image_name: photonvision_opi5pro.img.xz
minimum_free_mb: 1024 minimum_free_mb: 1024
- os: ubuntu-24.04-arm - os: ubuntu-24.04-arm
image_suffix: orangepi5max image_suffix: orangepi5max
plat_override: LINUX_RK3588_64 plat_override: LINUX_RK3588_64
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_opi5max.img.xz image_name: photonvision_opi5max.img.xz
minimum_free_mb: 1024 minimum_free_mb: 1024
- os: ubuntu-24.04-arm - os: ubuntu-24.04-arm
image_suffix: rock5c image_suffix: rock5c
plat_override: LINUX_RK3588_64 plat_override: LINUX_RK3588_64
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_rock5c.img.xz image_name: photonvision_rock5c.img.xz
minimum_free_mb: 1024 minimum_free_mb: 1024
- os: ubuntu-24.04-arm - os: ubuntu-24.04-arm
image_suffix: rubikpi3 image_suffix: rubikpi3
cache: 'yes'
plat_override: LINUX_QCS6490 plat_override: LINUX_QCS6490
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/$IMAGE_VERSION/photonvision_rubikpi3.tar.xz image_name: photonvision_rubikpi3.tar.xz
minimum_free_mb: 1024 minimum_free_mb: 1024
root_location: 'offset=569376768' root_location: 'offset=569376768'
shrink_image: 'no' shrink_image: 'no'
@@ -566,11 +560,12 @@ jobs:
- uses: actions/download-artifact@v8 - uses: actions/download-artifact@v8
with: with:
pattern: photonvision-*-linuxarm64.jar pattern: photonvision-*-linuxarm64.jar
- uses: photonvision/photon-image-runner@HEAD - uses: photonvision/photon-image-runner@v2.0.0
name: Generate image name: Generate image
id: generate_image id: generate_image
with: with:
image_url: ${{ matrix.image_url }} image_url: "https://github.com/PhotonVision/photon-image-modifier/releases/download/${{ env.IMAGE_VERSION }}/${{ matrix.image_name }}"
use_cache: ${{ matrix.cache || 'no' }}
minimum_free_mb: ${{ matrix.minimum_free_mb }} minimum_free_mb: ${{ matrix.minimum_free_mb }}
root_location: ${{ matrix.root_location || 'partition=2' }} root_location: ${{ matrix.root_location || 'partition=2' }}
shrink_image: ${{ matrix.shrink_image || 'yes' }} shrink_image: ${{ matrix.shrink_image || 'yes' }}

View File

@@ -109,3 +109,12 @@ jobs:
echo "::error ::Linting failed. See https://docs.photonvision.org/en/latest/docs/contributing/linting.html" echo "::error ::Linting failed. See https://docs.photonvision.org/en/latest/docs/contributing/linting.html"
exit $exit_code exit $exit_code
fi fi
- name: Run Typechecking
run: |
set +e
pnpm run type-check
exit_code=$?
if test "$exit_code" -ne "0"; then
echo "::error ::Linting failed. See https://docs.photonvision.org/en/latest/docs/contributing/linting.html"
exit $exit_code
fi

View File

@@ -34,7 +34,7 @@ ext {
wpilibVersion = "2027.0.0-alpha-6" wpilibVersion = "2027.0.0-alpha-6"
openCVversion = "2027-4.13.0-3" openCVversion = "2027-4.13.0-3"
ejmlVersion = "0.43.1"; ejmlVersion = "0.43.1";
avajeJsonbVersion = "3.14-RC4"; avajeJsonbVersion = "3.14";
msgpackVersion = "0.9.0"; msgpackVersion = "0.9.0";
quickbufVersion = "1.3.3"; quickbufVersion = "1.3.3";
jacocoVersion = "0.8.14"; jacocoVersion = "0.8.14";

View File

@@ -21,6 +21,7 @@
"type-check": "vue-tsc --noEmit" "type-check": "vue-tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@adam-rocska/units-and-measurement": "^1.2.0",
"@fontsource/prompt": "^5.2.6", "@fontsource/prompt": "^5.2.6",
"@mdi/font": "^7.4.47", "@mdi/font": "^7.4.47",
"@msgpack/msgpack": "^3.1.2", "@msgpack/msgpack": "^3.1.2",
@@ -36,11 +37,10 @@
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.31.0", "@eslint/js": "^9.31.0",
"@playwright/test": "^1.56.1", "@playwright/test": "^1.61.1",
"@types/node": "^24.0.0", "@types/node": "^24.0.0",
"@types/three": "^0.178.0", "@types/three": "^0.178.0",
"@vitejs/plugin-vue": "^6.0.6", "@vitejs/plugin-vue": "^6.0.6",
"vue-tsc": "^3.2.5",
"@vue/eslint-config-prettier": "^10.2.0", "@vue/eslint-config-prettier": "^10.2.0",
"@vue/eslint-config-typescript": "^14.5.0", "@vue/eslint-config-typescript": "^14.5.0",
"@vue/tsconfig": "^0.7.0", "@vue/tsconfig": "^0.7.0",
@@ -50,6 +50,7 @@
"sass": "^1.89.2", "sass": "^1.89.2",
"typescript": "^5.8.3", "typescript": "^5.8.3",
"vite": "^8.0.10", "vite": "^8.0.10",
"vite-plugin-vuetify": "^2.1.1" "vite-plugin-vuetify": "^2.1.1",
"vue-tsc": "^3.2.5"
} }
} }

View File

@@ -8,6 +8,9 @@ importers:
.: .:
dependencies: dependencies:
'@adam-rocska/units-and-measurement':
specifier: ^1.2.0
version: 1.2.0
'@fontsource/prompt': '@fontsource/prompt':
specifier: ^5.2.6 specifier: ^5.2.6
version: 5.2.6 version: 5.2.6
@@ -49,8 +52,8 @@ importers:
specifier: ^9.31.0 specifier: ^9.31.0
version: 9.31.0 version: 9.31.0
'@playwright/test': '@playwright/test':
specifier: ^1.56.1 specifier: ^1.61.1
version: 1.56.1 version: 1.61.1
'@types/node': '@types/node':
specifier: ^24.0.0 specifier: ^24.0.0
version: 24.12.2 version: 24.12.2
@@ -96,6 +99,10 @@ importers:
packages: packages:
'@adam-rocska/units-and-measurement@1.2.0':
resolution: {integrity: sha512-mBnZ8/STbztVec+Mz9DH932z0gny52SebtSJ/y3n+IVtuF7KqbtQ3t1u1lpFSkLFU1msaNGzFgqsW7Emj0lrXA==}
engines: {node: '>=20.0.0'}
'@babel/helper-string-parser@7.27.1': '@babel/helper-string-parser@7.27.1':
resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
@@ -313,8 +320,8 @@ packages:
resolution: {integrity: sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw==} resolution: {integrity: sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw==}
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
'@playwright/test@1.56.1': '@playwright/test@1.61.1':
resolution: {integrity: sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg==} resolution: {integrity: sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig==}
engines: {node: '>=18'} engines: {node: '>=18'}
hasBin: true hasBin: true
@@ -1242,13 +1249,13 @@ packages:
typescript: typescript:
optional: true optional: true
playwright-core@1.56.1: playwright-core@1.61.1:
resolution: {integrity: sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==} resolution: {integrity: sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==}
engines: {node: '>=18'} engines: {node: '>=18'}
hasBin: true hasBin: true
playwright@1.56.1: playwright@1.61.1:
resolution: {integrity: sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==} resolution: {integrity: sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==}
engines: {node: '>=18'} engines: {node: '>=18'}
hasBin: true hasBin: true
@@ -1550,6 +1557,8 @@ packages:
snapshots: snapshots:
'@adam-rocska/units-and-measurement@1.2.0': {}
'@babel/helper-string-parser@7.27.1': {} '@babel/helper-string-parser@7.27.1': {}
'@babel/helper-validator-identifier@7.27.1': {} '@babel/helper-validator-identifier@7.27.1': {}
@@ -1732,9 +1741,9 @@ snapshots:
'@pkgr/core@0.2.4': {} '@pkgr/core@0.2.4': {}
'@playwright/test@1.56.1': '@playwright/test@1.61.1':
dependencies: dependencies:
playwright: 1.56.1 playwright: 1.61.1
'@rolldown/binding-android-arm64@1.0.0-rc.17': '@rolldown/binding-android-arm64@1.0.0-rc.17':
optional: true optional: true
@@ -2637,11 +2646,11 @@ snapshots:
optionalDependencies: optionalDependencies:
typescript: 5.8.3 typescript: 5.8.3
playwright-core@1.56.1: {} playwright-core@1.61.1: {}
playwright@1.56.1: playwright@1.61.1:
dependencies: dependencies:
playwright-core: 1.56.1 playwright-core: 1.61.1
optionalDependencies: optionalDependencies:
fsevents: 2.3.2 fsevents: 2.3.2

View File

@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref, watchEffect } from "vue"; import { computed, ref, watch, watchEffect } from "vue";
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore"; import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
import { CalibrationBoardTypes, CalibrationTagFamilies, type VideoFormat } from "@/types/SettingTypes"; import { CalibrationBoardTypes, CalibrationTagFamilies, type VideoFormat } from "@/types/SettingTypes";
import MonoLogo from "@/assets/images/logoMono.png"; import MonoLogo from "@/assets/images/logoMono.png";
@@ -15,12 +15,12 @@ import CameraCalibrationInfoCard from "@/components/cameras/CameraCalibrationInf
import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore"; import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore";
import { useTheme } from "vuetify"; import { useTheme } from "vuetify";
import TooltippedLabel from "@/components/common/pv-tooltipped-label.vue"; import TooltippedLabel from "@/components/common/pv-tooltipped-label.vue";
import { length } from "@adam-rocska/units-and-measurement/length";
const PromptRegular = import("@/assets/fonts/PromptRegular"); const PromptRegular = import("@/assets/fonts/PromptRegular");
const jspdf = import("jspdf"); const jspdf = import("jspdf");
const theme = useTheme(); const theme = useTheme();
const MM_PER_INCH = 25.4;
const settingsValid = ref(true); const settingsValid = ref(true);
@@ -110,8 +110,8 @@ watchEffect(() => {
uniqueVideoResolutionIndex.value = currentIndex; uniqueVideoResolutionIndex.value = currentIndex;
}); });
const dimensionUnit = ref<"in" | "mm">("in"); const dimensionUnit = ref<"in" | "mm">("in");
const squareSizeIn = ref(1); const squareSize = ref(30);
const markerSizeIn = ref(0.75); const markerSize = ref(22);
const patternWidth = ref(8); const patternWidth = ref(8);
const patternHeight = ref(8); const patternHeight = ref(8);
const boardType = ref<CalibrationBoardTypes>(CalibrationBoardTypes.Charuco); const boardType = ref<CalibrationBoardTypes>(CalibrationBoardTypes.Charuco);
@@ -119,24 +119,9 @@ const useOldPattern = ref(false);
const tagFamily = ref<CalibrationTagFamilies>(CalibrationTagFamilies.Dict_4X4_1000); const tagFamily = ref<CalibrationTagFamilies>(CalibrationTagFamilies.Dict_4X4_1000);
const requestedVideoFormatIndex = ref(0); const requestedVideoFormatIndex = ref(0);
const convertInchesToDisplay = (valueInInches: number) => watch(dimensionUnit, (value, oldValue) => {
dimensionUnit.value === "mm" ? valueInInches * MM_PER_INCH : valueInInches; squareSize.value = length[oldValue](squareSize.value)[value].value;
markerSize.value = length[oldValue](markerSize.value)[value].value;
const convertDisplayToInches = (displayValue: number) =>
dimensionUnit.value === "mm" ? displayValue / MM_PER_INCH : displayValue;
const squareSize = computed({
get: () => convertInchesToDisplay(squareSizeIn.value),
set(value) {
squareSizeIn.value = convertDisplayToInches(value);
}
});
const markerSize = computed({
get: () => convertInchesToDisplay(markerSizeIn.value),
set(value) {
markerSizeIn.value = convertDisplayToInches(value);
}
}); });
const dimensionStep = computed(() => (dimensionUnit.value === "mm" ? 0.1 : 0.01)); const dimensionStep = computed(() => (dimensionUnit.value === "mm" ? 0.1 : 0.01));
@@ -161,25 +146,31 @@ const downloadCalibBoard = async () => {
switch (boardType.value) { switch (boardType.value) {
case CalibrationBoardTypes.Chessboard: case CalibrationBoardTypes.Chessboard:
const chessboardStartX = (paperWidth - patternWidth.value * squareSizeIn.value) / 2; const squareSizeIn = length[dimensionUnit.value](squareSize.value).in.value;
const chessboardStartX = (paperWidth - patternWidth.value * squareSizeIn) / 2;
const chessboardStartY = (paperHeight - patternWidth.value * squareSizeIn.value) / 2; const chessboardStartY = (paperHeight - patternHeight.value * squareSizeIn) / 2;
for (let squareY = 0; squareY < patternHeight.value; squareY++) { for (let squareY = 0; squareY < patternHeight.value; squareY++) {
for (let squareX = 0; squareX < patternWidth.value; squareX++) { for (let squareX = 0; squareX < patternWidth.value; squareX++) {
const xPos = chessboardStartX + squareX * squareSizeIn.value; const xPos = chessboardStartX + squareX * squareSizeIn;
const yPos = chessboardStartY + squareY * squareSizeIn.value; const yPos = chessboardStartY + squareY * squareSizeIn;
// Only draw the odd squares to create the chessboard pattern // Only draw the odd squares to create the chessboard pattern
if (squareY % 2 !== squareX % 2) { if (squareY % 2 !== squareX % 2) {
doc.rect(xPos, yPos, squareSizeIn.value, squareSizeIn.value, "F"); doc.rect(xPos, yPos, squareSizeIn, squareSizeIn, "F");
} }
} }
} }
doc.text(`${patternWidth.value} x ${patternHeight.value} | ${squareSizeIn.value}in`, paperWidth - 1, 1.0, { doc.text(
maxWidth: (paperWidth - 2.0) / 2, `${patternWidth.value} x ${patternHeight.value} | ${squareSize.value}${dimensionUnit.value}`,
align: "right" paperWidth - 1,
}); 1.0,
{
maxWidth: (paperWidth - 2.0) / 2,
align: "right"
}
);
break; break;
case CalibrationBoardTypes.Charuco: case CalibrationBoardTypes.Charuco:
@@ -220,8 +211,8 @@ const isCalibrating = computed(
const startCalibration = () => { const startCalibration = () => {
useCameraSettingsStore().startPnPCalibration({ useCameraSettingsStore().startPnPCalibration({
squareSizeIn: squareSizeIn.value, squareSizeMeters: length[dimensionUnit.value](squareSize.value).m.value,
markerSizeIn: markerSizeIn.value, markerSizeMeters: length[dimensionUnit.value](markerSize.value).m.value,
patternHeight: patternHeight.value, patternHeight: patternHeight.value,
patternWidth: patternWidth.value, patternWidth: patternWidth.value,
boardType: boardType.value, boardType: boardType.value,

View File

@@ -368,8 +368,8 @@ export const useCameraSettingsStore = defineStore("cameraSettings", {
*/ */
startPnPCalibration( startPnPCalibration(
calibrationInitData: { calibrationInitData: {
squareSizeIn: number; squareSizeMeters: number;
markerSizeIn: number; markerSizeMeters: number;
patternWidth: number; patternWidth: number;
patternHeight: number; patternHeight: number;
boardType: CalibrationBoardTypes; boardType: CalibrationBoardTypes;

View File

@@ -87,8 +87,8 @@ export interface WebsocketCalibrationData {
minCount: number; minCount: number;
videoModeIndex: number; videoModeIndex: number;
patternHeight: number; patternHeight: number;
squareSizeIn: number; squareSizeMm: number;
markerSizeIn: number; markerSizeMm: number;
} }
export interface IncomingWebsocketData { export interface IncomingWebsocketData {

View File

@@ -38,9 +38,10 @@ import org.photonvision.common.configuration.NeuralNetworkModelsSettings.ModelPr
import org.photonvision.common.hardware.Platform; import org.photonvision.common.hardware.Platform;
import org.photonvision.common.logging.LogGroup; import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.Logger; import org.photonvision.common.logging.Logger;
import org.photonvision.tflite.TFLiteJNI.TFLiteSource;
import org.photonvision.vision.objects.Model; import org.photonvision.vision.objects.Model;
import org.photonvision.vision.objects.RknnModel; import org.photonvision.vision.objects.RknnModel;
import org.photonvision.vision.objects.RubikModel; import org.photonvision.vision.objects.TFLiteModel;
/** /**
* Manages the loading of neural network models. * Manages the loading of neural network models.
@@ -280,10 +281,10 @@ public class NeuralNetworkModelManager {
* *
* <p>If this method returns `Optional.of(..)` then the model should be safe to load. * <p>If this method returns `Optional.of(..)` then the model should be safe to load.
* *
* @param modelUID the unique identifier of the model to retrieve * @param modelPath the unique identifier of the model to retrieve
* @return an Optional containing the model if found, or an empty Optional if not found * @return an Optional containing the model if found, or an empty Optional if not found
*/ */
public Optional<Model> getModel(String modelUID) { public Optional<Model> getModel(Path modelPath) {
if (models == null) { if (models == null) {
return Optional.empty(); return Optional.empty();
} }
@@ -292,7 +293,7 @@ public class NeuralNetworkModelManager {
for (Family backend : supportedBackends) { for (Family backend : supportedBackends) {
if (models.containsKey(backend)) { if (models.containsKey(backend)) {
Optional<Model> model = Optional<Model> model =
models.get(backend).stream().filter(m -> m.getUID().equals(modelUID)).findFirst(); models.get(backend).stream().filter(m -> m.getPath().equals(modelPath)).findFirst();
if (model.isPresent()) { if (model.isPresent()) {
return model; return model;
} }
@@ -360,7 +361,7 @@ public class NeuralNetworkModelManager {
models.get(properties.family()).add(new RknnModel(properties)); models.get(properties.family()).add(new RknnModel(properties));
} }
case RUBIK -> { case RUBIK -> {
models.get(properties.family()).add(new RubikModel(properties)); models.get(properties.family()).add(new TFLiteModel(properties, TFLiteSource.RUBIK));
} }
} }
logger.info( logger.info(
@@ -406,7 +407,8 @@ public class NeuralNetworkModelManager {
// After loading all of the models, sort them by name to ensure a consistent // After loading all of the models, sort them by name to ensure a consistent
// ordering // ordering
models.forEach( models.forEach(
(backend, backendModels) -> backendModels.sort((a, b) -> a.getUID().compareTo(b.getUID()))); (backend, backendModels) ->
backendModels.sort((a, b) -> a.getPath().compareTo(b.getPath())));
// Log // Log
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
@@ -414,7 +416,7 @@ public class NeuralNetworkModelManager {
models.forEach( models.forEach(
(backend, backendModels) -> { (backend, backendModels) -> {
sb.append(backend).append(" ["); sb.append(backend).append(" [");
backendModels.forEach(model -> sb.append(model.getUID()).append(", ")); backendModels.forEach(model -> sb.append(model.getPath()).append(", "));
sb.append("] "); sb.append("] ");
}); });
} }

View File

@@ -17,13 +17,19 @@
package org.photonvision.vision.objects; package org.photonvision.vision.objects;
import java.nio.file.Path;
import org.photonvision.common.configuration.NeuralNetworkModelManager.Family; import org.photonvision.common.configuration.NeuralNetworkModelManager.Family;
import org.photonvision.common.configuration.NeuralNetworkModelsSettings.ModelProperties; import org.photonvision.common.configuration.NeuralNetworkModelsSettings.ModelProperties;
public interface Model { public interface Model {
public ObjectDetector load(); public ObjectDetector load();
public String getUID(); /**
* Gets the path to the model file. This is being used as a UID.
*
* @return the path to the model file (for use as a UID)
*/
public Path getPath();
public String getNickname(); public String getNickname();

View File

@@ -17,6 +17,7 @@
package org.photonvision.vision.objects; package org.photonvision.vision.objects;
import java.nio.file.Path;
import java.util.List; import java.util.List;
import org.opencv.core.Mat; import org.opencv.core.Mat;
import org.photonvision.common.configuration.NeuralNetworkModelManager.Family; import org.photonvision.common.configuration.NeuralNetworkModelManager.Family;
@@ -43,8 +44,8 @@ public class NullModel implements Model, ObjectDetector {
} }
@Override @Override
public String getUID() { public Path getPath() {
return "NullModel"; return Path.of("null");
} }
@Override @Override

View File

@@ -18,7 +18,7 @@
package org.photonvision.vision.objects; package org.photonvision.vision.objects;
import java.io.File; import java.io.File;
import org.opencv.core.Size; import java.nio.file.Path;
import org.photonvision.common.configuration.NeuralNetworkModelManager.Family; import org.photonvision.common.configuration.NeuralNetworkModelManager.Family;
import org.photonvision.common.configuration.NeuralNetworkModelManager.Version; import org.photonvision.common.configuration.NeuralNetworkModelManager.Version;
import org.photonvision.common.configuration.NeuralNetworkModelsSettings.ModelProperties; import org.photonvision.common.configuration.NeuralNetworkModelsSettings.ModelProperties;
@@ -61,8 +61,8 @@ public class RknnModel implements Model {
} }
/** Return the unique identifier for the model. In this case, it's the model's path. */ /** Return the unique identifier for the model. In this case, it's the model's path. */
public String getUID() { public Path getPath() {
return properties.modelPath().toString(); return properties.modelPath();
} }
public String getNickname() { public String getNickname() {
@@ -78,8 +78,7 @@ public class RknnModel implements Model {
} }
public ObjectDetector load() { public ObjectDetector load() {
return new RknnObjectDetector( return new RknnObjectDetector(this);
this, new Size(properties.resolutionWidth(), properties.resolutionHeight()));
} }
public String toString() { public String toString() {

View File

@@ -61,9 +61,10 @@ public class RknnObjectDetector implements ObjectDetector {
* @param inputSize The required image dimensions for the model. Images will be {@link * @param inputSize The required image dimensions for the model. Images will be {@link
* Letterbox}ed to this shape. * Letterbox}ed to this shape.
*/ */
public RknnObjectDetector(RknnModel model, Size inputSize) { public RknnObjectDetector(RknnModel model) {
this.model = model; this.model = model;
this.inputSize = inputSize; this.inputSize =
new Size(model.properties.resolutionWidth(), model.properties.resolutionHeight());
// Create the detector // Create the detector
objPointer = objPointer =

View File

@@ -18,22 +18,27 @@
package org.photonvision.vision.objects; package org.photonvision.vision.objects;
import java.io.File; import java.io.File;
import org.opencv.core.Size; import java.nio.file.Path;
import org.photonvision.common.configuration.NeuralNetworkModelManager.Family; import org.photonvision.common.configuration.NeuralNetworkModelManager.Family;
import org.photonvision.common.configuration.NeuralNetworkModelManager.Version; import org.photonvision.common.configuration.NeuralNetworkModelManager.Version;
import org.photonvision.common.configuration.NeuralNetworkModelsSettings.ModelProperties; import org.photonvision.common.configuration.NeuralNetworkModelsSettings.ModelProperties;
import org.photonvision.tflite.TFLiteJNI.TFLiteSource;
public class RubikModel implements Model { public class TFLiteModel implements Model {
public final File modelFile; public final File modelFile;
public final ModelProperties properties; public final ModelProperties properties;
public final TFLiteSource backend;
/** /**
* Rubik model constructor. * TFLite model constructor.
* *
* @param properties The properties of the model. * @param properties The properties of the model.
* @param backend The backend of the model should run on.
* @throws IllegalArgumentException * @throws IllegalArgumentException
*/ */
public RubikModel(ModelProperties properties) throws IllegalArgumentException { public TFLiteModel(ModelProperties properties, TFLiteSource backend)
throws IllegalArgumentException {
this.backend = backend;
modelFile = new File(properties.modelPath().toString()); modelFile = new File(properties.modelPath().toString());
if (!modelFile.exists()) { if (!modelFile.exists()) {
throw new IllegalArgumentException("Model file does not exist: " + modelFile); throw new IllegalArgumentException("Model file does not exist: " + modelFile);
@@ -59,8 +64,8 @@ public class RubikModel implements Model {
} }
/** Return the unique identifier for the model. In this case, it's the model's path. */ /** Return the unique identifier for the model. In this case, it's the model's path. */
public String getUID() { public Path getPath() {
return properties.modelPath().toString(); return properties.modelPath();
} }
public String getNickname() { public String getNickname() {
@@ -76,11 +81,10 @@ public class RubikModel implements Model {
} }
public ObjectDetector load() { public ObjectDetector load() {
return new RubikObjectDetector( return new TFLiteObjectDetector(this, backend);
this, new Size(this.properties.resolutionWidth(), this.properties.resolutionHeight()));
} }
public String toString() { public String toString() {
return "RubikModel{" + "modelFile=" + modelFile + ", properties=" + properties + '}'; return "TFLiteModel{" + "modelFile=" + modelFile + ", properties=" + properties + '}';
} }
} }

View File

@@ -30,9 +30,9 @@ import org.photonvision.tflite.TFLiteJNI;
import org.photonvision.tflite.TFLiteJNI.TFLiteSource; import org.photonvision.tflite.TFLiteJNI.TFLiteSource;
import org.photonvision.vision.pipe.impl.NeuralNetworkPipeResult; import org.photonvision.vision.pipe.impl.NeuralNetworkPipeResult;
/** Manages an object detector using the rubik backend. */ /** Manages an object detector using the TFLite backend. */
public class RubikObjectDetector implements ObjectDetector { public class TFLiteObjectDetector implements ObjectDetector {
private static final Logger logger = new Logger(RubikObjectDetector.class, LogGroup.General); private static final Logger logger = new Logger(TFLiteObjectDetector.class, LogGroup.General);
private static final Cleaner cleaner = Cleaner.create(); private static final Cleaner cleaner = Cleaner.create();
@@ -45,26 +45,26 @@ public class RubikObjectDetector implements ObjectDetector {
/** Pointer to the native object */ /** Pointer to the native object */
private final long ptr; private final long ptr;
private final RubikModel model; private final TFLiteModel model;
private final Size inputSize; private final Size inputSize;
/** Returns the model in use by this detector. */ /** Returns the model in use by this detector. */
@Override @Override
public RubikModel getModel() { public TFLiteModel getModel() {
return model; return model;
} }
/** /**
* Creates a new rubikObjectDetector from the given model. * Creates a new TFLite detector from the given model.
* *
* @param model The model to create the detector from. * @param model The model to create the detector from.
* @param inputSize The required image dimensions for the model. Images will be {@link * @param source The backend to run the detector on.
* Letterbox}ed to this shape.
*/ */
public RubikObjectDetector(RubikModel model, Size inputSize) { public TFLiteObjectDetector(TFLiteModel model, TFLiteSource backend) {
this.model = model; this.model = model;
this.inputSize = inputSize; this.inputSize =
new Size(model.properties.resolutionWidth(), model.properties.resolutionHeight());
// Create the detector // Create the detector
try { try {
@@ -72,7 +72,7 @@ public class RubikObjectDetector implements ObjectDetector {
TFLiteJNI.create( TFLiteJNI.create(
model.modelFile.getPath().toString(), model.modelFile.getPath().toString(),
model.properties.version().ordinal(), model.properties.version().ordinal(),
TFLiteSource.RUBIK.value()); backend.value());
} catch (Exception e) { } catch (Exception e) {
logger.error("Failed to create detector from path " + model.modelFile.getPath(), e); logger.error("Failed to create detector from path " + model.modelFile.getPath(), e);
throw new RuntimeException( throw new RuntimeException(
@@ -83,7 +83,7 @@ public class RubikObjectDetector implements ObjectDetector {
logger.error( logger.error(
"Failed to create detector from path " "Failed to create detector from path "
+ model.modelFile.getPath() + model.modelFile.getPath()
+ ". Please ensure the model is valid and compatible with the Rubik backend."); + ". Please ensure the model is valid and compatible with the TFLite backend.");
throw new RuntimeException( throw new RuntimeException(
"Failed to create detector from path " + model.modelFile.getPath()); "Failed to create detector from path " + model.modelFile.getPath());
} else if (!TFLiteJNI.isQuantized(ptr)) { } else if (!TFLiteJNI.isQuantized(ptr)) {
@@ -107,7 +107,7 @@ public class RubikObjectDetector implements ObjectDetector {
} }
/** /**
* Detects objects in the given input image using the rubikDetector. * Detects objects in the given input image using the TFLite detector.
* *
* @param in The input image to perform object detection on. * @param in The input image to perform object detection on.
* @param nmsThresh The threshold value for non-maximum suppression. * @param nmsThresh The threshold value for non-maximum suppression.

View File

@@ -57,8 +57,7 @@ public class ObjectDetectionPipeline
protected void setPipeParamsImpl() { protected void setPipeParamsImpl() {
Optional<Model> selectedModel = Optional<Model> selectedModel =
settings.model != null settings.model != null
? NeuralNetworkModelManager.getInstance() ? NeuralNetworkModelManager.getInstance().getModel(settings.model.modelPath())
.getModel(settings.model.modelPath().toString())
: Optional.empty(); : Optional.empty();
// If the desired model couldn't be found, log an error and try to use the default model // If the desired model couldn't be found, log an error and try to use the default model

View File

@@ -24,11 +24,11 @@ import org.opencv.objdetect.Objdetect;
public class UICalibrationData { public class UICalibrationData {
public int videoModeIndex; public int videoModeIndex;
public int count; public int count;
public double squareSizeIn; public double squareSizeMeters;
public int patternWidth; public int patternWidth;
public int patternHeight; public int patternHeight;
public BoardType boardType; public BoardType boardType;
public double markerSizeIn; public double markerSizeMeters;
public boolean useOldPattern; public boolean useOldPattern;
public TagFamily tagFamily; public TagFamily tagFamily;
@@ -37,8 +37,8 @@ public class UICalibrationData {
public UICalibrationData( public UICalibrationData(
int count, int count,
int videoModeIndex, int videoModeIndex,
double squareSizeIn, double squareSizeMeters,
double markerSizeIn, double markerSizeMeters,
int patternWidth, int patternWidth,
int patternHeight, int patternHeight,
BoardType boardType, BoardType boardType,
@@ -46,8 +46,8 @@ public class UICalibrationData {
TagFamily tagFamily) { TagFamily tagFamily) {
this.count = count; this.count = count;
this.videoModeIndex = videoModeIndex; this.videoModeIndex = videoModeIndex;
this.squareSizeIn = squareSizeIn; this.squareSizeMeters = squareSizeMeters;
this.markerSizeIn = markerSizeIn; this.markerSizeMeters = markerSizeMeters;
this.patternWidth = patternWidth; this.patternWidth = patternWidth;
this.patternHeight = patternHeight; this.patternHeight = patternHeight;
this.boardType = boardType; this.boardType = boardType;
@@ -98,10 +98,10 @@ public class UICalibrationData {
+ videoModeIndex + videoModeIndex
+ ", count=" + ", count="
+ count + count
+ ", squareSizeIn=" + ", squareSizeMeters="
+ squareSizeIn + squareSizeMeters
+ ", markerSizeIn=" + ", markerSizeMeters="
+ markerSizeIn + markerSizeMeters
+ ", patternWidth=" + ", patternWidth="
+ patternWidth + patternWidth
+ ", patternHeight=" + ", patternHeight="

View File

@@ -55,7 +55,6 @@ import org.photonvision.vision.pipeline.UICalibrationData;
import org.photonvision.vision.pipeline.result.CVPipelineResult; import org.photonvision.vision.pipeline.result.CVPipelineResult;
import org.photonvision.vision.target.TargetModel; import org.photonvision.vision.target.TargetModel;
import org.photonvision.vision.target.TrackedTarget; import org.photonvision.vision.target.TrackedTarget;
import org.wpilib.math.util.Units;
import org.wpilib.vision.camera.CameraServerJNI; import org.wpilib.vision.camera.CameraServerJNI;
import org.wpilib.vision.camera.VideoException; import org.wpilib.vision.camera.VideoException;
@@ -391,8 +390,8 @@ public class VisionModule {
+ data.videoModeIndex + data.videoModeIndex
+ " and settings " + " and settings "
+ data); + data);
settings.gridSize = Units.inchesToMeters(data.squareSizeIn); settings.gridSize = data.squareSizeMeters;
settings.markerSize = Units.inchesToMeters(data.markerSizeIn); settings.markerSize = data.markerSizeMeters;
settings.boardHeight = data.patternHeight; settings.boardHeight = data.patternHeight;
settings.boardWidth = data.patternWidth; settings.boardWidth = data.patternWidth;
settings.boardType = data.boardType; settings.boardType = data.boardType;

View File

@@ -69,97 +69,28 @@ public enum TargetModel implements Releasable {
new Point3(Units.inchesToMeters(-19.625), Units.inchesToMeters(-8.5), 0), new Point3(Units.inchesToMeters(-19.625), Units.inchesToMeters(-8.5), 0),
new Point3(Units.inchesToMeters(19.625), Units.inchesToMeters(-8.5), 0)), new Point3(Units.inchesToMeters(19.625), Units.inchesToMeters(-8.5), 0)),
Units.inchesToMeters(12)), Units.inchesToMeters(12)),
kCircularPowerCell7in( kCircularPowerCell7in(circleTargetCorners(Units.inchesToMeters(7)), 0),
List.of( k2022CircularCargoBall(circleTargetCorners(Units.inchesToMeters(9.5)), 0),
new Point3( k2025Algae(circleTargetCorners(Units.inchesToMeters(16.25)), 0),
-Units.inchesToMeters(7) / 2,
-Units.inchesToMeters(7) / 2,
-Units.inchesToMeters(7) / 2),
new Point3(
-Units.inchesToMeters(7) / 2,
Units.inchesToMeters(7) / 2,
-Units.inchesToMeters(7) / 2),
new Point3(
Units.inchesToMeters(7) / 2,
Units.inchesToMeters(7) / 2,
-Units.inchesToMeters(7) / 2),
new Point3(
Units.inchesToMeters(7) / 2,
-Units.inchesToMeters(7) / 2,
-Units.inchesToMeters(7) / 2)),
0),
k2022CircularCargoBall(
List.of(
new Point3(
-Units.inchesToMeters(9.5) / 2,
-Units.inchesToMeters(9.5) / 2,
-Units.inchesToMeters(9.5) / 2),
new Point3(
-Units.inchesToMeters(9.5) / 2,
Units.inchesToMeters(9.5) / 2,
-Units.inchesToMeters(9.5) / 2),
new Point3(
Units.inchesToMeters(9.5) / 2,
Units.inchesToMeters(9.5) / 2,
-Units.inchesToMeters(9.5) / 2),
new Point3(
Units.inchesToMeters(9.5) / 2,
-Units.inchesToMeters(9.5) / 2,
-Units.inchesToMeters(9.5) / 2)),
0),
k2025Algae(
List.of(
new Point3(
-Units.inchesToMeters(16.25) / 2,
-Units.inchesToMeters(16.25) / 2,
-Units.inchesToMeters(16.25) / 2),
new Point3(
-Units.inchesToMeters(16.25) / 2,
Units.inchesToMeters(16.25) / 2,
-Units.inchesToMeters(16.25) / 2),
new Point3(
Units.inchesToMeters(16.25) / 2,
Units.inchesToMeters(16.25) / 2,
-Units.inchesToMeters(16.25) / 2),
new Point3(
Units.inchesToMeters(16.25) / 2,
-Units.inchesToMeters(16.25) / 2,
-Units.inchesToMeters(16.25) / 2)),
0),
// 2023 AprilTag, with 6 inch marker width (inner black square). // 2023 AprilTag, with 6 inch marker width (inner black square).
// MIGRATION: 2023 // MIGRATION: 2023
@Json.Alias({"k6in_16h5"}) @Json.Alias({"k6in_16h5"})
kAprilTag6in_16h5( kAprilTag6in_16h5(
// Corners of the tag's inner black square (excluding white border) // Corners of the tag's inner black square (excluding white border)
List.of( squareTargetCorners(Units.inchesToMeters(6)), Units.inchesToMeters(6)),
new Point3(Units.inchesToMeters(3), Units.inchesToMeters(3), 0),
new Point3(-Units.inchesToMeters(3), Units.inchesToMeters(3), 0),
new Point3(-Units.inchesToMeters(3), -Units.inchesToMeters(3), 0),
new Point3(Units.inchesToMeters(3), -Units.inchesToMeters(3), 0)),
Units.inchesToMeters(3 * 2)),
// 2024 AprilTag, with 6.5 inch marker width (inner black square). // 2024 AprilTag, with 6.5 inch marker width (inner black square).
// MIGRATION: 2023 // MIGRATION: 2023
@Json.Alias({"k6p5in_36h11", "k200mmAprilTag", "kAruco6p5in_36h11"}) @Json.Alias({"k6p5in_36h11", "k200mmAprilTag", "kAruco6p5in_36h11"})
kAprilTag6p5in_36h11( kAprilTag6p5in_36h11(
// Corners of the tag's inner black square (excluding white border) // Corners of the tag's inner black square (excluding white border)
List.of( squareTargetCorners(Units.inchesToMeters(6.5)), Units.inchesToMeters(6.5));
new Point3(-Units.inchesToMeters(6.5 / 2.0), Units.inchesToMeters(6.5 / 2.0), 0),
new Point3(Units.inchesToMeters(6.5 / 2.0), Units.inchesToMeters(6.5 / 2.0), 0),
new Point3(Units.inchesToMeters(6.5 / 2.0), -Units.inchesToMeters(6.5 / 2.0), 0),
new Point3(-Units.inchesToMeters(6.5 / 2.0), -Units.inchesToMeters(6.5 / 2.0), 0)),
Units.inchesToMeters(6.5));
@Json.Ignore private final MatOfPoint3f realWorldTargetCoordinates; @Json.Ignore private final MatOfPoint3f realWorldTargetCoordinates;
@Json.Ignore private final MatOfPoint3f visualizationBoxBottom = new MatOfPoint3f(); @Json.Ignore private final MatOfPoint3f visualizationBoxBottom = new MatOfPoint3f();
@Json.Ignore private final MatOfPoint3f visualizationBoxTop = new MatOfPoint3f(); @Json.Ignore private final MatOfPoint3f visualizationBoxTop = new MatOfPoint3f();
private List<Point3> realWorldCoordinatesArray;
private double boxHeight;
TargetModel(MatOfPoint3f realWorldTargetCoordinates, double boxHeight) { TargetModel(MatOfPoint3f realWorldTargetCoordinates, double boxHeight) {
this.realWorldTargetCoordinates = realWorldTargetCoordinates; this.realWorldTargetCoordinates = realWorldTargetCoordinates;
this.realWorldCoordinatesArray = realWorldTargetCoordinates.toList();
this.boxHeight = boxHeight;
var bottomList = realWorldTargetCoordinates.toList(); var bottomList = realWorldTargetCoordinates.toList();
var topList = new ArrayList<Point3>(); var topList = new ArrayList<Point3>();
@@ -175,22 +106,6 @@ public enum TargetModel implements Releasable {
this(listToMat(realWorldCoordinatesArray), boxHeight); this(listToMat(realWorldCoordinatesArray), boxHeight);
} }
public List<Point3> getRealWorldCoordinatesArray() {
return this.realWorldCoordinatesArray;
}
public double getBoxHeight() {
return boxHeight;
}
public void setRealWorldCoordinatesArray(List<Point3> realWorldCoordinatesArray) {
this.realWorldCoordinatesArray = realWorldCoordinatesArray;
}
public void setBoxHeight(double boxHeight) {
this.boxHeight = boxHeight;
}
private static MatOfPoint3f listToMat(List<Point3> points) { private static MatOfPoint3f listToMat(List<Point3> points) {
var mat = new MatOfPoint3f(); var mat = new MatOfPoint3f();
mat.fromList(points); mat.fromList(points);
@@ -209,15 +124,23 @@ public enum TargetModel implements Releasable {
return visualizationBoxTop; return visualizationBoxTop;
} }
// public static TargetModel getCircleTarget(double Units.inchesToMeters(7)) { private static List<Point3> circleTargetCorners(double diameter) {
// var corners = double radius = diameter / 2;
// List.of( return List.of(
// new Point3(-Units.inchesToMeters(7) / 2, -radius / 2, -radius / 2), new Point3(-radius, -radius, -radius),
// new Point3(-Units.inchesToMeters(7) / 2, radius / 2, -radius / 2), new Point3(-radius, radius, -radius),
// new Point3(Units.inchesToMeters(7) / 2, radius / 2, -radius / 2), new Point3(radius, radius, -radius),
// new Point3(Units.inchesToMeters(7) / 2, -radius / 2, -radius / 2)); new Point3(radius, -radius, -radius));
// return new TargetModel(corners, 0); }
// }
private static List<Point3> squareTargetCorners(double edgeLength) {
double radius = edgeLength / 2;
return List.of(
new Point3(-radius, -radius, 0),
new Point3(-radius, radius, 0),
new Point3(radius, radius, 0),
new Point3(radius, -radius, 0));
}
@Json.Value @Json.Value
@Override @Override

View File

@@ -62,7 +62,7 @@ public class TargetCalculationsTest {
null); null);
@BeforeAll @BeforeAll
public static void setup() { public static void init() {
LoadJNI.loadLibraries(); LoadJNI.loadLibraries();
} }

View File

@@ -0,0 +1,79 @@
/*
* 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.target;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.List;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.opencv.core.Point3;
import org.photonvision.common.LoadJNI;
import org.wpilib.math.util.Units;
public class TargetModelTest {
@BeforeAll
public static void init() {
LoadJNI.loadLibraries();
}
@Test
void testCircleTargetGeneration() {
assertApproxEquals(
List.of(
new Point3(
-Units.inchesToMeters(7) / 2,
-Units.inchesToMeters(7) / 2,
-Units.inchesToMeters(7) / 2),
new Point3(
-Units.inchesToMeters(7) / 2,
Units.inchesToMeters(7) / 2,
-Units.inchesToMeters(7) / 2),
new Point3(
Units.inchesToMeters(7) / 2,
Units.inchesToMeters(7) / 2,
-Units.inchesToMeters(7) / 2),
new Point3(
Units.inchesToMeters(7) / 2,
-Units.inchesToMeters(7) / 2,
-Units.inchesToMeters(7) / 2)),
TargetModel.kCircularPowerCell7in.getRealWorldTargetCoordinates().toList(),
1E-6);
}
@Test
void testSquareTargetGeneration() {
assertApproxEquals(
List.of(
new Point3(-Units.inchesToMeters(6.5 / 2.0), -Units.inchesToMeters(6.5 / 2.0), 0),
new Point3(-Units.inchesToMeters(6.5 / 2.0), Units.inchesToMeters(6.5 / 2.0), 0),
new Point3(Units.inchesToMeters(6.5 / 2.0), Units.inchesToMeters(6.5 / 2.0), 0),
new Point3(Units.inchesToMeters(6.5 / 2.0), -Units.inchesToMeters(6.5 / 2.0), 0)),
TargetModel.kAprilTag6p5in_36h11.getRealWorldTargetCoordinates().toList(),
1E-6);
}
static void assertApproxEquals(List<Point3> expected, List<Point3> actual, double delta) {
assertEquals(expected.size(), actual.size());
for (int i = 0; i < actual.size(); i++) {
assertEquals(expected.get(i).x, actual.get(i).x, delta, "Bad x for point %d".formatted(i));
assertEquals(expected.get(i).y, actual.get(i).y, delta, "Bad y for point %d".formatted(i));
assertEquals(expected.get(i).z, actual.get(i).z, delta, "Bad z for point %d".formatted(i));
}
}
}

View File

@@ -20,7 +20,7 @@ package org.photonvision.vision.target;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.List; import java.util.List;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.opencv.core.MatOfPoint; import org.opencv.core.MatOfPoint;
import org.opencv.core.Point; import org.opencv.core.Point;
@@ -30,8 +30,8 @@ import org.photonvision.vision.opencv.Contour;
import org.photonvision.vision.opencv.DualOffsetValues; import org.photonvision.vision.opencv.DualOffsetValues;
public class TrackedTargetTest { public class TrackedTargetTest {
@BeforeEach @BeforeAll
public void Init() { public static void init() {
LoadJNI.loadLibraries(); LoadJNI.loadLibraries();
} }

View File

@@ -17,6 +17,10 @@ dependencies {
// Needed for Javalin Runtime Logging // Needed for Javalin Runtime Logging
implementation "org.slf4j:slf4j-simple:2.0.7" implementation "org.slf4j:slf4j-simple:2.0.7"
implementation("org.photonvision:tflite_jni-java:$tfliteVersion") {
transitive = false
}
} }
group = 'org.photonvision' group = 'org.photonvision'

View File

@@ -55,12 +55,13 @@ import org.photonvision.common.networking.NetworkManager;
import org.photonvision.common.util.ShellExec; import org.photonvision.common.util.ShellExec;
import org.photonvision.common.util.TimedTaskManager; import org.photonvision.common.util.TimedTaskManager;
import org.photonvision.common.util.file.ProgramDirectoryUtilities; import org.photonvision.common.util.file.ProgramDirectoryUtilities;
import org.photonvision.tflite.TFLiteJNI.TFLiteSource;
import org.photonvision.vision.calibration.CameraCalibrationCoefficients; import org.photonvision.vision.calibration.CameraCalibrationCoefficients;
import org.photonvision.vision.camera.CameraQuirk; import org.photonvision.vision.camera.CameraQuirk;
import org.photonvision.vision.camera.PVCameraInfo; import org.photonvision.vision.camera.PVCameraInfo;
import org.photonvision.vision.objects.ObjectDetector; import org.photonvision.vision.objects.ObjectDetector;
import org.photonvision.vision.objects.RknnModel; import org.photonvision.vision.objects.RknnModel;
import org.photonvision.vision.objects.RubikModel; import org.photonvision.vision.objects.TFLiteModel;
import org.photonvision.vision.processes.VisionSourceManager; import org.photonvision.vision.processes.VisionSourceManager;
import org.zeroturnaround.zip.ZipUtil; import org.zeroturnaround.zip.ZipUtil;
@@ -679,7 +680,7 @@ public class RequestHandler {
try { try {
objDetector = objDetector =
switch (family) { switch (family) {
case RUBIK -> new RubikModel(modelProperties).load(); case RUBIK -> new TFLiteModel(modelProperties, TFLiteSource.RUBIK).load();
case RKNN -> new RknnModel(modelProperties).load(); case RKNN -> new RknnModel(modelProperties).load();
}; };
} catch (RuntimeException e) { } catch (RuntimeException e) {

View File

@@ -17,8 +17,10 @@
package org.photonvision.server; package org.photonvision.server;
import io.avaje.jsonb.javalin.JavalinJsonb;
import io.javalin.Javalin; import io.javalin.Javalin;
import io.javalin.plugin.bundled.CorsPlugin; import io.javalin.plugin.bundled.CorsPlugin;
import io.javalin.plugin.bundled.CorsPluginConfig.CorsRule;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.List; import java.util.List;
import java.util.StringJoiner; import java.util.StringJoiner;
@@ -60,14 +62,7 @@ public class Server {
javalinConfig -> { javalinConfig -> {
javalinConfig.showJavalinBanner = false; javalinConfig.showJavalinBanner = false;
javalinConfig.staticFiles.add("web"); javalinConfig.staticFiles.add("web");
javalinConfig.registerPlugin( javalinConfig.registerPlugin(new CorsPlugin(cors -> cors.addRule(CorsRule::anyHost)));
new CorsPlugin(
cors -> {
cors.addRule(
it -> {
it.anyHost();
});
}));
javalinConfig.requestLogger.http( javalinConfig.requestLogger.http(
(ctx, ms) -> { (ctx, ms) -> {
StringJoiner joiner = StringJoiner joiner =
@@ -103,6 +98,7 @@ public class Server {
return "Got WebSockets binary message from host: " + host; return "Got WebSockets binary message from host: " + host;
})); }));
}); });
javalinConfig.jsonMapper(new JavalinJsonb());
}); });
/* Web Socket Events for Data Exchange */ /* Web Socket Events for Data Exchange */

View File

@@ -37,6 +37,7 @@ dependencies {
implementation group: "io.avaje", name: "avaje-jsonb", version: avajeJsonbVersion implementation group: "io.avaje", name: "avaje-jsonb", version: avajeJsonbVersion
annotationProcessor group: "io.avaje", name: "avaje-jsonb-generator", version: avajeJsonbVersion annotationProcessor group: "io.avaje", name: "avaje-jsonb-generator", version: avajeJsonbVersion
implementation group: "io.avaje", name: "avaje-jsonb-jackson", version: avajeJsonbVersion implementation group: "io.avaje", name: "avaje-jsonb-jackson", version: avajeJsonbVersion
implementation group: "io.avaje", name: "avaje-jsonb-javalin-mapper", version: avajeJsonbVersion
implementation group: "org.ejml", name: "ejml-simple", version: ejmlVersion implementation group: "org.ejml", name: "ejml-simple", version: ejmlVersion
implementation group: "us.hebi.quickbuf", name: "quickbuf-runtime", version: quickbufVersion; implementation group: "us.hebi.quickbuf", name: "quickbuf-runtime", version: quickbufVersion;